重构代码的7个阶段
你曾去想重构一个很老的模块,但是你只看了一眼你就恶心极了。文档,奇怪的函数和类的命名,等等,整个模块就像一个带着脚镣的衣衫褴褛的人,虽然能走,但是其已经让人感到很不舒服。面对这种情况,真正的程序员会是不会认输的,他们会接受挑战认真分析,那怕重写也在所不惜。最终那个模块会被他们重构,就像以前和大家介绍过的那些令人销魂的编程方式中的屠宰式编程一样。下面是重构代码的几个阶段,文章来自:The 7 stages of refactoring,下面的翻译只是意译。
第一阶段 – 绝望
在你开始去查看你想要重构的模块的,你会觉得好像很简单,这里需要改一个类,那里需要改两到三个函数,重写几个函数,看上去没什么大不了的,一两天就搞定了。于是你着手开始重构,然后当你调整重构了一些代码,比如改了一些命名,修理了一些逻辑,渐渐地,你会发现这个怪物原来体型这么大,你会看到与代码不符甚至含糊不清的注释,完全摸不着头脑的数据结构,还有一些看似不需要方法被调了几次,你还会发现无法搞清一个函数调用链上的逻辑。你感到这个事可能一周都搞不定,你开始绝望了。
第二阶段 – 找最简单的做
你承认你要重构的这个模块就是一个可怕的怪物,不是一两下就可以搞定的,于是你开始着干一些简单的事,比如重新命名一下几个函数,移除一些代码的阻碍,产生几个常量来消除magic number,等等,你知道这样做至少不会让代码变得更糟糕。
第三阶段 – 再次绝望
但是接下来的事会让你再次撞墙。你会发现那些代码的瑕疵是些不痛不痒的事,改正这些事完全于事无补,你应该要做的事就是重写所有的东西。但是你却没有时间这么干,而这些代码剪不乱理还乱,耦合得太多,让你再一次绝望。所以,你只能部分重写那些不会花太多时间的部分,这样至少可以让这些老的代码能被更多的重用。虽然不完美,但是至少可以试试。
第四阶段 – 开始乐观
在你试着部分重构这个模块几天之后,随着重构了几个单元后,虽然你发现改善代码的进度太慢了,但此时,你已知道代码应该要被改成什么样,你在痛苦之后也锁定了那些那修改的类。是的,虽然你的时间预算已经超支,虽然要干的事比较多,但你还是充满希望,觉得那是值得的。你胸中的那团火又被点燃了。
第五阶段 – 快速了结
在这个时候,你发现你已花了太多的时间,而情况越来越复杂,你感到你所面对的情况越来越让你越到不安,你明白你自己已经陷入了困境。你原本以为只需要一次简单的重构,然而现在你要面对的是重写所有的东西。你开始意识到原因是因为你是一个完美主义者,你想让代码变得完美。于是你开始在怠慢你文档,并想找到一个捷径来重写老的代码,你开始采用一些简单而粗暴,快速而有点肮脏的方法。虽然不是很完美,但你就是这样去做了。然后,你开始运行测试做UT,发现UT报告上全是红色,几乎全都失败了,你恐慌了,于是快速地fix代码,然后让UT 能工作。此时,你拍拍自己胸口,说到,没问题 ,于是就把代码提交了。
第六阶段 – 修改大量的Bug
你的重写并不完美,虽然其过了测试,但是那些UT测试对于你的新的代码有点不太合适,虽然他们都没有报错,但是他们测试得范围太小了,没有覆盖到所有的情况和边界。所以,在这以后,你还需要几周或是更长的时间不得不来修正越来越多的bug,这使得你的设计和代码在每一次quick-fix后就变得越来越难看。此时,代码已经不像你所期望的那样完美了,但你依然觉得他还是比一开始要好一些。这个阶段可能历经几个月。
第七阶段 – 觉悟
经过了6个月,你重写的模块又出了一个比较严重的bug。这让你重构的那个模块变得更难堪。你发现出的这个问题是和当初的设计不一致,你还发现被你重构掉的那段老的代码并不是当初看上去的那么坏,那段老的代码确实考虑到了一些你未曾考虑到的事情。这个时候,你团队里有人站出来说这个模块应该被重构或是重写,而你却不动声色地一言不发,并希望那个站出来的人能在几个月后能觉悟起来。
——————
不知道这是不是你的经历,我经历过很多次这样的事。对于很多维护性质的项目,我犯过的错误让我成了一个实实在在的保守派,我几乎不敢动,那怕看到代码很不合口味。当然,那些从来没有写过代码的敏捷咨询师一定会说用TDD或是UT可以让你的重构更有效也更容易,因为这样会让他们显得更我价值,但我想告诉你,这种脱离实际的说法很不负责任,这就好比说—— 我在杀猪的时候遇到了一些麻烦,因为我对猪的生理结构不清楚,或是这本来就是一头畸形的猪,导致我杀的猪很难看,而伟大的敏捷咨询师却告诉我,要用一把更快更漂亮的刀。软件开发永远不是那么简单的事,杀猪也一样。
(转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)
《重构代码的7个阶段》的相关评论
感同身受,现在手上正接着一个维护的项目,开始那个痛苦呀,没文档,没注释,最大问题还是不按一般规范来,当时真想卷铺盖走人。慢慢开始看代码,研究里面的逻辑思路,等等,也是修改了其中很多bug,但是从来不改删除原来的代码,要么注释,要么开辟两条路,是在不行走原来的流程。改的多的地方现在是…哎…一言难尽呀,希望后来维护这项目的哥们儿别骂我才好,哥哥只能说,我写的新代码都是规规矩矩的
在没完全了解程序解决方案时,永远没资格断定这段代码是不是很烂。我曾经也遇到过,再重写写到最后才发现要解决一个难题,然后回想原来的代码,感叹下“艹 怪不得原代码要写成那样”
运行的好好的东西尤其是运行了N+X年的就不要动了。。。。你越动越乱
改着改着Bug就出来了。。。。
程序这种东西能用是最基本的要求吧?改来改去再清晰可读的代码不能运行有啥用。。。
你不能保证重构完了能运行【不比现在Bug多】还比现在清晰就不要动
另外TDD也没啥不好的,反正能运行就行了么。写着方便是第一要点,让运行效率都去死,反正运行程序不用我的CPU……
站在什么角度和立场去评判这件事情(重构超难的遗留代码),结论显然是不一样的。所以不说清楚立场,结论不免太片面。试着举例:
1、一个有洁癖希望锻炼自己TDD能力,不甚关心公司短期利益,过高估计自己的程序员:多好的一个重构的范例啊,我要尝试搞定它!(就如同一个新中医遇到疑难杂症用猛药似的)
2、一个伤了好多遍伤不起的老程序员:哈,这事儿谁动谁傻逼。看,新来的那个傻逼要蠢蠢欲动了,正好把我的bug的原因推给他,我好多休息下,我只管贴腐肉完成我的工作就行。老板看见我这么快,他那么慢一定会对我更倚重。反正这个产品已经老得不能进化了, 迟早要趁SOA概念火爆,重新开发一个新的,没必要为它吃力不讨好,背黑锅。
3、一个公司中层管理:我们的产品是够老,加新功能很难,但老功能能用,客户领导和我们关系很铁,而且YY公司的产品比我们更差劲儿~,客户领导不会因为我们烂而下台,因为对手更烂! thank god!
4、客户中层领导:MD,这个XX公司的东西实在难用,想加一点儿简单的东西都推三阻四的。 哎管他呢,反正另外一家更烂,我不会有位置的危机,XX公司的销售对我还不错,就这样吧!
5、客户的老总:我们的效率比起国外同行来,还是有不少差距啊!不过我们有更多定价权,他们进来还受到限制:我们利润比他们高,这多亏了社会主义的优越性啊~
6、政府官员:现在民族产业还不够强大,全面开放的时机还不成熟,还需要大力扶持啊——政府扶持的钱不够? 怎么会? 多卖点儿地,多征点房产改名税,不就有了吗!
7、老百姓(程序员): MD,税又高了,房价又涨了,省吃俭用吧也不够。 NND,不能干程序员啊, 看那个搞sales的,积累一些业界客户的关系,吃香的喝辣的,说不定榜上哪个老板~~ 哦不,, 老板的女儿,就少奋斗10年啦!重构这东西能当饭吃吗?都TM鬼扯淡!
….还可以推演更多,就不赘言了。
系统论给我的一个重要启发是: 很多时候在一个系统内部看不出问题,只要站在一个更大范围的系统去观察,说不定就会找到原因。 上述关于重构的故事的我的结论是: 在一个效率并非最重要竞争要素的市场机制里,单纯的纠结重构这样的效率手段应不应该做, 这样是不智的。 说到底是一个利益问题。
@joeaniu
Interesting point, well said.
@joeaniu
你好NB啊,从重构代码的问题推导到了社会机制的缺陷。您真是顶级程序猿啊。能否帮我们推导下在欧美这种情况是怎么回事?还是您认为如此美好的欧洲和米国的程序员是不会出现这种重构问题的?
我在杀猪的时候遇到了一些麻烦,因为我对猪的生理结构不清楚,或是这本来就是一头畸形的猪,导致我杀的猪很难看,而伟大的敏捷咨询师却告诉我,要用一把更快更漂亮的刀。软件开发永远不是那么简单的事,杀猪也一样。
到了这个层面,已经不是技术问题了!!呵呵!!
我也遇到了这个问题,我用三个月实习期来更改一个工程。 结果是过了接近一年后,我重写了这个工程.
我觉得觉悟就是成长的过程,有担当的程序员才能走的更远、站的更高,畏手畏脚的程序员一辈子也只能是程序员,我相信架构师也不是开始就能写出NB的程序,也是在不断总结,不断重构中提高。
个人认为重构之前必做的三件事:1、重构的业务模型必须特别清楚
2、重构的代码结构也必须清楚
3、重构一段代码之前要写好其单元测试(重点)
同意您的观点,对TW改观了,现在发现他们确实有点空
重构这个事情,越早越好。最好是原班人马。不然就别干。或者想都不要想。
重构不是从写代码一开始就要不断的重构的吗?如果真的到了代码全部写完了再进行重构也应该是一件很痛苦的事吧!
我自己重构过3、4个项目,自己只是享受重构的乐趣,代码量是以前的二分之一或更少,架构更加清晰。如果我以后招聘,就发一段代码,让面试者重构
重构应该在业务比较熟悉的前提下,小步伐前进。
开始可以变态的变量命令、大快头函数分割成小函数,把重复的代码封装为函数等等。
修改的代码经过实际考验,然后在慢慢进行更大的修改。
最后就得到重构完整的代码。
最近我重构了一个系统,使用C++,但是确实C风格的,连ADT的概念都没有。
各种重复代码。各种恶心。简单的问题复杂化等等。
我一开始雄心万丈,觉得在架构上重构的它,但是弄着弄着,发现自己有些掌控不了。因为业务逻辑还不熟悉。
后来要求增加新功能,就借着增加新功能的机会,进行了小幅度重构;
最后这样持续了一段时间,最后代码重构完毕。
在坏程序的基础上,学习到了怎样写好程序。
最后的配图总结了一切。
重构应该是谨慎的 小步骤循序渐进的进行 而且 应该是在写完某项小功能的第一时间进行 如果重构要在项目写完了N年以后再进行 还真是挺头疼的 就像是一个人得了点小感冒 有点爱咳嗽 当时就是不吃药治治 结果这病挨了几年之后成了肺结核了 恩 想起来得治了 道理大概就是这么个道理吧
过去半年基本上完整经历了这个文章的各个阶段,看完文章结合自己的经历,发现的确有些值得反思,但是哪怕过程太痛苦,但如果是一个自己要长期维护的模块,”至少亲生的bug更好看些吧”,如果一开始的需求就复杂,没有条理,代码第一版设计者没有充分的和需求方整理需求,盲目编码形成第一版难以维护的代码,后面的人几乎不可能根据代码还原出需求的原貌,甚至是需求方自己也很难确定自己最初的想法是什么样的,我们在重构代码的起点往往是一些难缠的bug或者新需求,因为要解决这些bug,或者实现新需求,发现原来的设计把所有路都堵死了,后来的人如果是追求完美的程序员,就会陷入这种境地,而和第一个程序员一样的人,会用更加不优雅的方式把新的代码黏在原来的代码上,直到遇到一个追求完美的程序员,或者给需求方说,这个我们做不到.原因不是技术瓶颈,而是我们做的不好,或者前人做的不好.
所以哪怕直到有这几个阶段,如果不试一下,也许永远被噩梦缠绕,如果做一下,后面最差也就是亲生了一个跛脚的框架.这过程中,对需求的把握,对未来的预期远远比一步不走要好的多.
这里的几个阶段,第五阶段是转折点,这里看你是不是一个彻底的完美主义者,如果是,时间不足的情况下,你可以选择短时间在自己底线内做些忍让,但是哪怕到了最后一个阶段,那个站起来说重构的人,我希望是我自己,我不希望”做一个重构别人代码的人”. 所以重构虽然痛苦,但是有些重构是必须进行的事情.特别是你预期到后面有大量的需求和变更的模块
当然值得反思的是,绝不做不分轻重的屠宰者,要”秉公办事”,轻急缓重要分得清,任何过度的事情都是失衡的状态,过度设计和过度重构,过度敏捷都是适得其反.
个人对重构的优先级:
1. 不稳定的地方,频繁出bug
2. 需求集中,变更频繁的地方
3. 核心模块内过时的代码
4. 核心模块内重复的代码
5. 核心模块内不清楚的代码
6. 一些无关痛痒的风格问题