重构代码的7个阶段

重构代码的7个阶段

你曾去想重构一个很老的模块,但是你只看了一眼你就恶心极了。文档,奇怪的函数和类的命名,等等,整个模块就像一个带着脚镣的衣衫褴褛的人,虽然能走,但是其已经让人感到很不舒服。面对这种情况,真正的程序员会是不会认输的,他们会接受挑战认真分析,那怕重写也在所不惜。最终那个模块会被他们重构,就像以前和大家介绍过的那些令人销魂的编程方式中的屠宰式编程一样。下面是重构代码的几个阶段,文章来自:The 7 stages of refactoring,下面的翻译只是意译。

第一阶段 – 绝望

在你开始去查看你想要重构的模块的,你会觉得好像很简单,这里需要改一个类,那里需要改两到三个函数,重写几个函数,看上去没什么大不了的,一两天就搞定了。于是你着手开始重构,然后当你调整重构了一些代码,比如改了一些命名,修理了一些逻辑,渐渐地,你会发现这个怪物原来体型这么大,你会看到与代码不符甚至含糊不清的注释,完全摸不着头脑的数据结构,还有一些看似不需要方法被调了几次,你还会发现无法搞清一个函数调用链上的逻辑。你感到这个事可能一周都搞不定,你开始绝望了。

第二阶段 – 找最简单的做

你承认你要重构的这个模块就是一个可怕的怪物,不是一两下就可以搞定的,于是你开始着干一些简单的事,比如重新命名一下几个函数,移除一些代码的阻碍,产生几个常量来消除magic number,等等,你知道这样做至少不会让代码变得更糟糕。

第三阶段 – 再次绝望

但是接下来的事会让你再次撞墙。你会发现那些代码的瑕疵是些不痛不痒的事,改正这些事完全于事无补,你应该要做的事就是重写所有的东西。但是你却没有时间这么干,而这些代码剪不乱理还乱,耦合得太多,让你再一次绝望。所以,你只能部分重写那些不会花太多时间的部分,这样至少可以让这些老的代码能被更多的重用。虽然不完美,但是至少可以试试。

第四阶段 – 开始乐观

在你试着部分重构这个模块几天之后,随着重构了几个单元后,虽然你发现改善代码的进度太慢了,但此时,你已知道代码应该要被改成什么样,你在痛苦之后也锁定了那些那修改的类。是的,虽然你的时间预算已经超支,虽然要干的事比较多,但你还是充满希望,觉得那是值得的。你胸中的那团火又被点燃了。

第五阶段  – 快速了结

在这个时候,你发现你已花了太多的时间,而情况越来越复杂,你感到你所面对的情况越来越让你越到不安,你明白你自己已经陷入了困境。你原本以为只需要一次简单的重构,然而现在你要面对的是重写所有的东西。你开始意识到原因是因为你是一个完美主义者,你想让代码变得完美。于是你开始在怠慢你文档,并想找到一个捷径来重写老的代码,你开始采用一些简单而粗暴,快速而有点肮脏的方法。虽然不是很完美,但你就是这样去做了。然后,你开始运行测试做UT,发现UT报告上全是红色,几乎全都失败了,你恐慌了,于是快速地fix代码,然后让UT 能工作。此时,你拍拍自己胸口,说到,没问题 ,于是就把代码提交了。

第六阶段 – 修改大量的Bug

你的重写并不完美,虽然其过了测试,但是那些UT测试对于你的新的代码有点不太合适,虽然他们都没有报错,但是他们测试得范围太小了,没有覆盖到所有的情况和边界。所以,在这以后,你还需要几周或是更长的时间不得不来修正越来越多的bug,这使得你的设计和代码在每一次quick-fix后就变得越来越难看。此时,代码已经不像你所期望的那样完美了,但你依然觉得他还是比一开始要好一些。这个阶段可能历经几个月。

第七阶段  – 觉悟

经过了6个月,你重写的模块又出了一个比较严重的bug。这让你重构的那个模块变得更难堪。你发现出的这个问题是和当初的设计不一致,你还发现被你重构掉的那段老的代码并不是当初看上去的那么坏,那段老的代码确实考虑到了一些你未曾考虑到的事情。这个时候,你团队里有人站出来说这个模块应该被重构或是重写,而你却不动声色地一言不发,并希望那个站出来的人能在几个月后能觉悟起来。

——————

不知道这是不是你的经历,我经历过很多次这样的事。对于很多维护性质的项目,我犯过的错误让我成了一个实实在在的保守派,我几乎不敢动,那怕看到代码很不合口味。当然,那些从来没有写过代码的敏捷咨询师一定会说用TDD或是UT可以让你的重构更有效也更容易,因为这样会让他们显得更我价值,但我想告诉你,这种脱离实际的说法很不负责任,这就好比说—— 我在杀猪的时候遇到了一些麻烦,因为我对猪的生理结构不清楚,或是这本来就是一头畸形的猪,导致我杀的猪很难看,而伟大的敏捷咨询师却告诉我,要用一把更快更漂亮的刀。软件开发永远不是那么简单的事,杀猪也一样。

(转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)

好烂啊有点差凑合看看还不错很精彩 (33 人打了分,平均分: 4.21 )
Loading...

重构代码的7个阶段》的相关评论

  1. 我们公司有一些代码,存在的时间超过了20年。在那个磁盘空间以MB来计算的年代下,代码里的函数名变量名极其精简且晦涩难懂。C风格的代码和后来新加的C++风格的混在一起。最恐怖的一个核心函数,里面只有一个switch/case但是有3000行之多。当我第一次看到这些代码时实在很难理解为什么这么多年没有人重构过,至少让代码的逻辑看起来清晰一些。但几年以后再看这些代码,几乎到处都有defect fix — 这不是说明代码烂,而是曾经有大量的QA和数百万的用户在20多年的时间里不断测试这些代码,把所有能碰到的极端情况都测到了,以至于那些貌似很丑陋的代码实际上极其稳定。

    在这种情况下,如果还有人想去重构一把,那只能是脑筋秀逗。

    1. @sumai,你经历过的这个事和我经历过的一个太像了,我经历过2000多个case,一个switch语句1万多行,但是不敢动啊,因为那是10年来的代码,运行地没有问题。

  2. 从我个人的思路上更倾向于把原本很多累赘无用的需求砍掉,再把最核心的之前未实现的功能实现。对,完全重写。

  3. 身有同感。我们公司的代码也是多年遗迹。完全散打,有类,但不是面向对象;有函数,但那就算函数式编程吗?命名方式千奇百怪,你能想到和想不到的都有。而且,历史上有人想要大规模重构,可是失败了,于是留下个畸形儿,就像手术一半终止了,剩下的会是个什么样子?没有统一架构,没有规范,没有准确完善的文档,当然也没有单元测试,这样的代码要重构,没有一年半载是别想。当然,我也没那么多时间重构,新项目都多到做不完,哪有时间重构。

  4. 重构代码太痛苦了。过去奉命去重构一个两三千行的程序,花了差不多一个月才搞定,并且感觉新的代码基本跟原来一样糟。。。
    @waterwu 需求在绝大多数时候不是由程序员决定的啊。并且完全重写出来的代码不见得会比原来的逻辑强很多,总有照顾不到的情况。

  5. 我觉着丑陋的代码,主要是风格不统一造成的,在我的经历中,一个项目真的需要一个统一的管理员,全程监控,让一个项目看起来像一个人完成的一样,我希望的重构是性能和结构上的重构

  6. 大文件大函数的东西我也见识过不少,一个文件就写好几个MB,一个函数就是几千行上万行,大家看到后的第一反应都是:这个需要重构……

    但是,我想说“但是”,这些真的需要重构么?重构这些东西的理由从何而来?另外,应该把自己的位子摆对,再重新考虑这个“重构”:如果你是需要对此负责的MGR/CEO,你会如何选择?

  7. 重构需要慎重,但是重构真的不需要吗?
    当然在你对代码了解程度为0的基础上,根本谈不上重构吧。
    当你对这段代码了然于胸的时候,为什么不重构呢?让代码更清晰
    这都是辩证的,重构只能说是解决问题的一种手段而已,而且这个手段是有一定的风险的
    实际情况下,大多数程序员在看见一段代码都会有想重构的冲动,不管是自己的还是别人的,因为思路总是会完善的。
    但是更多时候需要论证吧,这段代码的重构的必须性是否大于风险呢?

  8. @夜妄语 同意这个说法。不能因为有这么多 “伟大的敏捷咨询师” 就否定一切。重构绝对不是任何时候都有效。可是还是为了便于维护很有必要。混乱是一定有的,可需要尽量控制住混乱的范围。当然这个“更容易维护”是相对的。如果一个项目颇具古城规模,而且每段代码都像坨屎,基本让所有有经验的程序员都看不懂,搞不懂了… 那还有人敢增加一点点需求么…

  9. @villim @夜妄语
    你们误解了,我想告诉大家的是——
    1)重构要谨慎,并没有告诉你不要重构!重构是需要权衡的!
    2)任何说“用TDD可以来保证重构质量”的话都是不切合实际的。

  10. 如果重构能带来价值,那就是再难也要重构。对于维护性质的项目,显然不会加什么翻天覆地的新功能了,无非就是改几个BUG,加几个小功能,你重构干嘛?

    重构最好从自己的代码做起,别人的代码除非是你理解透了,否则别动。勿以善小而不为之,看到某个变量名没有揭示其含义,就rename;看到某段代码可以提取出来,就extract method。

    不是说没有UT,就不能重构,只是没那么方便和自由。

  11. 嗯,不用重构,看着代码一天天烂下去,当代码烂到实在无法维护的时候,有个好办法可以解决这个问题:

    辞工,换一家公司维护不那么烂的代码。

  12. 请大家一定要搞清出做事的目的,不要拿手段当目的。方法都是人来用的。最后又看到了皓哥对敏捷的吐槽,真是无语了。TDD什么的只是一种方法,当然有适用的范围。没有人说什么都可以用TDD解决。TW的部分人这样说只是为了公司赚钱。或者认识肤浅。跟电视购物里的没大区别。而TDD其本质是在能够通过所有测试,即产品合格的前提下避免过度的设计。应为很多人只是为了设计而设计,在实际产品中反而增加成本。 TDD这样的方法是为了实现这种目的而研究出来的,可能有很多问题但是在工程中是可量化、可复制、可控制的。皓哥你的很多观点其实就是敏捷的本质含义,只是你的观点都不可控,就是所谓的太“玄”了。西方人喜欢用可控的理论去抽象和研究问题,请不要总把对TW部分人鄙视的情绪带到TDD甚至敏捷之类的方法或理念上去。

    1. 不要给我的观点打上任何和敏捷有关的标签。我不是为敏捷或是TDD这些名词而生存的人,我只是为软件开发中的种种现实问题去实践去磨练的人。像那样开口闭都是敏捷的人来,其潜意识关注的是敏捷和那些名词,而不是实际的东西。

      很多很不错的软件开发的实践就那样发生了,只是有个组织和一些滥竽充数的人把这些东西打上标签,想以此来树立威信并达到普世的目的。WTF!

  13. 另外要说的是,敏捷是建立在对具体需求和问题的大料调研的基础上才可能实现的。咨询公司接到项目,一定是要做你所说的上看需求下看技术的。平时发表文章或观点当然会比较空泛一点,而且方法论的东西一般都是抽象的。你看比尔盖兹、拉力佩奇什么的做演讲,什么时候给你分析过客户需求或者给你将技术课程了。当然咨询师们良莠不齐,很多观点当然未必正确,鄙视也理所当然。但是如此反复地对于各种方法论和公司整体做如此低级的吐槽,只能说明你对这些根本不是很了解。可能你自己的能力确实比较强,但是不通过系统的方法,这些能力是不可复制也不可控制的,当然这也可以作为你YY大师的资本。本人和敏捷社区、TW这类的没有任何关系,只是觉得你一再做出低级的批评是在反感。

  14. 我觉得楼主有点偏执了,敏捷的存在就有它自己的理由,我也不喜欢所谓的敏捷和所谓的TDD。每个项目都有自己的特点,就和CMMI一样,它到了后来也就是所谓的定制,定制是什么,其实就是一定程度上的敏捷。过多的方法论只能让项目做更多无用的东西,所以在项目初期根据项目情况定制项目流程,使流程更容易的去适应变化,这个才是最重要的。每个人都有自己的方法,每个项目类型都是不一样的,别人的方法只能是参考,如果照搬,不管是敏捷还是别的,都不可能做的好。

  15. @Mic 没关系,你有表达你观点的权力,我捍卫你反感我的权力。我想我的观点很明确了,TDD不是用来解决重构中遇到的问题的,因为现实有很多问题,要重构就要学会权衡和稳重和努力调查实际问题。当我听到敏捷和TDD是普世的言论,我也很反感。

    关于你说的——比尔盖兹、拉力佩奇的例子,挺好!我期待着TW做出了像微软和谷歌那样的传大的软件后,再来像他们一样的说话。

  16. 深有同感,很多时候重构只能是几分钟、十几分钟的小小的规模,其他重构几乎是重写,但是给的时候却非重写的时间,一大堆看似杂乱的、像没道理的分支、判断往往决定了重构的失败。

  17. 陈皓 :
    @villim @夜妄语
    你们误解了,我想告诉大家的是——
    1)重构要谨慎,并没有告诉你不要重构!重构是需要权衡的!
    2)任何说“用TDD可以来保证重构质量”的话都是不切合实际的。

    @陈皓 同意。但是随之而来的问题是。你觉得如何才能做到保证重构或者说代码质量呢? 毕竟优秀的程序员是少数,而且优秀的人也会犯错。TDD这样的方法论之所以能流行,可能也就是因为它想普及大众,做出放之四海皆准的实践。本质上可能就错了,但是目前的大环境就是这样良莠不齐,很想知道你是如何解决或者思考这个问题的呢。

  18. 有点心有余悸,也曾经在某些阶段之间徘徊,虽然至今看到恶心的代码,还是有种重构/重写的冲动。
    我觉得在“第一阶段 – 绝望”之前还有个阶段是:冲动,重构的冲动。舍些,不能到达最后的觉悟阶段。
    个人的一个体会是:当你有重构的冲动时,往往(相对于你面对的系统来说)还是个新人,你掌握的信息有限,资源也有限,对重构的风险预估不当,处处碰壁。
    当你掌握足够的信息和资源、能完全掌控风险开展重构时,很可能你已经心灰意懒(一个不得志的老员工),或者你的关注点已不再代码质量,而是诸如预算、进度、发布等问题(获得升职)。无论何种情况,你都不会有重构的动力,心想“重构这种吃力不讨好的事情还是让新人去做吧”,这就是所谓的“觉悟”阶段。

  19. 关于重构…这个可能还是回到一句废话上了——具体问题具体分析。

    像我所在的公司,在编码之前,所有的概要设计都要经过下至同事上至架构师等多人的review。一些关键模块的设计审查更是谨慎,比如前一段时间关于到底用哪个并行库经过了数个月的详细研究。所以通常这种情况下已经从设计上保证不会出多大的问题。到了实现环节,每次代码提交都有人review,也基本上保证代码的质量。只有一次由于项目紧张,匆匆忙忙做了实现,结果到了下一个Sprint (忘了说我们是跑Scrum的哈哈 :)), 大家决定那个模块推倒重来。

    还有一种重构是架构级别的,但这种情况下的重构也不是完全重来,通常也只是函数/模块的移动,并且通过封装使得框架更清晰。即使是架构师也不会去做函数内部级别的重构。

    即使是scrum,我们通常给research或者design给出足够多的时间。从developer的角度来说,不能为了schedule而极大牺牲代码质量。当然,如果mgr只盯着进度,那只能说明他不是一个好的mgr。在理想情况下,不太有重构的可能。

  20. 在这个问题中,项目本身不是为 TDD 而设计,怎么可能半路修改为 TDD。

    真正优秀的程序员是可以具有比那些咨询师远强得多的驾驭 TDD 之能力的。但真正优秀的程序员也不会对咨询师进行吐槽很感兴趣。为什么呢?

    因为:你请来了只会忽悠,只会滥竽充数的咨询师,并且证明你自己很牛13,这并不是件光彩的事情,它只证明你们公司连请个靠谱的咨询师的能力都没有。或者证明你们的团队自我改进能力很差还需要咨询师帮助。

    真正牛13的团队,要么根本不需要咨询师,内部直接就能产生有广泛项目经验的人进行流程改进。要么就是能够请到真正靠谱的咨询师。

    我见过很多通过项目组内部变革以及进化的例子,并且也亲自带领过自有项目组进行内部TDD进化,这一点至少说明,项目经验的人有能力对项目组进行内部进化并且能够成功。而显然,“咨询师水平太次”并不是你拒绝进化的理由。“项目组缺乏领军人物”才是个合理的理由,真正的进化是不需要咨询师的。

  21. 建议大家看看“修改代码的艺术” (Working Effectively with Legacy Code), 看看如何使用Agile 所说的方法在重构中的应用,针对各种情况,可以参考的方法。
    没有一个简单的方法,其实就是LZ在译文中的写照

  22. 重构这件事情的确是需要谨慎的。而且必须要有足够经验的程序员来实施。最重要的是需要确认到底是否需要重构。正确评估重构的成本、收益和风险。做好最坏的打算才能开始动手。这主要靠的是经验,也是最难的。

  23. 陈皓 :@sumai,你经历过的这个事和我经历过的一个太像了,我经历过2000多个case,一个switch语句1万多行,但是不敢动啊,因为那是10年来的代码,运行地没有问题。

    我现在就正在维护一个几千行CASE语句,而且可悲的是,自己还要不断地让这个case变大。
    是个近10年的游戏项目,几年来对很多模块都重构过,因为需求变得太厉害,但每次重构后都会悲剧地找两周bug。。。

  24. @陈皓

    我一直是支持你在软件开发中的观点的。也支持你鄙视个别人的个别观点。 但是对你反复对着整个咨询公司和个别方法喷饭实在难以理解。只觉得是在下意识营造一种自己很牛的感觉。就像一个老中医总也看不起毛头医生会用听筒看病。方法论的价值不是要做完美的架构或者设计, 而是在工程中抽象出可行的方法,这样才能普及和控制。 毕竟不是所有人都有皓哥你大师级的功力,什么都能自己研究出好方法。 如果TW的个别人是那样说的,说明他们的理解也有问题, 那你怎么鄙视他们都无可厚非。 但是你自己对敏捷和方法论都不理解,就如此吐槽,实在是不敢恭维。 你随便看看关于敏捷的书(要正版哦),就知道你自己的具体问题具体分析的一些观点本来就是符合敏捷思想的, 居然说我在给你贴标签,好像是在泼脏水一样,真是无语。

    1. @Mic,谢谢你的支持。

      我这篇文章又没说TW,也没有说你,你也不是咨询师,我觉得我们俩应该相安无事才对啊。现在看下来似乎我们之间有矛盾了。有意思吗?

      P.S.“具体问题具体分析”这话不是“马克思主义的重要原则”吗?什么时候成了敏捷的观点啦?呵呵。我就想告诉你,很多敏捷的东西,在敏捷没出来之前都有,但敏捷出来后,好像这些东西全贴上敏捷的标签啦?你怎么知道我对敏捷不熟悉呢?

      P.S.S. 我对敏捷和方法论什么时候有过意见啊?我都强调了一万遍了。你听不懂吗?!你怎么知道我没有研究和实践过敏捷?

  25. 支持左耳朵耗子的论断,软件工程是一个长期而且庞大的项目,怎么可能有某一种方法能完全适用呢,这明显就是在偷换概念嘛,就像医生给人看病的方法也是在不断地改进和修正当中,如果一种配方被验证是非常有效的,那么何苦总是去重新配药呢,难道就是为了让药品的颜色好看一些?关键是需要多大的精力和时间呢?你的病人能等吗?尼玛谁等得起啊,再说了你能保证你的药就能跟之前的药物一样有效,敏捷没有什么错但也未必就多么伟大,敏捷伟大之处是他造就了很多以敏捷为教旨的公司和人,所以才会被成为如此伟大

  26. 我们原来维护和开发的系统也是10多年了,虽然有些代码也是很丑陋,之前一直不敢动。等到要写一个新版本和采用新的技术才开始彻底的重构

  27. sumai :我们公司有一些代码,存在的时间超过了20年。在那个磁盘空间以MB来计算的年代下,代码里的函数名变量名极其精简且晦涩难懂。C风格的代码和后来新加的C++风格的混在一起。最恐怖的一个核心函数,里面只有一个switch/case但是有3000行之多。当我第一次看到这些代码时实在很难理解为什么这么多年没有人重构过,至少让代码的逻辑看起来清晰一些。但几年以后再看这些代码,几乎到处都有defect fix — 这不是说明代码烂,而是曾经有大量的QA和数百万的用户在20多年的时间里不断测试这些代码,把所有能碰到的极端情况都测到了,以至于那些貌似很丑陋的代码实际上极其稳定。
    在这种情况下,如果还有人想去重构一把,那只能是脑筋秀逗。

    因此需要对重构代码进行单元测试的覆盖,这样才能保证重构后的代码正确

  28. 任何时候,你去重构一个别人的程序,潜意识里都只是依照自己过去的经验。你的经验不一定就比上一个就好多少,而且可能会出现一些未知的问题,是上个程序员没有标准出来的。

  29. mikel :没坏就不要动它

    这句话是至理名言,因为看不懂或者觉得不符合美感而去重构可以运行的代码。这是毕业生才会去干的事情。

  30. 说的很好,在经理自己对自己程序的重构也会遇到这些问题,在重构的时候,由于文档需求等方面的不完上,总是会落下很多东西或者再次把已经发现的bug引入重构的代码中

  31. 我觉得这跟人的因素有关吧.我们一直在重构我们的代码,没有像文章中说的那么糟,当然我们也有遇到过失败的,总是来说是更好了.

  32. Pingback: lidashuang

回复 W.Malloc 取消回复

您的电子邮箱地址不会被公开。 必填项已用*标注