代码质量随想录(六)-用心写好注释.docx

上传人:自*** 文档编号:126900257 上传时间:2020-03-28 格式:DOCX 页数:10 大小:43.67KB
返回 下载 相关 举报
代码质量随想录(六)-用心写好注释.docx_第1页
第1页 / 共10页
代码质量随想录(六)-用心写好注释.docx_第2页
第2页 / 共10页
代码质量随想录(六)-用心写好注释.docx_第3页
第3页 / 共10页
代码质量随想录(六)-用心写好注释.docx_第4页
第4页 / 共10页
代码质量随想录(六)-用心写好注释.docx_第5页
第5页 / 共10页
点击查看更多>>
资源描述

《代码质量随想录(六)-用心写好注释.docx》由会员分享,可在线阅读,更多相关《代码质量随想录(六)-用心写好注释.docx(10页珍藏版)》请在金锄头文库上搜索。

1、代码质量随想录(六)用心写好注释作者:爱飞翔发布时间: 2012-06-20 22:49我要先说说对待注释的态度问题。有一种不写注释的理由,叫做“代码是最好的注释”或是“好的代码应该是自解释型的”。这两个观点其实我都非常赞同,只不过,它们容易被人误用为不写注释的藉口。我们有理由质疑,那种胡乱拼凑瞎写出来的代码能当作“最好的注释”来用吗?即便是非常注重质量的代码,也可能会有程序本身所传达不尽的意思,这个时候,需要些微的注释来提点一下。其实这种心态和随意涂鸦式的注释风格所存在的毛病是一样的,就是靠“不写注释”或者“狂写注释”来回避、掩盖领域模型的缺陷,以及测试用例的不完备。所以我标题中的“用心”就

2、是这个意思,用正确的心态去写注释,须知注释能够发挥作用的前提是对领域模型有着正确的理解,以及对产品代码有着合适的测试覆盖度。在满足这两个前提的情况下,我们才会用注释来提升代码的可读性。否则,就是“宽严皆误”:如果没满足刚说的那两个前提,你写注释,就是掩盖错误;不写,就是逃避错误,实质是一样的。如果你发现注释不好写,写着不顺,你别怪注释,多半是业务模型或测试本身先出了问题。上一篇文章主要讲的是注释的重要性,这一篇则来谈谈注释的具体写作技巧。标题中所谓“写好”注释,除了能够通过注释来阐明代码的未尽之意,还有就是要让注释充当我们打磨领域模型与提升测试覆盖度的催化剂。有的时候我们先写了一定量的注释,然

3、后在代码审查中发现可以由此来找出业务逻辑中存在的问题,从而完善标识符的命名,同时删减原有注释(ARC作者没有太强调这一点,我下面的例子补充了一些自己的看法)。这样一来,它在很多场合,其实是提高代码质量的一种中间过程和手段。ARC一书的作者总结说,好的注释就是要“精准”(precise)和“简洁“(compact),也就是有高的“信息/空间比”(information-to-space ratio)。通俗一点说,就是注释要写得言简义赅、微言大义,尽量用最少的文句精准地表达代码意图。1.尽量减少注释所用的词语,同时考虑以更加精准的名称来取代注释。比如:/ int表示CategoryType。/ 内

4、部数值对的第一个浮点数是“分数”,/ 第二个是“重量”。typedef hash_mapint, pair ScoreMap;上一段代码的注释太长了,不如直接以映射关系来解释它:/ CategoryType - (score, weight)typedef hash_mapint, pair ScoreMap;ARC的作者觉得修改之后的注释将3行减少至1行,很合适。不过我却觉得,做到这一步并没有结束,既然写出了“CategoryType - (score, weight)”这个映射关系,那么是不是应该考虑将“(score, weight)”这个数值对封装起来呢?如果不是在程序级别进行封装,那么

5、至少应该在语义上进行封装,比如,如果它表示一个重量查询表,那么,ScoreMap这个变量就应该命名为CategoryToWeightQueryTable或TypeToScoreWeightPair。如果从业务领域的角度看,前者好,毕竟根据Score来查Weight,应该是业务模型的一部分,该模型应该在某个类或包的总结说明处深入阐释过了,不需要再重复解释了,提一下WeightQueryTable这个领域模型的名字,就足够了。但如果从直白度看,则后者好。如果这段代码没有对领域进行深入建模,那么类似后者这种“傻瓜式”的表达则是减少阅读难度所必须的命名技巧。小翔以上提出的两种命名方法,虽然长了一些,但

6、是该说的却都说了,而且省去了维护注释的麻烦。所以我说,“精简注释”与“琢磨命名”之间,并不矛盾,而且针对前者所做的努力,往往会激发后者。有些时候,我们精简了注释之后,发现可以用精准的命名来取代精简之后的注释;还有些时候,我们则发现,导致注释啰嗦,无法精简的根源,其实在于命名不当。或者更深入地看,有些情况下是由于对领域模型的不瞭解或者分析错误所致。通过“精简注释“这个工作流程,我们可以求得更好的命名,也可以厘清对领域模型的误解,所以我说,“精简注释“这一步,其实可以看作编写高质量代码的催化剂。2.减少有歧义的表述方式。例如:/ 将数据插入缓存,但要先检查它是否过大。到底是检查谁的大小?数据还是缓

7、存?如果是数据,应该写成:/ 将数据插入缓存,但要先检查数据是否过大。或者更简洁些:/ 若数据足够小,则将其插入缓存。3.澄清模糊的用词。其实注释的书写与标识符的命名一样,都要竭力避免歧义与模糊表述。比如在某个网络爬虫程序中:# 根据原来是否收录过该URL,对其赋予不同的优先级。优先度的高低到底怎么个“不同”法?没抓取过的网址优先度高,还是已经收录过的网址优先度高?应该用更为清晰的表述来阐明这个问题:# 给未曾收录的URL赋予更高的优先级。4.可能引发不同理解的情况,可以注释,但仍应尽力将其纳入标识符中。例如:/ 返回文件的行数。public int countLines(String fil

8、ename) . 那么,”hellonr crueln worldr”到底是几行?如果只考虑n,就是3行;如果只考虑”nr”,就是两行。还有,如果文件内容是”hellon”,那么考虑n之后的空字符串”吗?算的话就是两行,不算就是1行。如果类似Unix的wc程序那样,无视一切特例,只认n为标准,那么就应在注释中写明:/ 根据新行符n计算文件行数。public int countLines(String filename) . ARC作者举的这个例子并不错,不过小翔以为这种情况最好还想办法把可能消除理解歧义的因子纳入到标识符之中。比如我可能会直接将方法命名为:/* * 以“新行符数加1”为标准统计

9、文件内容所占行数。 * (其余的参数、返回值及异常说明) */public int countLinesByNewline(String filename) . 这样一来就可以省掉刚才那行注释了,而把宝贵的信息空间留给更有用的内容。而且,我心里还有个小算盘:万一将来更改了统计标准,那么标识符中赫然在目的“Newline”不得不引起修改者的注意,逼着其将它重构为符合新算法的名称,比如countLinesByCarriageReturn;反之,如果单单将它放到了注释中,那么很容易就会被忽视了。这里要多说两句。至少小爱我认为:第一,注释,尤其是Javadoc这样的API注释,是特别需要字斟句酌的。在

10、表达清晰不致引发歧义的前提下,应该尽量简省。这样代码拥有者维护起来也方便,类库使用者运用起来也方便,对双方都有好处。第二,简省下来的空间应当更加着墨于那种不能或者不便纳入标识符的问题,这包括:可接受的参数范围、返回值以及各种可能发生的异常情况。切忌将本来可以纳入API标识符中的意涵放到注释里面大说特说,这样注释本应强调的其他信息就会被淡化,而且代码也没有充分利用标识符来容纳重要信息。5.应该用注释来精确地描述那些微妙的函数行为,如边界状况、特殊输入值等。有些函数行为仅仅通过其标识符是无法判断的,如果硬要将这些可能引发理解问题的要素全部都纳入标识符中,恐怕很难有人能记住那么长的符号。这个时候最好

11、是请注释帮忙,来厘清那些微妙之处。在程序开发中,最精准和简洁的文句其实还是用例,有的时候只要举出例子来,代码阅读者就可以依此来理解程序的意图了。例如:/ 从src输入源中移除包含chars的前后缀。public String strip(String src, String chars) . 到底是将chars中指明的所有字符无差别地移除,还是精确地按照chars中字符的排列顺序来移除。如果前后缀中出现了多份匹配数据,那么是移除一份还是全部移除?要想澄清上述疑问,还是举特例吧:/ 例如,strip(abba/a/ba, ab)将返回/a/。public String strip(String

12、src, String chars) . 上述这个特例举得就很好,首先它说清了第一个问题,应该是无差别地移除,而不是精确匹配,不然的话,返回的就是”ba/a/ba”了。而且还说清楚了第二个问题,应该是尽可能地移除多份前后缀,而不是仅移除最外部的一份,不然的话,返回值就是了”ba/a/”了。所举的例子必须具备特殊性。太过简单的说明不了问题:/ 例如,strip(ab, a)将返回b。上面这个例子既没说清第一个疑问,也没说请第二个问题。其实,如果你和小翔一样,在代码质量这个问题上,是个“普瑞坦派”(Puritan,清教徒。至于为什么鸡毛蒜皮的小事都要分成这派那派的,请参考历史魔幻题材动漫巨著银魂)

13、,那么我低调地建议可以将上面这两个招人疑惑的问题通过标识符来澄清:/* * 移除所有与无序字符组相匹配之前后缀。 * (其余的参数、返回值及异常说明) */public String stripAllPrefixesAndSuffixes(String src, String unorderedCharsToRemove) . 这个颇有些自鸣得意的修改,和前面那个例子一样,可以将更多的文字空间留给那些更加“说不清”的事情,像是参数范围、返回值、异常等。所以我还是那句老话:不管怎么说,对注释的提炼毕竟还是很有可能促成对标识符甚至领域模型的完善。所以,注释这东西,确实是“写写更健康”。(本来我这广

14、告专业的老毛病又犯了,想用这五个字作为本文的标题,后来想想那太过做作了,于是就小清新了一把)对于这个问题,ARC一书举的第二个例子倒真的是很恰当:/ 重排l中元素的位置,使小于pivot的元素出现在大于等于它的元素之前。/ 然后返回小于pivot的元素中下标最大者之下标,若无此种元素,则返回-1。public int partition(List l, int pivot).如果举出特例,则可以厘清几个疑惑:/ / partition(8 5 9 8 2, 8)将会把l重排为5 2 | 8 9 8,并返回1。public int partition(List l, int pivot).这个特

15、例说明了好几个问题:重排所参考的标竿元素”8恰好与列表中的元素有重复,直接写出运行结果可以澄清该方法在边界状况的行为。由举例可知该方法可以接纳含有重复元素的列表。排列后的两段是各自无序的。返回值1不在列表元素之中,厘清了误解:该方法返回的是“小于指标值的元素所具有之最大下标”,而不是“小于指标值的最大元素”(那样的话返回的是5),也不是“左方区域下标最大者所对应的集合元素”(那样的话是2)。如果没有这个“多功能”的例子,那么上面这四个问题很容易惹人疑惑。毕竟写得再好的文字注释还是会有人视而不见,此时只能通过这种华丽的例子来吸引这些程序员的眼球了。6.遇到文档不完备等带有缺陷的第三方库时,应该编写学习型测试用例来掌握其用法,并进行适当封装。本来这一条是我阅读刚才那个例子想到的,但是鉴于它一来非常重要,二来有些程序员又有那么一种侥幸心理,对于含有明显缺陷的第三方库,希望通过马马虎虎的几行程序来随意应付过去,所以我必须将这个问题单独列出来说明。如果第三方代码的注释本身不是很清晰,需要如何来

展开阅读全文
相关资源
正为您匹配相似的精品文档
相关搜索

最新文档


当前位置:首页 > IT计算机/网络 > 其它相关文档

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号