3.4 约定注释风格

编程规范第三条,我们一起来啃一啃让程序员又爱又恨的注释风格。

关于注释,网络上众说纷纭,有些观点建议尽可能多写注释,而另外一些观点建议少写注释,追求代码即注释。

工作多年,我的团队在反复迭代中形成了一整套关于注释的理解,很难单纯的用多或少来区分,我们一一和大家道来。

在前两条编程规范中,已经描述了几条注释规范了,整理如下:

  1. 文件头部注释,用于描述该文件的功能;
  2. 节注释,描述节的用途;
  3. 分段注释,描述大段代码的用途。

除此之外,我们还有如下几条关于注释的形式化要求:
4. 对所有的未知变量(包括枚举,变量定义,结构,宏,函数等)进行注释;
5. 约定函数注释风格;
6. 对关键代码段进行注释;
7. 打破规则处需要注释加以说明。

◇◇◇

在详细描述这些注释之前,先和大家探讨一下注释的形式化问题。

在c89规范中约定“/* */”块注释风格,在c99规范中增加了“//”行注释风格。目前,虽然有些编译器不完全支持c99所有特征,但基本都支持“//”注释风格。

不知大家是否有这样的经历,调试代码时,经常需要快速写一些临时代码,或者快速注释掉某段代码。很不幸,我们匆忙做的改变经常忘记收尾。在我的职场生涯中,已经出现过多次因临时代码未删除,或被注释的代码未取消注释而引起的产品异常了。

为了尽可能规避这个问题,我们团队采用了如下形式化要求:

  1. 所有的正式注释仅允许使用“/* */”风格,其中两边增加一空格,用于凸显注释内容;
  2. 调试过程中的临时大块或单行代码,使用//@风格注释;
    通过该形式化约定后,代码提交前简单搜索一下“//”或“@”,就能发现是否还有临时代码段。

这两条形式化要求,表面看增加了大家写代码的难度,但便于工具化统一,该部分内容在3.6节介绍。其中使用符号“@”,是因为这是C程序中几乎唯一不应该出现的符号了。

◇◇◇

关于注释,我们团队形成的第一条形式化约定是:对未知进行中文注释。其中,未知这个概念包含了节,文件,函数,变量,结构等等之类,前面关于文件头、节、块的注释都属于这一条。

在《编写可读代码的艺术》一书中,作者强烈建议使用明确的单词来命名,这样就可以达到代码即注释了。我认为这条规范比较适合西方人,并不适合我们。我不能假设我们招聘的每个人英语都ok,也不能假设你用字典查出来的名字是合理的,更不能假设你会从多个相似语义的单词中选出最合适的那一个。因此,最好的策略就是形式化约定对所有未知进行中文注释。

现在很多优秀的编辑器都支持变量快速定位。采用这种策略后,我们阅读一段代码时,看到一个变量,不知道它的含义,仅需要立即跳转到定义处即可。因此,这种约定,方便了代码审核。

截取一段产品中的代码,便于大家理解基于这种约定的代码风格。

/* 接收缓冲区大小, 考虑最大容纳完整两帧数据情况 */
#define RXD_BUF_SIZE		512

/* 发送缓冲区大小, 为了防止干扰数据, 比256适度扩大,也可用于内存溢出检测 */
#define TXD_BUF_SIZE		(256+16)

/* 发送缓冲区个数 */
#define TXD_BUF_COUNT		4

/* 发送缓冲区 */
typedef struct TTxdBuf
{
	DWORD dwLen;				/* 发送数据长度 */
	BYTE pBuf[TXD_BUF_SIZE];	/* 数据缓冲区 */
}TTxBuf;

/* 串口属性结构 */
typedef struct TUartInf
{
	DWORD dwState;				/* 通讯状态 */
	DWORD dwOverTime;			/* 通讯状态超时计数器 */
	WORD wBaud;					/* 波特率,记录,便于重新初始化 */
	WORD wParity;				/* 校验方式,记录,便于重新初始化 */
	BYTE pRxdBuf[RXD_BUF_SIZE];	/* 接收缓冲区 */
……
}TUartInf;

/* 维护口实例 */
static TUartInf s_maintainInf;

◇◇◇

在c语言中的函数经常位于两处,一是声明位置,一是定义位置。前文提及需要对未知进行注释,但是在声明处,还是定义处对函数进行注释呢?

一般变量注释都比较短小,因此可以两边保持一致。但函数注释经常经常需要描述每个参数的含义,整块的代码复制会带来冗余,一不小心弄不一致,更易混淆。

针对这个问题,我们团队提出了一条约定:对外函数在声明处使用完整或简化注释,定义处使用简化注释。如下示例:

/*
 *  Description: 读取EEPROM内容
 *  Input: 
 *    DWORD dwOffset: 偏移位置
 *    BYTE* pBuf: 数据缓冲区地址
 *    DWORD dwLen: 读取长度
 *  Return: 成功返回TRUE,否则返回FALSE;
 */
BOOL hwReadEEPROM(DWORD dwOffset, BYTE *pBuf, DWORD dwLen);

/* 读取EEPROM内容 */
BOOL hwReadEEPROM(DWORD dwAddr, BYTE *pBuf, DWORD dwLen)
{
	...
}

◇◇◇

至此,我们基本约定了整个程序结构的注释风格,在阅读一段代码时,至少不会出现不知所云的名称。但在具体编码级别,难免会有各种关键代码并非阅读友好的,此时就需要额外的增加注释,便于代码理解。

真实产品的代码大多服从幂次分布,也就是说大概80%的都是简单流程代码,仅20%是关键代码。代码审核时,流程代码大致晃一眼就可以了,重点应该放在关键代码上。这样既可以节约代码审核工作量,又可以达到较好的审核效果。

何为关键代码,何为流程代码呢?我们团队约定:在关键段右侧增加强调注释。换句话说,只要加了注释的,都可能是关键代码,值得我们细细读一下。

我举一些真实产品代码示例,便于大家感受这种注释风格,其中程序右侧的注释都属于关键代码段注释。

/* 比例差动延时定值 */
if (SET_dw2HarmBlock == SELSET_BLOCK_NO)
	dwTime = 20-10;	/* 内部-10ms,用于补偿继电器动作时间,实际为20ms */
else
	dwTime = 10;	/* 有谐波闭锁时,减少启动延时时间 */
reComparer_setValue(&s_ratioDiftA, dwTime, 20);
reComparer_setValue(&s_ratioDiftB, dwTime, 20);

/* 记录数字录波信息 */
dw = s_byRelayWordMap[dwIndex];
if (dw < 32)
{
	if (g_byRelayWordValue[dwIndex] & 0x01)	/* 不能使用bState,否则会引起互斥*/
		SET_BIT(g_dwDigitChannel, dw);
	else
		CLEAR_BIT(g_dwDigitChannel, dw);
}

◇◇◇

为了帮助新人快速上手,也为了简化约定风格,便于审核,前面几乎所有的约定我们都采用了形式化的策略,简单来说就是一刀切。

程序是为产品服务的,真实产品中总是会有各种例外,如自动生成的代码,用tab缩进不如用空格缩进易控制;又如某些代码使用外部函数库,希望接口代码尽量同外部函数库风格保持一致等。

此时,我们额外约定:所有的约定都可以打破,但需要通过注释描述理由,以便于审核

返回目录

——————————————

我是小马儿,一个渴望良知与灵魂的嵌入式软件工程师,欢迎您的陪伴与同行,如感兴趣可加个人微信号nzn_xiaomaer交流,需备注“异维”二字。

原创文章 32 获赞 36 访问量 9370

猜你喜欢

转载自blog.csdn.net/zhangmalong/article/details/104552475
3.4