重构-改善既有代码设计
2022-09-29 08:53:05 23 举报
AI智能生成
重构-改善既有代码设计
作者其他创作
大纲/内容
重构的原则
1.1何为重构
名词重构:对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本
动词重构:使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构
动词重构:使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构
1.2两顶帽子
添加新功能:此时不应该修改旧代码,而是专注添加功能
重构:与上相反
1.3为何重构
重构改进代码的设计
防止程序内部设计(架构)逐渐腐败变质
重构使软件更容易理解
“ 代码是给人读的,只是顺便让机器执行一下”
几周/月后,会有人尝试读懂你写的代码并尝试修改它,那个人的体验才是最重要的
不需要花时间去记住某一部分代码,而是尽量让代码本身具有表达性
重构帮助找到bug
一些无法通过看代码检查出的bug,需要重构帮助
重构提高编程速度
当代码架构变得臃肿,添加功能需要的时间就越来越长,重构则有助于改善
1.4何时重构
事不过三法则
预备性重构:让添加功能更容易
添加功能前,看看既有代码能否稍作改善以便添加新功能
帮助理解的重构:让代码更易懂
如拆解函数,变量重命名
捡垃圾式重构
已经理解代码行为,但明显有改进空间
有计划的重构
当团队在很长一段时间里都忽略了重构,就需要专门安排时间重构
长期重构
有些重构可能会花上几个星期,比如替换一个正在使用的库,或将整块代码抽取到一个组件中共享给其他团队;
亦或处理一大堆依赖关系等等
亦或处理一大堆依赖关系等等
复审代码时重构
与提交PR者一同复审代码,并及时与ta沟通需要重构的部分
怎么对经理说
大部分情况下,“重构”会被认为是一个脏词,是弥补过去的错误,并且是有风险的一件事;
所以想要说服经理也是一件有难度的事,如果你的工期充足且经理懂得代码健康度对生产率的影响,
那么是可以与经理沟通的,反之则不推荐!
所以想要说服经理也是一件有难度的事,如果你的工期充足且经理懂得代码健康度对生产率的影响,
那么是可以与经理沟通的,反之则不推荐!
何时不应该重构
如果一块凌乱的代码封装在一个API之内,并且我不需要理解其原理时,就不需要重构!
另一种情况,重写比重构还容易,可以直接重写!(当然,这需要良好的判断力和丰富的经验)
1.5重构的挑战
1.延缓新功能开发
这是一个普遍的认知,没有对错。实战中更需要丰富经验判断出是先重构还是先添加功能
说出重构的价值
1.添加新功能更快
2.修复bug更快
3.长期视角来看,提高系统稳定性和可靠性
2.代码所有权
有些认为需要改名的API是已经发布出去的,改名会影响使用者;
如果一定要改,就得和使用者进行充分沟通之后再改,通常这并不容易!
如果一定要改,就得和使用者进行充分沟通之后再改,通常这并不容易!
好的建议:增加一个新的API,给旧API标记 deprecated(不推荐使用)
- 允许逐渐废弃旧API的使用
- 允许逐渐废弃旧API的使用
3.没有自测试代码
很多旧代码是没有对应的测试用例的,这导致我们很难轻易决定重构,因为无法确保不影响已实现功能
1.6重构、架构和YAGNI
1. 在刚开始设计架构时,适当设计,保留一定空间给重构(保持灵活性,不可能一步到位)
2.YAGNI是you aren't going to need it(你不需要它)缩写
1.7自动化重构
过去10年中,各类IDE已经支持了直接对函数/变量名进行重构,这极大方便了开发者
代码的坏味道
1.神秘命名
请思考一个直观的名字,如果英文是晦涩难懂的,可以使用拼音,比如桌球Billiards
2.重复代码
需要合并,注意将差异化的逻辑通过参数体现
3.过长函数
较长的函数不易理解,更难以改动;寻找添加注释的地方,将逻辑集中的代码提炼到单独函数中
4.过长参数列表
1.将同时出现的几个参数合并为一个对象参数
5.全局数据(变量)
最大的问题在于任何一个地方都能修改它,好的建议是封装为函数获取
6.可变数据
如果变量的作用域不是几行代码,那么建议数据更新操作通过几个函数来进行
如果一个变量不同时候存储不同类型的数据,最好拆分变量
7.发散式变化
模块经常因为不同原因在不同的方向上发生变化。比如几个函数需要同时修改,
这说明部分块的代码耦合严重, 我们需要使用提炼函数、拆分阶段、搬移函数、提炼类(对象)方式来重构
这说明部分块的代码耦合严重, 我们需要使用提炼函数、拆分阶段、搬移函数、提炼类(对象)方式来重构
8.霰弹式修改
类似发散式变化,但有些不同。模块经验因为不同原因在不同类/函数内作出许多小修改。
- 应该使用搬移函数和搬移字段把所有需要修改的代码放进一个模块里
- 应该使用搬移函数和搬移字段把所有需要修改的代码放进一个模块里
9.数据泥团
在不同位置同时出现了相同的几个字段,这些总是成对/成群出现的多个字段应该提炼单独的类/对象中
10.基本类型偏执
开发者不愿为有特定意义的基本类型字段创建新的类型/对象,这导致这些字段没有被一致约束
示例:手机号Phone,在中国它必须是11位,有时候也会收到带86国家代号的手机号,这个时候就可创建
一个Type Phone string,然后为Phone类型绑定一些method,方便用于检查其有效性。
在所有需要手机号的对象定义中都使用这个Type,那就能做到数据的统一约束。
示例:手机号Phone,在中国它必须是11位,有时候也会收到带86国家代号的手机号,这个时候就可创建
一个Type Phone string,然后为Phone类型绑定一些method,方便用于检查其有效性。
在所有需要手机号的对象定义中都使用这个Type,那就能做到数据的统一约束。
11.重复的Switch
面向对象布道者 强烈建议 任何Switch语句(甚至是if/else判断)都应该被【以多态取代条件表达式】消除掉;
Switch语句的问题在于:想要增加一个分支时,需要找到并更新所有的Switch
Switch语句的问题在于:想要增加一个分支时,需要找到并更新所有的Switch
12.循环语句
以管道操作(迭代器)取代循环,比如filter和map/zip,更能清晰看到每个元素的处理过程
13.冗赘的元素
一些过于简单的函数可以不用定义为函数;同理,过于简单的结构体也不用单独定义;
- 使用内联函数和内联类处理它们
- 使用内联函数和内联类处理它们
14.临时字段
使用提炼类和搬移函数的方式为这类字段创建一个新家
15.过长的消息链
如果看到一种逻辑是串联请求多个函数才能完成任务(其中上个函数依赖下个函数的返回值)
这种时候通常会看到许多临时变量, 且其中的依赖关系发生变化会导致不小的修改。
- 在这条链上的某个部分使用【隐藏委托关系】的手法处理
这种时候通常会看到许多临时变量, 且其中的依赖关系发生变化会导致不小的修改。
- 在这条链上的某个部分使用【隐藏委托关系】的手法处理
16.中间人
有时候过渡封装,就会产生过多的【中间人】,导致调用链过长,这时候需要内联函数或移除中间人
17.过长的类
这通常是因为它负责了太多的任务,需要通过提炼类、提炼超类、以子类取代类型码将其拆分出来
- 什么是子类取代类型码:比如有个Employee类型,内部使用name和type区分不同角色,这种方式
通常会导致代码中大量出现if e.type=="sale"这样的条件表达式,我们可以提炼子类Sales(Employee),
以多态处理条件逻辑
- 什么是子类取代类型码:比如有个Employee类型,内部使用name和type区分不同角色,这种方式
通常会导致代码中大量出现if e.type=="sale"这样的条件表达式,我们可以提炼子类Sales(Employee),
以多态处理条件逻辑
18.纯数据类/对象
指的是没有定义method的类,都是在所使用的函数中直接修改类,如果只修改一次还好
修改多次就说明这不是一个纯数据类。
- 请使用【封装记录】也就是带有构造函数的类/对象,对于真正的纯数据类,请不要定义设值函数
而是让调用者去重新创建新的类变量
修改多次就说明这不是一个纯数据类。
- 请使用【封装记录】也就是带有构造函数的类/对象,对于真正的纯数据类,请不要定义设值函数
而是让调用者去重新创建新的类变量
19.被拒绝的馈赠
指的是子类不愿接受/支持父类的某些接口,这时候正确做法是不应该继承
而是运用【以委托取代子类】或【以委托取代超类】的手法
而是运用【以委托取代子类】或【以委托取代超类】的手法
20.多余的注释
优先通过简单的逻辑或简明的函数命名来阐明用意,对于较长的函数或需要加以解释的地方再加上注释
构筑测试体系
先写测试代码
测试驱动开发——TDD
注重单元测试
通过单元测试组合成集成测试
注重测试的覆盖率
特别是边界&极端情况
迭代测试用例
1.不太可能第一次就写出完备的测试代码
2.功能代码也在不断增加
2.功能代码也在不断增加
测试用例验证bug
每当发现一个新bug,请添加相应的测试用例
重构手法——剩下的章节(6-12章)
这些是具体的手法介绍,细节太多无法记录,请参看原书;
- 它们都是用来解决上面的【代码坏味道】
- 它们都是用来解决上面的【代码坏味道】
0 条评论
下一页