0%

Clean Code-读书笔记

Clean Code-读书笔记

Clean Code-读书笔记

本书

如何提高代码质量。简洁代码,代码重构

书内容安排

  1. 代码规范
  2. 重构案例
  3. 重构总结

阅读建议

可以看多种整洁代码流派,吸纳优点。
书中很多建议都存在争议。可能会强烈反对其中的一些建议。

内容

1.整洁代码-记录

1.3.5什么是整洁代码

减少重复代码,只做一件事,表达力,小规模抽象。

1.4 思想流派

整洁代码有多种流派的规则。

1.6 童子军军规

时时保持代码整洁,让代码比来时更整洁。

1.8小结

本书只能告诉我们优秀程序员编码的思维过程,使用的技巧、技术和工具。不能直接让我们有好的”代码感“。
可以从本书中看到好代码,糟糕的代码,糟糕的代码如何转化为好代码。
可以看到启发、规条、技巧。

2.有意义的命名-记录

需要命名的地方:变量、函数、参数、类、封包命名、目录、jar、war

2.2名副其实

代码的模糊性:名称、判断
判断可以用函数取代,函数名称说明判断的意思

1
2
3
4
5
6
7
8
9
10
11
if(scan == 0){

}

if(isScan(scan)){

}

public static boolean isScan(Integer scan){
return scan == 0;
}

2.3避免误导

名称避免歧义。

大写字符O 和小写字符l 不要用。

2.4有意义的区分

User 与 UserData 没区别

2.9类名

类名和对象应该是名词而非动词。

2.10方法名

方法名应该是动词或动词短语

2.14使用解决方案领域名称

依据问题所涉领域来命名没有用本计算机领域命名好。本计算机领域命名可减少认知负担。

2.15使用源自所涉问题领域的名称

如果不 能用 程序员 熟悉 的 术语 来给 手头 的 工作 命名, 就 采用 从 所 涉 问题 领域 而来 的 名称 吧。 至少, 负责 维护 代码 的 程序员 就能 去请 教 领域 专家 了。

优秀工程师应该能分离解决方案领域和问题领域的概念。

2.16添加有意义的语境

3.函数-记录

造成模糊:

  • 魔法值,魔法字符串,双重嵌套,标识控制的if语句

    3.1短小

    内容短小。20行左右。

    3.2只做一件事

    只做一件事。函数名同一抽象层级内的一件事。不能再抽象出不同层级的函数。

    3.3每个函数一个抽象层级

    阅读顺序:自顶向下。
    抽象层级:
    如:
    高级:获取数据
    中级:处理数据中转换
    低级:判空
    函数实现:自定向下:
  • A函数后的B函数是顺序
  • A函数内的函数b的抽象层级比A低。
  • A函数内的语句包括每个函数(a、c、d)处于同一抽象层级,是在描速A

3.6函数参数

buildBean 需要用返回参数说明构建的实体是什么,而不是入参
两个参数比一个参数难懂。特殊:new Point(0,0);
二元函数转一元函数:将入参写成当前类的成员变量。outputStream.writeField(name);
三元函数的复杂性:排序,参数含义,是否需要忽略参数。
函数需要两个、三个、或三个以上参数,应该将部分参数封装为类。
函数名称和参数之间建立联系。

1
2
3
4
5
write(name)
writeField(name) // 说明name

assertEqual(message, expected, actual)
assertExpectedEqualsActual(message, expected, actual) // 说明哪两个参数比较,以及参数顺序

3.7无副作用

函数名称需要体现函数作用。函数名称之外的代码效果不应该在函数中。
可以用成员变量调用方法来避免使用返回参数。

1
2
3
4
5
6
7
8
9
10
11
12
appendFooter(StringBuffer report)

appendFooter(s);


StringBuffer report;
appendFooter(){
report.add("footer");
}
// 调用
report.appendFooter();

3.8函数分开做什么和询问

1
2
3
4
5
6
7
8
9
public boolean set(String attribute, String value){
if(set("username", "unclebob"))
}

if(attributeExists("username")) {
setAttribute("username", "unclebob");
}


3.9异常

抽离ry/catch块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

try {
...
...
} catch(Excaption e){

}

// 抽离
try {
doCreateRece(Rece rece);
} catch(Excaption e){

}

public Result doCreateRece(Rece rece){

}

错误处理就是一件事,处理错误的函数不应该做其他事。

3.12如何写出这样的函数

写代码如同写文章、论文。一开始想到什么写什么,然后斟酌推敲重构,直到函数达到心中最好的样子。比如:一开始冗长复杂,嵌套循环,比较长的参数列表,名称随意,多处重复代码,但需要有配套单元测试。第二步:打磨代码。分解函数、修改名称、消除重复,同时保持测试通过。

3.13

真正的目前是讲述系统故事。

3函数-总结

函数内容:方法名、入参、返回参数、内容。

  • 方法名:
    • 名称
      • 动词+名词
      • 描述入参
      • 名称需要体现实现内容,不要有实现了内容却不能通过名称看出来。
  • 入参:
    • 参数少比较好
    • 可以用成员变量减少入参
    • 参数两个以上可以封装为对象
  • 返回参数
  • 内容
    • 只做一件事
    • 同一抽象层级,依代码顺序
    • 将做什么和查询分开
    • 抽离try/catch
      一开始可以混乱,写出来之后逐步优化。

      4注释-记录

      注释可能提供错误信息。越陈旧的注释提供错误信息的可能性越大。
      注释可能不随着代码实现功能的变化而变化描述。
      注释是在弥补代码本身描述意图不清楚的失败。

      5格式-记录

5.1格式目的

代码格式和沟通相关。

5.2垂直方向上的格式

  1. 向报纸学习
  2. 不同概念间用空行隔开,这样看一段内容时目光会停留到空白行之前。如导包与类声明,函数间
  3. 相同概念不要隔开

    5.3水平(横向)方向上的格式

  4. 代码长度
  5. 运算符两边加空格
  6. 缩进

    5.4团队规则

    应该选用一套管理代码格式的简单规则,然后贯彻这些规则。
    团队中管理代码格式也是这样。

6.对象和数据结构-记录

6.1数据抽象

对象隐藏数据结构,暴露行为。
数据结构暴露数据,没有提供有意义的方法。

1
2
3
4
5
6
// 数据结构
public class Point {  
public double x;  
public double y;
}

1
2
3
4
5
6
7
8
9
10
// 对象
public interface Point {  
double getX();  
double getY();  
void setCartesian( double x, double y);  
double getR();  
double getTheta();  
void setPolar( double r, double theta);
}

6.5小结

面向过程更适合不改变其他类的情况下添加方法。
面向对象更适合不改变其他类的情况下添加新的类或数据类型。
如:形状类;矩形类,圆形类。

10.类-笔记

10.1类的组织

结构:

  1. 类的公有静态变量
  2. 类的私有静态变量
  3. 私有实体变量
  4. 类的公共方法

    10.2类应该短小

    类的第一条规则:短小。
    短小的衡量:权责(responsibility)。
    类的名称应当描述其权责。类名包含模糊的词时会有多个权责聚集。如:processor,Manager,Super。
    类内容的描述不应该有,if、and、or、but
    TODO 需要学权责的颗粒度,抽象一件事的层次
    10.2.1单一权责SRP(Single Responsibility Principle)
    判断方法:只有一条被修改的理由
    单一权责容易被破坏的原因:
    写完能工作的代码后就完事了。
    写的时候可能没有实现单一权责。
    因为业务的变化,添加的代码破坏了单一权责。
    因为功能需要新增的复杂性,导致类的单一权责抽象层次太高了。
    实际编写功能只给了实现的时间,没有让代码有结构、整洁,重构的时间。
    10.2.2内聚
    类应该只有少数变量,每个方法使用的变量越多,内聚性越强。
    保持函数和参数列表短小的策略, 导致实体变量数量增加时,应该创建新类,将变量和方法拆分到多个类中。让所有的类从整体上更内聚。
10.2.3保持内聚性就会得到许多短小的类

较大的函数切割成小函数,就会导致更多的类出现。
堆积类越来越多的少量函数使用的实体变量的类在一直丧失内聚性。
将大函数拆分为小函数,往往也是类拆分为小类的时机。

10.3 为了修改而组织

大部分系统,会持续修改。每次每处修改都可能造成其他地方无法正常工作。
隔离修改,依赖细节可以改为依赖抽象。

11.系统

11.1如何建造一个城市

一个人无法掌控所有细节。
城市中的一组组人管理不同的地方,供水系统、供电系统、交通、执法、立法。
一个城市有人负责全局(所有模块抽象层级高的内容),有更多的人负责细节(某一模块抽象层级低的内容)。
城市能运转,因为演化出恰当的抽象等级和模块。让人和其所管理的组件在不了解全局也能运转。
整洁的代码可以在较低的抽象层级模块化。较高的抽象层级-系统也需要模块化。

11.2将系统的构造与使用分开

构造和使用是完全不同的。不应该耦合在一起。
分离构造和使用的方法

  • 工厂模式隔离。
  • DI,依赖注入

延迟初始化缺点:

  • 会把构造和使用耦合在一起。

延后初始化的优点:

  • 多数 DI 容器 在 需要 对象 之前 并不 构造 对象。
  • 许多DI容器 提供 调用 工厂 或 构造 代理 的 机制, 而这 种 机制 可为 延迟 赋值 或 类似 的 优化 处理 所用

11.3扩展性

横贯式关注面

AOP,面向切面。按领域划分后,多个领域共同的东西用切面提取共性。比如:事务、日志

12.迭进-笔记

简单设计原则

按重要性排列

  1. 运行所有测试
  2. 消除重复
  3. 清晰表达作者的意图
  4. 尽量少的类和方法数量

简单设计规则1:运行所有测试

简单设计规则2-4:重构

用测试保证代码正确的前提下,递增式的重构代码。

  1. 思考设计
  2. 重构一小块
  3. 运行测试
优秀软件设计规则
  • 高内聚,低耦合
  • 切分关注面?
  • 模块化系统性关注面?
  • 缩小函数和类的尺寸
  • 用更好的名称
2消除重复
  • 类属性提取共性来切分为多个类
  • 抽取共性,抽取后方法用途不属于本类时,新建一个类存放,提升可见性。
  • 模板方法模式可以移出高层级重复
3表达力
  • 好名称,可以借助idea多次重命名名称
  • 保持函数和类尺寸短小,短小的类和函数易于命名,易于编写,易于理解。
  • 标准命名法
    4尽可能少的类和方法数量

17 味道与启发(规则总结)

1.注释

  1. 不需要的注释,代码自身已经解释清楚
  2. 注释与代码含义不同
  3. 删除过期注释
  4. 注释应该说明代码无法表达的东西。作者和日期通过代码版本工具管理。

    2.环境?

    3.函数

  5. 短小
  6. 只做一件事
  7. 函数内容的抽象层级是名称的下一级,函数内容的抽象层级在同一级。
  8. 函数个数尽量少

    4.一般性问题

  9. 小心不正确的边界行为,可以编写多个测试验证和调试
  10. 不要忽视安全,比如重构后不运行测试。
  11. 不要有重复代码。
    解决:
    1. 抽取公共函数
    2. 更隐蔽的重复,不同模块的检测同一组条件的switch/case,使用多态
    3. 更更隐蔽,类似算法,具体执行不同。使用模版方法模式或策略模式。
    4. ID生成,上游调用者信息,给第三方发消息,如钉钉,抽出公共组件,达成springboot的jar包。
  12. 信息过多
  13. 去除死代码
  14. 前后规则一致,比如命名风格
  15. 用接口,函数做区分比用变量区分更容易理解?
  16. 不恰当的静态方法。需要使用多态时,函数不能时静态的。
  17. 解释性变量
  18. 函数名称需要表达其行为
  19. 理解算法,途径:编写测试还不够,就重构
  20. 逻辑依赖改为物理依赖,一个模块依赖另一个模块时,依赖者不要去假定(逻辑依赖)被依赖者,应该显示依赖
  21. 多态替代IF/ELSE和Switch/Case
  22. 遵守标准约定
  23. 用命名常量替代魔术数
  24. 准确
    代码中的含糊和不准确要去除。意见不同和懒惰会影响准确
  25. 好结构甚于约定的次结构?待实践
  26. 封装条件,比如判断条件用描述性好的函数封装
  27. 肯定条件比否定条件好理解
  28. 必要的时序性耦合暴露出来
  29. 代码结构别随意
  30. 封装边界条件,边界难以追踪,难以理解
  31. 在较高层级放配置变量?
  32. 避免传递不需要的依赖?

    5.Java

    不要继承常量,应该导入常量所在的类
    枚举比常量表达力更强

    6.名称

    表明意图
    名称要与抽象层级相符。
    反映类或函数抽象层级的名称比实现内容的名称更好。
    较大作用范围选用较长的名称

    7.测试

    使用覆盖率工具
    测试边界条件
    全面测试相近的缺陷
    合理顺序的测试用例,测试工具会直接标记出测试失败的位置。

    8.小结

    并发编程

    内存模型保证的原子操作,32位 64位操作,一行代码对应多行字节码的非原子性,多个线程字节码执行路径指数级上升,锁syn,Executor。

1.整洁代码-感受

整洁代码是让作者和每一个读者都能读懂代码,觉得代码本该如此。
如何实现:
理想情况下:作者和读者对整洁代码的理解一致,认为的整洁代码规则一致。
实践:自己知道的整洁代码规则,自己按照规则去写代码,能够给别人讲出来这一套规则。
实践中会遇到的问题:
1.别人不理解规则;
2.别人理解规则,但不明白为什么这么做;
3.别人理解为什么这么做,但不去做;
4:和别人的规则冲突。
如何解决实践中遇到的问题:TODO

抽象层级总结

分离较高层级的一般性概念和较低层级的细节概念。
基类和派生类是高抽象层级和低抽象层级的关系。
抽像层级与单一原则。从高层次来看,符合单一原则,从低层次看,不符合单一原则。比如:提交收货,包括修改收货数据,提交到订单服务,库存服务,修改固定资产等。每个动作都属于提交收货处理,提交收货处理是单一的。

内容

包,类,函数,变量。

外部使用的类,内部使用的类
业务函数,工具函数
常量,临时变量

业务和项目架构抽象层级

Java中的抽象层级

高与低

基类 派生类
函数名称 函数内容

拆分时机

内容过多,相关内容迭代。
函数超过50行?
类超过500行?类变量超过10个?

没记住的条目 todo

  1. 逻辑依赖改为物理依赖,一个模块依赖另一个模块时,依赖者不要去假定(逻辑依赖)被依赖者,应该显示依赖。 比如直接feign掉用和二方包依赖后feign掉用
  2. 理解算法,途径:编写测试还不够,就重构
  3. 封装边界条件,边界难以追踪,难以理解
  4. 抽象层级
    1. 反映类或函数抽象层级的名称比实现内容的名称更好。–待实践

其他书

Practices( 中 译 版《 敏捷 软件 开发: 原则、 模式 与 实践》, 简称 PPP)