重构的目的:为什么要重构(why)?
什么是重构
重构的定义是:重构是一种对软件内部结构的改善,目的是在不改变软件的可见行为的情况下,使其更容易理解,修改成本更低。
这个定义中有一个值的强调的点:“重构不改变外部的可见行为”。我们可以把重构理解为,在保持功能不变的前提下,利用设计思想、原则、模式、编程规范等理论来优化代码,修改设计上的不足,提高代码质量。
为什么要进行重构
(1)首先,重构是时刻保证代码质量的一个很有效的手段,不至于让代码腐化到无可救药的地步。项目在演进,代码不停地在堆砌。如果没有人为代码的质量负责任,代码总是会往越来越混乱的方向演进。当混乱到一定程度之后,量变引起质变,项目的维护成本已经高过重新开发一套新代码的成本,想要再去重构,已经没有人能做到了。
(2)其次,优秀的代码或者架构不是一开始就能完全设计好的,就像优秀的公司和产品也都是迭代出来的。我们无法100%遇见未来的需求,也没有足够的精力、时间、资源为遥远的未来买单,所以,随着系统的演进,重构代码也是不可避免的
(3)最后,重构是避免过度设计的有效手段。在我们维护代码的过程中,真正遇到问题的时候,再对代码进行重构,能有效避免前期投入太多的过度设计,做的有的放矢。
除此之外,重构对一个工程师本身技术的成长也有重要的意义。
(1)重构实际上是对我们学习的经典设计思想、设计原则、设计模式、编程规范的一种应用。重构实际上就是将这些理论知识,应用到实践的一个很好的场景,能够锻炼我们熟练使用这些理论知识的能力。除吃之外,平时堆砌业务逻辑,你可能总觉得没啥成长,而将一个比较烂的代码重构成一个比较好的代码,会让你很有成就
(2)除此之外,重构能力也是衡量一个工程师代码能力的有效手段。所谓“初级工程师在维护代码,高级工程师在设计代码,资深工程师在重构代码”,这句话的意思是说,初级工程师在已有代码框架下修改 bug、修改添加功能代码;高级工程师从零开始设计代码结构、搭建代码框架;而资深工程师为代码质量负责,需要发觉代码存在的问题,重构代码,时刻保证代码质量处于一个可控的状态(当然这里的初级、高级、资深只是一个相对概念,并不是一个确定的职级)。
重构的时机:什么时候重构(when)?
搞清楚了为什么重构,到底重构什么,我们再来看一下,什么时候重构?是代码烂到一定程度之后才去重构吗?当然不是。因为当代码真的烂到出现“开发效率低,招了很多人,天天加班,出活却不多,线上 bug 频发,领导发飙,中层束手无策,工程师抱怨不断,查找bug 困难”的时候,基本上重构也无法解决问题了。
个人比较反对,平时不注重代码质量,堆砌烂代码,实在维护不了了就大刀阔斧地重构、甚至重写的行为。有时候项目代码太多了,重构很难做得彻底,最后又搞出来一个“四不像的怪物”,这就更麻烦了!所以,寄希望于在代码烂到一定程度之后,集中重构解决所有问题是不现实的,我们必须探索一条可持续、可演进的方式。
推荐重构策略是持续重构:
- 平时没有事情的时候,你可以看看项目中有哪些写得不够好的、可以优化的代码,主动去重构一下。或者,在修改、添加某个功能代码的时候,你也可以顺手把不符合编码规范、不好的设计重构一下。总之,就像把单元测试、Code REview作为开开发的一部分,我们如果能把持续重构也作为开发的一部分,成为一种开发习惯,对项目、对自己都会有好处。
- 尽管我们说重构能力很重要,但持续重构意识更重要。我们要正确地看待代码质量和重构这件事情。技术在更新、需求在变化、人员在流动,代码质量总会在下降,代码总会存在不完美,重构就会持续在进行。时刻具有持续重构意识,才能避免开发初期就过度设计,避免代码维护的过程中质量的下降。而那些看到别人代码有点瑕疵就一顿乱骂,或者花尽心思去构思一个完美设计的人,往往都是因为没有树立正确的代码质量观,没有持续重构意识。
重构的方法:又该如何重构(how)?
按照重构的规模,重构可以分为大型重构和小型重构。对于这两种不同规模的重构,我们要区别对待:
- 对于大型重构来说,因为涉及的模块、代码会比较多,如果项目代码质量又比较差,耦合比较严重,往往会牵一发而动全身,本来觉得一天就能完成的重构,你会发现越改越多、越改越乱,没一两个礼拜都搞不定。而新的业务开发又与重构相冲突,最后只能半途而废,revert 掉所有的改动,很失落地又去堆砌烂代码了。
- 在进行大型重构的时候,我们要提前做好完善的重构计划,有条不紊的分阶段来执行。每个阶段完成一小部分代码的重构,然后提交、测试、运行、发现没有问题之后,再继续进行下一阶段的重构,保证代码仓库中的怠慢一直处于可运行、逻辑正确的状态。每个阶段,我们都要控制好重构影响到的代码范围,考虑好如何兼容老的代码逻辑,必要的时候还需要写一些兼容过渡代码。只有这样,我们才能让每一阶段的重构都不至于耗时太长(最好一天就能完成),不至于与新的功能开发相冲突。
- 大规模高层次的重构一定是有组织、有计划,并且非常谨慎的,需要有经验、熟悉业务的资深同事来主导。而小规模低层次的重构,因为影响范围小,改动耗时短,所以,只要你愿意并且有时间,随时都可以去做。实际上,除了人工去发现低层次的质量问题,我们还可以借助很多成熟的静态代码分析工具(比如 CheckStyle、FindBugs、PMD),来自动发现代码中的问题,然后针对性地进行重构优化。
如何保证重构不出错呢?你需要熟练掌握各种设计原则、思想、模式,还需要对所重构的业务和代码有足够的了解。除了这些个人能力因素之外,最可落地执行、最有效的保证重构不出错的手段应该就是单元测试(UnitTesting)了。当重构完成之后,如果新的代码仍然能通过单元测试,那就说明代码原有逻辑的正确性未被破坏,原有的外部可见行为未变,就可以了
总结
(1)重构的目的:为什么重构(why)?
对于项目来言,重构可以保持代码质量持续处于一个可控状态,不至于腐化到无可救药的地步。对于个人而言,重构非常锻炼一个人的代码能力,并且是一件非常有成就感的事情。它是我们学习的经典设计思想、原则、模式、编程规范等理论知识的练兵场。
(2)重构的对象:重构什么(what)
按照重构的规模,我们可以将重构大致分为大规模高层次的重构和小规模低层次的重构。
- 大规模高层次重构包括对代码分层、模块化、解耦、梳理类之间的交互关系、抽象复用组件等。这部分工作利用得更多的是比较抽象、比较顶层的设计思想、原则、模式
- 小规模低层次的重构包括规范命令、注释、修正函数参数过多、消除超大类,提取重复代码等编程细节问题,主要是针对类、函数级别的重构。小规模低层次的重构更多的是利用编码规范这一理论知识
(3)重构的时机:什么时候重构(when)?
我们一定要建立持续重构意识,把重构作为开发必不可少的部分,融入到日常开发中,而不是等到代码出现很大问题的时候,再大刀阔斧地重构。
(4)重构的方法:如何重构(how)?
大规模高层次的重构难度比较大,需要组织、有计划地进行,分阶段地小步快跑,时刻让代码处于一个可运行的状态。而小规模低层次的重构,因为影响范围小,改动耗时短,所以,只要你愿意并且有时间,随时随地都可以去做。