代码整洁之道(摘录笔记)

《代码整洁之道.pdf》一书的观念:代码质量与其整洁度成正比。干净的代码,既在质量上较为可靠,也为后期维护、升级奠定了良好基础。

整洁代码

什么是整洁代码?

有多少程序员,就有多少定义。

我喜欢优雅高效的代码。代码逻辑应当直截了当,叫缺陷难以隐藏;尽量减少依赖关系,使之便于维护;依据某种分层战略完善错误处理代码;性能调至最优,省得引诱别人做没规矩的优化,搞出一堆混乱来。整洁的代码只做好一件事。

——Bjarne Stroustrup,C++ 语言发明者,《C++程序设计语言》一书作者。

整洁的代码简单直接。整洁的代码如同优美的散文。整洁的代码从不隐藏设计者的意图,充满了干净利落的抽象和直截了当的控制语句。

——GradyBooch,《面向对象分析与设计》一书作者。

整洁的代码应可由作者之外的开发者阅读和增补。它应当有单元测试和验收测试。它使用有意义的命名。它只提供一种而非多种做一件事的途径。它只有尽量少的依赖关系,并且要明确地定义和提供清晰、尽量少的API。代码应通过其字面表达含义,因为不同的语言导致并非所有必需信息均可通过代码自身清晰表达。

——DaveThomas,OTI 公司创始人,Eclipse 战略教父。

我可以列出我留意到的整洁代码的所有特点,但其中有一条是根本性的。整洁的代码总是看起来像是某位特别在意它的人写的。几乎没有改进的余地。代码作者什么都想到了,如果你企图改进它,总会回到原点,赞叹某人留给你的代码全心投入的某人留下的代码

——Michael Feathers,《修改代码的艺术》一书作者。

有意义的命名

软件中随处可见命名:变量命名、函数命名、参数命名、类命名、封包命名、目录命名、文件命名等。既然有这么多命名要做,不妨做好它。

名副其实

变量、函数或类的名称应该已经答复了所有的大问题。它该告诉你,它为什么会存在,它做什么事,应该怎么用。如果各称需要注释来补充那就不算是名副其实。

选个好名字要花时间,但省下来的时间比花掉的多。注意命名,而且一旦发现有更好的名称,就换掉旧的。这么做,读你代码的人(包括你自己)都会更开心。

统一命名规范

统一使用驼峰命名法(userCreateTime)或者匈牙利命名(user-create-time)或者下划线命名法(user_create_time)命名。

命名方式要保持一致:比如,类的命名使用首字母大写的名词(User)、实例对象的命名使用首字母小写的名词(user)、函数的命名使用动词+名词(saveUserInfo)、常量的命名使用全大写加下划线(MAX_USER_NUM)等。

清晰明确

避免使用与本意相停的命名,使用有意义的、容易解读、符合且不会有歧义的命名。

可搜索

使用便于搜索的命名。比如,搜索变量名 t,会出现许多不希望的结果,搜索变量名 timer 会好很多。

函数

短小

函数的第一规则是要短小。第二条规则是还要更短小。

在 20 世纪 80 年代,我们常说函数不该长于一屏。当然,说这话的时候,VT100 屏幕只有 24 行、80 列,而编辑器就得先占去 4 行空间放菜单。如今,用上了精致的字体和宽大的显示器,一屏里面可以显示 100 行,每行能容纳 150个 字符。每行都不应该有 150 个字符那么长。函数也不该有 100 行那么长,20 行封顶最佳。

只做一件事

函数应该做一件事。做好这件事。只做这一件事。

使用描述性的函数名

让函数名来描述要做的事。

别害怕长名称。长而具有描述性的名称,要比短而令人费解的名称好。长而具有描述性的名称,要比描述性的长注释好。使用某种命名约定,让函数名称中的多个单词容易阅读,然后使用这些单词给函数取个能说清其功用的名称。

别害怕花时间取名字。你当尝试不同的名称,实测其阅读效果。在 Eclipse 或 Intellij 等现代 IDE 中改名称易如反掌。使用这些 IDE 测试不同名称;直至找到最具有描述性的那一个为止。

选择描述性的名称能理清你关于模块的设计思路,并帮你改进之。追索好名称,往往导致对代码的改善重构。

参数

最理想的参数数量是零(零参数函数),其次是一(单参数函数),再次是二(双参数函数),应尽量避免三(三 参数函数)。有足够特殊的理由才能用三个以上参数(多参数函数)———所以无论如何也不要这么做。

如果函数看来需要两个、三个或三个以上参数,就说明其中一些参数应该封装为类了。

从参数创建对象,从而减少参数数量,看起来像是在作聲,但实则并非如此。当一组参数被共同传递,对象中的属性往往就是对像的某个概念的一部分。

无副作用

函数承诺只做一件事,但还是会做其他被藏起来的事。有时,它会对自己类中的变量做出未能预期的改动。有时,它会把变量搞成向函数传递的参数或是系统全局变量。无论哪种情况,都是具有破坏性的,会导致古怪的时序性耦合及顺序依赖

避免重复

代码块重复带来的问题是,当代码修改时,需要改多处地方,且代码也会因此臃肿。

重复最明显的形态是你不断看到明显一样的代码,就像是某位程序员疯狂地用鼠标不断复制粘贴代码。可以用单一方法来替代之。

重复可能是软件中一切邪恶的根源。许多原则与实践规则都是为控制与消除重复而创建。

丢弃死函数

永不被调用的方法应该丢弃。保留死代码纯属浪费。别害怕删除函数。记住,源代码控制系统还会记得它。

注释

别给槽糕的代码加注释—重新写吧。

——Brian W Kernighan 与 PJ.Plaugher。

什么也比不上放置良好的注释来得有用。什么也不会比乱七八糟的注释更有本事摘乱一个模块。什么也不会比陈旧、提供错误信息的注释更有破坏性。

注释并不“纯然地好”。实际上,注释最多也就是一种必须的恶。若编程语言足够有表达力,或者我们长于用这些语言来表达意图,就不那么需要注释——也许根本不需要。

注释的恰当用法是弥补我们在用代码表达意图时遭遇的失败。**注释总是一种失败。**我们总无法找到不用注释就能表达自我的方法,所以总要有注释,这并不值得庆贺。

如果你发现自己需要写注释,再想想看是否有办法翻盘,用代码来表达。每次用代码表达,你都该夸奖一下自己。每次写注释,你都该做个鬼脸,感受自己在表达能力上的失败。

我为什么要极力变低注释?因为注释会撒谎。也不是说总是如此或有意如此,但出现得实在太频繁。注释存在的时间越久,就离其所描述的代码越远,越来越变得全然错误。原因很简单。程序员不能坚持维护注释代码在变动,在演化。从这里移到那里。彼此分离、重造又合到一处。很不幸,注释并不总是随之变动——不能总是跟着走。注释常常会与其所描述的代码分隔开来,孜然飘零,越来越不准确。

程序员应当负责将注释保持在可维护、有关联、精确的高度。我同意这种说法。但我更主张把力气用在写清楚代码上,直接保证无须编写注释。

**不准确的注释要比没注释坏得多。**它们满口胡言。它们预期的东西永不能实现。它们设定了无需也不应再遵循的旧规则。

**真实只在一处地方有:代码。**只有代码能忠实地告诉你它做的事。那是唯一真正准确的信息来源。所以,尽管有时也需要注释,我们也该多花心思尽量减少注释量。

注释不能美化糟糕的代码

写注释的常见动机之一是槽糕的代码的存在。我们编写一个模块,发现它令人困扰、乱七八糟。我们知道,它烂透了。我们告诉自己:“,最好写点注释!”不!最好是把代码弄干净!

带有少量注释的整洁而有表达力的代码,要比带有大量注释的零碎而复杂的代码像样得多。与其花时间编写解释你搞出的槽糕的代码的注释,不如花时间清洁那堆槽糕的代码。

用代码来闸述

有时,代码本身不足以解释其行为。不幸的是,许多程序员据此以为代码很少—-如果有的话能做好解释工作。这种观点纯属错误。

你愿意看到这个:

// Check to see if the employee is eligible for full benefits
if ((employee.flags & HOURLY_FLAG) && (employee.age> 65))

还是这个?

if(employee.isEligibleForFullBenefits())

只要想上那么几秒钟,就能用代码解释你大部分的意图。很多时候,简单到只需要创建个描述与注释所言同一事物的函数即可。

好注释

有些注释是必须的,也是有利的。

不过要记住,唯一真正好的注释是你想办法不去写的注释。

  • 法律信息: 版权及著作权声明就是必须和有理由在每个源文件开头注释处放置的内容。

  • 提供信息的注释: 有时,用注释来提供基本信息也有其用处。

  • 对意图的解释: 有时,注释不仅提供了有关实现的有用信息,而且还提供了某个决定后面的意图。

  • 闸释: 有时,注释把某些嗨涩难明的参数或返回值的意义翻译为某种可读形式,也会是有用的。通常,更好的方法是尽量让参数或返回值自身就足够清楚;但如果参数或返回值是某个标准库的一部分,或是你不能修改的代码,帮助闸释其含义的代码就会有用。当然,这也会冒闸释性注释本身就不正确的风险。

  • 警示: 有时,用于警告其他程序员会出现某种后果的注释也是有用的。

  • TODO注释: 有时,有理由用 //TODO 形式在源代码中放置要做的工作列表。

    TODO 是一种程序员认为应该做,但由于某些原因目前还没做的工作。它可能是要提醒删除某个不必要的特性,或者要求他人注意某个问题。它可能是恳请别人取个好名字,或者提示对依赖于某个计划事件的修改。无论 TODO 的目的如何,它都不是在系统中留下糟糕的代码的借口。

    如今,大多数好 IDE 都提供了特别的手段来定位所有TODO注释,这些注释看来丢不了。

  • 公共 API 中的 Javadoc: 没有什么比被良好描述的公共 API 更有用和令人满意的了。标准 Java 库中的 Javadoc 就是一例。没有它们,写 Java 程序就会变得很难。

    如果你在编写公共 API,就该为它编写良好的 Javadoc。不过要记住本章中的其他建议。就像其他注释一样,Javadoc 也可能误导、不适用或者提供错误信息。

坏注释

大多数注释都属此类。通常,坏注释都是糟糕的代码的支撑或借口,或者对错误决策的修正,基本上等于程序员自说自话。

  • 废话注释: 写给自己看的注释,或者毫无价值的注释。

  • 多余的注释: 代码本身已经提供了信息。注释没有证明代码的意义,也没有给出代码的意图或逻辑。读它并不比读代码更容易。事实上,它不如代码精确,误导读者接受不精确的信息,而不是正确地理解代码。

  • 误导性注释: 有时,尽管初束可嘉,程序员还是会写出不够精确的注释。不准确的注释要比没注释坏得多。

  • 循规式注释: 所谓每个函数都要有 Javadoc 或每个变量都要有注释的规矩全然是愚蠢可笑的。

  • 日志式注释: 有人会在每次编辑代码时,在模块开始处添加一条注释。这类注释就像是一种记录每次修改的日志。很久以前,在模块开始处创建并维护这些记录还算有道理。那时,我们还没有源代码控制系统可用。如今,这种觉长的记录只会让模块变得凌乱不堪,应当全部删除。

  • 归属与署名: 源代码控制系统非常善于记住是谁在何时添加了什么。没必要用那些小小的签名搞脏代码。事实上,注释在那儿放了一年又一年,越来越不准确,越来越和原作者没关系。

  • 能用函数或变量时就别用注释。

  • 注释掉的代码。

  • 信息过多。

  • 不明显的联系:注释及其描述的代码之间的联系应该显而易见。

  • 函数头:短函数不需要太多描述。为只做一件事的短函数选个好名字,通常要比写函数头注释要好。

  • 非公共 API 的 Javadoc。

格式

代码格式不可忽略,必须严肃对待。代码格式关乎沟通,而沟通是专业开发者的头等大事。

或许你认为“让代码能工作”才是专业开发者的头等大事。然而,你今天编写的功能,极有可能在下一版本中被修改,但代码的可读性却会对以后可能发生的修改行为产生深远影响。原始代码修改之后很久,其代码风格和可读性仍会影响到可维护性和扩展性。即便代码已不复存在,你的风格和律条仍存活来。

垂直格式(行数)

单文件的代码行数不宜过多,建议为 200 ~ 500 行。尽管这并非不可违背的原则,也应该乐于接受。短文件通常比长文件易于理解。

横向格式(每行字符数)

每行代码的字符数不宜长,建议不超过 80 个字符,当然,100 ~ 120个字符也还行。再多的话,大抵就是肆意妄为了。

缩进

源文件是一种继承结构,而不是一种大纲结构。其中的信息涉及整个文件、文件中每个类、类中的方法、方法中的代码块,也涉及代码块中的代码块。这种继承结构中的每一层级都圈出一个范围,名称可以在其中声明,而声明和执行语句也可以在其中解释。

要让这种范围式继承结构可见,我们依源代码行在继承结构中的位置对源代码行做缩进处理。

缩进是代码块表达程序结构的一种约定,有助于更好地向人类阅读者表达程序的结构。尤其是用于澄清控制流程结构(if、for、while)与其内部、外部代码之间的关系。

也有一些语言(如,Python、occam)使用缩进而非大括号或关键词来确定结构,这被称为越位规则。在这种语言中,缩进对编译器或解释器有意义,而不仅仅是清晰度或风格问题。

团队规则

每个程序员都有自己喜欢的格式规则,但如果在一个团队中工作,就是团队说了算。

组开发者应当认同一种格式风格,每个成员都应该采用那种风格。我们想要让软件拥有一以贯之的风格。我们不想让它显得是由一大票意见相左的个人所写成。

记住,好的软件系统是由一系列读起来不错的代码文件组成的。它们需要拥有一致和顺畅的风格。读者要能确信,他们在一个源文件中看到的格式风格在其他文件中也是同样的用法。绝对不要用各种不同的风格来编写源代码,这样会增加其复杂度。

最后更新于