《C++覆辙录》——常见错误1:过分积极的注释

简介:

本节书摘来自异步社区出版社《C++覆辙录》一书中的第1章,第1节,作者: 【美】Stephen C. Dewhurst(史蒂芬 C. 杜赫斯特),更多章节内容可以访问云栖社区“异步社区”公众号查看。

第1章 基础问题

C++覆辙录
说一个问题是基础的,并不就是说它不是严重的或不是普遍存在的。事实上,本章所讨论的基础问题的共同特点比起在以后章节讨论的技术复杂度而言,可能更侧重于使人警醒。这里讨论的问题,由于它们的基础性,在某种程度上可以说它们普遍存在于几乎所有的C++代码中。

常见错误1:过分积极的注释

很多注释都是画蛇添足,它们只会让源代码更难读,更难维护,并经常把维护工程师引入歧途。考虑下面的简单语句:

a = b; // 将b赋值给a
这个注释难道比代码本身更能说明这个语句的意义吗?因而它是完全无用的。事实上,它比完全无用还要坏。它是害人精。首先,这条注释转移了代码阅读者的注意力,增加了阅读量因而使代码更费解。其次,要维护的东西更多了,因为注释也是要随着它描述的代码的更改而更改的。最后,这个对注释的更改常常会被遗忘1:

c = b; // 将b赋值给a
仔细的维护工程师不会武断地说注释是错的2,所以他就被迫要去检视整个程序以确定到底是注释错了呢,还是有意为之呢(c可能是a的引用),还是本来正确只是比较间接的呢(赋值给c可能引发一些传播效应以使a的值也发生相应变化),等等,总之这一行就根本不应该带注释。

a = b;
还是这代码本来的样子最清楚地表明了其意义,也没有额外的注释需要维护。这在精神上也符合老生常谈,亦即“最有效率的代码就是根本不存在的代码”。这条经验对于注释也适用:最好的注释就是根本用不着写的注释,因为要注释的代码已经“自注释”了。

另一些常见的非必要的注释的例子经常可以在型别的定义里见到,它们要么是病态的编码标准的怪胎,要么就是出自C++新手:

class C {
 // 公开接口
 public:
  C(); // 默认构造函数
  ~C(); // 析构函数
  // ...
};```
你会觉得别人在挑战你的智商。要是某个维护工程师连“public:”是什么意思都需要教,你还敢让他碰你的代码吗?对于任何有经验的C++软件工程师而言,这些注释除了给代码添乱、增加需要维护的文本数量以外没有任何用处:

class C {
// 公开接口
protected:
  C( int ); // 默认构造函数
public:
  virtual~C(); // 析构函数
  // ...
};`
软件工程师还有一种强烈的心理趋势就是尽量不要“平白无故”地在源文件文本中多写哪怕一行。这里公布一个有趣的本行业秘密:如果某种结构(函数啦、型别的公开接口啦什么的)能被塞在一“页”里,也就在三四十行左右3的话,它就很容易理解。假如有些内容跑到第二页去了,它理解起来就难了一倍。如果三页才塞得下,据估计理解难度就成原来的4倍了4。一种特别声名狼藉的编码实践就是把更改日志作为注释插入到源文件的头部或尾部:

/* 6/17/02 SCD把一个该死的bug干掉了 */
这到底是有用的信息,抑或是仅仅是维护工程师的自吹自擂?在这行注释被写下以后的一两个星期,它怎么看也不再像是有用的了,但它却也许要在代码里粘上很多年,欺骗着一批又一批的维护工程师。最好是用你的版本控制软件来做这种无用注释真正想做的事,C++的源代码文件里可没有闲地方来放这些劳什子。

想不用注释却又要使代码意义明确、容易维护的最好办法就是遵循简单易行的、定义良好的命名习惯来为你使用的实体(函数、型别、变量等)取个清晰的、反映其抽象含义的名字。(函数)声明中形参的名字尤其重要。考虑一个带有3个同一型别引数的函数:

/*
从源到目的执行一个动作
第一个引数是动作编码(action code),第二个引数是源(source),第三个引数是目的(destination)
*/
void perform( int, int, int );```
这也不算太坏吧,不过如果引数是七八个而不是3个你又该写多少东西呢?我们明明可以做得更好:

void perform( int actionCode, int source, int destination);①`
①译者注:这很明显是Herb Sutter倡导的命名规则(原谅我又多写了一行注释)。
这就好多了。按理,我们还需要写一行注释来说明这个函数的用途(而不是如何实现的)。形参的一个最引人入胜之处就是,不像注释,它们是随着余下的代码一起更改的,即使改了也不影响代码的意义。话虽然这么说,但我不能想像任何一个软件工程师在引数意义改变了的时候,会不给它取个新名字5。但我能举出一串软件工程师来,他们改了代码但老是忘记维护注释。

Kathy Stark在Programming in C++中说得好:“如果在程序里用意义明确、脱口而出的名字,那么注释只是偶尔才需要。如果不用意义明确的名字,即使加上了注释也不能让代码更好懂一些。”

另一种最大程度地减少注释书写的办法是采用标准库中的或人尽皆知的组件:
`
printf( "Hello, World!" ); // 在屏幕上打印“Hello, World” `
上面这个注释不但是无用的,而且只在部分情况下正确。标准库组件不仅是“自注释”的,并且有关它们的文档汗牛充栋,有口皆碑。

swap( a, a+1 ); 
sort( a, a+max );
copy( a, a+max, ostream_iterator(cout,"\n") );```
因为swap、sort和copy都是标准库组件,对它们加上任何注释都是成心添乱,而且给定义得好好的标准操作规格描述带来了(非必要的)不确定性。
相关文章
|
2月前
|
自然语言处理 算法 Java
C/C++ 程序员编程规范之注释
C/C++ 程序员编程规范之注释
45 1
|
6天前
|
C++
【期末不挂科-C++考前速过系列P6】大二C++实验作业-模板(4道代码题)【解析,注释】
【期末不挂科-C++考前速过系列P6】大二C++实验作业-模板(4道代码题)【解析,注释】
【期末不挂科-C++考前速过系列P6】大二C++实验作业-模板(4道代码题)【解析,注释】
|
6天前
|
Serverless C++ 容器
【期末不挂科-C++考前速过系列P5】大二C++实验作业-多态性(3道代码题)【解析,注释】
【期末不挂科-C++考前速过系列P5】大二C++实验作业-多态性(3道代码题)【解析,注释】
|
6天前
|
C++ 芯片
【期末不挂科-C++考前速过系列P4】大二C++实验作业-继承和派生(3道代码题)【解析,注释】
【期末不挂科-C++考前速过系列P4】大二C++实验作业-继承和派生(3道代码题)【解析,注释】
|
6天前
|
编译器 C++
【期末不挂科-C++考前速过系列P3】大二C++第3次过程考核(20道选择题&12道判断题&2道代码题)【解析,注释】
【期末不挂科-C++考前速过系列P3】大二C++第3次过程考核(20道选择题&12道判断题&2道代码题)【解析,注释】
|
6天前
|
C++
【期末不挂科-C++考前速过系列P2】大二C++第2次过程考核(20道选择题&10道判断题&3道代码题)【解析,注释】
【期末不挂科-C++考前速过系列P2】大二C++第2次过程考核(20道选择题&10道判断题&3道代码题)【解析,注释】
|
6天前
|
存储 数据安全/隐私保护 C++
【期末不挂科-C++考前速过系列P1】大二C++第1次过程考核(3道简述题&7道代码题)【解析,注释】
【期末不挂科-C++考前速过系列P1】大二C++第1次过程考核(3道简述题&7道代码题)【解析,注释】
|
28天前
|
存储 程序员 编译器
C++注释、变量、常量、关键字、标识符、输入输出
C++注释、变量、常量、关键字、标识符、输入输出
|
2月前
|
算法 Unix 程序员
【C/C++ 基本知识 注释规范】C/C++中注释方式以及规范
【C/C++ 基本知识 注释规范】C/C++中注释方式以及规范
22 0
|
2月前
|
编译器 C++
C++ 注释
C++ 注释
12 0