技术债:人的因素
在维基百科中,技术债是这么定义的:
Technical debt is a concept in software development that reflects the implied cost of additional rework caused by choosing an easy (limited) solution now instead of using a better approach that would take longer.
所以我们可以把技术债看做是一种捷径 —— 一种带来短期利益(交付期更短)但需要未来花费更多时间弥补的技术捷径。「债」在这里是一个非常形象的比喻,它和我们日常生活中产生的银行债务有异曲同工之妙:
1) 债务总有归还的时间 —— 技术债在未来的某个时刻也需要通过重构来解决
2) 债务需要付利息,而不断借债,甚至为了还「利息」继续借债,债务成本会越来越高,累积的利息会呈指数增长 —— 在技术债基础上继续引入技术债,会让事情变得越来越糟糕,引入新功能(软件资产)的时间会越来越长。
3) 当债务累积到一定程度,超过资产时,如果此时没有足够的自由现金流来偿还本息,那么个人或者公司可能不得不寻求破产保护 —— 当技术债太大无法通过重构来解决时(或者成本太高),此时重写是最好的方案。
技术债分成几个部分:1) 架构和设计上的技术债 2) 代码实现层引入的技术债 3) 软件测试的技术债 4) 文档技术债。
我们平时对技术债有些误区,认为技术债的引入很多时候是开发工期不够,开发团队不得不做出妥协,选择短平快的解决方案。其实在实际工作中,技术债的引入很大程度上是因为人和流程的原因,而流程是人制定和人来执行的,所以归根结底是人的原因。
首先说人的原因。
架构和设计自不必说,开发者需要有足够的知识,经验和对需求的敏锐把握,才能做出足够好的架构和设计。除此之外,开发者在接到一个新的功能时,他需要处理的不仅仅是这个功能本身,还涉及这个功能和已有系统的对接。开发者是否对已有系统有足够的了解,往往决定了这个功能的质量。
那么,什么叫对已有系统有足够的了解?
首先,开发者需要知道系统提供了哪些 API,哪些是稳定的 API,哪些是不稳定的 API,哪些是在这个层级可以调用的 API,哪些是不该调用的内部 API。这里 API 是广义的 API,可以是后端提供的某个 REST/gRPC 接口,也可以是前端的某个组件,甚至是某个算法的实现。
这件事情在很多开发团队中是很模糊的,在系统内部(比如一个 android 项目内部),大部分时候可能没有成熟稳定的 API 文档,仅仅靠目录结构约定俗成,以及开发人员间的口口相传;在系统间(前端后端之间),即便有文档,可能也是描述地十分简陋的 swagger 或 protobuf 文档,协作还是得靠口口相传。
其次,开发者需要知道当前的系统有哪些技术债,这些技术债产生的原因是什么。
如果说对 API 的理解还不算太大的挑战,那么,全面了解当前系统,尤其是自己所做功能涉及到的技术债,则难度高了一大截。很少有公司或者团队能在引入技术债时撰写文档阐述这里面的 tradeoff,所以,不像 API,你还可以有具体的东西向别人咨询,技术债是个无形的东西,除非你读代码的时候读到了 TODO
或者 FIXME
,或者是代码的问题太过明显(比如复制粘贴出来的代码),否则你都不太会知道某段代码有某些问题。
为什么在开发新功能的时候需要知道当前的系统有哪些技术债呢?首要的目的是你不想新的功能无意间加深或者说强化了技术债,使其更难重构;此外,如果能在开发新的功能时,顺手将能解决的技术债解决掉一部分,那将是大大的功劳。
所以,我说技术债里面,人的因素很大 —— 单单是对已有系统有足够的了解这一条,就足以刷掉很多开发者。但开发任务摆在那里,不可能所有的开发都放在团队中少数的几个人那里完成,所以,很多时候,系统的技术债是不断地,慢慢地叠加的。
我们再看流程的问题。
当开发团队没有一套合理的代码构建流程时,技术债也会不断累加。比方说,开发者等待 code review 的 PR 没有代码格式化,没有 lint,没有 UT,那么,code review 还有多大的意义呢?很可能 reviewer 主要的精力都放在了基本的代码问题上,而没有功夫去管更重要的问题:接口设计是否合理,数据结构是否合理,逻辑是否有问题等。
对于这样的问题,很多团队会使用一套完整的流程来应对:
本地代码使用 precommit hook,处理代码风格,lint,UT 等,只有在所有检查通过,才允许 commit
生成 PR 后,CI 至少会做 precommit hook 做的所有事情,以及测试覆盖检查,甚至还会做集成测试。
如果测试覆盖率有明显下降,PR 的 CI 不会通过。开发者必须要更新 UT,达到足够的覆盖率才能通过。
至少两个 reviewer 通过后,并且 PR 跟 master up-to-date,PR 才能被合并。
这个流程不但考虑了尽可能减少代码层面的技术债,还考虑了减少测试层面的技术债。但很可惜,根据我跟很多微信朋友的了解,一半以上的团队都没有这样的流程。
即便这个流程通过机器(比如 precommit / travis / github action)来确保执行,但 code review 依然是其中不稳定的部分。这是因为做 code review 的是人,他们往往受限于自己对系统的理解和对新功能需求的理解,以及自己的时间精力,无法时刻做出最佳的应对 —— 有时候 review 质量很高,发现了隐藏的问题,但很多时候,review 可能没有太多发现。所以,它是不稳定的。
上述的流程还缺乏对文档的约束 —— 很多语言,比如 rust,会在 linter 里提供对文档的检查:任何外部接口(pub)都需要要有文档描述。但这还远远不够:文档需要足够的深思熟虑,而不是随手应付的一两句话;当代码发生改变的时候,文档也需要跟着改变。而这往往是最困难的,需要开发者强大的自律。很多时候,过期文档带来的问题可能比没有文档还要大。
正因为人的因素在软件开发的各个环节中如此重要,为了减少不必要的技术债的产生,最好的方法是不断招募足够优秀的人才,进行合适的培训,并且给予他们最大的上下文来处理要处理的问题;同时,不断优化和自动化开发流程,使得每一次新的 commit,引入的熵尽可能少。
以上。你也可以看我四年前的文章:技术债:the good, the bad and the ugly。
Long-press QR code to transfer me a reward
小伙子不错,继续努力!
As required by Apple's new policy, the Reward feature has been disabled on Weixin for iOS. You can still reward an Official Account by transferring money via QR code.