[转载][翻译]偿还技术债 - 通过重构拯救老代码
偿还技术债 - 通过重构拯救老代码
尝试去接管一个陈旧的代码库使他成为达成一个可控的状态?这几年的大型的旧web应用程序开发给了我们如下这些建议。
通过重构去拯救旧代码
松鼠会因为忘记自己把松果放在那里,使得每年多了几千棵松树。类似的,这些事情在项目中都没有什么关系。你的项目是可以被拯救的
无论代码多么的杂乱,让人疯狂。但是你的老板让你上了,你要相信不管前路多么曲折坎坷,你总能搞定的!
心无畏惧
公平的说。比起冲进一个四处鲜血还有巨龙守在门口的沼泽地待几年,你更愿意在一片清新的草原上散步。
不幸的是,你的老板和这片沼泽地的公爵达成了一个协议,派你上了。
不管怎么样,你上了,所要做的就是把这块泥泞的沼泽变成一块美丽的牧场。
技术债 - 它怎么会这样
当你在项目中遇到一个又一个坑的时候,你可能在想:这是哪个“无能”的人做的?有人肯定能预见这个问题,但是为什么不是这样呢。
可能吧,不过更多的时候并不是这样。包括我们自己,很自信的写出的东西可能之后自己也不知道是什么了。
“无能”这个词并不适合解释这种情况。从工作本身的角度解释,它应该叫做“技术债”。
代码癌
“代码癌”这个东西充斥在各个项目的开发周期内,指的是为了快速解决当下问题采取的临时方案(原文中为“Ugly hacks”)。随着这种只完成了功能但是很难维护的东西的出现,然后技术债就开始越积越多。
然后,一点一点的,项目就开始失控。不经手真正代码的的人根本不会在意这一点,但是,最终,他可能最后落在你的手上。
对于任何项目来说,快速完成需求,推出新的功能都是非常重要的。如果做不到,珍贵的用户就会离开。举个例子,就像是他们宁愿去隔壁泥泞沼泽上的酒吧喝着长岛冰茶,也不会在你花了一整年修剪工整的草坪上傻站着。
所以,就算是一个健康的项目,技术债也一定会产生,但是为了避免代码最终变得无法维护,这种积累的技术债在某些时候一定要解决的。
为了避免让一群疯狂的受害者冲进你的沼泽地找你讨债。作为沼泽地的主人,你可能需要一些“重构”:交换一些代码的位置,让他能够更好地维护和拓展。
说服客户
对于这片泥沼的公爵,你需要告诉他他的土地上正在不断的进入小怪物。因为总有客户想要这样那样的功能。当你面对一个deadline定为一周但是你完成它至少要两周的需求,你需要学会向他解释,让他明白你需要花一点时间来做点小小的重构。
如果做不到这一点,可以预见,技术债将不断增加,最后无法收场。
其实你的目的和你的客户是一致的,愉快而顺利的工作,然后大家都能赚钱~ 这个的前提就是需要有一个稳定的项目可以让你从容的写出你的代码。
争取你的自由
你需要学会如何将情况解释清楚。我觉得最好的办法就是向这片泥沼的主人解释清楚有哪些潜在的风险隐藏在角落里,要让他知道你的好意。你可以试试下面几条建议:
1.解释什么叫做“技术债”,告诉这种债务过高会让开发越来越慢。我们可能需要花费很多时间才能找到问题,而且要花费很多时间去解决它。
2.给出清晰的短期目标和计划,告诉客户你可以选择花一周去实现一个功能,也可以给我吗一周时间重构,然后花一天去完成这个功能。但是第二种方案对于以后的开发有很多好处。
3.告诉客户,其实你们最终的目标是一样的。而且,你们担忧的事情也是类似的。
一旦你和泥沼的主人达成一致,事实上最困难的事情就已经结束了。下面,让我们的项目走上正确的方向吧。
不要再弄出一个新的沼泽地
你可以尝试修复它,不要重写它。
你可能说服了沼泽地的主人,你可以为他创造一个崭新的牧场。你会用更新的工具,更好的方式。但是,听我的,千万不要。
重新的风险有很多:
1.这样做就像按下了一个危险的开关,新代码从来没在生产环境下跑过,谁都不知道会发生什么。
2.数据迁移,从旧的系统向新系统迁入和迁出数据很容易出现问题。
3.复刻旧的错误,你开始重写了,就有可能重复旧项目中的问题,旧项目的一些巧妙的特性也会被弄丢。很多问题都发生在系统的一些你本身刚开始就有疑问的地方。你会浪费很多时间,不管是你还是你的客户。
4.你需要紧跟着业务,你在做新工程的时候,旧的工程也需要同时跟进业务需求。这样相同的东西需要在两边同时开展。
所以,听我的,不要从头开始,优化现有的。
让问题变得可见
这种事情很容易让人不爽,把问题都暴露出来。就像是让沼泽里的怪物们统统跑到了你的眼前向你的脸上吐痰,巨龙喷着火要烧掉你的头发,住在蘑菇旁的小侏儒踢着你的小腿。
不管你有没有注意到,这些害虫一直在侵蚀你的项目,你得想办法摆脱他们。所以必须得在任何时候都能看到到底问题出在哪。
预见错误。每周花一点时间处理最常出现的一些问题。将问题图表化,可能能更有效的做到这一点,
监控环境变化。这可能是找到瓶颈和预见危机的关键(见下文)。
这样,你就能知道是什么问题对你的项目伤害最大,你就能提早发现问题展开救治,而不是最后面对一个垂死的病人束手无策。
处理最重要的问题
做到这一点。你就需要有一个对系统完整的目标。就像你清楚的知道你最终要构建的完美的牧场是什么样,在你的每一步工作都是朝着这个目标。
这样你就不会忘记你每一小步做了什么。在这一小步一小步中间,你就会发现自己到达了自己最终的目标。
结合上文所说的监控方式以及你的最终目标,确定什么是你最先要处理的。你最大的问题应该不是马上实现某个目标,而且开始管理重构路上你找出的问题。
还有个建议,有些小问题就像是一写坡脚的小精灵,你可能看她们不爽,但是实际上她们是无害的。你最好把时间花到如何踢出真正对人有伤害的那些食人魔。
这行代码是我写的
解决问题是最重要的,但是不意味着其中的实现细节不重要。事实上,他们同样重要。
这里有个沼泽生存原则,保持你的宿营地比你发现他的时候更干净。随着你持续的整理,注意不留下什么垃圾。你的环境就会越来越好直到你发现这篇泥沼变成了绿洲。
态度决定一切:
1.细心。代码是你写的,你要能向所有接触它的解释清楚它,不要草率马虎。
2.团队要一样细心。你辛苦的填上一个有一个坑,但是后来人还在不断挖坑的话,依然还是那个样子。
3.纪律。团队任何一个人开始让事情变糟,都不要让他逃掉责任。
4.保持小步骤。在正确的方向上,进度比完美实现重要。
5.小小的胜利都能让人信心十足。你会发现一些地方优秀的修改会促使你想去修复它边上的问题。
建立标准模块
一个对文明评价的重要指标是 在每平方米的土地上有多少图书馆。同样的,这对你的项目来说也是一个类似的指标(当然在项目里我们不叫他(Library)图书馆)。
举个例子,即使在最烂的沼泽地里,也会有几个不是特别糟糕的景点。无论何时,当你发现了一些好代码,他们完成了一些很不错的事情,就把他们挪到你的标准模块里,是的他们可以复用。
毫无疑问,你要修复的代码做的都是不好的。它们完全忽略和和避开了能让程序员觉得更轻松的可以陈祚最佳实践标准的原则。没有人会帮你搞定他们,你只有一条路可走:在你的新代码中实践下面的标准:
让你的代码标准化和模块化。有些情况下你不得不使用旧的方式和不标准的方式处理旧的问题,但是这不意味着,你不能不在新的代码中实践这些正确的东西。
用你的新模块重构
每当你的新模块写好,你就可以用它来重构旧的。你做这件事的时候没必要向马拉松的冲刺阶段一样紧迫。每当你偶然发现你有一个好的新方式替代那部分老代码的时候。保持小步骤,最后你会发现一点一点你就找不到那些困惑你的老代码了。
通常,上述步骤是可行的。不过有时候你发现有些代码包含无数的依赖关系四处分布(比如会话访问,各种模块和服务纠缠在一起)。这时候,有个好方法是把它们的依赖方法和函数从他们内部暴露出来,外部就可以通过这些函数和方法访问他们。这种方式使得这些代码相对独立,以后更利于分离和移动它们。
同时,一旦代码独立了,它们就变得可测试了。
通过测试建立信任
自由穿行的食人魔们可以比带着枷锁的食人魔更可怕。把系统中最为关键的部件(同时它们可能造成最严重的问题)加上锁链对于掌控一个系统是非常重要的。在每次构建的时候进行测试(最好是自动化的)尤为关键。
如果有更多的对你的系统行为的自动化测试,你就能在改动项目的时候有更充足的信心。一点有东西出错,这些测试就能让你在发版前及时修改它们。
高级别测试
有一个有效的测试方式叫做:关键场景验收测试。对于一个电子商务系统,肯定包括一个结账的流程。没有订单你肯定不赚钱,测试这个订单流程,你就能在客户没有发觉之前解决这个问题。有了这样的测试,你就可以避免自己不知不觉的引进重大问题。
低级别测试
这种低级别测试我们推荐单元测试。使用上文说的小步骤逐一分解依赖的方式,你会使得你的代码有一套清晰的输入和输出结构。这种情况下就很适合用单元测试覆盖这些代码的功能。你可以定义一组输入,并且定义它预期的输出。这些测试可以确保这些代码的行为和你预期的一致。
不要测试所有的东西
没必要测试系统的每一行代码。原则上让系统的每一个方面可以使我们对所有事情充满信心,但是让所有东西可测试并为他们编写和维护测试用例会让成本变过高。根据我的经验,你最好把时间花在为重要的业务逻辑和有可能造成重大问题的代码上(即使他们看起来很简单)。
这样你就相当于把沼泽里最危险的怪物们锁起来了,整个沼泽变得更安全咯。
隔离和更换
结合我们上文提到的战略模式和技术,我们总结唯一个原则:“隔离和替换”。
每当你遇到某些代码,他们的内部逻辑很难理解,而且打算用那种小步骤重构的方式整理出来。下面有些步骤建议:
1.隔离混乱的部分,把他们放到一些单独的方法里。
2.把依赖的部分拆出来作为他的参数
3.把逻辑里的副作用分离出来(比如向数据库保存数据),这里也是小步骤进行
4.隔离单独的逻辑到不同的方法里
5.添加单元测试覆盖逻辑模块
6.重写的逻辑模块。就算他能通过测试,也要保证至少他的行为要类似老系统中的行为。
7.保持新老版本同时能在线上运行。保证旧版本也能运行
8.log记录下新老版本的输出。
9.对比日志,看看他们输出的不同,如果有不同就为该模块添加一个新的单元测试,然后开始修复它。
10.重复上述步骤直到他们一致为止。
11.最后新版本能完全替换旧版本的相关逻辑。
这种做法就像是不需要停车就能为他换了发动机,甚至是没有人能主要到他的内部发生了变化。
说服自己
在这里,上述的态度和方法,至少对于我来说,是一个拯救一个濒临崩溃的系统之路。
最初我还在犹豫,为什么要把我的时间花在这样一个长期的旧工程上。但是这期间我也获得了很多能帮助我变得更好的技能。
1.识别糟糕的代码。你认识到什么是糟糕的代码因为你花了大量的是在在它上面。你在新项目中就不会使用它这种方式因为你知道它会在未来搞出多少问题。
2.回归本质。我认识到系统的每一个方面都值得质疑和重新评估。项目里没什么是无懈可击的,你会发现许多更现代的框架可以帮你处理很多问题。
3.你和项目同时成长。随着你对项目的关注越多,你就会看到更多的挑战。你知道了哪些是这个系统的瓶颈,越来越多的人在依赖的工作成果,而你是整件事情的中心。
4.人们总是不断的生产新的东西。如果赶在在他们对项目造成伤害之前,理解他们的运作方式也是个不错的技能。
5.有趣的经历。回头看你这一路的历程,看看这个系统的样子和你几年前第一次遇到他的时候。是不是也蛮有成就感的呢。
去完成这个巨大(至少是长期)的任务吧
你可以选择做一个胆小的农民,期待中谁能把你从这片沼泽地带到迪士尼乐园。或者你可以摆正你的姿态,扮演好救世主的角色。在任何一个有坚固地面的沼泽地里,你都可以找到你的位置。
最后
几年之后,你漫步在这片牧场上。你看到公爵在地平线上竖起了啤酒广告代替了原来“这里有怪物”的标志牌。
感觉翻译的不太像人话。。。。
原文链接:http://blog.intracto.com/paying-technical-debt-how-to-rescue-legacy-code-through-refactoring