3 重构
什么是重构?
重构,英文单词Refactoring,是指通过调整程序代码,改善优化软件的质量、性能,使程序的设计和架构更合理,可以更好的进行后期维护和迭代扩展。
为什么要重构?
从重构的定义中,我们可以看到,重构的目的是便于后期维护和扩展,如果一个程序不需要迭代维护,也不需要进行功能扩展,那就没有重构的必要。
不过,作为后端服务开发,服务上线的那一刻,不是工作的结束,而是工作的开始。只要服务还在线上运行,相应的业务还在运营状态,迭代维护就避免不了。除非这是个夕阳业务,随时准备下线了。
服务设计之初,能够很好的满足当时的业务场景,也有很好的架构设计。但随着迭代次数的增多,不仅业务场景在变化,开发人员也在不断变化。很多初始的良好设计没有被延续下来,有些原有设计不再满足目前的业务场景,维护迭代变得越来越麻烦。
开发人员做线上业务迭代,就像在进行高空作业,战战兢兢不敢越雷池一步,小心谨慎的打上一个个补丁。程序员喜欢用祖传屎山来形容这样的项目,摇摇欲坠却屹立不倒。
总结一句话,项目中充斥着各种不合理的实现,每次迭代维护都很难受。
什么情况下重构
虽然项目看着很多地方都不合理,但我仍希望给出一个忠告:项目如果能够稳定运行,就不要轻易重构和修改它。
大规模重构很少发生
整个服务级别的重构,需要看机会。一个复杂的服务项目,重构可能需要2-3个人月,而服务重构,一般很难在业务层面带来比较明显的变化,即用户侧一般无法感知到变化。非技术序列的管理者,不喜欢这样的项目,在他们看来有投入,没有太有效的产出。对于技术主管来说,也要有充足的理由来说服上级管理者,支持服务级别的重构。
不要在没把握的时候重构
修改既有逻辑,会存在一定的风险,可能导致已有业务逻辑出现错误。在一个没有良好设计的系统中,很多时候,一个微小的改动,可能造成灾难性的后果。
重构完的代码,如何做到心里踏实,确定不会出现问题?
充分的测试,是系统功能正确的保障。
单元测试是第一重保障。开发时就完善的单元测试,在重构之后仍能全部通过,可以保证修改的逻辑没有漏洞。
压力测试是第二重保障。压力测试中包含各种业务流程的测试,并且请求的规模很大,压力测试没有出现问题,至少业务流程不用再担心。
这些测试工具,应该是随着平时的开发逐步完善的,而不是在重构前临时做。
重构应该在开发中不断进行
重构不需要等待。
每次项目迭代,都是重构的好时机。
写出计算机能够理解的程序很简单,写出人类容易理解的代码,才是优秀的程序员。
新写的逻辑要有合适的设计,与老逻辑结合的部分,要仔细观察,是否需要改进,而不是机械的堆加逻辑。
大项目要做小,小项目要做大
如果一次迭代,修改的业务逻辑很多,这时候不适合做太多的重构内容。因为本身修改就很多,如果再做额外的修改,会消耗注意力,无论是代码review,代码评审,还是自测,都会增加很多工作量,增加额外的风险。
有时候一次项目迭代,修改内容很少,这时候适合做一些模块级别的重构。因为业务迭代把握较高,做些重构工作,可以使工作量更饱和,重构操作更从容,出风险的概率更低,也更容易获得成长和出绩效。
作为一个开发人员,做项目的数量其实是有限的,需要把握好每一个项目,做出亮点,做出成绩。
如何重构
具体的重构策略,可以学习《重构 改善既有代码的设计》,这本书里详细介绍了各种重构的手段,虽然不用每一种策略都使用,但熟读此书,会让你知道,哪些情况需要重构,以及如何进行操作。
以下介绍几种常见的代码级别的重构情形
多次出现的代码段
如果一段功能相同的代码,出现在两个以上的地方,需要把这段代码提出来,写成一个函数,在两个地方调用这个提炼出来的函数。
重复出现的代码,通常是由拷贝代码引发的,新手容易做这类操作,发现后需要提取函数。
if-else-else堆叠
只有两个分支的情况,很容易写成if-else模式,当添加第三个分支时,一般会继续在下面追加else形成新的条件分支。
此时最好改写为switch-case模式,命中效率会有提升,代码逻辑也更清晰。
过长的函数
如果一个函数很长,通常是这个函数操作太多,函数职责不明确。
应该积极的分解函数,每当觉得需要给代码段增加一些注释时,最好提取出一个新的函数,实现这个逻辑。
函数名能够明确的表示出函数的功能。
当阅读函数的时候,就像读文章一样,调用的每个函数都是一句话,无需关注子函数内部实现细节,很容易理解整个函数做了什么。
密集调用另一个类的成员变量
成员变量原则上不要暴露出去,由外部直接调用,而是采用函数调用的方式操作数据。
但有时候,项目代码中确实存在了暴露成员的变量的情行。
如果在B类一个函数中,操作了很多A类的成员变量,较好的做法是在A类中创建一个函数,进行统一操作,由B类调用这个函数。
过大的类
一个类,如果很大,代码行数很多,函数也有很多,往往是聚集了太多的职责。
按照职责单一原则,应该将一些相互关联的变量和函数拆分出来,单独形成一个新的类。每个类只背负一个特定的职责。
Last updated