写点什么

你写注释吗?写你就输了

  • 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:503185

评论

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

时序数据库能做什么|用 GreptimeDB 进行程序员键盘行为分析,最高频按键竟然是它

Greptime 格睿科技

云原生 时序数据库 数据库·

技术实力与社区贡献的双重认可!乔嘉林成为 Apache 软件基金会 Member

Apache IoTDB

IoTDB Apache IoTDB

IoTDB Raft协议核心研发宋子阳成为Ratis Committer:分布式IoTDB深度并创新运用Raft协议保障高可用

Apache IoTDB

IoTDB Apache IoTDB

华为云助力制造业解决数字化转型困局:高效上云,加速创新

科技怪授

如何使用文件传输协议ftp,教你使用文件传输协议命令行

镭速

华为云助力制造业高效实现数字化转型

科技怪授

趣谈之什么是 API 货币化?

API7.ai 技术团队

api 网关 APISIX api 货币化

华为云推出场景化解决方案帮助制造企业加速数字化转型

科技怪授

如何解决制造企业数字化转型中的数据散乱和管理难题,实现顺利转型?

i生活i科技

3DCAT实时云渲染助力数字孪生检修车间建设,为智能制造赋能!

3DCAT实时渲染

数字孪生 实时渲染 3D实时云渲染

打卡智能中国(三):一位水厂文员的多重身份

脑极体

云计算

“PMC零距离” 赖晖:在 IoTDB 我实现了参与贡献共识协议的兴趣方向!

Apache IoTDB

IoTDB Apache IoTDB

翻过三座大山:MatrixOne从 NewSQL 到 HTAP 分布式架构演进

MatrixOrigin

分布式数据库 MatrixOrigin MatrixOne 架构升级

码头风云——5G降临

白洞计划

5G 智慧码头

什么是安全沙箱技术?如何保护用户隐私和系统安全?

FinFish

前端容器 小程序容器 安全沙箱 小程序安全沙箱

架构师应该具备的特质

agnostic

架构师

BUFF NETWORK:去中心化衍生品交易的未来

股市老人

MobTech MobPush|推送的下发逻辑是什么样的

MobTech袤博科技

Go 语言读取文件的几种方式

宇宙之一粟

Go 语言

ChatGPT-5到底有多强?Battle!咱貌似也不输呀!

加入高科技仿生人

人工智能 AI 低代码 ChatGPT GPT-4

阿里正式加入ChatGPT战局,“通义千问”上线后表现如何?

引迈信息

AI 阿里 低代码 语言模型 ChatGPT

StarRocks 3.0 新特性介绍

StarRocks

c++ 数据湖 #java 数据库· 大数据‘’

历史性的时刻!华为云跨端、跨框架开源组件库项目 OpenTiny 正式升级 TypeScript,10 万行代码重获新生!

英勇无比的消炎药

开源 前端 UI组件库

借力函数计算 FC,HEROZ 打造专业级 AI 日本将棋服务

阿里巴巴云原生

阿里云 云原生 函数计算

Excelize 发布 2.7.1 版本,Go 语言 Excel 文档基础库

xuri

开源 编程 Excel Go 语言 Excelize

前端线下面授培训机构该怎么选择

小谷哥

华为云ERP上云解决方案助力企业迈向云端,提升业务效率与安全性

i生活i科技

软件测试/测试开发丨该如何测客户端专项测试?

测试人

软件测试 自动化测试 测试开发 专项测试

前端技术培训学习哪个机构靠谱?

小谷哥

新晋 Committer 也有 “产学研联动”?速来围观不同视角共建 IoTDB 社区的故事!

Apache IoTDB

IoTDB Apache IoTDB

多库多表场景下使用 Amazon EMR CDC 实时入湖最佳实践

亚马逊云科技 (Amazon Web Services)

Java

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