目录

《Clean-Code》第17章-味道与启发阅读总结

1. 注释

不恰当的信息

注释只应该描述相关代码和设计的技术性信息。

废弃的注释

过时,无关或不正确的注释就是废弃的注释。

冗余注释

如果注释描述的是某种充分自我描述的东西,那么注释就是多余的。

例如: i++; // i自加1

糟糕的注释

注释要好好写,花时间保证写出最好的注释,使用正确的语法和拼写,不要说多余的话。

注释掉的代码

注释掉的代码影响整体的代码的可读性,应该讲起删掉,Git会记住它。

2. 环境

使用多步才能实现的构建

应当使用单个命令来检出系统,并用单个指令构建它,而不是一点一点的检出,寻找额外的配置文件或依赖包。

需要多步才能做到的测试

应当能够使用单个指令就可以运行全部的单元测试。

3. 函数

过多的参数

函数的参数数量应该少,没参数最好,一个次之。三个以上应该避免。

输出参数

输出参数违背直觉,读者期望参数用于输入而非输出,如果要修改状态,应该它所在对象的状态即可。

标识参数

布尔值参数宣告参数做了不止一件事。他们让人迷惑,应该处理掉。

死函数

永不调用的函数应该删掉,保留死代码没有一点必要,Git会帮你保存下来。

4. 一般性问题

一个源文件中存在多个语言

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

明显的行为未被实现

遵循“最小惊异原则”(The Principle of Least Surprise),函数或类应该实现其他程序员期待的行为。

不正确的边界行为

代码应该有正确的行为,每种边界条件、每种极端情形、每个异常都代表了某种可能扰乱优雅的代码,别依赖直觉,追溯每种边界条件,并编写测试。

忽视安全问题

不要忽略代码中的各种警告,如果不重视安全,它会影响程序的稳定运行,一时的构建成功并不代表代码不会出问题。

代码重复问题

Kent Beck将它列为极限编程的核心原则之一,并称之为“一次,也只一次”。面对重复的代码,可以使用模板方法模式和策略模式来修正它。

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

良好的软件设计要求分离位于不同层级的概念,将它们放置在不同的容器中。

基类依赖于派生类

通常来讲,基类应该对派生类一无所知。将基类的派生类部署到不同的jar文件中,确保基类jar文件对派生类jar文件一无所知,我们就能把系统部署为分散和独立的组件。

信息过多

设计良好的模块有着非常小的接口,让你能事半功倍。设计良好的接口具有少量的依赖,所以耦合度较低。不要创建拥有大量方法和大量实体变量的类。不要为子类创建大量受保护的变量和函数。尽力保持接口紧凑。通过限制信息来控制耦合度。

死代码

死代码的问题是过不了多久,他就产生了负面的影响,时间越久,受到的影响就越大。每当遇到死代码的时候,就将它从代码中删除。

垂直分隔

变量和函数应该在靠近被使用的地方定义。本地变量应该正好在其首次被使用的位置上面声明,垂直距离要远。

前后不一致

前后一致的命名,前后一致的代码格式,更加能提高代码的可读性。

混淆代码

没有用到的变量,从不调用的函数,没有信息量的注释等都属于混淆代码,都应该删除。保持代码的整洁,良好的组织,才能不会被搞乱。

人为耦合

不互相依赖的东西不应该耦合。一般来讲,人为耦合是指两个没有直接目的之间的模块的耦合,其根源是讲变量、常量或函数不恰当的放在临时方便的位置。

特性依赖

晦涩的意图

代码要尽可能具有表达力。

位置错误的权责

程序员做出的最重要决定之一就是代码放在那里。最小惊异原则在这里就起到作用了。代码应该放在读者自然而然期待它所在的地方。做出这个决定的的途径之一就是看函数名称。

不恰当的静态方法

通常应该倾向于选用非静态方法。如果需要使用静态函数,应该确保其不打算有多态性为。

使用解释型变量

让程序可读的最有力方法之一就是将计算过程打散成有意义的命名的中间值(来自《设计模式》一书)。解释型变量多比少好。只要把计算过程打散成一系列良好命名的中间值,不透明的模块就会突然变的透明,这很值得注意。

函数名称应该表达其行为

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

应该把逻辑依赖改为物理依赖

用 [[多态]] 代替if/else或switch/case

使用switch之前,应该考虑使用多态。其次,函数变化胜于类型变化的情形相对罕见,每个switch语句都值得怀疑。对于给定的选择类型,不应有多于一个switch语句。在那个switch语句中的多个case,必须创建多态对象,取代系统中其他类似switch语句。

遵循标准约定

每个团队中都应有遵循基于行业代码规范的一套标准。供团队开发人员遵循。

用命名常量替代代码中的魔术数。

准确

代码中的含糊和不准确要么是意见不同的结果,要么源于懒惰。不管什么原因,都要消除。

结构胜于约定

坚守结构胜于约定的设计决策。命名约定很好,但却次于强制性的结构。例如,用到良好命名的枚举的switch/case要弱于拥有抽象方法的基类。

封装条件

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

避免否定式条件

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

函数只该做一件事情

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

保证时序耦合

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 程序员可以在调用saturateGradient()函数之前调用reticulateSplines()函数,从而导致抛出异常.
public void dive(String reason){
    saturateGradient();
    reticulateSplines();
    diveForMoog(reason);
}
// 改进后的版本保证了时序耦合。
public void dive(String reason){
    Gradient gradient = saturateGradient();
    List<Spline> splines = reticulateSplines(gradient);
    diveForMoog(splines, reason);
}

封装边界条件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
if(level + 1 < tags.length){
    parts = new Parts(body, tags, level + 1, offset + endTag);
    body = null;
}
// 以上代码出现了两次level + 1,这个应该封装到名为nextLevel的变量中的边界条件.
int nextLevel = level + 1;
if(nextLevel < tags.length){
    parts = new Parts(body, tags, nextLevel, offset + endTag);
    body = null;
}

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

位于较高层级的配置型常量易于修改。它们向下贯穿应用程序。应用程序的较低层级并不拥有这些常量的值。

5. 关于Java的建议

不要继承常量

不要使用继承机构来导入常量,应该使用静态导入来导入常量。

常量 vs 枚举

不要再用public static final int旧方式定义常量,应该使用enum来定义类型常量。enum具有更高的灵活性。

6. 名称

采用描述性名称

名称与抽象层级相符

尽可能使用标准命名法

无歧义的名称

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

避免编码(如: m_ f_ vis_等)

名称应该说明副作用

名称应该说明函数、变量或类的一切信息。

7. 测试

测试不足

使用覆盖率工具

别略过小测试

被忽略的测试就是对不确定事物的疑问

测试边界条件

全面测试相近的缺陷

测试失败的模式有启发性

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

测试应该快速