每一个程序员,项目经理或团队负责人的生命周期中至少发生一次,你接手一坨超过百万行代码的系统,原来的程序员好久之前就离职,如今也许正在某个阳光明媚的地方度假,文档(若是有的话)最有可能的状况就是与现有的系统不一样步。
html
而你的工做则是带领团队脱离这个混乱。linux
在经历逃离的本能回应以后,您开始对项目进行了了解,公司高层领导是不能容忍项目失败的状况。然而,经过你手头现有的东西,失败是大几率发生的事件。那么该如何应对?程序员
我有幸(或不幸)已经经历过若干次相似的经历,我和一小部分朋友发现,若是可以把这些垃圾代码变成健康的可维护的项目,其实是很是值得一试的事情。如下是咱们总结改进旧版代码库的一些经验(或者叫军规)。数据库
一、数据备份后端
在开始作任何事情以前,您须要备份全部可能相关的内容,这样能够确保无论发生什么状况不会丢失数据。咱们很难记得天天修改了哪些东西,特别是配置数据容易受到这种问题的影响,配置一般不会进行版本控制,若是可以进行按期备份,那则能够规避不少麻烦。把全部东西复制到一个很是安全的地方,除非它是只读模式,不然永远不会触摸到。安全
二、重要的先决条件,构建一个真实的仿真环境服务器
我以前的文章中错过了这一步,假设这个环境已经存在了,但许多 HN 网友指出了这一点,他们是绝对正确的。数据结构
第一步是确保你知道如今正在生产环境运行的是什么,这意味着您须要可以构建一个软件版本 —— 和您的真实环境保持一致 —— 相同的软件环境与二进制版本。架构
若是你找不到一个方法来实现这一点,那么若是你提交代码到生产环境,就可能会遇到一些使人不快的意外。确保新的代码在合适环境尽量地被测试,而后你才会有足够信心将其运行到生产环境。上线时作好准备能够随时切换回老的代码,并确保经过日志记录了相关重要内容,以便在后续排查问题能派上用场。模块化
三、冻结 DB 修改
尽量冻结数据库修改,直到完成第一阶段的改进,直到团队对代码库已经有了完全的了解,遗留代码已经弃之身后时,才考虑修改数据库结构。在此以前任何的数据库修改可能会致使一些棘手的问题,你失去了并行运行旧系统和新的代码库的能力。保持 DB 彻底不变,您能够比较新的业务逻辑代码与旧的业务逻辑代码,若是全部这些效果都与预期同样,则应该彻底没有区别。
四、编写测试
在进行任何修改以前,编写尽量多的端到端以及集成测试,确保这些测试可以产生正确的输出,并覆盖全部潜在的状况。
这些测试将具备两个重要功能:帮助在早期阶段清除任何误解,另一方面,一旦您开始编写新代码来替换旧代码,这些测试将能够更好保护您的系统。
自动化您的全部测试,若是您已经有 CI 的经验则尽快使用它,并确保您的测试运行足够快,以便在每次提交后运行全套测试。
五、Instrumentation 和日志
若是旧平台仍然能够增长 Instrumentation,在一个全新的数据库表中执行此操做,为您能够考虑的每一个事件添加一个简单的计数器,并添加一个单个函数来实现此功能,以根据事件的名称来增长这些计数器。
这样,您可使用一些额外的代码行实现带有时间戳的事件日志,您将了解到有多少事件致使另外一种事件。一个例子:用户打开应用程序,用户关闭应用程序。若是两个事件应该致使一些后端请求,那么这两个计数器应该在长期上保持不变,差别是当前打开的应用程序的数量。若是您看到更多的应用程序打开,而不是应用程序关闭,你知道必须有另外一种应用程序结束的方式(例如崩溃)。
这个简单的技巧能够将每一个后端应用程序变成一个相似的簿记(bookkeeping)系统,就像一个真正的簿记系统那样,全部的数字必须匹配,确保它们在全部用到的地方没有问题。
随着时间的推移,这个系统将监控健康方面变得很是宝贵,而且将成为源代码控制系统变动日志的一个很好的伴侣,您能够在其中肯定每一个错误引入的时间点,以及对各类状况产生影响的计数。
我一般保留这些计数器的分辨率为 5 分钟(所以每小时记录 12 次),但若是你的系统有更少或更多事件,则可能须要修改这个时间间隔。全部计数器使用同一个数据库表,所以每一个计数器只是该表中的一列。
六、每次只修改一个点
在添加新功能或修复错误的同时,不要陷入同时改进代码以及修改其运行的平台的陷阱。这会致使不少头大的问题。
七、平台更改
若是您决定将应用程序迁移到另外一个平台,那么请先执行此操做,但要保持一切功能彻底同样。你能够添加更多的文档或测试,但不能超过这一点,全部业务逻辑和相互依赖关系应该保持原样。
八、架构变化
接下来要解决的是改变应用程序的架构(若是须要)。在这个时候,您能够随意更改代码的较高级别结构,一般经过减小模块之间的水平连接数量,从而减小与最终用户进行任何交互时代码活动的范围。若是旧代码本质上是一体的,如今将是一个很好的时机使其更加模块化,将大型功能分解成较小的功能,可是保留变量和数据结构的名称。
HN 网友 mannykannot 指出,架构修改并不老是可行,若是特别不幸运,那么可能须要很是深刻理解代码才能进行任何架构更改。我赞成这一点,所以我作个小的补充,若是您同时进行高级别更改和低级别更改,至少须要将其限制在一个文件,或最坏状况下限制在一个子系统,以便尽量限制更改的范围。不然你可能很难调试刚才所作的更改。
九、低级重构
到目前为止,您应该对每一个模块的功能有很好的了解,并为实际工做作好准备:重构代码以提升可维护性,并使代码具有扩展新的功能的能力。这极可能是项目中耗时最多的一部分,文档须要随之进行,在完整编写文档介绍并完全了解一个模块以前,不要随意更改模块。
这个阶段也能够修改变量和函数命名、修改数据结构,以提升代码清晰度和一致性。记得添加相关测试代码(根据须要,可进行单元测试)。
十、修复 bug
如今你准备好进行一些最终用户可见的变化,第一件事情将是修复多年来积累在队列中的 bug。像往常同样,首先确认 bug 仍然存在,而后编写一个测试并修复 bug,您的持续集成和端到端的测试应帮您避免因为缺少理解或某些错误而致使的任何错误及外围问题。
十一、数据库升级
若是上述工做都已经完成,你能够再次拥有可靠且可维护的代码库,您能够选择更改数据库 schema 甚至替换数据库。已经完成的上述工做都将有助于您以无负担的方式进行变革,而无需担忧任何意外,您可使用新的代码和全部的测试来测试新的数据库,以确保您的迁移没有任何问题。
在路线图上前行
恭喜,到这里您已经走出了丛林,如今已经准备好能够实施任何新功能了。
不要尝试完全重写
完全重写是一种几乎保证会失败的项目。一方面,你是在未知的领域开始,你甚至会不知道要重构什么,另外一方面,你也将全部的问题推到最后一天,就在你用新系统启用以前的那一天。很悲剧的是,那也是你失败的时刻。业务逻辑的假设最终会证明存在问题,那时您将忽然了解到为何旧系统会用某种奇怪的方式来工做,最终也会意识到能将旧系统放在一块儿工做的人也不都是白痴。 若是你真的想要将公司(以及你本身的信誉)带向一个泥潭,就来一个完全大重写吧,但若是你足够聪明,完全重写系统一般不会成为桌上的一个讨论选项。
替代方案:迭代式改进
要解开这些线团最快方法就是从你已经理解的代码入手(它多是一个外围设备,但也多是一些核心模块),并在它的旧的上下文的范围内尝试逐步改进。
若是旧的构建工具再也不可用,您将不得不使用一些技巧(见下文),但至少在您开始更改时,尽量多地保持旧的系统工做。一个典型的提交一般只包含数行代码。
发布!
全部修改尽量发布到生产环境,即便修改的代码是最终用户不可见的,由于当你对系统了解不足时,只有生产环境才会告诉你新的修改哪里会有问题。若是这个问题只是在小的改变以后出现,你将得到几个优点:
很容易弄清楚出了什么问题
您将处于改善流程的良好状态
您应该当即更新文档,以记录得到的新看法
合理使用代理服务器
若是您正在重构一个 Web 系统,感谢上帝,你能够在最终用户和旧系统之间部署一个代理服务器。您能够精确控制每一个 URL 哪些请求进入旧系统,哪些请求路由到新系统,从而能够更轻松,更精细地控制运行的内容。
若是您的代理足够强大,您甚至能够控制将某个 URL 必定百分比的流量发送到新系统,以便观察新系统的运行状况。若是您的集成测试也可以链接到这个代理那就更好了。
颇有道理,但这一切须要太多的时间!
那么这取决于你如何看待它。若是按照这些步骤确实存在很多工做,可是它的确有效,并且这个过程的任何优化都让你进一步完全了解整个系统。我我的在这方面也有一个很好的声誉,我真的不但愿这样的工做中出现任何负面的问题。
有时候若是公司系统已经出现问题,并且可能会影响客户时,若是按照这个流程可能使事情好转,我宁愿彻底控制和和使用这个过程,而不是为了表面的节省几天或几周的时间的方式。若是你更多地是牛仔的作事方式,你的老板也赞成 —— 那么也许那是能够接受的高风险方式,可是大多数公司宁愿采起稍慢一点,更稳健的重构之路。
本文地址:http://www.linuxprobe.com/change-code-experience.html