代码重构之道
1.干净代码
重构的主要目的是为了消除技术债务,将复杂转换成干净代码和简单设计。
干净代码主要有下面几个特点:
- 其他工程师很容易理解
这里不是聊超级复杂算法,糟糕的变量命名和冗余的类和方法,黄金数字,你能说到的这些都会让代码变得臃肿
并且很难理解。
- 没有重复代码
每次你不得不在重复代码中变更代码时,你不得不在所有的实例中做同样的变化同步,最终会提高认知负担,减慢进度。
- 包含最小数量的类和辅助部分
更少的代码意味着更少的维护量,更少的 bug。
代码是一种责任,要保持简短和简单。
- 可以通过所有测试
当只有 95%的代码通过测试时,你的代码就不会干净。
测试覆盖率是零时,你的代码就有问题。
- 维护更加简单
2.脏代码/技术债务
每个人一开始都会尽力编写优质代码。
没有一个程序员会有意编写脏代码搞砸项目。
但什么时候代码会变得不干净呢?
脏代码的技术债务比喻最早是 Ward Cunningham 提出。
如果你从银行贷款,会让你更快付款。
你付出额外的经费加速过程。
代码也是这样,为了加快进度完成新功能,不写测试,最后会逐渐的减慢进度,最终
编写所有的测试来偿还技术债务。
-
业务压力
有时业务场景会迫使你在功能全部完成之前铺开。
这时,各种补丁和黑科技都会在代码中出现,进而隐藏项目未完成的部分。
-
缺乏技术债务结果的理解
有时你的雇员不会理解随着技术债务的积累,技术债务也会有收益,降低开发的节奏。
这会让后续重构变得更加困难,因为故那里层不会重视重构的价值。
- 无法抵制组件之间的复杂依赖
这会发生在项目组装成大型应用,而不是独立产品模块时。
这种场景中,项目中的任何变化将会影响其他部分。
团队开发会变得更加困难,因为很难分割每个成员的工作。
- 缺乏测试
缺乏快速反馈鼓励快速但有风险的折中方案或替代技巧。
更加糟糕的场景中,生产环境会在没有预先测试的情况下集成和实施这些变化。
这会产生灾难性的后果,例如,某项修复会发送奇怪的测试邮件给数千计客户,更糟糕的是,导致整个数据库崩溃。
- 缺乏文档
缺乏文档將降低新人熟悉项目的速度,如果有关键人员离开时,会加快项目停滞。
- 缺乏团队成员之间的互动
如公司缺乏完整的知识库,开发人员将会获得过时的项目过程信息。
当导师错误地培训初级开发者时,这种情况会恶化。
-
不同分支长期并行开发
-
重构延期
项目需求一直在变,有时部分代码会废弃成为累赘,必须重新设计以满足新需求。
另外一方面,项目开发者每天都在围绕废弃部分写新代码。因此,重构延期越长,未来需要重做的关联代码会更多。
- 缺乏兼容性监控
当所有开发者都在同一个项目编写他们认为合适的代码时,就如同上个项目一样,就会缺乏相应的监控。
- 代码不完整
当开发者不知道如何编写合适的代码时会导致代码不完整。
3.何时重构
-
三原则
当第一次就把事情做好时;
当第二次重复类似的事情时,不得不重复第一次的套路;
当你开始做第三次时,开始重构;
-
添加新功能时
- 重构帮助理解其他人的代码,如果你不得不处理其他人的脏代码时,首先尝试重构。
清晰代码更容易理解。你的代码改进不仅仅为了自己,也为了那些之后使用这些代码的人。
- 重构可以更容易地添加新功能,很容易改变清晰代码。
-
修复 BUG
-
bug 和现实生活中一样,它们生活在最阴暗肮脏的地方,清除代码和错误实际上会暴露它们。
-
管理合适有前瞻性的重构会降低特定重构任务的需求。
-
-
代码审查
代码审查可能是上线之前清洁代码的最后机会。
最好和作者进行结对审查,可以很快修复简单问题,预估时间修复更难的问题。
4.如何重构
重构应当通过一系列的变化,会逐步优化当前代码,保证程序正常工作。
-
正确实现重构清单
-
代码应该变得简化
-
新功能重构期间不能继续增加
-
重构完成后必须通过所有测试
-
5.代码味道
-
代码肿胀
代码肿胀是指代码、方法、类上升巨大的数量以至于很难维护。
通常代码肿胀不会突然出现,而是随着程序演进逐步积累。
-
冗长方法
-
原始类型冗余
-
笨重数据
-
大型类
-
长参数列表
-
-
滥用面向对象
-
使用不同接口替代类
-
拒绝遗留
-
switch 声明
-
临时字段
-
-
代码重构的阻碍
-
分歧变化
-
并行继承结构
-
散弹式修改
-
-
冗余代码
-
注释
-
重复代码
-
数据类
-
无用代码
-
懒惰类
-
投机性
-
-
代码耦合
-
功能聚合
-
不完整的类库
-
中间人
-
不合适的耦合
-
消息链
-
6.重构技巧
-
方法组合
-
导出方法
-
使用临时变量替换查询
-
使用方法对象替换方法
-
内联方法
-
分割临时变量
-
导出变量
-
删除参数赋值
-
替代逻辑
-
内联临时变量
-
-
转移不同对象的功能
-
迁移方法
-
隐藏代理
-
引入外部方法
-
迁移字段
-
移除中间调用
-
引入本地扩展
-
导出类
-
内联类
-
-
合理组织数据
-
改变值为引用类型
-
改变引用类型为值
-
改变单向联系为双向联系
-
改变双向联系为单向联系
-
使用类替换类型代码
-
使用子类替换类型代码
-
使用状态或策略替换类型代码
-
使用字段替换子类
-
自封装字段
-
封装字段
-
使用对象替换数据值
-
封装集合
-
使用对象替换数组
-
使用符合常量替换魔法数字
-
-
简化条件表达式
-
巩固条件表达式
-
巩固重复条件片段
-
分解条件表达式
-
使用多态替换条件表达式
-
移除控制标志
-
使用守卫语句替换嵌套条件表达式
-
引入空对象
-
引入断言
-
-
简化方法调用
-
添加参数
-
删除参数
-
重命名方法
-
隔离修饰符和查询
-
参数化方法
-
引入参数对象
-
保留整个对象
-
删除设置方法
-
替换参数为显式方法
-
替换参数为方法调用
-
隐藏方法
-
替换构造函数为工厂方法
-
替换错误代码为错误
-
使用测试替换错误
-
-
处理通用场景
-
提升字段
-
提升方法
-
提升构造函数体
-
降级字段
-
降级方法
-
导出子类
-
导出父类
-
导出接口
-
收拢层级
-
形成模板方法
-
使用代理替换继承
-
使用继承替换代码
-
7.译者注
-
原文有删减,因译者水平有限,如有错误,欢迎留言指正交流
-
因原文过长,后续持续更新