代码重构,是每个程序员都会遇到的事情,很多人,包括我,一开始都对重构充满热情,觉得重构就是把烂代码改成好代码,很简单,很有成就感。
但是,真正做了几次大的重构之后,我才发现,重构没有那么简单,踩了很多坑,甚至,有一次重构,差点把项目搞垮,从那以后,我对重构,有了新的认识,从入门到放弃,又从放弃到理性看待。
今天,就来分享一下,我在代码重构上的真实经历,踩过的坑,以及最后的反思和总结,希望能给大家一些参考。
一、为什么要重构
先说说,我为什么要重构。
刚工作的时候,我维护的是一个老项目,代码写得很烂,方法很长,一个方法几百行,嵌套很深,if-else套了好几层,命名也不规范,a、b、c、temp、data,什么都有,注释也很少,很多逻辑,看半天都看不懂,改一个Bug,要花好几天,还经常改出问题,很痛苦。
那时候,我就想,这代码太烂了,我要重构,把它改成优雅的、易维护的代码,这样,以后改Bug,加新功能,就容易多了。
而且,那时候,我刚看了《重构:改善既有代码的设计》这本书,觉得里面的技巧很厉害,提取方法、提取类、分解条件表达式、以卫语句取代嵌套条件表达式,等等,觉得学会了这些,就能把任何烂代码,都改成好代码,充满了热情。
所以,我就开始了我的重构之旅。
二、第一次重构的热情
第一次重构,我充满了热情,干劲十足。
我选了一个最烂的模块,那个模块,是订单处理的模块,代码最乱,方法最长,嵌套最深,改Bug最痛苦,我决定,从这个模块开始,彻底重构。
我花了一周的时间,把这个模块的代码,从头到尾看了一遍,理清楚了逻辑,然后,开始重构,提取方法,提取类,分解条件表达式,改命名,加注释,忙得不亦乐乎,每天都加班到很晚,但是,很有成就感,看着烂代码,一点点变成优雅的代码,觉得很值。
重构了大概两周,终于把这个模块重构完了,代码量减少了三分之一,方法都很短,命名都很规范,注释也很全,看起来舒服多了,我很满意,觉得自己做了一件很有意义的事情。
然后,我提交了代码,测试,上线,一开始,没什么问题,我很高兴,觉得重构成功了。
但是,好景不长,上线后第三天,客服开始反馈,有用户说,下单的时候,价格算错了,有的多算了,有的少算了,还有的用户,下单后,订单状态不对,我一下子就慌了,赶紧排查,查了很久,才发现,是重构的时候,有个条件判断,我理解错了,原来的代码,虽然烂,但是逻辑是对的,我重构的时候,把条件判断改了,以为是一样的,其实,有个边界情况,我没考虑到,导致价格算错了。
没办法,只能紧急回滚,回滚到重构前的版本,然后,再仔细排查,把那个边界情况的逻辑,改对,再测试,再上线,折腾了好几天,才解决。
这次重构,虽然最后解决了问题,但是,给我敲了一个警钟,重构,没有那么简单,一不小心,就会引入新的Bug,影响线上业务。
但是,那时候,我还没有太在意,觉得只是一次意外,下次小心点就好了,还是对重构充满热情,继续重构其他模块。
三、踩过的坑
后来,我又做了几次重构,大大小小的,踩了很多坑,才慢慢明白,重构,真的不是一件简单的事情。
坑1:重构引入新Bug 这是最常见的坑,重构的时候,以为自己理解了原来的逻辑,但是,其实没有完全理解,或者,有一些边界情况,没有考虑到,重构之后,逻辑变了,引入了新的Bug。
而且,很多时候,这些Bug,不是马上就能发现的,可能上线后,过了很久,才被用户发现,这时候,已经影响了很多用户,排查起来也很困难,因为,代码已经改了很多,不知道是哪次重构引入的问题。
我有一次重构,改了一个计算优惠的逻辑,重构后,测试都通过了,上线也没问题,但是,过了一个月,有个用户反馈,说他的优惠券用不了,排查了很久,才发现,是重构的时候,有个优惠券的叠加规则,我理解错了,改了逻辑,导致某些情况下,优惠券用不了,但是,因为这种情况比较少见,所以,测试的时候没发现,上线后,过了一个月,才被用户发现,影响了不少用户,很被动。
坑2:重构范围失控 重构的时候,很容易范围失控,本来只想重构一个方法,结果,重构这个方法的时候,发现它调用的另一个方法也很烂,就顺便重构了,重构那个方法的时候,又发现另一个类也很烂,又顺便重构了,结果,越重构越多,范围越来越大,最后,改了半个项目的代码,自己都记不清改了哪些地方,测试也测不过来,很容易出问题。
我有一次,本来只想重构一个用户登录的方法,结果,重构的时候,发现用户类也很烂,就重构了用户类,重构用户类的时候,发现权限类也很烂,又重构了权限类,重构权限类的时候,发现角色类也很烂,又重构了角色类,最后,改了用户、权限、角色、登录、注册等好几个模块,代码改了上万行,自己都记不清改了哪些地方,测试的时候,测了很久,还是漏了很多地方,上线后,出了好几个Bug,折腾了很久才解决。
从那以后,我就明白了,重构,一定要控制范围,每次只重构一个小的部分,不要贪多,不要顺便重构其他的,不然,范围失控,很容易出问题。
坑3:团队协作问题 重构,不是一个人的事情,特别是大的重构,涉及到很多模块,很多人,如果团队没有达成共识,一个人偷偷重构,很容易出问题。
我有一次,觉得一个公共模块的代码很烂,就自己偷偷重构了,重构完,提交了代码,结果,另一个同事,也在改这个模块,两个人的代码,冲突很严重,合并的时候,花了很久,还合并错了,引入了好几个Bug,上线后,出了问题,排查了很久才发现,是合并代码的时候,合并错了。
而且,重构公共模块,会影响所有使用这个模块的人,如果没有提前沟通,大家都不知道你改了什么,可能会导致别人的代码出问题,或者,别人还在按旧的方式使用,导致兼容性问题。
从那以后,我就明白了,重构,特别是公共模块的重构,一定要提前和团队沟通,达成共识,制定计划,分工合作,不要一个人偷偷重构,不然,很容易出问题。
坑4:测试不足 重构,最关键的就是测试,没有测试保障的重构,就是盲人摸象,很容易把功能改坏。但是,很多老项目,根本没有测试,单元测试、集成测试,都没有,重构的时候,只能靠人工测试,人工测试,很难覆盖所有的情况,很容易漏测,导致上线后出问题。
我维护的那个老项目,就没有任何测试,每次重构,都只能靠人工测试,测几个主要的流程,就上线了,结果,经常有一些边界情况,没测到,上线后出问题。
有一次,我重构了一个订单退款的逻辑,测试的时候,测了正常退款,部分退款,都没问题,就上线了,结果,上线后,有用户反馈,说退款失败,排查了很久,才发现,是有个特殊情况,订单已经部分发货了,这时候退款,逻辑不一样,我重构的时候,没考虑到这个情况,测试的时候也没测,导致上线后,这种情况的退款,都失败了,影响了不少用户。
从那以后,我就明白了,重构之前,一定要先补测试,单元测试、集成测试,覆盖主要的功能和边界情况,有了测试保障,再重构,这样,才能保证重构之后,功能不变。
坑5:性能下降 重构的时候,有时候,为了代码的可读性和可维护性,会把一些性能很好,但是很难读的代码,改成易读,但是性能稍差的代码,一般情况下,这点性能差异,可以忽略,但是,在一些性能敏感的地方,比如高频调用的方法,大数据量的处理,可能会导致性能下降,影响系统的响应时间。
我有一次,重构了一个价格计算的方法,这个方法,在下单的时候,会被调用很多次,原来的代码,写得很巧妙,性能很好,但是很难读,我重构的时候,为了易读,改成了比较直观的写法,但是,性能下降了不少,下单的时候,价格计算的时间,从原来的几毫秒,变成了几十毫秒,下单的响应时间,增加了不少,用户反馈,下单变慢了,排查了很久,才发现,是重构导致的性能下降,没办法,只能又改回去,或者,做一些优化,才解决。
从那以后,我就明白了,重构的时候,要注意性能,特别是性能敏感的地方,不能为了易读,就牺牲太多性能,要在可读性和性能之间,找到平衡。
坑6:业务中断 大的重构,可能需要很长时间,几周,甚至几个月,这期间,代码一直在改,业务功能的开发,可能会受到影响,因为,大家都在忙重构,没有时间做新功能,或者,重构的时候,代码不稳定,不敢加新功能,导致业务需求积压,业务方不满意。
我有一次,做了一个大的重构,把整个订单系统,都重构了,花了两个多月,这期间,团队的人,都在忙重构,新功能的开发,几乎停滞了,业务方提了好几个需求,都排不上期,很不满意,天天催,我们压力也很大,只能加班加点,一边重构,一边做新功能,很累,也很容易出问题。
从那以后,我就明白了,重构,要和业务开发平衡,不能因为重构,就影响业务功能的开发,要合理安排时间,比如,每个迭代,留一部分时间做重构,一部分时间做新功能,或者,在业务相对空闲的时候,做大的重构,不要因为重构,导致业务中断。
四、差点放弃的那次重构
如果说,前面的那些坑,只是让我对重构,有了一些谨慎,那么,有一次重构,差点让我彻底放弃重构。
那是一个更大的项目,我们团队,决定把一个跑了五六年的老系统,彻底重构,因为,这个老系统,代码太烂了,维护成本太高,加一个新功能,要改很多地方,还经常出问题,团队都很痛苦,所以,决定,彻底重构,用新的技术栈,新的架构,重新写一遍。
一开始,我们都充满了热情,觉得,终于可以摆脱这个烂系统了,重新写一个优雅的、易维护的系统,大家都干劲十足。
我们花了一个月的时间,做需求分析,架构设计,然后,开始写代码,写了三个月,终于把主要的功能,都写完了,然后,测试,测试了一个月,改了很多Bug,觉得差不多了,就准备上线。
上线的时候,我们很谨慎,先切了10%的流量,观察了一天,没什么问题,又切了30%,观察了一天,也没什么问题,然后,切了50%,这时候,问题来了,有用户反馈,下单的时候,偶尔会失败,而且,系统的响应时间,也变慢了,我们赶紧排查,查了很久,才发现,是新系统的数据库设计,有问题,某个表的索引没建好,高并发的时候,查询很慢,导致下单失败,响应时间变长。
没办法,只能紧急加索引,加完索引,问题解决了,我们松了一口气,继续切流量,切到80%的时候,又出问题了,有用户反馈,订单状态不对,有的订单,付款了,但是状态还是待付款,有的订单,发货了,但是状态还是待发货,我们又慌了,赶紧排查,查了很久,才发现,是新系统的分布式事务,有问题,订单状态更新,和库存扣减,不是原子的,某些情况下,会导致状态不一致,这个问题,很严重,影响了很多用户,我们只能紧急回滚,把流量切回老系统,然后,慢慢排查问题。
回滚之后,我们开始排查分布式事务的问题,这一查,就查了一个月,发现,新系统的分布式事务,设计得有问题,很多地方,都有数据不一致的风险,要改的话,几乎要把核心逻辑,都改一遍,而且,还有很多其他的问题,性能问题,兼容性问题,边界情况的问题,等等,一堆问题。
这时候,团队的士气,很低落,大家都觉得,这个重构,可能要失败了,花了这么多时间,这么多精力,结果,还是一堆问题,还不如老系统稳定,我自己也很沮丧,觉得,重构真的太难了,差点就放弃了,觉得,以后再也不做重构了。
后来,我们冷静下来,认真分析了一下,觉得,问题主要出在,我们太急于求成了,想一下子把整个系统都重构了,范围太大,风险太高,而且,测试不充分,很多问题,都没测出来,还有,分布式事务这种复杂的问题,没有设计好,就开始写代码,导致出问题。
最后,我们决定,不放弃,但是,调整策略,不一下子全部重构了,而是,分模块,逐步重构,一个模块一个模块地来,每个模块,重构完,测试充分,再上线,稳定了,再重构下一个模块,而且,先重构非核心的模块,积累经验,再重构核心模块,分布式事务,也重新设计,用更稳妥的方案。
又花了半年的时间,我们才把整个系统,逐步重构完,这期间,虽然也出了一些问题,但是,都是小问题,很快就解决了,没有再出现大的故障,最后,新系统,终于稳定上线了,比老系统,性能更好,更易维护,团队也终于松了一口气。
这次重构,虽然最后成功了,但是,过程很曲折,差点就放弃了,也让我对重构,有了更深刻的认识,重构,真的不是一件简单的事情,不能急于求成,不能贪大求全,要小步快跑,逐步来,才能做好。
五、正确的重构姿势
经历了这么多,踩了这么多坑,我也总结了一些正确的重构姿势,分享给大家。
1. 重构之前,先补测试 没有测试,不要重构,测试是重构的安全网,重构之前,一定要先补测试,单元测试、集成测试,覆盖主要的功能和边界情况,有了测试保障,再重构,这样,重构之后,运行测试,就能知道,有没有把功能改坏,心里有底。
如果是老项目,没有测试,至少要给要重构的部分,补上测试,或者,做一些回归测试,保证主要的功能正常。
2. 小步快跑,控制范围 重构,要小步快跑,每次只重构一个小的部分,比如,一个方法,一个类,一个小模块,重构完,测试通过,提交,再重构下一个,不要一下子重构很大的范围,不然,范围失控,很容易出问题,出了问题,也很难定位。
而且,每次重构,只做一件事,不要同时改功能,不要同时优化性能,就只重构,改善代码结构,不改变功能,这样,出了问题,很容易定位,就是重构导致的。
3. 每次重构,都要可运行,可测试 重构的过程中,代码要始终保持可运行的状态,每做一个小的重构,就运行一下,测试一下,确保没有问题,再做下一个,不要改了很多代码,最后运行不起来,不知道哪里出了问题。
而且,每次重构完,都要提交到版本控制,这样,出了问题,可以随时回滚,不会丢失代码。
4. 团队协作,提前沟通 重构,特别是公共模块的重构,大的重构,一定要提前和团队沟通,达成共识,制定计划,分工合作,不要一个人偷偷重构,不然,很容易和别人的代码冲突,也容易影响别人的工作。
而且,重构的时候,要让团队的人,都知道你在改什么,为什么改,改成什么样,这样,大家才能配合,也能避免,别人还在按旧的方式使用,导致兼容性问题。
5. 注意性能和兼容性 重构的时候,要注意性能,特别是性能敏感的地方,不能为了易读,就牺牲太多性能,要在可读性和性能之间,找到平衡,重构完,要做一下性能测试,确保性能没有下降,或者,下降在可接受的范围内。
还要注意兼容性,特别是公共模块,对外的接口,不要随便改方法签名,不要随便删方法,不然,会影响调用方,导致编译失败,或者运行错误,如果要改,要做兼容,或者,提前通知调用方,一起改。
6. 大重构,分阶段,逐步来 大的重构,不要一下子全部重构,要分阶段,逐步来,一个模块一个模块地重构,每个模块,重构完,测试充分,再上线,稳定了,再重构下一个模块,先重构非核心的模块,积累经验,再重构核心模块,这样,风险小,出了问题,影响也小。
而且,大的重构,要和业务开发平衡,不要因为重构,就影响业务功能的开发,要合理安排时间,比如,每个迭代,留一部分时间做重构,一部分时间做新功能。
7. 重构不是目的,是手段 最后,要记住,重构不是目的,是手段,不是为了重构而重构,而是为了让代码更好维护,更好扩展,提高开发效率,减少Bug,如果代码已经很好了,就不需要重构,不要为了重构而重构,过度设计,把简单的事情搞复杂。
而且,重构,要在有价值的地方做,比如,经常改的代码,核心的代码,容易出问题的代码,这些地方,重构的价值大,而那些,很少改的,稳定的代码,就没必要重构,浪费时间,还可能引入新的Bug。
六、我的感悟和建议
经历了这么多,从对重构充满热情,到踩了很多坑,差点放弃,到最后,理性看待重构,我有很多感悟,也有一些建议,分享给大家。
1. 重构,是程序员的基本功,但是,不要神化它 重构,是程序员的基本功,每个程序员,都应该掌握基本的重构技巧,能把烂代码,改成好代码,但是,不要神化它,觉得重构是万能的,能解决所有问题,其实,重构,只是改善代码的结构,不能解决所有的问题,比如,架构的问题,业务的问题,不是重构就能解决的。
2. 重构,要谨慎,不要急于求成 重构,是有风险的,一不小心,就会引入新的Bug,影响线上业务,所以,要谨慎,不要急于求成,不要贪大求全,要小步快跑,逐步来,有测试保障,团队协作,才能做好。
3. 不要为了重构而重构 重构,是为了让代码更好维护,更好扩展,不是为了重构而重构,如果代码已经很好了,就不需要重构,不要为了显示自己的技术,就去重构一些,本来就很好的代码,那样,不仅没有价值,还可能引入新的Bug。
4. 写代码的时候,就要写好,不要等烂了再重构 最好的重构,就是不需要重构,写代码的时候,就要写好,命名规范,方法简短,结构清晰,注释完善,这样,代码一开始就是好的,就不需要重构了,不要一开始,就写烂代码,想着以后再重构,那样,以后可能就没有时间重构了,或者,重构的成本很高。
5. 重构,是一个持续的过程 重构,不是一次就能完成的,是一个持续的过程,每天写代码的时候,都可以做一些小的重构,看到烂代码,就顺手改一下,积少成多,代码质量,就会慢慢提高,不要等到代码烂得不行了,才想起来重构,那时候,重构的成本和风险,都很高。
写在最后
代码重构技巧从入门到放弃,我经历了什么。
从一开始,对重构充满热情,觉得重构很简单,很有成就感,到踩了很多坑,重构引入新Bug,范围失控,团队协作问题,测试不足,性能下降,业务中断,甚至,有一次大重构,差点把项目搞垮,差点放弃重构,到最后,理性看待重构,总结正确的重构姿势,这一路走来,不容易,但是,也学到了很多。
重构,是程序员的基本功,但是,不是一件简单的事情,要谨慎,要小步快跑,要有测试保障,要团队协作,要注意性能和兼容性,大重构要分阶段逐步来,才能做好。
而且,重构不是目的,是手段,不是为了重构而重构,而是为了让代码更好维护,更好扩展,提高开发效率,减少Bug,要在有价值的地方做,不要过度重构。
希望我的经历和经验,能给大家一些参考,也希望大家,都能写出好代码,少踩重构的坑,重构顺利,代码越来越优雅,越来越易维护。
最后,用一句话结尾:
"重构,不是把烂代码改成好代码的魔法,而是一个谨慎的、持续的、小步快跑的过程,需要测试保障,需要团队协作,需要理性看待,才能真正发挥它的价值。"
祝大家都能写出优雅的代码,重构顺利,工作顺利,少加班,少出Bug!
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录