优秀的代码不写注释也可轻易读懂,注释无法把糟糕的代码变好,需要很多注释来解释的代码 往往存在坏味道,需要重构。
示例:注释不能消除代码的坏味道:
/* 判断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;
}
有歧义的注释反而会导致维护者更难看懂代码,正如带两块表反而不知道准确时间。
示例:注释与代码相矛盾,注释内容也不清楚,前后矛盾。
/* 上报网管时要求故障ID与恢复ID相一致*/
/* 因此在此由告警级别获知是不是恢复ID */
/* 若是恢复ID则设置为ClearId,否则设置为AlarmId */
if (CLEAR_ALARM_LEVEL != RcData.level)
{
SetAlarmID(RcData.AlarmId);
}
else
{
SetAlarmID(RcData.ClearId);
}
正确做法:修改注释描述如下:
/* 网管达成协议:上报故障ID与恢复ID由告警级别确定,若是清除级别,ID设置为ClearId,否
则设为AlarmId。*/
注释的目的是解释代码的目的、功能和采用的方法,提供代码以外的信息,帮助读者理解代码,防止没必要的重复注释信息。
对于实现代码中巧妙的、晦涩的、有趣的、重要的地方加以注释。
注释不是为了名词解释(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));
不要将无用的代码留在注释中,随时可以从源代码配置库中找回代码;即使只是想暂时排除代码,也要留个标注,不然可能会忘记处理它。
说明:通常头文件要对功能和用法作简单说明,源文件包含了更多的实现细节或算法讨论。
示例:下面这段头文件的头注释比较标准,当然,并不局限于此格式,但上述信息建议要包含在内。
/*************************************************
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. ...
*************************************************/
重要的、复杂的函数,提供外部使用的接口函数应编写详细的注释。
示例:
/* 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;
示例:
/* 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*/
};
示例:
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;
}
……
}
除非必要,不应在代码或表达中间插入注释,否则容易使代码可理解性变差。
注释语言不统一,影响程序易读性和外观排版,出于对维护人员的考虑,建议使用中文。
采用工具可识别的注释格式,例如 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;
函数头注释建议写到声明处。并非所有函数都必须写注释,建议针对这样的函数写注释:重要的、复杂的函数,提供外部使用的接口函数。