作者:James Nash
原文:Best practices for writing code comments @stackoverflow.blog
译者:legend80s@
JavaScript与编程艺术
+ AI总结:本文详细介绍了何时写&何时不写注释,希望能给你启发
Hey小伙伴们,今天来聊聊编程中超级重要的一环------代码注释!👩💻👨💻
麻省理工的大佬Hal Abelson说过:"程序首先是写给人看的,顺便让机器执行。" 这话虽然听起来有点夸张,但确实点出了程序的主要受众。编译器和解释器无视注释,对它们来说,所有语法正确的程序都一目了然。但我们人类读者就不同了,有些程序比其他的更难理解,这时候就需要注释来帮忙啦!
虽然有很多资源教我们怎么写更好的代码,比如书籍和静态分析器,但关于怎么写更好的注释的资源却很少。注释数量容易衡量,但其质量却很难衡量,而且两者并不总是成正比。一个糟糕的注释还不如没有注释呢。Peter Vogel就说过:
- 写注释和维护注释都是成本。
- 你的编译器不会检查你的注释,所以没有办法确定注释是否正确。
- 另一方面,你可以确定电脑完全按照你的代码指示来执行。
虽然这些点都没错,但如果因此就完全不写注释,那就走极端了。下面有些规则可以帮助你找到平衡点:
🔹 规则1:注释不应该重复代码。 🚫
🔹 规则2:好的注释不能为不清晰的代码开脱。 🚫
🔹 规则3:如果你写不出清晰的注释,那可能是代码有问题。 🚫
🔹 规则4:注释应该消除困惑,而不是制造困惑。 🚫
🔹 规则5:在注释中解释非惯用代码。 ✔️
🔹 规则6:提供复制代码的原始来源链接。 ✔️
🔹 规则7:在最有帮助的地方包含外部参考链接。 ✔️
🔹 规则8:修复bug时添加注释。 ✔️
🔹 规则9:用注释标记未完成的实现。 ✔️
接下来的部分会解释每条规则,提供示例并解释如何以及何时应用它们。
✍️ 规则1:注释不应该重复代码 🚫
很多初级程序员因为入门老师的训练而写太多注释。我见过高年级计算机科学课的学生在每个闭合大括号旁边加注释,说明哪个代码块结束了:
java
if (x > 3) {
...
} // if
我也听说过老师要求学生给每一行代码加注释。虽然这可能对极端初学者来说是合理的政策,但这样的注释就像学步车,当你和大孩子们一起骑自行车时应该去掉。
没有添加任何信息的注释具有负面价值,因为它们:
- 增加视觉混乱
- 花时间写和读
- 可能变得过时
最典型的糟糕例子是:
java
i = i + 1; // 给i加1
这完全没有添加任何信息,却带来了维护成本。
要求每行代码都要加注释的政策在 Reddit 上被嘲笑过:
java
// 创建一个for循环 // <-- 注释
for // 开始for循环
( // 圆括号
// 新行
int // 类型声明
i // 声明名称
= // 赋值运算符声明
0 // i的初始值
✍️ 规则2:好的注释不能为不清晰的代码开脱 🚫
注释的另一个误用是提供本应在代码中的信息。一个简单的例子是,有人用单字母命名变量,然后加注释描述其目的:
java
private static Node getBestChildNode(Node node) {
Node n; // 最佳子节点候选
for (Node node: node.getChildren()) {
// 如果当前状态更好,则更新n
if (n == null || utility(node) > utility(n)) {
n = node;
}
}
return n;
}
通过更好的变量命名可以消除对注释的需求:
java
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自己都不理解它,后来不得不重写它。
这让人想起了 Kernighan定律:
调试在一开始就比编写程序困难一倍。因此,按照定义,如果你的代码写得非常巧妙,那么你就没有足够的能力来调试它。
警告读者远离你的代码就像打开汽车的双闪警告灯:承认你知道自己在做非法的事情。相反,重写代码,使其足够简单,可以解释清楚,或者更好的是,代码本身就很简单。
✍️ 规则4:注释应该消除困惑,而不是制造困惑 🚫
如果不讨论糟糕的注释,就不完整。来自Steven Levy的《黑客:计算机革命的英雄》中的这个故事:
PeterSamson
特别难以理解,他拒绝在源代码中添加注释来解释他在某个时间点正在做什么。Samson编写的一个广泛传播的程序包含了数百行汇编语言指令,只有一个注释在包含数字1750的指令旁边。注释是RIPJSB,人们绞尽脑汁想知道它的意思,直到有人发现1750年是巴赫去世的年份,Samson写了一个缩写,意思是"安息吧,约翰·塞巴斯蒂安·巴赫"。
虽然我很欣赏一个好的黑客行为,但这并不是一个好例子。如果你的注释引起困惑而不是消除困惑,那就删除它。
✍️ 规则5:在注释中解释非惯用代码 ✔️
注释可能被认为是多余或重复的代码是个好主意,比如这个来自 App Inventor 的代码(我所有正面例子的来源 ^_^):
java
final Object value = (new JSONTokener(jsonString)).nextValue();
// 注意JSONTokener.nextValue()可能返回一个等于null的值。
if (value == null || value.equals(null)) {
return null;
}
没有注释,有人可能会"简化"代码或将其视为一个神秘但必要的咒语。通过写下为什么需要这段代码,为未来的读者节省时间和焦虑。
需要做出判断,代码是否需要解释。当我学习Kotlin时,我遇到了一个Android教程中的代码:
java
if (b == true)
我立刻想知道是否可以替换为:
java
if (b)
就像在Java中一样。经过一点研究,我了解到可空的布尔变量显式地与true进行比较,以避免丑陋的null检查:
css
if (b != null && b)
我建议不要为常见的习惯用法添加注释,除非是专门为新手编写教程。
✍️ 规则6:提供复制代码的原始来源链接 ✔️
如果你像大多数程序员一样,有时使用你在网上找到的代码。包括对来源的引用可以让未来的读者获得完整的上下文,例如:
- 解决了什么问题
- 谁提供了代码
- 为什么推荐这个解决方案
- 评论者对此有何看法
- 它是否仍然有效
- 如何改进
例如,考虑这个注释:
ruby
/** 将Drawable转换为Bitmap。通过 https://stackoverflow.com/a/46018816/2219998。 */
点击链接到答案揭示了:
- 代码的作者是Tomáš Procházka,他在Stack Overflow上排名前3%。
- 一位评论者提供了一个优化,已经纳入代码库。
- 另一位评论者建议避免一个边缘情况。
与这个注释(稍微修改以保护无辜)形成对比:
arduino
// 从stackoverflow帖子中取出的神奇公式,据说与人类视觉感知有关。
return (int) (0.3 * red + 0.59 * green + 0.11 * blue);
任何想要理解这段代码的人都必须搜索这个公式。粘贴URL比以后找到参考要快得多。
一些程序员可能不愿意表明他们自己没有编写代码,但重用代码是一个明智的举动,节省时间并让你受益于更多眼睛的审视。当然,你不应该粘贴你不理解的代码。
人们从Stack Overflow的问题和答案中复制了很多代码。引用来源满足了Creative Commons许可要求的归属要求。
同样,你应该引用有帮助的教程,以便它们可以再次找到,并感谢它们的作者:
ruby
// 非常感谢Chris Veness在http://www.movable-type.co.uk/scripts/latlong.html
// 提供了一个很好的参考和示例。
✍️ 规则7:在最有帮助的地方包含外部参考链接 ✔️
当然,并非所有的参考都是Stack Overflow。比如:
swift
// http://tools.ietf.org/html/rfc4180建议CSV行
// 应该以CRLF结束,因此是\r\n。
csvStringBuilder.append("\r\n");
链接到标准和其他文档可以帮助读者理解你的代码正在解决的问题。虽然这些信息可能在某个设计文档中,但在最需要的时候和地点放置一个恰到好处的注释可以给读者提供指导。在这种情况下,点击链接表明RFC 4180已被RFC 7111更新------这是有用的信息。
✍️ 规则8:修复bug时添加注释 ✔️
注释不仅在编写代码时添加,而且在修改代码时,特别是修复bug时。考虑这个注释:
java
// 注意:至少在Firefox 2中,如果用户将鼠标拖到浏览器窗口外,
// 鼠标移动(甚至鼠标按下)事件将不会收到,
// 直到用户将鼠标拖回窗口。一个解决方法
// 存在于onMouseLeave()的实现中。
@Override
public void onMouseMove(Widget sender, int x, int y) { .. }
注释不仅帮助读者理解当前和引用方法中的代码,而且有助于确定代码是否仍然需要以及如何测试它。
引用 issue 也很有帮助:
arduino
// 如果属性中没有包含标题,则使用名称作为标题(问题#1425)
虽然可以使用git blame找到添加或修改某行的提交,但提交信息往往是简短的,最重要的更改(例如,修复问题#1425)可能不是最近的提交(例如,将一个方法从一个文件移动到另一个文件)的一部分。
✍️ 规则9:用注释标记未完成的实现 ✔️
有时即使代码有已知的限制,也需要提交代码。虽然可能倾向于隐瞒代码中的已知缺陷,但最好明确这些,例如使用TODO注释:
arduino
// TODO(hal):我们正在使小数分隔符成为句点,
// 无论手机的区域设置如何。我们需要考虑
// 如何允许逗号作为小数分隔符,这将需要
// 更新数字解析和其他将数字转换为字符串的地方,
// 例如FormatAsDecimal
使用标准格式的此类注释有助于衡量和解决技术债务。更好的是,在你的 issue 添加一个问题,并在注释中引用该问题。
🏁 结论
我希望上述示例已经表明注释不能为糟糕的代码开脱或修复 ;它们通过提供不同类型的信息来补充好的代码 。正如Stack Overflow联合创始人Jeff Atwood所写,"代码告诉你怎么做,注释告诉你为什么。"
遵循这些规则应该可以为你和你的团队节省时间。
话虽如此,我相信这些规则并不全面,并期待在评论中看到建议的补充。
🎭 Memes and cartoons 有关注释的搞笑图片
供你参考...
来源: www.reddit.com/r/Programme...
来源:www.reddit.com/r/Programme...
来源:geekandpoke.typepad.com/geekandpoke...
来源:geekandpoke.typepad.com/geekandpoke...
来源:www.commitstrip.com/en/2016/09/...?
来源:www.commitstrip.com/en/2013/07/...
来源:geekandpoke.typepad.com/geekandpoke...
来源:geekandpoke.typepad.com/geekandpoke...
以上就是今天的分享啦,希望对大家有所帮助!如果你也有自己的编程小技巧,欢迎在评论区分享哦!👇👇👇
欢迎关注公众号"
JavaScript与编程艺术
"。