写点什么

你写注释吗?写你就输了

  • 2020-07-20
  • 本文字数:5078 字

    阅读完需:约 17 分钟

你写注释吗?写你就输了

本文最初发布于 Level Up Coding 官方博客,经原作者授权由 InfoQ 中文站翻译并分享。


我并不是提倡不写代码注释,只是建议不要过于依赖注释,这样可以使代码更干净、更有表现力,这也能提高开发人员的水平。我自己也在寻求编写更简洁的代码,我尽力不编写糟糕的注释,并在可能时重构代码。



这篇文章的标题可能会让你情绪激动,但请先耐心听我说完。在适当的位置写下适当的注释可能非常有用,但是没有什么比无用的注释更让代码混乱了。在某些情况下,我敢说,注释可以弥补我们在代码中没有完全表达出来的意思。因此,写注释不值得赞美,而是应该停下来问问自己,是否有更好的方式可以用代码来表达自己。


带有少量注释的清晰而富于表现力的代码,要比带有大量注释的混乱而复杂的代码好得多。如果你已经把代码弄得一团糟,不要花时间写注释来解释,而是要花时间梳理代码。如果每次写注释的时候,你都冥思苦想,觉得自己的表达能力不足,那么最终你就会写出简洁明了的代码,完全没有必要写注释。鼓励自己用代码表达。

为什么对注释如此不屑?

因为它们会说谎,还会把代码弄得乱七八糟。虽说并非总是如此,也并非有意如此,但却经常如此。糟糕的代码和带有大量注释的代码之间有很高的相关性。注释存在的时间越久就越容易偏离它们所描述的代码——在某些情况下,它们可能是完全错误的。实际上,随着代码库和团队的增长,维护注释成了不可能的事情。


注释不同于《辛德勒的名单》。它们不是“纯善的”。事实上,注释充其量是一种必要的恶。——Robert C.Martin


当谈论关于注释的话题时,很重要的一点是,我们要看一下什么是恰当的注释,什么是糟糕的注释,这样我们才能学会写更好的注释,或者完全避免注释。

恰当的注释

并不是所有的注释都是不好的——有些注释实际上非常必要。

出于法律目的的注释

有时候,你可能需要出于法律目的编写特定的注释,比如开源项目的创作许可。一些现代化的 IDE 和文本编辑器会自动将它们折叠起来,保持工作区的整洁。

魔术表达式

如果你有一个复杂的 SQL 或正则表达式,它以神奇的方式做了一些令人兴奋的事,那么请务必注释,以便让读者更容易理解,因为我们都不是 Regex 忍者。


// 匹配电子邮件地址的正则表达式var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;return re.test(String(email).toLowerCase());// 注意:添加一个富于表现力的函数名,注释就变得没有必要了function validateEmail(email) {     var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;    return re.test(String(email).toLowerCase());}
复制代码

说明意图

在某些情况下,注释有助于解释决策或特定解决方案背后的意图。例如,测试套件中的一条注释告诉我们添加这行代码是为了降低死锁的几率。


for x in range(1, 500):    # 这是运行多个并行测试时预防死锁的最好方法  time.sleep(0.5)  runTest(x)
复制代码

结果预警

用注释说明代码可能会产生严重的或可怕的后果,甚至鼓励这样做。在本例中,开发人员让读者知道,当与回调函数一起使用时,QT 函数不是线程安全的。一般来说,如果一条注释可以避免某个人在编程时陷入绝望,那么它就是有用的。


"""  许多Qt函数都不是线程安全的。如果你使用回调函数,  即使你在所有绘制调用代码的周围都加上锁,你也会遇到段错误,  因为Qt的主事件循环仍在运行,并且使用了没加锁的资源。"""from multiprocessing.pool import ThreadPoolimport sysfrom threading import Lockimport timefrom PyQt5 import QtCore, QtWidgetsclass Task(QtCore.QObject):    updated = QtCore.pyqtSignal(int, int)    ...............    ...............
复制代码

TODO 注释

这些注释可以帮助我们标记那些我们认为应该做,但是由于某些原因没有做到的事情。它可能会提醒你删除废弃的特性,或者请求其他人查看某个问题。它可能是要求其他人想一个更好的名字,或者是提醒他们根据计划事件做出修改。


请记住,TODO 注释不是在系统中留下糟糕代码的借口。本质上,每一行代码都是一种负担——最安全、最快的代码是根本没有代码。


现在,大多数优秀的 IDE 都提供了特殊的指令和特性来定位所有的 TODO 注释,所以不太可能漏掉它们。尽管如此,你也不希望代码中到处都是 TODO。所以要经常浏览一下,删除那些你能删除的。

糟糕的注释

这个清单比较长,但在本节中,我们将看到一些更为老生常谈而又随处可见的注释。

明知故问的注释

有些注释的意思显而易见,即它们没有增加任何实际的价值,而且大多是噪音。


下面是一个开源项目的代码片段,其中包含大量明知故问型的注释,这些注释使代码变得混乱而晦涩。它们所提供的信息并不比代码本身多,而且在某些情况下,阅读注释的时间甚至比阅读代码长。


/*** 与该容器相关的集群*/protected Cluster cluster = null;/*** 人类可读的容器名 */protected String name = null;/*** 该容器的父容器*/protected Container parent = null;/*** 创建一个Loader配置父类加载器*/protected ClassLoader parentClassLoader = null;
复制代码

不清不楚的注释

如果你写注释是为了符合公司规定,或者你只是觉得有必要添加一些注释,那么你在注释时就不会进行适当的思考。所以,如果你真的写了一条注释,花点时间让它对阅读它的人有所帮助。


def load_config():  try:    do_useful_stuff()  except Exception as ex:     # 如有异常,退回到默认状态。
复制代码


在这个例子中,作者想要传达一些有关异常情况的重要信息。但这条注释没能解释清楚我们将退回到什么样的默认状态。如果一条注释要求我们转到另一个模块来找出默认值,那么它就没有发挥应有的作用。

注释掉代码

在团队准备好删除代码之前先将其注释掉似乎是一个好主意,但是不要这样做。注释代码是一种弊端,团队中的其他成员不会删除它,因为他们会认为它很重要。我们不是都在使用源码控制吗?所以我们不需要保留旧的代码。我们可以跳到任何我们想要的版本。

噪音注释

有些注释毫无意义,纯粹是噪音。时间久了,我们的大脑就会走马观花,我们也会开始跳过那些需要注意的重要注释。考虑一下下面的例子,其中的注释提供了很多价值吗?


-----------------------------# Exhibit A# 默认构造函数def get_todays_date():  return date.today()-----------------------------# Exhibit B# 返回月份的天# @return: 月份的天def get_day_of_month()  return day_of_month
复制代码


用编写干净代码的决心取代制造噪音的诱惑,你将成为一个更好、更快乐的程序员。

强制性注释

这肯定会引起争议。如果规定每个函数都需要一个 Java 文档或 Python docstring,是不是有点傻?大多数时候,类或函数名已经告诉我们注释所描述的内容,它们是多余的。在这个例子中,注释的数量比代码的数量还多——这让我很恼火。


class ComplexNumber:     """     这是一个用于复数的数学运算类。          属性:        real (int):复数的实部。        imag (int):复数的虚部。    """      def __init__(self, real, imag):         """         ComplexNumber类的构造函数。          参数:           real (int):复数的实部。           imag (int):复数的虚部。          """      def add(self, num):         """         该函数用于复数求和。          参数:            num (ComplexNumber):要加的复数。                  返回值:            ComplexNumber:包含和的复数。        """          re = self.real + num.real         im = self.imag + num.imag           return ComplexNumber(re, im)   help(ComplexNumber)  #访问类的docstring help(ComplexNumber.add)  # 访问方法的docstring 
复制代码

使用好的函数名或变量名

你可以使用更具表达性的函数和变量名替换注释,从而使代码更简洁。考虑下面的例子,第一个例子中的注释就变得没有必要了,因为有一个更好的函数名可以准确地告诉读者这个函数做了什么。


# 检查日期是否是过去的日期def check_date(date):   if date < today:     return true    return false 
def is_past_date(date): if date < today: return true return false
复制代码

注释不能弥补代码的糟糕

编写注释有一个比较常见的原因是糟糕的代码。我们以前都见过这种情况,在某种程度上,我们自己也犯过这样的错误。我们写一个模块或类,我们心里知道它混乱而无序。我们知道它一团糟。所以我们对自己说,“哦,我最好加下注释!”不!你最好把代码梳理清楚!


/* 这段代码糟透了。我知道,你知道,每个人都知道。我们假装什么都没发生,然后继续前进。以后你叫我白痴好了。*/
复制代码

小结

我并不是提倡不写代码注释,只是建议不要过于依赖注释,这样可以使代码更干净、更有表现力,这也能提高开发人员的水平。我自己也在寻求编写更简洁的代码,我尽力不编写糟糕的注释,并在可能时重构代码——将我的代码从宜家的一幅画变成梵高的作品。


所以让我们约法三章,不要写这么多注释。


原文链接:


Every time you comment code — you’ve already failed.


2020-07-20 13:503140

评论

发布
暂无评论
发现更多内容

epoll底层实现源码及epoll反应堆模型

hanaper

IntelliJ IDEA 如何快速查看提交代码的对比

HoneyMoose

Vue进阶(八十九):watch 用法详解

No Silver Bullet

Vue 9月日更

【LeetCode】二叉搜索树的后序遍历序列Java题解

Albert

算法 LeetCode 9月日更

cxx 的枚举类型

hedzr

c++ 算法 枚举

粗放生长时代结束,高精地图收紧灰色地带

脑极体

在线JSON转Mongoose工具

入门小站

工具

08. 语音识别与第二次AI热潮

Databri_AI

人工智能

JDK 8 及其后续 JDK 中 Period 和 Duration

HoneyMoose

LeetCode刷题13-简单-罗马数字转整数

ベ布小禅

9月日更

网络攻防学习笔记 Day123

穿过生命散发芬芳

9月日更 互联网安全 流量采集

JavaScript数组常用的方法总结

孙叫兽

JavaScript 大前端 数组 引航计划

看懂这个故事,轻松实现从技术到管理的华丽转身!

博文视点Broadview

Java中高级核心知识全面解析,Java工程师需要掌握的技能

Java 程序员 后端

C语言:十进制、十六进制数据互换

不脱发的程序猿

C'语言 进制转换

活动回顾 | Apache Hudi x Pulsar Meetup 杭州站(戳进看视频)

Apache Pulsar

阿里云 Apache Pulsar StreamNative

打分吧!客服小姐姐,评分页面与客户总分页面的 Django 实现

梦想橡皮擦

9月日更

Java全面学习视频书籍,阿里架构师看到都觉得好,Java自学教程

Java 程序员 后端

Excelize 开源五周年 🎉

xuri

Excel Excel数据分析 Go 语言 Excelize

【Flutter 专题】59 图解 Android Native 获取 Flutter 资源文件

阿策小和尚

Flutter 小菜 0 基础学习 Flutter Android 小菜鸟 9月日更

Linux之lastb命令

入门小站

Linux

架构实战训练营|作业|模块2

Frode

#架构实战营

Go 专栏|开发环境搭建以及开发工具 VS Code 配置

AlwaysBeta

Go 语言

找啊找啊找工长,找到一个好工长

escray

生活记录 9月日更

【LeetCode】所有奇数长度子数组的和Java题解

Albert

算法 LeetCode 9月日更

HTTP系列之:HTTP缓存

程序那些事

缓存 Netty HTTP 程序那些事

linux网络包收发讲解

赖猫

Linux

LeetCode题解:143. 重排链表,数组,JavaScript,详细注释

Lee Chen

算法 大前端 LeetCode

C语言:十进制、BCD码互换

不脱发的程序猿

C语言 十进制、BCD码互换

架构实战营模块七作业

老猎人

架构实战营

前端开发css这些样式你还熟悉吗,Chrome是必备技能

你好bk

CSS html css3 大前端

你写注释吗?写你就输了_语言 & 开发_Tameem Iftikhar_InfoQ精选文章