代码可读性与命名之道
许多人认为代码的可读性取决于使用的字母和符号。他们相信通过添加、删除或更改这些符号可以使代码更易读。在某种程度上,他们是对的。然而,根本原则是:代码的可读性主要取决于字母和符号如何占据空间。
这意味着两件事:
- 代码周围应有适当的空白。不要太多,也不要太少。
- 一行代码中应有适当的空间来分隔不同部分。不同的操作通常应放在不同的行上。应适当使用缩进来分组代码块。
根据这一原则,实际上是代码的缺失使事物可读。这是生活中的一个普遍原则——例如,如果书中字母和单词之间完全没有空格,就很难阅读。另一方面,在晴朗的夜空中很容易看到月亮,因为有很多清晰的黑色空间不是月亮。同样,当你的代码有适量的空间时,你可以轻松地看出代码的位置和内容。
例如,这段代码很难阅读:
|
|
而在行内、周围和行之间有适当间距的情况下,它变得易于阅读:
|
|
然而,也可能有太多或错误的空格。这段代码也很难阅读:
|
|
代码本身应占据与其含义成比例的空间。
基本上,含义丰富的小符号使代码难以阅读。含义不多的非常长的名称也使代码难以阅读。含义的数量和占据的空间应密切相关。
例如,这段代码不可读,因为名称太短:
|
|
这些名称占据的空间与它们的含义相比非常少。然而,使用适当大小的名称,代码块的功能变得更加明显:
|
|
另一方面,如果名称与它们所代表的含义相比太长,那么代码又变得难以阅读:
|
|
这一原则同样适用于整个代码块和单个名称。我们可以用单个函数调用替换上面的整个代码块:
|
|
这甚至比之前的任何示例都更易读。尽管我们使用的名称——print_quarterly_total
——比我们其他事物的名称稍长,但这没关系,因为它代表了比其他代码片段更多的含义。事实上,它甚至比我们的代码块本身更易读。为什么?因为代码块占据了大量空间,但实际上含义很少,而函数为相同的含义占据了更合理的空间。
如果一个代码块占据大量空间但实际上没有太多含义,那么它是重构的好候选。例如,这里有一个处理一些用户输入的代码块:
|
|
如果那是我们的整个程序,那可能足够可读。然而,如果这在许多其他代码中,我们可以这样使其更可读:
|
|
我们可以通过将其简化为这样来使其更可读:
|
|
在我们的代码中间阅读“handle_input”比尝试阅读上面的整个第一个块要容易得多,因为“handle_input”占据了正确的空间量,而块占据了太多空间。但请注意,如果我们做了类似h(input)
的事情,那将令人困惑且不可读,因为“h”太短,无法正确告诉我们代码在做什么。此外,handle_this_input_and_figure_out_if_it_is_x_or_y_and_then_do_the_right_thing(input)
不仅会让程序员输入起来烦人,还会导致代码不可读。
命名事物
一位著名程序员曾说过,命名事物是计算机科学中最难的问题之一。然而,这些可读性原则为我们如何命名事物提供了一些好线索。基本上,变量、函数等的名称应足够长以完全传达它是什么或做什么,但不应太长以至于难以阅读。
思考函数或变量将如何被使用也很重要。一旦我们开始将其放入代码行中,它是否会使这些代码行对于它们实际具有的含义来说太长?例如,如果你有一个函数只被调用一次,单独在一行上,该行没有其他代码,那么它可以有一个相当长的名称。然而,你将在复杂表达式中频繁使用的函数可能应该有一个短名称(尽管仍然足够长以完全传达它的功能)。
-Max
评论精选
Ahmed 说: 2011年1月25日上午11:21
优秀的“空间编码原则”。我认为这是你即将出版的书中一章。你可能需要更多示例,比如比较两段代码:
是编码更好
SELECT * FROM table WHERE x = y
还是
SELECT * FROM table WHERE x = y
为什么?
谢谢,
Ahmed。
回复 抱歉 Ahmed 说: 2011年1月26日下午12:19 你的两个示例看起来一样…
回复 Rob, Robot Vacuum 说: 2012年2月13日晚上7:54 我也看不出区别。
Max Kanat-Alexander 说: 2011年1月26日下午12:23 嘿 Ahmed!是的,我目前计划将其纳入书中,当然。 至于 SQL 间距风格,我们在 Bugzilla 项目中发现这种方法创建了最清晰的 SQL: http://www.bugzilla.org/docs/developer.html#sql-style -Max
Havvy 说: 2011年1月25日上午11:51 你用文字表达了我潜意识中认为是真实的东西。为此,谢谢。
Max Kanat-Alexander 说: 2011年1月26日下午12:25 谢谢!是的,这是我所有博客的目标之一,就是准确陈述我们中一些人“只知道”的这些事物是什么,并通过这样做帮助我们更准确地理解它们,并能够与每个人交流。 -Max
James Napolitano 说: 2011年1月25日晚上11:13
一个类似的例子,我发现间距对可读性有很大影响,是对齐相似的代码行:
firstX = rect.width + 5; lastX = rect.width + 50; firstY = rect.height + 10; lastY = rect.height + 20;
这在编写几行相关公式时特别有用;它让你几乎一眼就能看出它们之间的相似和不同之处。这样的代码几乎恳求放入某种表格格式;如果你的 IDE、代码编辑器或其他工具能为你做到这一点,那不是很好吗?
ccr 说: 2011年1月27日凌晨1:37 有些编辑器这样做。我知道 TextMate 可以(Cmd-Alt+]),除非我的记忆真的很模糊,Emacs 也给你这个选项。
James Napolitano 说: 2011年1月25日晚上11:34
啊,看起来代码标签不像 pre 那样保留空白,而且 pre 似乎在评论中不允许。(也许你可以在你的 css 中添加类似“code { white-space: pre }”的东西?也许为你的博客添加一个“预览评论”功能?)
这里再次是我的代码示例,空白转换为下划线:
firstX_=_rect.width__+__5; _lastX_=_rect.width__+_50; firstY_=_rect.height_+_10; _lastY_=_rect.height_+_20;
Michael Campbell 说: 2011年1月26日上午6:58 我从未理解这种“对齐”论点,尽管我一次又一次地看到它。它对我所做的只是让我的眼睛想垂直阅读代码,而其语义意义是水平获得的。我的代码不是列式的,我不明白为什么它应该被格式化为这样。
Michael Chermside 说: 2011年1月26日上午9:59
致 Michael Campbell:
实际上,我认为你正好击中了这种垂直布局之所以重要的确切原因——因为它使语义意义是垂直的而不是水平的时候变得清晰。
考虑给出的代码:firstX_=_rect.width__+__5; lastX__=_rect.width__+_50; firstY_=_rect.height_+_10; lastY__=_rect.height_+_20;
从编译器的角度来看,我们发布了4个赋值语句,它们可能彼此完全无关。但这不是代码的真正语义意义。代码的真正语义意义更接近于“用一定的填充设置由 rect 给出的点的所有4个边界”。
在一个真正有表现力的编程语言中,我们可能能够以不同的方式编写这个…像这样:xyBounds = boundsWithMargin(center=rect, leftPad=5, rightPad=50, topPad=10, bottomPad=20);
我想我们都会同意这更清晰,也不容易出错,但不幸的是,大多数时候我使用的语言不够强大,无法使用这种清晰的语法。将字段对齐在列中是一种穷人的方式,表明这些行是强相关的…实际上只是同一更大过程的不同部分。
James Napolitano 说: 2011年1月26日下午1:07
我同意,但即使有表达性语言语法,仍然会有几种强相关代码行的情况,例如在你的 boundsWithMargin 函数的实现中,或在这种情况下:xyBounds1 = boundsWithMargin(center=rect1, leftPad=5, rightPad=50, topPad=10, bottomPad=20); xyBounds2 = boundsWithMargin(center=rect2, leftPad=0, rightPad=15, topPad=20, bottomPad=10);
我习惯编写很多数学公式;这是一个你经常有相似代码行的情况,看到它们之间的对称性很重要。
Loup Vaillant 说: 2011年1月26日上午8:51 我同意关于空白管理,但大多数时候我们不会偏离漂亮打印机的输出太多。 然而,我不同意你关于名称长度的标准。我的规则是将名称的长度与其使用频率和命名事物的范围相关联。 使用越频繁,名称越短。范围越短,名称越短。例如,循环索引可以很容易地是一个字母,因为它是一个常见的习惯用法,而且范围非常短(循环内部)。 有些事物承载很多含义,但 nevertheless 有短名称。map 和 fold 是一个很好的例子,相当有意义,范围广(它们通常是标准库中的全局名称),但使用非常频繁。 现在是吹毛求疵:名称背后的复杂性和其使用频率之间存在非常强的负相关。所以你的规则大多会奏效。
Max Kanat-Alexander 说: 2011年1月26日下午12:30 嘿 Loup。如果你看博客的结尾,我确实谈到了名称的使用频率。 我不同意“范围越短,名称越短”作为一般原则——有时甚至循环变量也需要更长的名称来消除歧义(尽管我同意这是一个不寻常的情况)。 “map”和“fold”实际上含义相对很少。给它们一个短名称是可以的。 -Max
Curt Sampson 说: 2012年3月28日晚上11:49 我在“较小范围让你使用较短名称”这件事上与 Loup 一致;当你能在单屏文本中看到变量的每一次使用时,名称长于一两个字母有什么优势?那只是 clutter。我们为范围较大的事物使用较长的变量名,因为我们使用的变量上下文较少。 我也相当惊讶你会说“map”和“fold”含义很少;这在我看来就像说“加法”和“乘法”这些词含义很少。Map 和 fold 是操作值集合的基本构建块。(诚然,许多程序员似乎不知道这一点,更喜欢像“for”循环这样的东西,这些循环产生更长、更复杂、更难以理解的代码。 cjs@cynic.net
Michael Powell 说: 2011年1月26日下午3:01 另一个常见的误解是,可读性完全来自注释。 注释通常有用,但像标识符一样,它们应刚好足够长以传达重要含义。此外,它们应仅在确实需要澄清语句时使用。好的代码应 largely 为自己说话。 当我看到代码中每个函数名上方有一个10行注释块,并且每隔一行有一个注释描述它在做什么时,我内心死了一点。那不是可读的代码。那只是冗长。实际结构在注释中丢失。
Max Kanat-Alexander 说: 2011年1月26日下午3:05 是的,有趣的是你提到那个。在我的书中,这整篇文章最初是一个单段,谈论空间如何重要。就在那段下面,是另一段谈论注释通常只应解释为什么你做了某事,而不是什么它正在做。 我认为人们添加这么多无用的注释是过度简化实际上导致复杂性的一个很好的例子,我在一段时间前的一篇博客中作为一个段落中的单句写过,但自那以后没有真正扩展。 -Max
Michael Powell 说: 2011年1月26日晚上7:32 我 mostly 同意,但有时解释你在做什么是有用的。主要是在处理非常性能关键的代码片段时,你被迫以怪异和非直观的方式构建它以获得最大速度。此外,当你只是做一些特别复杂的事情时。在这些情况下,代码的实际功能——它做什么——可能不会立即明显,无论你的标识符名称和空白有多好。在这些情况下,解释你在做什么的注释,而不仅仅是你正在做什么,可能非常有价值。 我还发现注释有时方便用于分解有些笨拙的代码块。当你有一个200行长的函数(有时不可避免)时,将其分解成几个块并在每个块前放置一个短注释,给出其基本目的,可以对可读性创造奇迹。在这种情况下,注释既是为了视觉上间隔事物和分解它们,也是关于实际内容。 但注释,像任何东西一样,很容易被过度使用,很多对良好实践大声疾呼的人似乎支持在我看来只是疯狂的注释策略。他们让代码如此杂乱注释,以至于其实际结构丢失。 作为旁注,我目前正在实现 Perlin 噪声函数(并在其工作后继续到 Perlin simplex 噪声)。阅读 Ken Perlin 的参考实现是痛苦的。他会从这篇文章中受益匪浅。(见这里:http://www.flipcode.com/archives/Perlin_Noise_Class.shtml)
Max Kanat-Alexander 说: 2011年1月26日晚上8:31 哦是的,我完全同意你解释经常是必需的。例如,在 Perl 中我们使用很多正则表达式,那些通常需要注释才能容易理解。 我也同意分解长函数。我肯定有过一些。(尽管我通常确实至少能分解它们一点,但并不总是,这是真的。) 哈哈哈,Perlin 算法太糟糕了。 -Max
Max Kanat-Alexander 说: 2011年1月26日晚上8:32 好吧,哇,是的,我刚读了参考实现,那太可怕了。 -Max
Florian Over 说: 2011年7月26日晚上11:42 也许你可以重新思考一下。我很确定这个话题有一些文化方面。在其他时代(例如中世纪,单词之间没有空格)和其他文化(日语 anyone?)中,间距有不同的“默认值”。 这也取决于上下文。如果你(并理解自己)作为计算机科学家编码,你有像数学一样的间距上下文。如果你作为程序员编码,你有不同的上下文。 参见(例如 LISP 类)语言中的编码约定,其中函数看起来像是直接从科学论文中取出的。 将其与 Java 代码约定进行比较。方式更多空白。
Ricky 说: 2011年11月17日上午7:18
对我来说,代码的可读性全在于命名事物。
这个很烂:gU(2);
这个规则:getUserById(2)
Mike 说: 2011年11月29日凌晨3:52 我同意!! 代码在这方面就像诗歌。 它如何躺在页面上是美的一部分,艺术的一部分!
O’Boise 说: 2012年3月29日凌晨5:14 关于注释,我 sadly 在中间组。我倾向于在代码顶部解释基础知识,我写它的时间,以及我做了哪些更改,以防我需要在某个时候 revert。然后,通过时,尝试在块中注释代码,用十个词或更少说明接下来的几行要做什么。我认为这很标准。 然而,在外包世界中经常溜走的一件事是用客户的语言命名你的变量。如果支持它的个人不理解你的语言,你的名称有多清晰都无关紧要。我有幸接管了一个 xBase 程序的支持,其中一名合同员工为一些严肃的数据建模编写了几千行代码,但所有代码都用匈牙利表示法混合英语和斯洛伐克语。绝对 brilliant 代码,但不得不用斯洛伐克-英语词典调试相当耗时。
Anubhav 说: 2013年3月18日下午5:30 《Clean Code》书中关于命名约定的所有建议都很好。我最初对长但描述性的名称感到困惑,但它有它的优点,只有当你选择真正好的长名称形成句子时。