有效的注释

前言

随着软件技术的不断发展,开发人员正日益重视设计模式和实现模式。然而有效的注释也是代码的重要组成部分。世界级软件大师Kent Beck(大名鼎鼎的JUnit作者)在其著作Implementation Patterns中指出”Programs are read more often than they are written”大意是“代码被阅读的时间远比写出来的时间要多得多”。为代码增加有效的注释是一种提高代码可读性的方法。有效的注释能够帮助代码阅读者理解设计者思路,便于代码的维护。对于缺少经验的开发人员,他们往往不知道何时需要增加注释,如何增加注释,怎样增加准确的注释。如果增加不雅的注释,反而会比没有注释更加糟糕,更加令人费解。

作者通过阅读一些优秀开源框架的源代码,分析其中注释的技巧,对有效的注释进行归纳和总结,总结出以下四个原则和20条方法注释技巧。总结出来供有志于编写可读性好的代码的开发人员参考。当然,更应当提倡编写清晰的无需解释的代码——让代码自己说明自己。

注释的原则

有效的注释不是为拙劣的代码雪中送炭,而是为优美的代码锦上添花。如果代码需要大量的注释来解释,这说明代码的本身实现并不理想,更好的做法是对代码进行重构。优秀的代码与有效的注释相辅相成、相得益彰。虽然优秀的代码可以不需要注释,但有效的注释可以让代码更易理解,方便未来的维护。有效的注释需要遵循四个原则:唯一性、说明性、简单性和一致性。下面分别说明这四个原则。

唯一性,如果代码能够明确表达意图,比如简单的赋值语句和简单的条件判断,则没有必要增加注释,注释也需要遵循DRY原则(Don’t repeat yourself)。

说明性,如果代码难以说明意图,则需要增加清晰的注释帮助阅读者理解。这一点要求开发人员始终能从代码阅读者的角度来编写代码和注释,要考虑阅读者是否能够很容易理解代码。如果觉得代码中有特殊的技巧或是关键的语句,则应该立即增加注释。这样不仅减少将来理解代码的时间,而且也及时记录了设计思路,一举两得。

简单性,注释需要清晰明了,不宜过长。阅读者重点关注的是代码的功能,而不是欣赏注释有多艺术。注释不应当有喧宾夺主的嫌疑。

一致性,保持代码和注释的同步性,修改了代码应当立即更新注释。如果代码重构后觉得注释已经没有必要,应该毫不吝惜地删除无用的注释。与代码不一致的注释反而比没有注释更加糟糕,会干扰阅读者的理解。

注释的技巧

1解释为什么,而非是什么

假设代码阅读者都是开发人员(通常也只有开发人员才会去阅读代码),开发人员在修改bug,维护软件时都需要阅读代码,那代码的注释也理所当然面向开发人员。既然合格的开发人员都已经熟悉语言语法,就没有必要增加语法层面的注释,而是需要增加注释说明代码为什么这样写,这样写需要实现什么功能。这样的注释既方便阅读者理解代码,也可以起到文档的功能。此类注释在代码中最为重要,使用最为频繁。

2增强变量含义说明

通常一个公司或者项目中都会存在一些编码规范,这些规范要求变量名不能超过约定的字数。于是开发人员不得不使用单词缩写,以满足字数的限制,但这样也降低了代码的可读性。一种弥补的方法就是增加注释。如果代码的变量名不足以说明其意义,增加注释加以解释可以方便阅读者理解。

3说明段落大意

如果一个方法的实现比较复杂,但又必须在一个方法中实现,可以通过代码分段,并增加注释解释段落大意,从而使方法的结构更加清晰、更易理解。具体做法是对完成同一功能的代码实施分段(代码排版),然后对每个段落加以注释,相当于给一个段落作一个概括。类似于我们在小学学习语文时给文章分段,然后概括段落大意。

此类注释在单元测试中经常出现。在单元测试中,一个方法中可能包括多个测试用例,此时分段增加注释,告诉代码阅读者接下来这段代码是何作用。一个段落用一段注释加以阐述。通常更好的做法是将一个复杂的方法重构为多个功能明确的方法。具体做法请参考重构技巧。

4解释异常处理

异常处理是编写代码中经常遇到的问题。有时异常处理会包括特殊的业务意义,增加注释可以清楚表达作者的意图。例如,可以在异常处理中增加注释说明什么情况会出现此异常,可以针对某些异常做特殊处理(如忽略异常)。

5解释边界值

很大程度上涉及边界值的处理比较容易出错——很多bug的出现都是因为边界值处理不当的缘故。边界值问题最显著体现在是否包括边界值的处理上。如果适当增加注释说明什么情况不包括边界值,什么情况包括边界值,会使代码一目了然,避免未来阅读代码时耗费太多的时间去揣摩。

6解释布尔型变量的操作

虽然布尔型变量只有两个值,但是其代表的意义却随上下文环境而大相径庭。所以在操作一个布尔型变量时,适当增加注释说明操作的作用,会极大地提高代码的可读性。当然,使用合适的单词命名布尔型变量会让代码可读性更强,如done/found/success/ok/error

7解释可能出现的情况

一般地,一个方法中会处理很多情况,可以针对每种情况增加一段注释。此类注释通常出现在if判断条件之前,用于解释何种情况执行if语句体。

8说明特殊用法

如果代码中使用了特殊的语法或者较少使用的API来实现功能,需要增加注释说明,帮助阅读者理解。

9使用示例说明功能

有时示例比文字更能清楚表达功能。可以在注释中使用示例说明功能。示例可以是输出的结果,也可以是参数的结构。

10解释elsedefault条件

If-else结构中的else条件,以及switch-case-default结构中default语句包含的业务逻辑比较泛化,代码阅读者不易明确把握条件内容,需要增加注释加以解释。

11针对每个条件注释

如果判断条件比较复杂,而且每个条件包含一种业务意义,此时可以分别为每个条件增加注释,使阅读者很方便理解业务逻辑。当然,更好的做法也是对代码进行重构,使用方法名或者变量名来表达意图。具体做法请参考重构技巧。

12解释提高性能的代码

对于提高性能的代码,如果实现并不完美或者存在潜在的缺陷,有改善的要求,可以增加注释,说明代码的特殊性和重要性,以引起阅读者的注意,提醒未来维护代码时重点关注,寻求更优实现。

13解释何时需要默认值

代码中常常会出现默认值,在出现默认值时需要解释什么情况下使用,以及该默认值的作用。

14解释突兀代码

如果某段代码在方法中出现得比较突兀,使人不能明白其出现的作用,需要增加注释说明其出现的必要性,必要时需要解释其出现的时机和在代码中位置。

15注释语言版本差别

部分功能因语言版本差别而有不同实现,需要通过注释解释差别,解释不同版本的实现方式。

16解释代码取舍原因

一种功能可能会有多种实现,但是有时必须使用特定的实现,此时需要说明取舍的原因,方便阅读者理解。避免未来维护人员产生好奇而修改代码。

17避免误解

功能实现时,如果两处代码非常相识,仅存在一些细微的差别,为了避免阅读者将差别当成错误,需要增加注释加以澄清。

例如下表中两段代码均包含范围语句,前者为[0,size()),表示范围包括0(闭括号),但不包括size()(开括号),后者为[0,size()],表示范围包括0(闭括号),且包括size()(闭括号)。虽然阅读者通过if条件判断语句可以明白意图,但是增加注释后更加清楚,避免阅读者误解两处不一致是程序设计有误。

private void checkRange(int index) {

if(index < 0 || index >= size()) {

    //includes 0, and not includes size()

        throw new IndexOutOfBoundsException("index " + index + " not in[0," + size() + ")");

    }

}

private void checkRangeIncludingEndpoint(int index) {

if(index < 0 || index > size()) {

    // includes 0, and includes size()

        throw new IndexOutOfBoundsException("index " + index + " not in[0," + size() + "]");

    }

}

18注释短路语句

短路语句是提前结束方法执行的语句,一般用于处理特殊的逻辑。在短路语句处增加一段注释解释原因可以使代码更加清晰,让代码阅读者更易理解。

19解释批量null赋值操作

一般批量的null赋值操作具有特殊意义,应该增加注释说明原因(当然除了对象的初始化操作)。

20标记待实现/完善部分

如果代码中存在未实现或者需要完善的部分,可以使用TODO注释,便于开发工具收集定位。

以上20个技巧中,“解释为什么,而非是什么”是核心,其他技巧均在此技巧上演化而来,是方法注释常用的辅助手段。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值