《代码不朽:编写可维护软件的10大要则》读书笔记。
1 三个基本理论
本书提供了10条可以实现高可维护性的原则。这些原则的理论如下:
理论一:坚持简单的原则最有助于提高可维护性。
理论二:可维护性不是开发完才考虑的事情,而应该是在项目开发的一开始就加以考虑,每个人的贡献都应当计算在内。
理论三:对各个原则的违背会带来不同的影响,有些严重程度甚于其他。一个软件系统越遵循原则,可维护性越高。
2 原则1:编写短小的代码单元
2.1 原则描述
- 代码单元的长度应该限制在15行代码以内。
- 为此首先不要编写超过15行代码的单元,或者将长的单元分解成多个更短的单元,直到每个单元都不超过15行代码。
- 该原则能提高可维护性的原因在于:短小的代码单元易于理解、测试及重用。
在java中,代码单元指方法或者构造函数。
2.2 如何使用本原则
2.2.1 重构技巧:提取方法
该重构方法较为简单,不详细描述。当我们将一个代码单元拆分成多个是,有可能会增加总的代码行数。但同时也降低了这个代码单元的长度和复杂度。因此编写可维护代码总是在不断的做权衡,达到最佳效果。
2.2.2 重构技巧:将方法替换为方法对象
参考原则4。
当我们使用“提取方法“的重构技巧,如果提取的代码单元访问了局部变量,虽然可以将局部变量作为参数传递。但有可能导致参数列表过多的问题。则可以尝试使用”将方法替换为方法对象“的技巧。创建一个新的类来代替方法。(参考书18页)。
3 原则2:编写简单的代码单元
3.1 原则描述
- 限制每个代码单元分支点的数量不超过4个。
- 应该将复杂的代码单元拆分成多个简单的单元,避免多个复杂的单元在一起。
- 该原则能提高可维护性的原因在于:分支点越少,代码单元越容易被修改和测试。
分支点:在Java中,以下语句和操作符都被认为是分支点:
if
、case
、?
、&&, ||
、while
、for
、catch
。
关于书中关于覆盖率的概念,可以查看 代码覆盖率浅谈。
3.2 如何使用本原则
3.3.1 处理链式条件语句
链式条件语句:每个条件都是互斥的。例如:switch语句,if…else if… 语句。
【方式一:采用Map数据结构重构】(参考书34页)
【方式二:使用多态来代替条件判断(Replace Conditional with Polymorphism)】(参考书34页)
抽取公共接口,为每个条件定义不同的实现类。
这种方式不好之处是引入了更多的类和代码。
3.3.2 处理嵌套条件语句
【方式:使用卫语句来代替嵌套的条件语句(Replace Nested Conditional with Guard Clauses)】(参考书36页)
卫语句:如果某个条件极其罕见,就应该单独检查该条件,并在该条件为真时立刻从函数中返回,以减少嵌套语句的发生。
4 原则3:不写重复的代码
4.1 原则描述
- 不要复制代码。
- 你应该编写可重用、通用的代码,并且/或者调用已有的代码。
- 该原则能提高可维护性的原因在于:如果复制代码,就需要在多个地方修复bug,这样做不仅低效,而且容易出错。
重复代码/克隆代码的定义:一段至少6行相同的代码。不包括空行和注释行。
重复代码的类型:
- 1类克隆:这几行代码必须完全一样(文字上完全相同)。
- 2类克隆:在语法上相同,只在空格、注释、标识符名称和字面值不同。但2类克隆不一定是1类克隆。
移除1类克隆对可维护性的提升最大。
1类克隆更容易检测和识别,2类克隆都需要完整的语法分析才能识别。(参考书44页)
4.2 如何使用本原则
【方式一:永远不要复制粘贴已有的代码。】
【方式二:可以使用原则1中所说的“提取方法”的重构技巧解决重复的代码问题。】
【方式三:重构技巧:提取父类。】
4.2.1 重构技巧:提取父类
因为二者有一些相同的功能而造成了重复的代码,可以使用“提取父类”的重构技巧,不仅将代码行片段提取出方法,而且提取到原类的一个新的父类中。(参数书47页)
5 原则4:保持代码单元的接口简单
5.1 原则描述
- 限制每个代码单元的参数不能超过4个。
- 你应该将多个参数提取成对象。
- 该原则能提高可维护性的原因在于:较少的参数可以让代码单元更容易理解和重用。
5.2 如何使用本原则
过长的接口本身不是问题,而是实际问题的一个表现—意味着代码中可能存在着设计不合理的数据模型,或者随意的改动。
【方式一:将多个参数提取成对象】:
将多个参数提取成对象。这些对象通常被称为数据传输对象(DTO)或者参数对象。可以在代码中被频繁的重用。(参考书59页)
如果各个参数无法组合,仍然可以将它们封装成对象,但这个类只能使用一次了。使用原则1中所提到的”将方法替换为方法对象”的重构技巧。将原来的方法,放在新类中。(参考书61页)
【方式二:使用方法重载,提供默认值,简化方法的调用】:(参考书60页)
使用方法重载的方式,重载一个方法,只是参数减少,其他的参数采用默认值。
若第三方库/框架中使用了长参数列表的接口,最好的办法是使用包装类或者适配器对他们进行隔离。
6 原则5:分离模块之间的关注点
6.1 原则描述
- 避免形成大型模块,以便达到模块之间的松耦合。
- 你应该将不同的职责分给不同的模块,并且隐藏接口内部的实现细节。
- 该原则能提高可维护性的原因在于:相比起紧耦合的代码库来说,对松耦合代码库的修改更容易监督和执行。
这里的模块概念,对应java中的类的概念。
6.2 如何使用本原则
本原则会要求保持类的体积尽可能的小。(专注于某一个关注点),并限制外部对该类的调用数量。
6.2.1 根据不同关注点拆分类
为了避免类变大越来越大,必须在类承担超过一个职责时,对类进行拆分。(参考书69页)
6.2.2 隐藏接口背后的特定实现
可以通过隐藏高层接口背后特定、具体的实现细节(继承和接口),来达到松耦合的目的。(参考书70页)
6.2.3 可以使用第三方库、框架来替换自定义代码
通用功能或者工具方法的类通常会导致模块紧耦合的情况。例如StringUtils
工具类。可以使用第三方库例如:Apache Commons
。
7 原则6:架构组件松耦合(不理解)
7.1 原则描述
- 顶层组件之间应该做到松耦合。
- 你应该尽可能的减少当前模块中需要暴露给(例如,被调用)其他组件中模块的相关方法。
- 该原则能提高可维护性的原因在于:独立的组件可以单独进行维护。
模块耦合度关注于单个模块(类)对系统其他部分的暴露程度,而组件耦合度关注的是一个组件中的模块,对其他组件中的模块的暴露程度。
能够提升可维护性的调用分为两种:
- 内部调用是健康的。内部逻辑对于外部是隐藏起来的。
- 传出调用也是健康的。把它们要做的任务代理给其他组件。
一个组件的传出调用,就是其他组件的传入调用。
对可维护性有负面影响的调用:
- 传入调用,为其他组件提供功能。组件内的代码应当尽可能的进行封装,即应当避免来自其他组件的直接调用,。同样,修改传入依赖中的代码,会对其他组件造成很大影响,应降低传入依赖所涉及的代码。
- 透传代码必须要避免,既接受传入调用,又同时代理给其他组件。
7.2 如何使用本原则
7.2.1 抽象工厂设计模式
(参考书79页)
关于抽象工厂设计模式,可以查看 链接。
8 原则7:保持架构组件之间的平衡
8.1 原则描述
- 需要平衡代码中顶层组件的数量和体积。
- 应该保持源代码中组件的数量接近9,并且这些组件的体积基本一致。
- 该原则能提高可维护性的原因在于:平衡的组件可以帮助定位代码,并且允许独立对组件进行维护。
8.2 如何使用本原则
关于组件平衡的两个原则:
- 顶层系统组件个数在理想状态下应为9,通常来说位于6到12之间。
- 各个组件的代码量应该大致相当。
对组件的划分应该是自然而然的,而不是刻意要划分成9个组件。
9 原则8:保持小规模代码库
9.1 原则描述
- 保持代码库规模尽可能小。
- 应该控制代码库增长,并主动减少系统的代码体积。
- 该原则能提高可维护性的原因在于:拥有小型的代码,项目和团队是成功的一个因素。
9.2 如何使用本原则
- 功能层面:
- 控制需求蔓延。
- 功能标准化。将功能统一标准化,在程序的行为和交互方面保持一致。可以避免多个地方实现基本相同,但略有区别的功能;有利于重用已有代码。
- 技术层面:用更少的代码实现相同的功能。
- 不要复制粘贴代码。
- 重构已有代码。
- 使用第三方库和框架。
- 拆分大型系统:将一个大型系统拆分成多个较小的系统。
10 原则9:自动化开发部署和测试
10.1 原则描述
- 对你的代码做自动化测试。
- 应该通过使用测试框架来编写自动化测试。
- 该原则能提高可维护性的原因在于:自动化测试让开发过程可预测并且能够降低风险。
10.2 如何使用本原则
- 单元测试的基本原则:
- 正常情况和特殊情况都要测试。
- 像维护非测试(生产)代码一样维护测试代码。
- 编写独立的测试,不依赖于其他测试文件的写入。
11 原则10:编写简洁的代码
11.1 原则描述
- 编写简洁的代码。
- 不应该在完成开发工作后留下代码坏味道。
- 该原则能提高可维护性的原因在于:简洁的代码是可维护的代码。
11.2 如何使用本原则
【不要编写单元级别的代码坏味道】
即不要编写过长的代码(原则1),不要编写复杂的代码单元(原则2),不要编写长接口的代码(原则4)。
【不要编写不好的注释】
【不要注释代码】
版本控制系统会永远保留一份旧代码的记录,因此可以很安全的删除。
【不要保留废弃代码】
废弃代码指的是根本不会被执行或者输出被废弃(代码可能被执行,但没有任何地方使用它的输出)的代码。
- 废弃代码:方法无法到达的代码。
- 废弃代码:无用的私有方法,如果私有方法没有被类中的任何代码调用,就是废弃的。
- 注释中的代码
【不要使用过长的标志符名称】
【不要使用魔术常量】
魔术常量:代码中没有被清晰定义的数字或者字符串。
【不要使用未正确处理的异常】
- 捕获一切异常,并且空的cache代码库是不好的实践行为。
- 捕获特定异常,不应该直接捕获
Throwable
、Exception
、RuntimeException
异常。 - 将特定的异常信息转换成通用的信息展示给用户。