Refresh Clean Code
这是一本被前辈称赞, 另一个马丁的知名著作, 被赋予学习如何写出漂亮代码的教皇级手册, 最近(再)浏览, 有诸多感受.
总结一下就是如果你是新手, 可能看优秀的open source学习更好; 如果你是老手, 可能他说的东西要么你会觉得太啰嗦, 要么你会觉得例子太细节, 太教条, 无法举一反三.
不过一些General的要点思想我摘录了出来, 有些还包括个人的观点, 记录在此, 以备查阅.
- 勒布朗(LeBlanc)法则, Later equals never
- 代码如果烂了就会越来越烂, 所谓破窗原理, 以前老是也提过叫代码腐化
- 命名
- 命名应该合理, 成员变量不用加前缀, 靠高亮就很好
- 接口加前缀I也不太好, 不如实现加Imp
- 方法命名最好使用动词或动词短语
- 多使用计算机领域的词汇
- 有时候命名在语境里才有意义, 有时候变量命名也不需要重复语境
- 函数
- 函数短小一些比较易懂, 20行内最佳
- if/else/while代码块封装函数, 只有一行最好 (有点激进?)
- 一个函数只干一件事情
- 复杂的switch试图用多态取代, 然后封装在抽象工厂里
- 同一类型函数命名风格应该一致, 使用描述性的语句描述比难懂的短词更好
- 一元函数最普遍, 标识函数(参数为boolean)的不如分成两个函数, 二元函数尽可能转换成一元函数
- 参数太多可能需要封装类了
- 好名字的函数一般是动词, 或者动词+关键字
- 函数一般要么做事, 要么回答事, 不可兼得 (其实也不一定, 比如很多返回boolean状态的函数)
- 推荐使用异常代替错误代码
- 最好将try/catch单独抽入一个函数 (如Android代码会对RemoteService调用进行类似封装, 吃掉exception)
- 使用枚举表示错误码会让改动变得繁琐 (重新导入或者编译部署), 大家都依赖这个枚举, 应该用异常与继承取代
- 不要重复, 偶尔可以考虑AOP这类的方法解决
- 注释
- 注释少要比注释多有用, 注释多说明代码糟糕 (其实分情况吧)
- 有目的性的注释还是有用的, 比如阐述, 放大, 警告, 定期维护的TODO等
- 如果代码不用, 不要注释掉, 直接删掉
- 格式
- 横向, 竖向对齐, 间隔, 缩进等. 其实目前formater已经很强大了, 养成良好的format习惯, 渐渐的写出来的代码就会像直接format后一样
- 对象与数据结构
- 面向过程与面向对象对立, 前者容易加函数, 不容易加对象, 后者容易加对象, 而不容易加函数
- 理想情况下, Law of Demeter认为, 类不应该操作对象内部的东西, 如不应该操作函数返回对象的方法. 目的只是为了降低复杂度, 认为这样将私有变量公开化, 增加了重构的难度, 如添加新函数, 方法等, 将数据结构与对象逻辑耦合在一起.
- 针对于上面的问题, 原则应该是对象暴露行为, 隐藏数据, 数据结构暴露数据
- 错误处理
- Java特色的受控异常(Checked Exception), 必须得被catch, 对受控异常的修改会引起上层所有调用方法的改动, 尽量不要使用, 其他语言只有RuntimeException
- 可以包装第三方逻辑, 封装自己的异常类, 简化为只catch一种异常
- 简化使用也可以将异常处理完全封装进去, 返回特例即可
- 轻易不要返回null, 传入null
- 代码的坚固与干净不冲突, 所以添加一些check, throw相应的Exception也合理
- 边界
- 所谓边界就是自己可以控制到的程序与第三方的边界, 通常需要通过封装的办法来划清边界, 限制那些无法控制的第三方, 如直接Wrapper或者Adapter模式等
- 单元测试
- 测试需要整洁, 需要跟随代码一起更新, 测试的最大用途是保证你后续的修改有信心
- 测试的整洁主要讲的是可读性, 即分为三个环节, build->operate->check, 可以把繁杂的准备封装起来
- 测试API是渐渐重构演进过来的, 也不可能是起初就设计出来
- 有人建议每个测试一个assert, 但是这样会有很多重复的代码, 不过可以利用
Template Method
来解决. 不过也不一定必须一个, 做到最小化就可以了 - 整洁还有五条规则, Fast, Independent, Repeatable, Self-Validation, Timely
- 这里讲到TDD的好处, 主要是帮助你覆盖更多的测试, 如果写完再测, 发现测不了, 就不写了.
- 类
- 类应该短小, 权责应该足够单一, 内聚性应该高
- 如果期望抽走一部分逻辑, 最好连相关函数参数也抽走
- 简化类的过程应该小步, 每一步都运行测试一下
- 类的精简是为了更好的体现开闭原则, 整体结构为修改而设计
- 类应依赖于接口, 不依赖实现, 可以隔离修改, 符合依赖倒置原则, 类似策略模式
- 系统
- 构造与使用分开, 通过依赖注入等
- 通过AOP, Proxy的方式, 无侵入性的插入逻辑
- 系统如果充分模块化, 领域之间相互直接松耦合, 最为理想, 就可以通过测试来驱动
- DSL的使用可以平衡领域与技术
- 迭代
- 代码是在不断迭代中进步的, 比如通过抽函数, 运用设计模式等
- 并发
- 应该尽可能分离并发代码与其他代码
- 尽可能让线程之间独立, 不要有共享
- 线程模型有典型的生产者与消费者, 读者与作者, 宴席哲学家模型, 分别用来说明互斥, 读写, 竞争死锁等特例
- 锁定代码块应该尽可能小
- 线程最好可插拔, 遵循单一权责, 分离线程与其他代码, 测试先保证除线程之外的逻辑
- 最后一张通过实例来介绍并发代码如何重构的比较清晰
- 逐步改进
- 这片使用了一个例子, 先采用蛮干进行重构发现越来越难, 后来采用逐步的办法, 还写的很细节, 但是比较Tricky的地方是, 这里提出的每步改动都通过测试来印证, 然后保证测试通过, 或者补充一个测试, 让代码通过, 但是所谓TDD部分只有思想, 老代码既然写的不好, 测试是如何出来的, 如何做到Cover全的. 如果假设在一个有比较好测试覆盖的基础上重构, 我感觉即便蛮力也不会太差…
- JUnit
- 比较神奇的一章, 感觉更像是用一个算法类的重构来说明如何把代码改简单, 但是开头先介绍了100%覆盖的Junit测试长什么样子… 感觉作者是想说以前的人做的还不错, 都用JUnit给覆盖全了, 还是可以重构的更漂亮的, 所以感觉标题不太好
- 重构SerialDate
- 跟上一个对比, 这个测试覆盖不全, 所以在补测试的过程中进行重构, 还发现了缺陷, 又最终把代码改清晰了. 不过同样由于太过细节, 一些重构理论也很教条, 如果想通过看别人重构的例子来学习如果写简洁的代码, 不如直接看优秀的代码是怎么写的
- 味道
- 里面类比了很多Bad Smell, 同样是很细节, 比如太多, 太死, 太复杂, 重复, 不一致, 耦合, 测试不足等, 提供的Tips大多的中心思想就是抽, 封装, 单一职责, 命名清晰
- 提到使用*来避免过长import, 不过这个与现在流行的lint检测违背, 可以因为以前都是手动, 通配符简单吧.
- 不要通过继承来使用常量, 还提到不要用静态常量, 用枚举, 这个也与当前的思想有出入, 现在经常将Java的enum太冗余, 不必要时可以用常量