Clean Code-读书笔记
Clean Code-读书笔记
本书
如何提高代码质量。简洁代码,代码重构
书内容安排
- 代码规范
- 重构案例
- 重构总结
阅读建议
可以看多种整洁代码流派,吸纳优点。
书中很多建议都存在争议。可能会强烈反对其中的一些建议。
内容
1.整洁代码-记录
1.3.5什么是整洁代码
减少重复代码,只做一件事,表达力,小规模抽象。
1.4 思想流派
整洁代码有多种流派的规则。
1.6 童子军军规
时时保持代码整洁,让代码比来时更整洁。
1.8小结
本书只能告诉我们优秀程序员编码的思维过程,使用的技巧、技术和工具。不能直接让我们有好的”代码感“。
可以从本书中看到好代码,糟糕的代码,糟糕的代码如何转化为好代码。
可以看到启发、规条、技巧。
2.有意义的命名-记录
需要命名的地方:变量、函数、参数、类、封包命名、目录、jar、war
2.2名副其实
代码的模糊性:名称、判断
判断可以用函数取代,函数名称说明判断的意思
1 | if(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 | write(name) |
3.7无副作用
函数名称需要体现函数作用。函数名称之外的代码效果不应该在函数中。
可以用成员变量调用方法来避免使用返回参数。
1 | appendFooter(StringBuffer report) |
3.8函数分开做什么和询问
1 | public boolean set(String attribute, String value){ |
3.9异常
抽离ry/catch块
1 |
|
错误处理就是一件事,处理错误的函数不应该做其他事。
3.12如何写出这样的函数
写代码如同写文章、论文。一开始想到什么写什么,然后斟酌推敲重构,直到函数达到心中最好的样子。比如:一开始冗长复杂,嵌套循环,比较长的参数列表,名称随意,多处重复代码,但需要有配套单元测试。第二步:打磨代码。分解函数、修改名称、消除重复,同时保持测试通过。
3.13
真正的目前是讲述系统故事。
3函数-总结
函数内容:方法名、入参、返回参数、内容。
- 方法名:
- 名称
- 动词+名词
- 描述入参
- 名称需要体现实现内容,不要有实现了内容却不能通过名称看出来。
- 名称
- 入参:
- 参数少比较好
- 可以用成员变量减少入参
- 参数两个以上可以封装为对象
- 返回参数
- 内容
5.1格式目的
代码格式和沟通相关。
5.2垂直方向上的格式
- 向报纸学习
- 不同概念间用空行隔开,这样看一段内容时目光会停留到空白行之前。如导包与类声明,函数间
- 相同概念不要隔开
5.3水平(横向)方向上的格式
- 代码长度
- 运算符两边加空格
- 缩进
5.4团队规则
应该选用一套管理代码格式的简单规则,然后贯彻这些规则。
团队中管理代码格式也是这样。
6.对象和数据结构-记录
6.1数据抽象
对象隐藏数据结构,暴露行为。
数据结构暴露数据,没有提供有意义的方法。
1 | // 数据结构 |
1 | // 对象 |
6.5小结
面向过程更适合不改变其他类的情况下添加方法。
面向对象更适合不改变其他类的情况下添加新的类或数据类型。
如:形状类;矩形类,圆形类。
10.类-笔记
10.1类的组织
结构:
- 类的公有静态变量
- 类的私有静态变量
- 私有实体变量
- 类的公共方法
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-4:重构
用测试保证代码正确的前提下,递增式的重构代码。
- 思考设计
- 重构一小块
- 运行测试
优秀软件设计规则
- 高内聚,低耦合
- 切分关注面?
- 模块化系统性关注面?
- 缩小函数和类的尺寸
- 用更好的名称
- …
2消除重复
- 类属性提取共性来切分为多个类
- 抽取共性,抽取后方法用途不属于本类时,新建一个类存放,提升可见性。
- 模板方法模式可以移出高层级重复
3表达力
17 味道与启发(规则总结)
1.注释
- 不需要的注释,代码自身已经解释清楚
- 注释与代码含义不同
- 删除过期注释
- 注释应该说明代码无法表达的东西。作者和日期通过代码版本工具管理。
2.环境?
3.函数
- 短小
- 只做一件事
- 函数内容的抽象层级是名称的下一级,函数内容的抽象层级在同一级。
- 函数个数尽量少
4.一般性问题
- 小心不正确的边界行为,可以编写多个测试验证和调试
- 不要忽视安全,比如重构后不运行测试。
- 不要有重复代码。
解决:- 抽取公共函数
- 更隐蔽的重复,不同模块的检测同一组条件的switch/case,使用多态
- 更更隐蔽,类似算法,具体执行不同。使用模版方法模式或策略模式。
- ID生成,上游调用者信息,给第三方发消息,如钉钉,抽出公共组件,达成springboot的jar包。
- 信息过多
- 去除死代码
- 前后规则一致,比如命名风格
- 用接口,函数做区分比用变量区分更容易理解?
- 不恰当的静态方法。需要使用多态时,函数不能时静态的。
- 解释性变量
- 函数名称需要表达其行为
- 理解算法,途径:编写测试还不够,就重构
- 逻辑依赖改为物理依赖,一个模块依赖另一个模块时,依赖者不要去假定(逻辑依赖)被依赖者,应该显示依赖
- 多态替代IF/ELSE和Switch/Case
- 遵守标准约定
- 用命名常量替代魔术数
- 准确
代码中的含糊和不准确要去除。意见不同和懒惰会影响准确 - 好结构甚于约定的次结构?待实践
- 封装条件,比如判断条件用描述性好的函数封装
- 肯定条件比否定条件好理解
- 必要的时序性耦合暴露出来
- 代码结构别随意
- 封装边界条件,边界难以追踪,难以理解
- 在较高层级放配置变量?
- 避免传递不需要的依赖?
5.Java
不要继承常量,应该导入常量所在的类
枚举比常量表达力更强6.名称
表明意图
名称要与抽象层级相符。
反映类或函数抽象层级的名称比实现内容的名称更好。
较大作用范围选用较长的名称7.测试
使用覆盖率工具
测试边界条件
全面测试相近的缺陷
合理顺序的测试用例,测试工具会直接标记出测试失败的位置。8.小结
并发编程
内存模型保证的原子操作,32位 64位操作,一行代码对应多行字节码的非原子性,多个线程字节码执行路径指数级上升,锁syn,Executor。
1.整洁代码-感受
整洁代码是让作者和每一个读者都能读懂代码,觉得代码本该如此。
如何实现:
理想情况下:作者和读者对整洁代码的理解一致,认为的整洁代码规则一致。
实践:自己知道的整洁代码规则,自己按照规则去写代码,能够给别人讲出来这一套规则。
实践中会遇到的问题:
1.别人不理解规则;
2.别人理解规则,但不明白为什么这么做;
3.别人理解为什么这么做,但不去做;
4:和别人的规则冲突。
如何解决实践中遇到的问题:TODO
抽象层级总结
分离较高层级的一般性概念和较低层级的细节概念。
基类和派生类是高抽象层级和低抽象层级的关系。
抽像层级与单一原则。从高层次来看,符合单一原则,从低层次看,不符合单一原则。比如:提交收货,包括修改收货数据,提交到订单服务,库存服务,修改固定资产等。每个动作都属于提交收货处理,提交收货处理是单一的。
内容
包,类,函数,变量。
外部使用的类,内部使用的类
业务函数,工具函数
常量,临时变量
业务和项目架构抽象层级
Java中的抽象层级
高与低
基类 派生类
函数名称 函数内容
拆分时机
内容过多,相关内容迭代。
函数超过50行?
类超过500行?类变量超过10个?
没记住的条目 todo
- 逻辑依赖改为物理依赖,一个模块依赖另一个模块时,依赖者不要去假定(逻辑依赖)被依赖者,应该显示依赖。 比如直接feign掉用和二方包依赖后feign掉用
- 理解算法,途径:编写测试还不够,就重构
- 封装边界条件,边界难以追踪,难以理解
- 抽象层级
- 反映类或函数抽象层级的名称比实现内容的名称更好。–待实践
其他书
Practices( 中 译 版《 敏捷 软件 开发: 原则、 模式 与 实践》, 简称 PPP)