著名的麻省理工学院教授哈尔-艾贝尔森(Hal Abelson)曾说过: 代码首先是写给人看的,只是计算机拿去运行了而已。html
虽然他可能故意的低估了计算机运行代码的重要性,但他说的是很是正确的。咱们的成型有两个很是不一样的受众。编译器和解释器不会关注代码的注释,对于计算机来讲,全部语法正确的程序都是一样的容易理解的。而对于阅读代码的人来讲,则彻底是不同的。咱们发现有些的程序代码很是难以理解,咱们但愿经过增长注释来帮咱们阅读。node
有不少资源能够帮助程序员们写出更好的代码,例如图书文档和静态代码分析工具等。可是如何才能写出更好的代码注释的资源却不多。虽然,咱们能够很容易的度量程序中注释的数量,但很难去度量其质量,并且这二者之间也不存在必然的联系。一个糟糕的注释比没有注释更加糟糕。git
正如Peter Vogel所写的:程序员
•编写和维护注释是一项有开支工做。•你的编译器并不检查你的注释,因此计算机没有办法肯定注释是否正确而有效。•另外一方面,你能够保证计算机彻底按照你的代码的要求来运行。编程
虽然全部这些观点都是正确的,但若是走到另外一个极端:历来不写注释,也是一个错误。json
这里有一些基本的规则,能够帮助你提高写注释的能力:app
1.规则1:注释不该该与代码重复;2.规则2:好的注释不能做为代码不清晰的借口;3.规则3:若是你不能写出一个清晰的注释,那么你的代码可能也是有问题的;4.规则4:注释应该消除混乱,而不是制造混乱;5.规则5:在注释中解释不规范的代码;6.规则6:提供复制的代码的原始来源的连接;7.规则7:最最有帮助的地方加入外部参考资料的连接;8.规则8:在修复bug时添加注释;9.规则9:使用注释来标记不完整的实现;less
下面是对以上9条规则的详细说明,以及结合具体的案例,来阐释如何在实际编码中应用这些规则。ide
规则1:注释不该该与代码重复许多初级程序员在代码中写了太多的注释。由于他们在初学代码是被老师训练成这样。工具
例如不少人在每一个闭合的大括号上都加上一行注释,以代表那个代码块要结束。
if (x > 3) {…} // if
还有更严重的要求,在每行代码上都要加上注释。虽然这对初学者来讲多是一个有效的措施,但这样的注释习惯,就像孩子学习骑自行车的辅助轮同样,是最终须要放弃的。
不能增长任何信息的注释是负面价值的东西,应为他们:
•增长了视觉混乱;•浪费了读写的时间;•可能会过期;
典型的一个坏列子以下:
i = i + 1; // Add one to i
注释不附加任何有效的信息,但产生了维护成本。
要求对每行代码都写注释的规则,在Reddit上受到了嘲讽:
// create a for loop // <-- commentfor // start for loop( // round bracket// newlineint // type for declarationi // name for declaration= // assignment operator for declaration0 // start value for i规则2:好的注释不能做为代码不清晰的借口
注释的另一个被误用,就是提供了本应该在代码中出现的信息。一个简单的例子,有人用一个字母来命名一个变量,而后给变量添加一个注释来描述变量的用途:
private static Node getBestChildNode(Node node) {Node n; // best child node candidatefor (Node node: node.getChildren()) {// update n if the current state is betterif (n == null || utility(node) > utility(n)) {n = node;}}return n;}
其实,更好的变量命名能够消除对注释的须要:
private static Node getBestChildNode(Node node) {Node bestNode;for (Node currentNode: node.getChildren()) {if (bestNode == null || utility(currentNode) > utility(bestNode)) {bestNode = currentNode;}}return bestNode;}
正如 Kernighan和 Plauger 在《编程风格的要素》中写道:"不要注释坏的代码,而是重写它"。
规则3:若是你不能写出一个清晰的注释,那么你的代码可能也是有问题的在Unix源代码中最臭名昭著的注释是:你不该该理解这一点。她出如今一些毛茸茸的上下文切换代码以前。丹尼斯·里奇 (Dennis Ritchie) 后来解释说,它的目的是“本着‘这不会出如今考试中’的精神,而不是无礼的挑战。” 不幸的是,事实证实,他和合著者肯·汤普森 (Ken Thompson) 本身并不理解,后来不得不重写。
这让人想起克尼汉定律:
调试一段代码的难度是编写它们的两倍,所以若是你的代码写的尽量巧妙,按照定义而言,你可能没有能力来调试它了。
警告阅读者远离你的代码,就像打开你的汽车的危险信号灯:认可你正在作的事情是非法的。相反,将代码重写为你能很好理解并易解释的,最好是简单直接的。
规则4:注释应该消除混乱,而不是制造混乱若是没有史蒂文·列维的《***:计算机革命的英雄》中的这段故事,关于坏注释的讨论就不完整了:
[Peter Samson] 拒绝在他的源代码中添加注释来解释他在特定时间所作的事情,这一点尤为晦涩。Samson 编写的一个分布普遍的程序继续执行数百条汇编语言指令,在包含数字 1750 的指令旁边只有一个注释。注释是 RIPJSB,人们绞尽脑汁思考它的含义,直到有人发现 1750 是巴赫去世的那一年,而Samson写的是Rest In Peace Johann Sebastian Bach的缩写。
虽然我和其余人同样的欣赏一个好***,但这不是典范。若是你的注释引发了混乱,而不是消除混乱,那就请删除它吧。
规则5:在注释中解释不规范的代码对其余人可能认为不须要或者多余的代码进行注释是一个好主意,例如来自App Inventor 的代码:
final Object value = (new JSONTokener(jsonString)).nextValue();// Note that JSONTokener.nextValue() may return// a value equals() to null.if (value == null || value.equals(null)) {return null;}
若是没有注释,有人可能会简化代码,或者将其视为神秘但必不可少的咒语。经过写下为何须要好代码来节省将来阅读者的时间和焦虑。
须要判断代码是否须要注释,在学习Kotlin的时候,遇到过一个Android教程中的代码,形式以下:
if (b == true)
我立刻想到是否能够替换为:
if (b)
就像在 Java 中所作的那样。通过一些研究,我了解到可空布尔变量明确地与 true 进行比较,以免丑陋的空检查:
if (b != null && b)
所以,我建议不要对常见习语的去写注释,除非是专门为新手编写的教程。
规则6:提供复制的代码的原始来源的连接若是你像大多数程序员同样,有时会使用在网上找到的代码。包括对来源的引用使将来的读者可以得到完整的上下文,例如:
•正在解决什么问题•谁提供了代码•为何推荐该解决方案•评论者是怎么想的•它是否仍然有效•如何改进
例如,请考虑如下注释:
/** Converts a Drawable to Bitmap. via https://stackoverflow.com/a/46018816/2219998. */
按照注释中连接中信息能够看出:
•该代码的做者是Tomáš Procházka,他在Stack Overflow上排名前3%。•一个评论者提供了一个优化方法,已经被归入到repo中。•另外一个评论者提出了一个避免边缘状况的方法。
与此注释造成鲜明对比的是(稍做改动,为保护犯错者):
// Magical formula taken from a stackoverflow post, reputedly related to// human vision perception.return (int) (0.3 * red + 0.59 * green + 0.11 * blue);
任何想要了解上面代码的人都将不得不去搜索查找公式。粘贴 URL 比稍后查找引用要快得多。
一些程序员可能不肯意代表他们没有本身编写代码,但重用代码多是一个明智的举动,既节省了时间,又让你得到了更多关注。固然,你永远不该该粘贴您不理解的代码。
人们从 Stack Overflow 问题和答案中复制了大量代码。该代码属于须要署名的知识共享许可。引用注释就知足该要求。
一样地,你应该参考那些有帮助的教程,以即可以再次找到它们,并感谢他们的做者:
// Many thanks to Chris Veness at http://www.movable-type.co.uk/scripts/latlong.html// for a great reference and examples.规则7:最最有帮助的地方加入外部参考资料的连接
固然,并不是全部引用都指向了 Stack Overflow,能够考虑:
// http://tools.ietf.org/html/rfc4180 suggests that CSV lines// should be terminated by CRLF, hence the \r\n.csvStringBuilder.append("\r\n");
到标准和其余文档的连接能够帮助读者理解你的代码正在解决的问题。虽然这些信息可能会出如今设计文件中,但恰当的注释会在什么时候何地提供给读者最须要的信息。在这种状况下,跟随连接代表RFC 4180已经被RFC 7111更新,这是有用的信息。
规则8:在修复bug时添加注释不只应该在最初编写代码时添加注释,还应该在修改代码时添加注释,尤为是在修复错误时。考虑这个注释:
// NOTE: At least in Firefox 2, if the user drags outside of the browser window,// mouse-move (and even mouse-down) events will not be received until// the user drags back inside the window. A workaround for this issue// exists in the implementation for onMouseLeave().@Overridepublic void onMouseMove(Widget sender, int x, int y) { .. }
注释不只帮助读者理解当前和引用的方法中的代码,还有助于肯定是否仍然须要该代码以及如何测试它。
也能够帮助问题修复的跟进:
// Use the name as the title if the properties did not include one (issue #1425)
虽然git blame可用于查找添加或修改行的提交,但提交消息每每很简短,而且最重要的更改(例如,修复问题 #1425)可能不是最近提交的一部分(例如,移动从一个文件到另外一个文件的方法)。
规则9:使用注释来标记不完整的实现有时即便代码有已知的缺陷,也有必要签入代码。虽然不共享代码中已知的缺陷可能很诱人,但最好使这些缺陷明确,例如使用 TODO 注释:
// TODO(hal): We are making the decimal separator be a period,// regardless of the locale of the phone. We need to think about// how to allow comma as decimal separator, which will require// updating number parsing and other places that transform numbers// to strings, such as FormatAsDecimal
对此类注释使用标准格式有助于衡量和解决技术债务。更好的是,向你的跟进列表中添加一个问题,并在你的注释中引用该问题。
结论我但愿上面的例子已经代表注释不会成为错误代码的借口或修复;它们经过提供不一样类型的信息来补充良好的代码。正如 Stack Overflow 联合创始人 Jeff Atwood 所写,“代码告诉你如何,注释告诉你为何。”
遵循这些规则应该能够节省你和你的队友的时间和挫折感。
最后,我确信这些规则并不是详尽无遗,并期待在评论中看到更多的建议和补充。
原文:https://stackoverflow.blog/2021/07/05/best-practices-for-writing-code-comments/