《代码整洁之道 clean code》学习笔记

文章目录

0 前言

最近在前辈的推荐下阅读了这本《代码整洁之道》,希望借此提升自己编写优雅代码的能力,提高工程素养。
本书在最后一章列出了编写整洁代码时所需要注意的事项清单,是对全书非常好的总结,可以作为一种参考来使用。
将重点内容整理在此,经常翻阅吧,努力提高自己的编码素养吧。

1 注释

C1:不恰当的信息

注释只应该描述有关代码和设计的技术性信息,其他信息如作者、最后修改时间、SPR数等元数据不应该在注释中。注释不应该传达在源代码控制系统、问题追踪系统或任何其他记录系统中应该保存的信息。

C2:废弃的注释

过时、无关或不正确的注释就是废弃的注释 ,废弃的注释会远离它们曾经描述的代码,变成代码中无关和误导阅读者的浮岛。

C3:冗余的注释

如果注释描述的是某种充分自我描述的东西,那么注释就是多余的。
注释应该谈及代码自身没提到的东西。

C4:糟糕的注释

如果要编写一条注释,就花时间保证写出最好的注释,字斟句酌,使用正确的语法和拼写,别闲扯,别画蛇添足,要保持简洁。

C5:注释掉的代码

注释掉的代码纯属厌物,请直接删除。不用担心代码丢失,源代码控制系统还会记得它。

2 环境

E1:需要多步才能实现的构建

构建系统应该是单步的小操作。不应该从源代码控制系统中一小点一小点签出代码。不应该需要一系列神秘指令或环境依赖脚本来构建单个元素。

E2:需要多步才能做到的测试

应该发出单个指令就可以运行全部的单元测试。能够运行全部测试是如此基础和重要,应该快速、轻易和直接了当。

3 函数

F1:过多的参数

函数的参数量应该少。没参数最好,一个次之,两个、三个再次之。三个以上的参数非常值得质疑,应坚决避免。

F2:输出参数

输出参数违反直觉,因为读者期望参数用于输入而非输出。如果函数非要修改什么东西的状态,就修改它所在对象的状态就好了。

F3:标识参数

函数应该保持短小,一个函数只做一件事情。布尔值参数表示函数做了不止一件事,应该被消灭掉。

F4:死函数

永远不调用的函数应该被丢弃。

4 一般性问题

G1:一个源文件中存在多种语言

理想的源文件包括且只包括一种语言。现实中,我们可能会不得不使用多于一种语言,但应该尽力缩小源文件中额外语言的数量和范围。

G2:明显的行为未被实现

遵循“最小惊异原则”,函数或类应该实现其他程序员有理由期待的行为。如果明显的行为未被实现,读者和用户就不能再依靠他们对函数名称的直觉,而不得不阅读原代码。

G3:不正确的边界行为

别依赖直觉。追索每种边界条件,并编写测试。

G4:忽视安全

忽视安全相当危险。

G5:重复

尽可能找到并消除重复。

G6:在错误的抽象层级上的代码

创建分离较高层级一般性概念与较低层级细节概念的抽象模型,这很重要。我们创建抽象类来容纳较高层级概念,创建派生类来容纳较低层级概念。需要确保分离完整,所有较低层级概念放在派生类中,所有较高层级概念放在基类中。良好的软件设计要求分离位于不同层级的概念。

G7:基类依赖派生类

将概念分解到基类和派生类最普遍的原因是,较高层级基类概念可以不依赖较低层级派生类的概念。通常来说,基类对派生类应该一无所知。

G8:信息过多

优秀的软件开发人员要学会限制类或模块中暴露的接口数量。类中的方法越少越好,函数知道的变量越少越好,类拥有的实体变量越少越好。

G9:死代码

死代码就是不执行的代码。如果找到死代码,请将它从系统中删除掉。

G10:垂直分隔

变量和函数应该在靠近被使用的地方定义。本地变量应该正好在其首次被使用的位置上面声明,垂直距离要短。本地变量不应该在距离其被使用之处几百行以外的位置声明。私有函数应该刚好在其首次被使用的位置下面定义。

G11:前后不一致

从一而终,小心选择约定,一旦选中,就小心持续遵循。
前后一致,就能让代码更加易于阅读和修改。

G12:混淆视听

没有用到的变量,从不调用的函数,没有信息量的注释,等等,这些都是应该移除的废物。
保持源文件整洁,组织良好,不被搞乱。

G13:人为耦合

人为耦合是指两个没有直接目的的模块之间的耦合。其根源是将变量、常量或函数不恰当地放在临时方便的位置。

G14:特性依恋

类的方法只应该对其所属类中的变量和函数感兴趣,不应该垂青其他类的变量和函数。当方法通过某个其他对象的访问器和修改器来操作该对象内部数据时,它就依赖该对象所属类的范围。它期望自己在那个类里面,这样就能直接访问它操作的变量。

G15:选择算子参数

不仅一个选择算子(?:)参数的目的令人难以记住,而且每个选择算子参数将多个函数绑到了一起。使用多个函数,通常优于向单个函数传递某些代码来选择函数行为。

G16:晦涩的意图

代码要尽可能具有表达力。联排表达式、匈牙利标记法和魔术数都遮蔽了作者的意图。

G17:位置错误的权责

代码应该放在读者自然而然期待它所在的地方。

G18:不恰当的静态方法

通常应该倾向于选用非静态方法。如果有疑问,就用非静态函数。如果的确需要静态函数,确保没机会打算让它有多态行为。

G19:使用解释性变量

让程序可读的最有力方法之一就是将计算过程打散成在用有意义的单词命名的变量中放置的中间值。
解释性变量多比少好。

G20:函数名称应该表达其行为

如果你必须查看函数的实现(或文档)才知道它是做什么的,就该换个更好的函数名,或者重新安排功能代码,放到有较好名称的函数中。

G21:理解算法

在你认为自己完成某个函数之前,确认自己理解了它是怎么工作的。通过全部测试还不够好。你必须知道解决方案是正确的。
获得这种知识和理解的最好途径,往往是重构函数,得到某种整洁而足具表达力、清楚呈未如何工作的东西。

G22:把逻辑依赖改为物理依赖

如果某个模块依赖另一个模块,依赖就该是物理上的而不是逻辑上的。依赖者模块不应该对被依赖者模块有假定(逻辑依赖),它应当明确地询问后者全部信息。

G23:用多态替代if/else或switch/case

对于给定的选择类型,不应有多于一个的switch语句,在那个switch语句中的多个case,必须创建多态对象,取代系统中其他类似的switch语句。

G24:遵循标准约定

每个团队都应遵循基于通用行业规范的一套编码标准,团队中的每个成员都应该遵循这些约定。

G25:用命名常量替代魔术数

在代码中出现原始形态数字通常来说是坏现象,应该用命名良好的常量来隐藏它。
有些常量与励晶太平洋这具有自我解释能力的代码协同工作时,如此易于识别,也就不必总是需要命名常量来隐藏了。

G26:准确

在代码中做决定时,确认自己足够准确。明确自己为何要这么做,如果遇到异常情况如何处理。
代码中的含糊和不准确要不是意见不同的结果,要么源于懒惰,都应该消除。

G27:结构甚于约定

坚守结构甚于约定的设计原则。命名约定很好,但却次于强制性结构。

G28:封装条件

如果没有if或while语句的上下文,布尔逻辑就难以理解。
应该把解释了条件意图的函数抽离出来,用良好的命名表达意图。

G29:避免否定条件

否定式要比肯定式难明白一些。所以,尽可能将条件表示为肯定形式。

G30:函数只该做一件事

编写执行一系列操作的包括多做代码的函数常常是诱人的。这类函数做了不只一件事,应该转换为多个更小的函数,每个小函数只做一件事。

G31:掩蔽时序耦合

常常有必要使用时序耦合,但你不应该掩蔽它。排列函数参数,好让它们被调用的次序显而易见。
可以通过创建顺序队列暴露时序耦合。每个函数都产生出下一个函数所需要的结果,这样一来就没理由不按顺序调用的。

G32:别随意

构建代码需要理由,而且理由应该与代码结构相契合。如果结构显得太随意,其他人就会想修改它。如果结构自始至终保持一致,其他人就会使用它,并且遵循约定。

G33:封装边界条件

边界条件难以追踪。把处理边界条件的代码集中到一处,不要散落于代码。

G34:函数应该只在一个抽象层级上

函数中的语句应该在同一抽象层级上,该层级应该是函数名所示操作的下一层。

G35:在较高层级放置可配置数据

如果你有一个已知并在较高抽象层级的默认常量或配置值,不要将它埋藏至较低层级的函数中。把它作为较高层级函数调用较低层级函数的一个参数。
位于较高层级的配置性常量易于修改,它们向下贯穿应用程序。应用程序的较低层级并不拥有这些常量的值。

G36:避免传递浏览

通常我们不想让某个模块了解太多其协作者的信息。如果A与B协作,B与C协作,我们不想让使用A的模块了解C的信息。
正确的做法是让直接协作者提供所需的全部服务,而不必逛遍系统的对象全图,搜寻我们要调用的方法。

5 JAVA

J1:通过使用通配符避免过长的导入清单

指定导入包是一种硬依赖,而通配符导入则不是。过长的导入清单令读者望而却步。

J2:不要继承常量

有些程序在接口中放了一些常量,再通过继承结构来访问这些常量。常量躲在在继承结构的最顶端。
别利用继承欺骗编程语言的作用范围规则,而应该用表态导入。

J3:常量和枚举

枚举拥有方法和字段,从而成为能比int提供更多表达力和灵活性的强有力工具。

6 名称

N1:采用描述性名称

确认名称具有描述性。
仔细起好的名称的威力在于,它用描述性信息覆盖了代码。

N2:名称应与抽象层级相符

不要起沟通实现的名称,而起反映类或函数抽象层级的名称。

N3:尽可能使用标准命名法

如果名称基于既存约定或用法,就比较易于理解。
对于特定项目,开发团队常常发明自己的命名标准系统。具有与项目有关的特定意义的名称用得越多,读者就越容易明白你的代码是做什么的。

N4:无歧义的名称

选用不会混淆函数或变量意义的名称。

N5:为较大作用范围选用较长名称

名称的长度应与作用范围的广泛度相关。对于较小的作用范围,可以用很短的名称,而对于较大的作用范围,就应该用较长的名称。
名称的作用范围越大,名称就该越长,越准确。

N6:避免编码

不应该在名称中包括类型要作用范围信息。
不要用匈牙利语命名法污染你的名称。

N7:名称应该说明副作用

名称应该说明函数、变量或类的一切信息。不要用名称掩蔽副作用。不要用简单的动词来描述做了不止一个简单动作的函数。

7 测试

T1:测试不足

只要还有没被测试探测过的条件,或是还有没被验证过的计算,测试就还不够。

T2:使用覆盖率工具

覆盖率工具能汇报你测试中的缺口。使用覆盖率工具就更容易地找到测试不足的模块、类和函数。

T3:别略过小测试

小测试易于编写,其文档上的价值高于编写成本。

T4:被忽略的测试就是对不确定事件的疑问

有时,我们会因为需求不明而不能确定某个行为的细节。可以用注释掉的测试或用@Ignore注解的测试来表达我们对于需求的疑问。

T5:测试边界条件

特别注意测试边界条件。算法的中间部分正确但边界条件判断错误的情形很常见。

T6:全面测试相近的缺陷

缺陷趋向于扎堆。在某个函数中发现一个缺陷时,最好全面测试那个函数。

T7:测试失败的模式具有启发性

有时,你可以通过找到测试用例失败的模式来诊断问题所在。

T8:测试覆盖率的模式具有启发性

查看被或未被已通过的测试执行的代码,往往能发现失败的测试为何失败的线索。

T9:测试应该快速

慢速的测试是不会被运行的测试。时间一紧,较慢的测试就会被摘掉。
竭尽所能让测试够快。

书摘

面向对象设计原则:
单一权责原则(Single Responsibility Principle, SRP):类或模块应该有且只有一个加以修改的理由。
开放闭合原则(Open Closed Principle,OCP):类应该依赖抽象而不是依赖具体细节。
依赖倒置原则(Dependency Inversion Principle, DIP):类应该对扩展开放,对修改封装。在理想系统中,我们通过扩展系统而非修改现有代码来添加新特性。

如果名称需要注释来补充,那就不算是名副其实。
提防使用外形相似度较高的名称。
要区分名称,就要以读者能鉴别不同之处的方式来区分。
明确是王道,编写他人能理解的代码。
避免将同一单词用于不同目的。
只要短名称足够清楚,就比长名称好,别给名称添加不必要的语境。

函数应该做一件事。做好这件事。只做这一件事。
别害怕长名称。长而具有描述性的名称,要比短而令人费解的名称好。长而具有描述性的名称,要比描述性的长注释好。使用某种命名约定,让函数中的多个单词容易阅读,然后使用这些单词给函数起个能说清其功用的名称。
我们根本不应该忽略任何代码,忽略掉的部分就是缺陷藏身之地。
函数要么做什么事,要么回答什么事,但二者不可得兼。函数应该修改某对象的状态,或是返回该对象的有关信息。
重复可能是软件中的一切邪恶的根源。

程序员就当负责将注释保持在可维护、有关联、精确的高度。
不准确的注释要比没注释糟糕的多。
注释不仅提供了有关实现的有用信息,而且还可以提供某个决定后面的意图。
注释可用于警示其他程序员可能会出现某种后果。
有理由用TODO在源代码中放置要做的工作列表,但要定期查看,删除不再需要的TODO注释。
注释可以用来放大某种看来不合理之物的重要性。

如果你决定写注释,就要花必要的时间确保写出最好的注释。
如果滥用标记栏,就会沉没在背景噪音中而被忽略。
请确保注释描述了离它最近的代码。注释及其描述的代码之间的联系应该显而易见。

几乎所有的代码都是从上往下读,从左往右读。每行展示一个表达式或一个子句,每组代码行展示一条完整的思路。这些思路用空白行区隔开。
靠近的代码行暗示了它们之间的紧密关系。
关系密切的概念应该相互靠近。变量的场景应该尽可能靠近其使用位置。
确保空范围体的缩进,并用括号包围起来。

我们不大头瘟暴露数据细节,而更愿意以抽象形态表述数据。
对象把数据隐藏于抽象之后,暴露操作数据的函数;而数据结构暴露其数据,没有提供有意义的函数。
过程式代码(使用数据结构的代码)便于在不改动既有数据结构的前提下添加新函数;面向对象代码便于在不改动既有函数的前提下添加新类。过程式代码难以添加新数据结构,因为必须修改所有函数;面向对象代码难以添加新函数,因为必须要修改所有类。
得墨忒耳律:模块不应该了解它所操作对象的内部情形。

错误处理很重要,但如果它搞乱了代码逻辑,就是错误的做法。
你抛出的每个异常,都应当提供足够的环境说明,以便判断错误的来源和位置。应创建信息充分的错误消息,并和异常一起传递出去,在消息中,应包括失败的操作和失败类型。
将第三方API打包是个良好的实践手段。当你打包一个第三访API时,你就降低了对它的依赖:未来你可以不太痛苦地改用其他代码库。
在传递null值。

学习性测试:编写测试来遍览和理解第三访代码。
adapter封装了与API的互动,也提供了一个当API发生变动时唯一需要改动的地方。
边界上的代码需要清晰的分割和定义了期望的测试。
尽可能减少每个概念的断言数量,每个测试函数只测试一个概念。
整洁的测试应遵循5条规则:快速、独立、可重复、自足验证、及时。

类的名称就当描述其权责。
类名越含糊,该类越有可能 拥有过多权责。
系统应该由许多短小的类而不是少量巨大的类组成,每个小类封装一个权责,只有一个修改的原因,并与少数其他类一起协同达成期望的行为。

猜你喜欢

转载自blog.csdn.net/PAN_Andy/article/details/121185969