注释

原则 8.1 优秀的代码可以自我解释,不通过注释即可轻易读懂。

优秀的代码不写注释也可轻易读懂,注释无法把糟糕的代码变好,需要很多注释来解释的代码 往往存在坏味道,需要重构。

示例:注释不能消除代码的坏味道:

/* 判断m是否为素数*/
/* 返回值:: 是素数,: 不是素数*/
int p(int m)
{
 int k = sqrt(m);
 for (int i = 2; i <= k; i++)
 if (m % i == 0)
 break; /* 发现整除,表示m不为素数,结束遍历*/
 /* 遍历中没有发现整除的情况,返回*/
 if (i > k)
 return 1;
 /* 遍历中没有发现整除的情况,返回*/
 else
 return 0;
}

重构代码后,不需要注释:

int IsPrimeNumber(int num)
{
 int sqrt_of_num = sqrt (num);
 for (int i = 2; i <= sqrt_of_num; i++)
 {
 if (num % i == 0)
 {
 return FALSE;
 }
 }
 return TRUE;
}

原则 8.2 注释的内容要清楚、明了,含义准确,防止注释二义性。

有歧义的注释反而会导致维护者更难看懂代码,正如带两块表反而不知道准确时间。

示例:注释与代码相矛盾,注释内容也不清楚,前后矛盾。

/* 上报网管时要求故障ID与恢复ID相一致*/
/* 因此在此由告警级别获知是不是恢复ID */
/* 若是恢复ID则设置为ClearId,否则设置为AlarmId */
if (CLEAR_ALARM_LEVEL != RcData.level)
{
 SetAlarmID(RcData.AlarmId);
}
else
{
 SetAlarmID(RcData.ClearId);
}

正确做法:修改注释描述如下:

/* 网管达成协议:上报故障ID与恢复ID由告警级别确定,若是清除级别,ID设置为ClearId,否
则设为AlarmId。*/

原则 8.3 在代码的功能、意图层次上进行注释,即注释解释代码难以直接表达的意图,而不是重复描 述代码。

注释的目的是解释代码的目的、功能和采用的方法,提供代码以外的信息,帮助读者理解代码,防止没必要的重复注释信息。

对于实现代码中巧妙的、晦涩的、有趣的、重要的地方加以注释。

注释不是为了名词解释(what),而是说明用途(why)。

示例:如下注释纯属多余。

++i; /* increment i */
if (receive_flag) /* if receive_flag is TRUE */

而如下的注释则给出了有用的信息:

/* 由于xx编号网上问题,在xx情况下,芯片可能存在写错误,此芯片进行写操作后,必须进行回读校
验,如果回读不正确,需要再重复写-回读操作,最多重复三次,这样可以解决绝大多数网上应用时的
写错误问题*/
int time = 0;
do
{
 write_reg(some_addr, value);
 time++;
} while ((read_reg(some_addr) != value) && (time < 3));

规则 8.1 修改代码时,维护代码周边的所有注释,以保证注释与代码的一致性。不再有用的注释要删除。

不要将无用的代码留在注释中,随时可以从源代码配置库中找回代码;即使只是想暂时排除代码,也要留个标注,不然可能会忘记处理它。

规则 8.2 文件头部应进行注释,注释必须列出:版权说明、版本号、生成日期、作者姓名、工号、内容、功能说明、与其它文件的关系、修改日志等,头文件的注释中还应有函数功能简要说明。

说明:通常头文件要对功能和用法作简单说明,源文件包含了更多的实现细节或算法讨论。

示例:下面这段头文件的头注释比较标准,当然,并不局限于此格式,但上述信息建议要包含在内。

/*************************************************
 Copyright © Huawei Technologies Co., Ltd. 1998-2011. All rights reserved.
 File name: // 文件名
 Author: ID: Version: Date: // 作者、工号、版本及完成日期
 Description: // 用于详细说明此程序文件完成的主要功能,与其他模块
 // 或函数的接口,输出值、取值范围、含义及参数间的控
 // 制、顺序、独立或依赖等关系
 Others: // 其它内容的说明
 History: // 修改历史记录列表,每条修改记录应包括修改日期、修改
 // 者及修改内容简述
 1. Date:
 Author: ID:
 Modification:
 2. ...
*************************************************/

规则 8.3 函数声明处注释描述函数功能、性能及用法,包括输入和输出参数、函数返回值、可重入的要求等;定义处详细描述函数功能和实现要点,如实现的简要步骤、实现的理由、设计约束等。

重要的、复杂的函数,提供外部使用的接口函数应编写详细的注释。

规则 8.4 全局变量要有较详细的注释,包括对其功能、取值范围以及存取时注意事项等的说明。

示例:

/* The ErrorCode when SCCP translate */
/* Global Title failure, as follows */ /* 变量作用、含义*/
/* 0 -SUCCESS 1 -GT Table error */
/* 2 -GT error Others -no use */ /* 变量取值范围*/
/* only function SCCPTranslate() in */
/* this modual can modify it, and other */
/* module can visit it through call */
/* the function GetGTTransErrorCode() */ /* 使用方法*/
BYTE g_GTTranErrorCode;

规则 8.5 注释应放在其代码上方相邻位置或右方,不可放在下面。如放于上方则需与其上面的代码用空行隔开,且与下方代码缩进相同。

示例:

/* active statistic task number */
#define MAX_ACT_TASK_NUMBER 1000
#define MAX_ACT_TASK_NUMBER 1000 /* active statistic task number */
可按如下形式说明枚举/数据/联合结构。
/* sccp interface with sccp user primitive message name */
enum SCCP_USER_PRIMITIVE
{
 N_UNITDATA_IND, /* sccp notify sccp user unit data come */
 N_NOTICE_IND, /* sccp notify user the No.7 network can not transmission this message */
 N_UNITDATA_REQ, /* sccp user's unit data transmission request*/
};

规则 8.6 对于 switch 语句下的 case 语句,如果因为特殊情况需要处理完一个 case 后进入下一个 case 处理,必须在该 case 语句处理完、下一个 case 语句前加上明确的注释。

示例:

case CMD_FWD:
 ProcessFwd();
 /* now jump into case CMD_A */
case CMD_A:
 ProcessA();
 break;
 //对于中间无处理的连续case,已能较清晰说明意图,不强制注释。
switch (cmd_flag)
{
case CMD_A:
case CMD_B:
 {
 ProcessCMD();
 break;
 }
 ……
}

规则 8.7 避免在注释中使用缩写,除非是业界通用或子系统内标准化的缩写。

规则 8.8 同一产品或项目组统一注释风格。

建议 8.1 避免在一行代码或表达式的中间插入注释。

除非必要,不应在代码或表达中间插入注释,否则容易使代码可理解性变差。

建议 8.2 注释应考虑程序易读及外观排版的因素,使用的语言若是中、英兼有的,建议多使用中文,除非能用非常流利准确的英文表达。对于有外籍员工的,由产品确定注释语言。

注释语言不统一,影响程序易读性和外观排版,出于对维护人员的考虑,建议使用中文。

建议 8.3 文件头、函数头、全局常量变量、类型定义的注释格式采用工具可识别的格式。

采用工具可识别的注释格式,例如 doxygen 格式,方便工具导出注释形成帮助文档。 以 doxygen 格式为例,文件头,函数和全部变量的注释的示例如下:

文件头注释:

/**
* @file (本文件的文件名eg:mib.h)
* @brief (本文件实现的功能的简述)
* @version 1.1 (版本声明)
* @author (作者,eg:张三)
* @date (文件创建日期,eg:2010年12月15日)
*/

函数头注释:

/**
*@ Description:向接收方发送SET请求
* @param req - 指向整个SNMP SET 请求报文.
* @param ind - 需要处理的subrequest 索引.
* @return 成功:SNMP_ERROR_SUCCESS,失败:SNMP_ERROR_COMITFAIL
*/
Int commit_set_request(Request *req, int ind);

全局变量注释:

/** 模拟的Agent MIB */
agentpp_simulation_mib * g_agtSimMib;

函数头注释建议写到声明处。并非所有函数都必须写注释,建议针对这样的函数写注释:重要的、复杂的函数,提供外部使用的接口函数。