如何写好代码注释?

注释的本质

对于代码注释来说,在不同的教程或者原则中有不同的规定或者解释。有的原则是需要使用 JavaDoc 来描写每个方法,而有的原则是要求每一个属性标注命名。我愿意相信每一份看起来不那么妥当的注释都是出于一些善意的目的,这就是注释的本质:

对未能自行解释的代码做出解释。

在进行代码工作的时候我们多多少少会有一些陈旧的、与业务无关的逻辑在代码中运行。有时候并不是一个变量名或者一个方法名就能阐述清楚产品同学所期望的业务内容。我们希望将代码外的逻辑也加入到其中,但是一篇长篇大论的注释似乎也不那么妥当,所以对于注释我一般会加入一个约束的条件:

尽可能精简地描述当前方法、属性未能解释的逻辑。

那么这个约束中的关键词是:精简、当前、未能解释。这是我现在的理解,如果有更好的见解也希望可以联系我进行å沟通。当然这些关键词在后文中也会进行解释。

代码的退化

对于代码的退化这个概念在很多的领域之中都有阐述。而一种比较主流的观点是:应用程序的生产寿命可能为 3-5 年。当然这个大限将至无法使用的定义是有很大空间的。但是这个说法的主要思想是:代码随着业务的迭代开发,会逐渐地降低可维护性,直到它的生命终结。代码是这样,对应的代码注释也是这样的。

对于代码注释来说,不知道大家是否经历过类似的内容:

  • 几个位置的方法是同样的注释,看起来是来自于一次方法的批量注释。

  • 代码上有一段业务的注释,但是看起来和代码逻辑并不一致

  • 代码上描述了一段业务,但是产品同学说业务已经下线了

他们可能都是痛苦的回忆,但是这就是刚刚所描述的,注释的退化。

对于代码来说,我们有许多的方法论、设计模式来尽可能地将代码的职责划分清晰从而延长。而对于注释来说,我认为最大的方法是“不写注释”(关于这个在后面的章节中会再次讨论)。

不写注释

只要不写注释,注释就不会有问题。之所以会有这个问题的原因是,通常人们在调整业务逻辑后无法完美地维护注释。而更多情况是当代码逻辑发生变动了之后,注释直接编成了业务谜题中的一部分,开发人员不仅要在破败不堪的代码中梳理逻辑,还同时要小心错误注释发出的诱惑。

同时,当代码发生退化逐渐变得不可控的时候,比起重新抽象代码结果,看起来以一段注释来描述新的逻辑比较简单。但是带来的问题就是:“在尝试用注释弥补代码逻辑的问题”。显然注释无法帮你抽象代码,只是看起来将问题爆发的时间延后了(甚至引入了更严重的问题)。

那么“不写注释”的思想换一个角度来描述的话就是:让代码注释自己。这是一种习惯,举个最简单的例子:

//判断订单是否满足支付条件
if(order.getRealPirce()>0 && PAID != order.getPayState())

可以改为:

if(order.isPayable())

哪怕是调整为:

boolean isPayable = order.getRealPirce()>0 && PAID != order.getPayState();
if(isPayable)

可以看出来,第二种方法是指用对象内的方法,如果 order 只是一个自动生成的实体对象或者是一种值对象的话,那么也可以使用第三种方法。

这样调整的好处就是,用代码自身去描述逻辑,从而尽量“不写注释”。

人不能,至少不该

对于不写注释的这个论点,除了代码自动解释以外,还有很多情况下的注释都是不那么合适的。

简单的方法添加注释

对于简单的函数来说,代码本身就已经有足够的解释性了,如果为其增加了注释,反而会降低代码的关注度,让开发人员将注意力放到注释上,而如果在简单逻辑变更后,就更糟糕了,你能想象set()方法上进行方法注释告诉你这是一个设置数值的方法吗。

那么我对简单方法的定义是,如果方法实际逻辑(刨除括号部分)5-7 行(我自己的标准)则可以认为是一个简单方法。读这样的方法的注释反而可能比直接看代码还来得复杂。

无法精确描述的注释

对于一些无法直接用语言进行直接描述的逻辑,我觉得要不就直接不要写注释了,让他们直接进行代码的阅读比较好。否则有歧义性的注释反而会误导人员进行理解。当然,对于我自己而言,内部项目中对于不便与用文字精确描述的逻辑,我会贴上 wiki 的连接进行图文描述。这样对于不希望阅读的开发人员注释不会产生干扰,而同时图文配合又便于理解。

无情的 JavaDoc 机器人

JavaDoc 标准要求对每个参数进行定义,但是这样带来的问题就是一些足够简明的参数的注释本身就是冗余的,例如:

public class User {
    /**
     * @param id 准备构建用户的用户id
     * @param name 准备构建用户的用户名称
     * @param age 准备构建用户的用户年龄
     */
   
    public User(long id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }


    private long id;


    private String name;
    
    private int age;
    
}

尽管看起来很完美,但是它本身没有任何意义。所以对于代码中具有自解释性的变量名称(它们本应该具有自解释性),JavaDoc 的注释其实是非必要的。

行为注释

行为注释在在 IDE 里面行为的注释会导致代码的可读性大大降低,有的可能会在很长一段代码之后,有的则可能在很短的代码之后,他们的格式是不容易统一的,所以在现在广为流传的阿里巴巴开发手册中就明确的加上了在上一行中注释。

用户名称

事实上,我还在用用户署名,因为之前创建了文件模版之后就一直沿用了。不过原则上来说,java 文件署名的这个习惯是源于早期的代码版本控制并不是很发达的时代。而现代版本控制中,文件的来世今生都由版本控制来进行了,所以事到如今的用户署名已经没有意义了。

注释代码

注释掉的代码就应该直接删除,否则会对后续的人员产生干扰。人们可能会下意识地与注释掉的代码产生逻辑交互,并且认为这部分由其保留的目的而并不动。和上条一样,在版本控制比较发达的今天,已经被注释的代码如果有其特殊的功能作用的话,应该是由一个单独的分支进行保护,而非一段会干扰到正常业务开发的注释代码。

非当前功能注释

如果注释所表达的功能与当前的方法无关的话,说明这部分的注释并不是完全为当前的方法服务的。那么它就不应该出现在这里,也许在一个另外的 readme 或者是 wiki 文档中才是它更好的位置。

如果将非当前功能的注释添加到方法上的话,那这里就会造成:如果要理解注释,就要去知道注释的上下文,那么这部分的注释本身就需要额外的说明,就与注释本身的功能背道而驰了。

充分必要条件

尽管我们希望不使用注释,直接让代码就能完成逻辑解释的功能,但有时候代码确实是无法完全胜任。一般来说,以下的情况下是适用于进行注释补充的。

声明

对于法律、许可的声明,是可以放到一些文件头上的,他们有额外的作用。好消息是现在的 IDE 可以自动折叠这部分的信息,就如果可以自动折叠 import 一样,将他们对于逻辑理解的干扰降到最低。

描述背景

有时候,对于额外的程序外的背景描述,是可以补充的。这样可以在你尽管理解了代码逻辑,但是不知道他为什么这么实现的时候增加了额外的判断条件。也可以帮你确认是由于什么考虑才成为了当前的代码逻辑,比如:

//由于数据库实例读写分离,所以这个地方在延迟后进行异步调用。

这样的话尽管你可能不认同之前人员实现的方法,但是至少可以知道他的目的,从而判断是否可以进行业务调整。

TODO

代码是临时的在代码中进行的笔记。尽管是注释,但却和单纯的代码注释的意义不一样,可以进行标记。但一定要记得定时的处理 TODO,否则大家的敏感度会降低。

警示

警告如果业务或者功能方法在特定的情况下不应该执行。这些情况往往是组合了一些业务情况的信息,或者是实际生产情况下产生 bug 的记录。这样的注释将可以确实地提高解决问题的效率。但是要注意的是这部分的内容可能会遭受到代码腐化的影响。题外话:idea 里面可以设置不同颜色的”@xxx”的标签,例如我新建了”@ATTENTION”从而将后续的内容调整成了紫色以提供相应的警示作用。

中国地区特色注释

事实上,对于上面的观点很多观点,也是大多数全球程序员的观点。而中国自有我大国之情:便是语言问题。上文所说的代码自解释性的前提是对代码的表达意思了解得准确。而会想一下你对一些领域模型进行起名的时候,有多少是通过谷歌翻译、或者百度结果而查出来的?对于这种得到的名词来说,很难定义它是准确地描述了开发人员意图的变量名称。所以如果是这种前提的话,我认为对于指定对象中的字段、或者指定使用的参数、局部变量等名称,是可以酌情在属性、方法名上对指定的名字进行约束解释,以便由于方法名称和其他的概念混淆。简单点来说:

对于没有信心使用英文精准描述的名称,还是用注释进行约束较好。

最后

本文主要内容来自《Clean Code》中注释一章。阅读感悟主要是文章中的内容有一些是针对超长工作经验的老油条说的,他们可能由于过往的习惯导致注释问题,而这一点在中国互联网存在的问题不明显;中国的母语区别导致代码的自解释性降低,学好英语还是非常重要的呀。

本文文字及图片出自 InfoQ

你也许感兴趣的:

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注