2016年,我已经写了五年代码了。

从2011年毕业,到现在,整整五年。这五年里,我换过两份工作,做过十几个项目,写过几十万行代码,也删过几十万行代码。从刚毕业的新手,写个功能都要查半天文档,到现在能独立负责项目,带团队,做架构设计。

这五年里,我踩过很多坑,也学到了很多东西。其中,让我感触最深的,就是"技术债务"这个概念。

刚工作的时候,我不懂什么是技术债务。觉得代码能跑就行,怎么快怎么来。需求来了,赶紧实现,上线再说。至于代码质量、架构设计、可维护性,那都是以后的事情。

但是随着项目越来越大、维护时间越来越长,我开始尝到技术债务的苦果了。

一、什么是技术债务

"技术债务"这个概念,最早是由Ward Cunningham在1992年提出的。他用金融中的"债务"来比喻软件开发中的技术问题。

简单来说,技术债务就是:为了短期的速度,而牺牲了代码质量、架构设计、可维护性,导致未来需要花更多的时间和精力来修复和重构。

就像借钱一样,你现在借了钱,能快速解决问题,但是未来要连本带利地还。技术债务也是一样,你现在图快,写了烂代码,能快速上线,但是未来要花更多的时间来维护、修复、重构,这就是"利息"。

技术债务有很多种表现形式:

  • 代码写得很乱,变量名不规范,没有注释,别人看不懂
  • 架构设计不合理,模块之间耦合严重,改一个地方要动很多地方
  • 没有单元测试,改代码不敢改,怕改出bug
  • 技术选型过时,用的是已经被淘汰的技术,招人难,维护难
  • 文档缺失,新人上手慢,很多知识只存在于老员工的脑子里
  • 重复代码很多,同样的逻辑在很多地方复制粘贴,改的时候要改很多地方
  • 性能问题,系统越来越慢,但是不知道怎么优化

这些都是技术债务。刚开始的时候,可能感觉不到什么,但是随着时间的推移,技术债务会越积越多,最终会让项目无法维护,甚至崩溃。

二、技术债务是怎么产生的

技术债务的产生,有很多原因。根据我这五年的经验,主要有以下几个原因:

1. 时间压力 这是最常见的原因。产品经理说"这个需求下周必须上线",老板说"这个功能客户等着用",市场说"竞争对手已经有了,我们必须跟上"。在时间压力下,开发者只能选择最快的方式实现,而不是最好的方式。代码能跑就行,先上线再说。至于代码质量、架构设计,那都是以后的事情。

我刚工作的时候,就经常遇到这种情况。有一次,产品经理说"这个功能明天就要演示",我连夜写代码,怎么快怎么来,复制粘贴,硬编码,什么都不管。功能是实现了,演示也通过了,但是那代码写得,我自己都不想看。后来,这个功能要改,我花了整整一周才改完,比当初好好写多花了好几倍的时间。这就是技术债务的利息。

2. 缺乏经验 刚毕业的新手,不知道什么是好的代码,什么是好的架构。觉得代码能跑就行,不知道要考虑可维护性、可扩展性、性能。写出来的代码,虽然能跑,但是问题很多。这也是技术债务的一个来源。

我刚工作的时候,写代码就很烂。变量名用a、b、c,函数写几百行,一个类做所有事情,复制粘贴到处都是。那时候觉得自己写得挺快的,还挺得意。但是后来,我自己维护自己写的代码,都觉得头疼。很多地方,我自己都看不懂当初为什么那么写。后来,我花了很多时间重构,才把那些烂代码慢慢改好。

3. 需求变化 软件开发中,需求变化是常态。今天产品经理说要这样,明天又说要那样;今天老板说要加这个功能,明天又说要砍掉。需求不断变化,代码也不断修改。在修改的过程中,原来的架构可能就不适用了,但是又没有时间重构,只能在原来的基础上打补丁。补丁越打越多,代码越来越乱,技术债务也就越积越多。

我做过的一个项目,就是这样。最初的需求很简单,就是一个内容管理系统。但是后来,需求不断增加,要加电商功能,要加社区功能,要加数据分析功能。每加一个功能,就在原来的代码基础上改,没有重构。到最后,这个项目的代码乱得一塌糊涂,一个简单的修改,都要改好几个地方,而且很容易改出bug。后来,我们花了三个月的时间,把整个项目重构了一遍,才解决了这个问题。

4. 技术选型失误 技术选型是软件开发中很重要的一环。如果技术选型失误,选了一个不合适的技术,或者选了一个已经被淘汰的技术,那么未来就会面临很多问题。招人难、维护难、扩展难,这些都是技术债务。

我之前的一家公司,就犯过这个错误。他们选了一个很冷门的框架来做项目,说是这个框架性能好。但是后来,这个框架的社区越来越小,维护的人越来越少,文档也不全。我们遇到问题,查不到资料,只能自己看源码。而且,招人也很难,会这个框架的人很少。到最后,我们不得不花了半年的时间,把整个项目迁移到了一个更主流的框架上。这就是技术选型失误带来的技术债务。

5. 缺乏代码审查 代码审查是保证代码质量的重要手段。如果没有代码审查,每个人写代码的风格不一样,质量也参差不齐。烂代码没有人指出来,就会一直留在代码库里,越积越多。

我现在的团队,就很重视代码审查。每个人提交代码,都要经过另一个人的审查。审查的时候,会看代码逻辑是否正确、变量名是否规范、有没有注释、有没有更好的实现方式。通过代码审查,很多烂代码在提交的时候就被发现了,避免了技术债务的积累。

三、技术债务的危害

技术债务的危害,是渐进的。刚开始的时候,可能感觉不到什么,但是随着时间的推移,危害会越来越大。

1. 开发效率越来越低 技术债务越多,开发效率越低。因为代码乱、架构差,改一个功能要动很多地方,而且很容易改出bug。开发者要花很多时间来理解代码、调试bug,而不是开发新功能。

我做过的一个项目,技术债务很严重。刚开始的时候,一个简单的功能,一天就能做完。但是到后来,同样的功能,要花一周才能做完,因为要改很多地方,还要调试很多bug。开发效率越来越低,产品经理天天催,开发者天天加班,但是进度还是很慢。这就是技术债务的危害。

2. Bug越来越多 技术债务越多,Bug越多。因为代码乱、没有测试、耦合严重,改一个地方,可能会影响到其他地方,导致新的bug。而且,因为代码乱,bug也很难定位,修复起来很费劲。

那个技术债务严重的项目,就是这样。几乎每天都有新的bug,修复了一个,又出来一个。测试团队天天提bug,开发团队天天修bug,但是bug还是修不完。到最后,用户都开始投诉了,说系统不稳定,经常出问题。

3. 团队士气低落 技术债务多的项目,团队士气通常都很低落。因为开发者每天都在和烂代码打交道,改bug、救火,没有成就感。而且,因为技术债务多,项目进度慢,产品经理和老板天天催,压力很大。时间长了,开发者就会产生倦怠感,甚至离职。

我之前的一家公司,就是这样。那个项目技术债务很严重,团队里的开发者,每天都在抱怨代码烂、bug多、压力大。不到一年,团队里的人走了一半。新人来了,看到代码这么烂,也待不住。到最后,这个项目几乎做不下去了。

4. 项目最终无法维护 技术债务积累到一定程度,项目就会无法维护。代码乱得没人能看懂,改一个地方会影响到很多地方,bug修不完,性能越来越差。到最后,只能推倒重来,重新写一个新的系统。

推倒重来的成本是很高的。不仅要花很多时间和金钱,而且还要承担风险——新系统可能会有新的问题,用户可能不适应新系统,数据迁移可能会出问题。所以,技术债务积累到最后,代价是很大的。

四、如何管理技术债务

技术债务是不可避免的。就像金融中的债务一样,适度的债务是可以的,甚至是有益的——比如,为了快速上线,先写一个简单的版本,占领市场,然后再慢慢优化。但是,技术债务不能失控,必须要管理。

根据我这五年的经验,管理技术债务,主要有以下几个方法:

1. 要有意识 首先,团队要有技术债务的意识。要认识到技术债务的存在,以及技术债务的危害。不能觉得"代码能跑就行",要重视代码质量和架构设计。

很多团队,就是因为没有技术债务的意识,才导致技术债务越积越多。所以,第一步,就是要让团队里的每个人,都认识到技术债务的重要性。

2. 定期重构 重构是偿还技术债务的主要方式。要定期安排时间,对代码进行重构,优化架构,提高代码质量。

重构不能等到技术债务积累到无法收拾的时候才做,那样成本太高。要在平时就定期做,比如每个迭代安排20%的时间做重构,或者每个季度安排一次集中的重构。这样,技术债务就不会越积越多。

我现在的团队,就是这样。每个迭代,我们都会安排20%的时间,用来做重构和技术优化。这样,虽然新功能的开发速度会慢一点,但是代码质量有保证,长期来看,开发效率反而更高。

3. 写单元测试 单元测试是重构的保障。没有单元测试,重构的时候就不敢改,怕改出bug。有了单元测试,重构的时候就有信心,改完之后跑一下测试,就能知道有没有改出问题。

所以,要偿还技术债务,首先要补单元测试。对那些没有测试的核心模块,先补上测试,然后再进行重构。

我现在的团队,要求新写的代码必须有单元测试,覆盖率要达到80%以上。对于老代码,我们也在慢慢补测试。有了测试之后,重构的信心就足了很多。

4. 代码审查 代码审查是防止技术债务积累的重要手段。通过代码审查,可以在代码提交的时候,就发现问题,及时纠正,避免烂代码进入代码库。

代码审查要常态化,每个人提交的代码,都要经过另一个人的审查。审查的时候,不仅要看代码逻辑是否正确,还要看代码质量、架构设计、可维护性。

我现在的团队,代码审查已经成为了习惯。每个人提交代码,都会主动找人审查。通过代码审查,我们发现了很多问题,也学到了很多东西,团队的整体代码水平也在不断提高。

5. 技术选型要谨慎 技术选型是产生技术债务的一个重要原因。所以,在做技术选型的时候,要谨慎,要考虑长期的可维护性、社区活跃度、招人难度等因素,不能只看性能或者新奇。

一般来说,尽量选择主流的、成熟的技术,不要选太冷门的、太新的技术。主流技术,社区活跃,文档齐全,招人容易,长期维护有保障。冷门技术,虽然可能在某些方面有优势,但是长期来看,维护成本很高。

6. 文档要齐全 文档是知识的沉淀。没有文档,很多知识只存在于开发者的脑子里,人一走,知识就没了。新人上手慢,维护成本高,这也是技术债务。

所以,要重视文档。架构设计、API接口、部署流程、常见问题,都要有文档。文档要及时更新,不能写完就不管了。

我现在的团队,就很重视文档。每个项目,都有完整的文档,包括架构设计、API文档、部署文档、运维手册。新人来了,先看文档,就能快速上手。

五、如何偿还技术债务

如果技术债务已经积累了很多,怎么办?

我的建议是:不要急,慢慢来。

技术债务不是一天积累的,也不可能一天就还清。要制定计划,分步骤偿还。

1. 先评估 首先,要对技术债务进行评估。哪些地方技术债务最严重?哪些地方对开发效率影响最大?哪些地方最容易出bug?把这些问题列出来,排个优先级。

2. 先补测试 在重构之前,先补单元测试。没有测试,重构就是瞎改,很容易改出bug。所以,先对核心模块补上测试,然后再进行重构。

3. 从最严重的地方开始 偿还技术债务,要从最严重的地方开始。那些对开发效率影响最大、最容易出bug的地方,优先重构。这样,能最快地看到效果,也能提升团队的信心。

4. 小步快跑 重构不要一下子改太多,要小步快跑。每次改一小部分,改完就测试,测试通过了再改下一部分。这样,风险小,也容易控制。

5. 不要在重构的同时加新功能 重构的时候,不要同时加新功能。因为加新功能会引入新的复杂度,增加重构的风险。重构就是重构,只改代码结构,不改功能逻辑。等重构完了,再加新功能。

六、写在最后

写了五年代码,我终于理解了什么叫"技术债务"。

技术债务不是什么可怕的东西,它是软件开发中的常态。适度的技术债务是可以的,甚至是有益的。但是,技术债务不能失控,必须要管理,要定期偿还。

作为开发者,我们要有技术债务的意识,要重视代码质量和架构设计,不要只图眼前的快,而忽略了长期的可维护性。

作为团队,要建立代码审查、单元测试、定期重构等机制,防止技术债务的积累,及时偿还技术债务。

技术债务就像信用卡,适度使用,可以帮你解决问题;但是如果失控,就会让你陷入困境。

希望每一个开发者,都能理解技术债务,管理好技术债务,写出高质量的代码,做出可维护的项目。

写了五年代码,这是我最大的感悟。