回想一下有多少次在临近发布的时候你是这样的状态:
很多时候,迫于项目时间压力,我们都会忍不住走捷径。先上线吧,虽然现在的解决方案不是最优的,好歹凑合能用,等有时间了再重构;先上线吧,等有时间了再做自动化测试,也许用户发现不了这个bug。然后下一个迭代又有新任务来了,周而复始。终于在几个迭代以后,欠的债越来越多,整个项目没有人能完全知道这次的发布会有什么问题,上线后都要拜关公了。
第一次发布代码,就好比借了一笔钱。只要通过不断重写来偿还债务,小额负债可以加速开发。但久未偿还债务会引发危险。复用马马虎虎的代码,类似于负债的利息。整个部门有可能因为松散的实现,不完全的面向对象的设计或其他诸如此类的负债而陷入窘境。[wiki]
Ward Cunningham 不但是个天才程序员,也是一个语言天才,使用了“技术债”这样一个极富画面感的词。即使跟非技术人员交流也不需要过多解释这个词,大家都能领会大体的意思。并且当你向老板解释这个迭代为什么会延期时,说一句“我们用了一周来还技术债”,比说“我们之前的某某设计不是很好,我们花了一周的时间进行了重构”有效得多。
书中提到的技术债的来源有很多种,就笔者的实际项目经验看,主要是下面几种:
1. 前任甩锅。接手项目的时候已经设计实现成这样了,而且当初的架构师和程序员早就离开团队了。项目已上线,服务不能停,必须在高速公路上换轮子。
我们花了很大力气重构原有系统,但是诸如数据库设计不合理等问题却是在当前版本不敢轻易去修改的。
2. 测试不到位,基本没有单元测试或者测试用例写得不够灵活。回归测试没有实现自动化,全靠手工完成。验收的时候只测试新功能,以前功能的测试用例根本来不及做。我们这个迭代完成了x个功能(同时引入了多少个bu无人知晓,直到线上出问题)。
这仍然是我们的深深痛!
3. 缺乏工程化的自动构建持续集成和自动化部署的框架。项目构建工具混乱,ant,maven,gradle,grunt,gulp全用上了,可是关键组件和配置还是得靠手工拷贝。有时候还只能在开发者的电脑上构建成功,在持续集成服务器上构建不出来。部署更是恐怖的事情,好不容易打好包了,发现生产环境的配置文件还得改。
现在这个债基本还完了,专门让一个开发人员抽出时间来做了一个spike,实现了一键部署。(虽然还有两个外包模块没搞定。那两个模块得彻底重构才行,一笔巨债啊!)
4.微服务架构的引入在解决功能模块间强耦合的同时,不可避免的削弱了项目间的依赖印象。开发人员往往缺乏全局视角,只顾着自己的一亩三分地,对于其维护的服务变更对系统其它服务造成的影响缺乏必要的警示。由于缺乏相应的治理工具,以前在大型单体应用中能够在编译期发现的问题需要到运行以后才能发现。如果有很好的自动化测试覆盖率还可以在持续集成的时候就发现问题,然而正如第二点所说,并没有在CI上跑自动化测试,从而未能阻止问题代码上线。
原书里面说到有一个普遍误区:认为减少测试可以加快速度。虽然我们都知道减少测试会增加技术债,但大家普遍是将这个债务先背着争取更快上线。然而作者告诉我们减少测试不但增加债务,还会减缓速度。表面上看似乎很难理解,你看我不做测试省了好多时间。其实我们也是经常这样想的,所以测试往往就被牺牲了。我不知道有没有团队将测试做好以后再来评估自己的速度,总之我想接下来要全面推动TDD和自动化测试,到时候再分享经验。
书中关于技术债管理的原则和偿还技术债的策略确实很值得学习。与财务债相同的是,技术债不都是坏事。例如举债买房也是一个负责任的行为。对于一个公司来说,合理的安全债务水平恰恰能反应他的财务能力。技术债也一样,我们需要保障写出clean的代码,但是不能过于洁癖。
与财务债不同的是,有些技术债真的不需要还。例如一些你认为很SB然而不得不做的需求(我通常叫他们伪需求),不要想那么完美,怎么简单粗暴怎么实现,反正就是演示一下,永远也不会真的被用到。如果真的被使用了呢?那就再重构呗。
还有一类不需要偿还的技术债就是一次性使用的脚本、工具。例如数据迁移脚本,通常而言只会被使用一次,能完成任务就行了,没必要那么完美,效率能接受即可。
当然大部分的技术债都是要还的,正如题图所示,只是迟早问题。我们该怎么制定偿还策略?作者给出的指导原则跟财务债也有类似的地方。例如我们应该先还利息高的负债。什么是利息高的技术债呢?我相信凭团队的直觉都能判断出来,抱怨越多的绝对是利息高的负债,越快解决越好。分期付款原则也很重要,在每个迭代里面加入一些还债的任务,不知不觉就把债还了。尽量避免出现一个单纯还债的冲刺,可以让某个团队成员在这个冲刺中还债而不应该使整个团队都陷入债务中。(嗯,看到这里我打消了封闭还债的计划)。