问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

代码注释规范最佳实践:如何编写高质量注释

创作时间:
作者:
@小白创作中心

代码注释规范最佳实践:如何编写高质量注释

引用
1
来源
1.
https://blog.axiaoxin.com/post/how-to-comment-your-codes/

在编程过程中,编写优质的代码注释对提升代码的可读性和维护性至关重要。虽然有很多资源可以帮助程序员编写更好的代码,例如书籍和静态分析工具,但针对如何编写优质注释的资源却比较少。注释的数量容易衡量,而质量则难以评估,两者之间也不一定有直接关系。实际上,糟糕的注释可能比没有注释还要糟糕。以下这些规则将帮助你在注释和代码之间找到平衡点。

著名的 MIT 教授 Hal Abelson 曾说:“程序必须为人类阅读而写,只有附带地为机器执行。”这句话强调了程序有两类不同的读者。编译器和解释器忽略注释,只要语法正确,它们就认为程序是一样易于理解的。但人类读者却不一定如此,我们常常发现有些程序难以理解,这时候注释的作用就显得尤为重要。

规则 1:注释不应重复代码

许多初学者因为在入门课程中被教导写注释,结果往往在代码的每个大括号后面都加上注释,标明块的结束位置:

   // some code
} // if

这样的注释增加了视觉混乱,耗费了编写和阅读的时间,并且容易过时。典型的坏例子是:

这样的注释没有提供任何有用的信息,反而增加了维护成本。

规则 2:好的注释不能成为写不清楚代码的借口

注释应该提供代码中没有的背景信息。例如,使用单字母变量名并添加注释来说明它的用途:

func getBestChildNode(node *Node) *Node {
    var n *Node // 最佳子节点候选者
    for _, child := range node.Children {
        // 如果当前状态更好则更新n
        if n == nil || utility(child) > utility(n) {
            n = child
        }
    }
    return n
}

通过更清晰的变量命名,可以避免使用注释:

func getBestChildNode(node *Node) *Node {
    var bestNode *Node
    for _, currentNode := range node.Children {
        if bestNode == nil || utility(currentNode) > utility(bestNode) {
            bestNode = currentNode
        }
    }
    return bestNode
}

正如《编程风格要素》中的 Kernighan 和 Plauger 所说:“不要为糟糕的代码写注释——重写它。”

规则 3:如果无法写出清晰的注释,可能是代码本身存在问题

Unix 源码中有一句最臭名昭著的注释是“你不需要理解这段代码”,这段注释出现在复杂的上下文切换代码之前。Dennis Ritchie 后来解释说,这注释的本意是“这段代码不会出现在考试中”,而不是挑衅。不幸的是,连他和合著者 Ken Thompson 自己也没能理解这段代码,最终不得不重写。

这让人想起 Kernighan 的法则:

调试代码的难度是编写代码的两倍。因此,如果你尽可能聪明地编写代码,那么从定义上来说,你就无法足够聪明地来调试它。

规则 4:注释应消除困惑,而非制造困惑

Steven Levy 的《黑客:计算机革命的英雄》讲述了一个故事:

Peter Samson 拒绝在他的源代码中添加解释当前操作的注释。他写的一个包含数百条汇编指令的程序只有一个注释,注释内容是 RIPJSB,而这一指令的值是 1750。有人绞尽脑汁试图理解其含义,直到有人发现 1750 是巴赫去世的年份,而 RIPJSB 是“愿约翰·塞巴斯蒂安·巴赫安息”的缩写。

尽管这样的注释令人印象深刻,但并不是一个好的示范。如果注释只会引起混淆而不是消除困惑,就应该将其删除。

规则 5:解释非惯用代码

注释应解释那些其他人可能认为不必要或冗余的代码,例如:

value := (new JSONTokener(jsonString)).nextValue()
// JSONTokener.nextValue() 可能返回一个等于 null 的值。
if value == nil || value == interface{}(nil) {
    return nil
}

如果没有注释,某人可能会“简化”代码或将其视为神秘但必需的咒语。通过编写注释来说明代码的必要性,为后续读者节省时间和精力。

判断代码是否需要解释时需慎重。如果是常见的惯用法,无需注释,除非是写给新手的教程。

规则 6:提供复制代码的原始来源链接

许多程序员会使用网上找到的代码。提供来源可以让后续读者获取完整的上下文,例如:

// 将 Drawable 转换为 Bitmap,代码来自 https://stackoverflow.com/a/46018816/2219998。

通过链接,读者可以了解代码作者是谁,背景是什么,评论者的建议等。避免让读者自己去搜索公式或代码,直接粘贴 URL 更为方便。

在复制代码时,应理解其含义,避免直接粘贴不理解的代码。

规则 7:在最有帮助的地方包含外部引用链接

不仅是 Stack Overflow 的链接,标准文档等也应在适当的位置引用:

// http://tools.ietf.org/html/rfc4180 建议CSV行应以CRLF结尾,因此使用 \r\n。
csvStringBuilder.WriteString("\r\n")

这些链接帮助读者理解代码解决的问题,设计文档中的信息也应在需要时通过注释指明。

规则 8:修复 bug 时添加注释

不仅在编写代码时要添加注释,修改代码尤其是修复 bug 时也应添加注释。例如:

// 注意:在 Firefox 2 中,如果用户将鼠标拖出浏览器窗口,则不会接收到 mouse-move(甚至 mouse-down)事件,
// 直到用户将鼠标拖回窗口内。onMouseLeave() 方法实现了一个变通方法来解决这个问题。
func onMouseMove(sender *Widget, x, y int) {
    // 实现代码
}

这样的注释有助于理解当前和相关方法中的代码,也有助于判断代码是否仍然需要以及如何进行测试。

也可以参考 Issue 跟踪器:

// 如果属性中没有标题,则使用名称作为标题(Issue #1425)

规则 9:用注释标记未完成的实现

有时需要提交代码,即使它还有已知的不足之处。为了明确这些不足,可以使用 TODO 注释来标记,例如:

// TODO: 目前我们只支持使用点号作为小数分隔符,未来需要支持逗号作为小数分隔符,
// 这将需要更新数字解析和其他将数字转换为字符串的地方,例如 FormatAsDecimal。

使用标准格式的注释有助于度量和解决技术债务。最好在 Issue 跟踪器中添加问题,并在注释中引用问题。

总结

希望以上例子能说明注释不能作为糟糕代码的借口;它们应该通过提供不同类型的信息来补充优质代码。正如 Stack Overflow 联合创始人 Jeff Atwood 所说:“代码告诉你如何做,注释告诉你为什么。”

遵循这些规则应能节省团队的时间和减少挫折。

当然,这些规则并不详尽,期待在评论区看到更多建议。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号