重构是一种对软件内部结构的改善,目的是在不改变软件的可见行为的情况下,使其更易理解,修改成本更低。
简单说,在保存功能不变的前提下,利用设计思想、原则、模式、编程规范等理论来优化代码,修改设计上的不足,提高代码质量。
重构的目的:为什么要重构(why)
- 重构是时刻保证代码质量的一个极其有效的手段,不至于让代码腐化到无可救药的地步。
项目在演进,代码在不停地堆砌,如果没有人为代码的质量负责任,代码会越来越混乱,到一定程度后项目的维护成本已经高过重新开发一套的成本 - 优秀的代码或架构不是一开始就能完全设计好的
未来的需求是多变的,是需要不断迭代的。随着系统的演进,重构代码也是不可避免的。 - 重构是避免过度设计的有效手段。
在维护代码的过程中,真正遇到问题的时候再对代码进行重构,能有效避免前期投入太多时间做过度的设计,做到有的放矢 - 重构能力也是衡量一个工程师代码能力的有效手段
初级工程师在维护代码,高级工程师在设计代码,资深工程师在重构代码。资深工程师为代码质量负责,需要发觉代码存在的问题,重构代码,时刻保证代码质量处于一个可控的状态
重构的对象:到底重构什么(what)
根据重构的规模,可以笼统的分为大规模高层次重构和小规模低层次重构
-
大规模高层次重构
对顶层代码设计的重构,包括:系统、模块、代码结构、类与类之间的关系等的重构,重构的手段有:分层、模块化、解耦、抽象可复用组件等等。重构的工具:设计思想、原则和模式。这类重构涉及的代码改动会比较多,影响面会比较大,难度也较大,耗时会比较长,引入BUG的风险也会相对比较大。 -
小规模低层次重构
对代码细节的重构,主要是针对类、函数、变量等代码级别的重构,比如规范命名、规范注释、消除超级大类或函数、提取重复代码等等。小型重构更多的是利用编码规范。这类重构要修改的地方比较集中,比较简单,可操作性较强,耗时比较短,引入BUG的风险相对来说也会比较小。
重构的时机:什么时候重构(when)
提倡持续重构。在任务不多时看看项目中哪些写得不够好的、可以优化的代码,主动重构;在修改、添加某个功能代码时,把不符合编程规范、不好的设计重构一下。
要正确看待代码质量和重构这件事。技术在更新、需求在变化、人员在流动,代码质量总会在下降,代码总会存在不完美,重构就回持续进行。时刻具有持续重构意识,才能避免开发初级就过度设计,避免代码维护的过程中质量的下降。看到别人代码有瑕疵就一顿乱骂,或花尽心思去构思一个完美设计的人,往往是因为没有树立正确的代码质量观,没有持续重构的意识。
重构的方法:该如何重构(how)
进行大型重构的时候,要提前做好完善的重构计划,有条不紊地分阶段来进行。每个阶段完成一小部分代码的重构,然后提交、测试、运行,发现没有问题后再继续进行下一阶段的重构,保证代码仓库中的代码一直处于可运行、逻辑正确的状态。每个阶段都需要控制好重构影响范围,考虑如何兼容老的代码逻辑,必要的时候还需要写一些兼容过渡代码。
进行小型重构的时候,因为影响范围小,改动耗时短,所以,只要愿意并且有时间,随时都可以去做。还可以借助成熟的静态代码分析工具(CheckStyle、FindBugs、PMD)来自动发现代码中的问题,然后针对性地进行重构优化
重构的保障:单元测试
如何保证重构不出错?需要熟练掌握各种设计原则、思想、模式,还需要对所重构的业务和代码有足够的了解。除此之外,最可落地执行、最有效的保证重构不出错的手段就是单元测试(Unit Testing)
什么是单元测试
单元测试由研发工程师自己来编写,用来测试自己写的代码的正确性。单元测试的测试对象是类或者函数,用来测试一个类和函数是否都按照预期的逻辑执行。这是代码层级的测试。
为什么要写单元测试
-
单元测试能有效地帮你发现代码中的BUG
单元测试常常会发现代码中的很多考虑不全面的地方 -
写单元测试能帮你发现代码设计上的问题
代码的可测试性是评判代码质量的一个重要标准。对于一段代码,如果很难为其编写单元测试,或者单元测试写起来很吃力,需要依靠单元测试框架里很高级的特性才能完成,那往往就意味着代码设计得不够合理。 -
单元测试是对集成测试的有力补充
程序运行的BUG往往出现在一些边界条件、异常情况下,比如除数未判空、网络超时等。大部分异常情况都比较难在测试环境中模拟,而单元测试可以利用mock的方式,控制mock的对象返回需要模拟的异常,来测试代码在这些异常情况的表现。 -
写单元测试的过程本身就是代码重构的过程
写单元测试实际就是落地执行持续重构的一个有效途径。设计和实现代码的时候,很难把所有问题都想清除,而编写单元测试就相当于对代码的一次自我Code Review,在这个过程中,可以发现一些设计上的问题以及代码编写方面的问题,然后针对性的进行重构 -
阅读单元测试能帮助快速熟悉代码
单元测试用例实际上就是用户用例,反映了代码的功能和如何使用。借助单元测试,不需要深入的阅读代码,便能知道代码实现了什么功能,有哪些特殊情况需要考虑,有哪些边界条件需要处理。 -
单元测试是TDD可落地执行的改进方案
测试驱动开发(Test-Driven Development)是一个经常被提及但很少被执行的开发模式,它的核心指导思想就是测试用例先于代码编写。单元测试正好是对TDD的一种改进方案,先写代码,紧接着写单元测试,最后根据单元测试反馈出来问题,再回头重构代码。这个开发流程更容易被接受,更加容易落地执行,而且兼顾TDD的优点。
如何编写单元测试
写单元测试就是针对代码设计覆盖各种输入、异常、边界条件的测试用例,并将这些测试用例翻译成代码的过程。
-
写单元测试真的时间很耗时的事情吗
单元测试的代码量可能是被测试代码本身的1~2倍,写的过程很繁琐,但并不是很耗时。因为不需要考虑太多代码设计上的问题,测试代码实现起来也比较简单 -
对单元测试的代码质量有什么要求吗
单元测试不会在产线上运行,而且每个类的测试代码也比较独立,基本不互相依赖。所以相对于被测代码,单元测试代码的质量可以放低一些要求 -
单元测试只要覆盖率高就够了吗
将覆盖率作为衡量单元测试质量的唯一标准是不合理的,更重要的是看测试用例是否覆盖了所有可能的情况。针对下面这段代码,我们只需要cal(10.0, 2.0)就可以做到100%覆盖率,但并不代表测试足够全面,例如当除数等于0的情况
public double cal(double a, double b) {
if (b != 0) {
return a / b;
}
}
-
写单元测试需要了解代码的实现逻辑吗
单元测试不要依赖被测试函数的具体实现逻辑,它只关心被测函数实现了什么功能。切不可为了追求覆盖率,逐行阅读代码,然后针对实现逻辑编写单元测试。 -
如何选择单元测试框架
写单元测试本身不需要太复杂的技术,大部分单元测试框架都能满足。只需要公司(团队)内部统一单元测试框架即可。
网友评论