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