业务系统技术债治理终极指南
![业务系统技术债治理终极指南](https://static001.geekbang.org/infoq/19/197265f2a8df3c9acee07a6eb896c96b.png)
序言
随着软件系统的不断发展,它们往往会变得不那么灵活,业务响应不那么快速,系统变得难改不敢改、脆弱,隐性风险随时可能暴露,线上事故无征兆地发生,更不用谈什么难理解、难维护、难梳理。技术->产品->商业,这种消极影响会依次传导,而由于商业往往站在价值链(食物链)的最顶端,它势必继续向下传递,最终传递到作为价值链的末端:技术,技术团队需要有实际有效的行动来应对,HOW?高级技术管理者通常会归因于架构设计问题,中层技术管理者会觉得是流程、机制、考核、激励、人员能力问题,而一线开发对此可能没那么敏感和关心,造成这种现象的原因,一方面是关注点不同,另一方面也是利益诉求和信息传递的丢失问题。(技术,产品,商业),技术(高层,中层,一线),这些对技术问题的认知,都直指系统中猖獗泛滥的“技术债”问题。
技术债是常态化的、随时随地可能发生,甚至必然发生的,它实际上是软件生命周期中重要的组成部分,它就像病毒一样侵入我们本该健康的身体。虽然技术债符合宇宙第一(悲观)定律:熵增定律,但少见讨论导致技术债的根因和系统化解决办法。我们,最为技术人员,应该怎么办?
本文希望把技术债讲明讲透,通过技术债分类让(中层,一线研发)可以把技术债显性化和明确范围,并分门别类地逐一击破,在认知层面拉通(技术,产品,商业)。战略层面,提出了技术债治理原则和最佳实践;战术层面提供了详细的技术债治理 4 个关键动作:预防、识别、标记、管理,提供了相应的方法、工具,指导了具体项目的 ACTION。
一、业界经验
1.1 定义
技术债是 Ward Cunningham(敏捷宣言的合著者) 在1992年首次提出的,用来描述理想中的解决方案和当前解决方案中间的差距所隐含的潜在成本:
技术债 = 当我们有意或无意地做了错误的或不理想的技术决策所累积的债务
the key feature of technical debt is that it is something we incur by choice.
技术债与其他债务一样,是一种透支行为,通过牺牲未来来满足当下的一些需求。跟其他债务一样,技术债也有利息,而且随着时间利滚利,会成为埋在项目里的定时炸弹。小额负债对企业是有积极意义的,可以加速产生业务价值(降本增效、收入、试错等),但久未偿还债务会引发技术危险,甚至技术破产(technically bankrupt)。
不仅限于业务系统,机器学习领域也面临同样的问题。有技术的地方就存在技术债,因此这个话题在业界获得了广泛的关注和讨论:成立于 2018 的技术债国际会议,致力于技术债的研究和实践;Gartner[3],Martin Fowler[4],等都对技术债产生了浓厚兴趣。
对待技术债,我们不是要消灭它,不应该消灭它,也不可能消灭它,而是短期接受它,长期管理它。就像 Google SRE 书里讲的error budget一样,我们追求的是 technical debt budget,即像预算管理一样把技术债控制在预期范围内。
管理技术债,业界从学术研究到工程实践已经有了一定的积累,包括原则、最佳实践、工具、方法等,但并不存在 one-for-all-solution,因为它与业务特点、团队人员能力、市场竞争压力等多种因素密切相关。
1.2 后果
总体来说,背上沉重的技术债会产生四个方面的消极后果:
团队士气
业务交付效率
产品质量
项目风险
具体表现为,新需求的开发速度慢下来,交付风险高起来,团队士气低落,业务排期堆积,代码需要更资深的研发才可能驾驭,时不时冒出来个 COE。
1.3 根因
Ken Rubin 于 2012 提出技术债的三种主要形式:幼稚的技术债、不可避免的技术债和策略性技术债,并分析它们各自产生的根本原因。
![](https://static001.geekbang.org/infoq/d1/d121084018782929c5990258b53cd38e.png)
Martin Fowler 于 2009 年在与鲍勃大叔讨论技术债范围时提出了技术债的四象限,这里我们通过颜色表示其破坏性,红色是必须要解决的。可以看到,Martin Fowler 的根因分析结论与 Ken Rubin 非常相似。
![](https://static001.geekbang.org/infoq/41/41dbd7b10fc5d69e4899ebbab695bf13.png)
此外,N-iX 给出了更具体的根因分析:
![](https://static001.geekbang.org/infoq/66/66145de6d3e1094abbff76b61f55e7b1.png)
1.4 生命周期
![](https://static001.geekbang.org/infoq/4c/4cc6f2b3a4443164f585243172bb6409.png)
这张图很好的解释了技术债的消极和积极的效应以及关键生命周期节点:
T1:技术债被引入(Occurrence)
T2:技术债被识别(Awareness)
T3:技术债引爆点(Tipping point)
T4:技术债被偿还(Remediation)
在技术债爆发前的(T1, T3)时间内,技术债对业务的影响都是积极的。过了引爆点后,技术债开始产生消极影响。遗憾的是,T3 是无法预测、甚至是无法准确评估的。
1.5 全景图模型
卡内基梅龙大学软件工程研究所(SEI)的 Robert Nord 于 2016 年在《The Future of Managing Technical Debt》 提出了“技术债务全景图”(Tech Debt Landscape)模型。
![](https://static001.geekbang.org/infoq/f5/f54c9fdfe9cc602000ecec80acf354c8.png)
这张全景图主要从两个方向来分析技术债对于软件的影响:可维护性(Maintainability)、可演进性(Evolvability),同时结合技术债的可见性(Visibility)分析技术债对于软件开发过程的影响。这里的可维护性(Maintainability)主要指的是狭义上的代码问题,即代码本身可读性如何、是否容易被他人所理解、是否有明显的代码坏味道、是否容易扩展和增强。其中可演进性(Evolvability)指的是系统适应变化的能力,描述的是软件架构趋于目标演进的能力,演进目标并不仅局限于支撑功能快速迭代的灵活性,也可以是其他的架构属性(Quality Attribute),比如高可用性、可扩展性。技术债全景图的分类方法可以帮助我们更全面地了解技术债导致的问题,在可演进性和可维护性这两个维度,我们都可以提取出一些指标来量化“利息”形成债务指标,但是这些指标和业务功能相比终究显得太过苍白无力,并不足以说服主要的业务端干系人并获得对于技术改进的支持。
此外它也提供了技术债的概念模型,解释了业务目标、系统、技术债、产生原因、技术债形式、征兆、后果的关系。
![](https://static001.geekbang.org/infoq/62/6217561486786547549e5d0137e9a3a0.png)
其中,TD Item 的关键要素:
![](https://static001.geekbang.org/infoq/dd/dd0f8cfeb2ce5be8448a757199adb7e5.png)
1.6 工具
下图是StepSize针对技术债工具的 200 多家企业调查报告:
![](https://static001.geekbang.org/infoq/bf/bfc3705ce3951fc839f0b2d65aaf4863.png)
1.6.1 识别工具
SonarQube, Teamscale,Sonargraph,Klocwork,ArchGuad,JArchitect,ArchUnit,IDEA 内置的 Code Inspection 等工具,都用来识别技术债,它们的原理基本相同,都是通过静态分析源代码 AST 并配合预置的规则,来发现技术债。它们都只能支持语法级别的技术债,无法识别语义级别的技术债。例如,SonarQube 有“认知复杂度”指标,是“圈复杂度”的升级版本,它只能通过源代码里 number of decisions 来计算“认知复杂度”,而对于语义上的 naming 无能为力,但往往命名对于代码的可理解性、设计合理性更为重要。
hercules是一个开源的通过 git 提交历史获取技术债洞察的工具。
copilot,CodeWhisperer,aiXcoder XL,都属于 AI 编程,也可以尝试。
Designite,可以用于识别 design smells。
jtrace,是利用 Java Agent 机制在运行时获取系统交互的关系。
CodeClimate Velocity,也用来发现技术债,但原理与上述工具不同,它通过分析 Jira 的数据来生成洞察报告。
1.6.2 治理工具
StepSize, Jira 的 Issue 管理、Excel 等都是在识别出技术债后进行管理的工具。管理包括技术债的可视化管理,技术债的合并,分配任务,排序,分类,修订优先级等。由于源代码是产生技术债的源头,因此这些工具都会通过 git 等版本管理与源代码关联。同时,这类工具也会分析 AST,以便生成技术债分布的 dashboard。
1.7 认知误区
对技术债零容忍,现在我们要做专项的技术债清零计划。
现有的老研发说他们的代码写的很好内部都能理解,因此不存在技术债。
技术债是技术团队的事,产品同学不需要关注。
BUG 属于技术债。
...
二、战略层
2.1 技术债分类
我们有哪些技术债?分类是为了细化问题,不同类型的技术债产生的根因不同,征兆不同,识别手段不同,解决方式也不同。我们把技术债分为 5 大类:
![](https://static001.geekbang.org/infoq/db/db989aff7def79002270d225f1f3faeb.png)
2.1.1 代码债
代码异味,重复代码,Copy Paste,长方法,大类,hacking 方案,破坏代码风格,高认知复杂度,令人困惑的命名,已经不调用的代码不及时删除,过多无效注释等,都属于代码债,是代码写的不好,主要原因是不知道怎么写出好的代码,也可能因为对技术栈和业务的不熟悉导致。怎么把代码写好?GRASP 原则、SOLID 原则、SQALE 质量模型、ISO 25010、《重构》书里列举的 22 种坏味道、软件开发的 201 个原则等都与写好代码相关,但从代码实践上看往往被忽略,我们认为这样来定义“好的代码”更有助于落地:
it works.
it is easy to understand.
it is safe to change.
对于工具可以自动扫描出来的代码债,识别成本低,通常修复成本也低,应该集成到 CI 流水线,做到零发生,发生后及时清零。
code review 是抑制代码债产生的主要手段,重构是偿还代码债的主要手段。
2.1.2 架构债
架构就是关键设计决策,架构最终要落地到代码上,如何区分代码债和架构债?
![](https://static001.geekbang.org/infoq/f0/f0d598a01202bfb8334a1655e84e5544.png)
目前大部分工具采用的成本计算方法如下,可见架构债成本远高于代码债:
![](https://static001.geekbang.org/infoq/e6/e6add25a7b1c9119092778bb3929d217.png)
产生架构债的主要原因有:架构与落地的 gap,架构滞后,违反架构设计的实践,对架构理解偏差,架构的假设发生了变化而架构未做及时修正,关键环节架构缺位。
如下的系统腐化征兆有助于发现架构债:
![](https://static001.geekbang.org/infoq/c9/c981b613e5961b2515c049c43f71c415.png)
![](https://static001.geekbang.org/infoq/9c/9c54b48111cf22234fce3d053ce3e28e.png)
架构债具体表现为不合理的依赖关系、循环依赖,包结构、数据模型,形同虚设的分层架构,边界模糊无强约束,低内聚高耦合,职责不清,复用性差,重复造轮子,过度设计,不合理的同步异步选择等。
JArchitect、ArchUnit、structure101等工具,可以帮助我们识别出简单明显的架构债,但这远远不够。
架构的本质是维持系统有序,有序来源于约束,它用来指导研发如何正确地开发(do things in the right way),但通常架构设计的约束力有限,游离于代码之外存在,无法成为强约束,它通常表现为原则、模型、方法论、规范等高阶抽象形式,无法以公式化的 step by step guide 形式表达,认知误区、认知空白经常发生,我们可以把架构思想转换为开发代码时的 checklist 吗?不可行,那样的话 checklist 太长,每写几行代码就去 check 吗?这是因为高阶认知需要大脑的充分理解才可能产生约束效果。面对同一个设计原则,初级研发与专家大脑内部激发的效应截然不同。对于专家,在该领域已经积累了大量经验、相关理论,接收到“新观点”时,大脑迅速唤醒了相关经验、理论,进而直接吸纳(内化);而对普通研发,既缺乏相关经验,也缺乏关联理论知识,“该结论”在脑海中处于“孤立”状态,纵然勉强“死记硬背”,也无法实践。
![](https://static001.geekbang.org/infoq/47/474b88e150c3e271dcf80761bc6b1f35.png)
因此,初级研发往往产生了架构债却浑然不觉(对架构约束不敏感),如果团队授权初级研发进行完整或重要的设计,架构债产生的风险会徒增。为了事前降低架构债的产生风险,需要专家进行整体设计关键设计,把专家知识注入团队开发,仅授权初级研发进行局部设计。按照这个思路,如果初级研发完全放弃设计是不是架构债风险就大大降低了?理论上是这样的,但通常团队里专家是稀缺资源,他们的设计产出物无法覆盖到每一个具体的局部细节设计。此外,即便是专家,他的设计在项目变化过程中也面临着适配和调整、甚至修订,没人可以预测未来业务的变化,一个架构设计在设计时是合理的,但业务变化后可能不再合理,这就是架构的基本原则之“演化原则”。这就更要求我们及早识别出架构债,及时止血。
上面提到了架构设计与代码的距离:它们是割裂的,是不一致的。DDD 强调复杂业务模型与代码模型的一致性,我们在实践中发现确实可以在一定的细节层次上做到,DDD 投入大,对易变核心业务域收益大,但需要谨慎评估。
有效的 design review 是抑制架构债产生的主要手段,及早发现 design smells 并做架构修正非常重要。Mike Cohn(《Scrum 敏捷软件开发》作者)在《Managing Software Debt: Building for Inevitable Change》里使用下面这张图来阐述这样一种情况:在第一个迭代需要花费大量的时间和精力来进行架构的设计,在后续的迭代中对于架构方面的投资不断降低,期望可以一直延续之前的架构设计并从中持续受益。这违反了架构的演化原则:设计出来的架构要满足当时的业务需要;架构要不断地在实际应用过程中迭代,保留优秀的设计,修复有缺陷的设计,改正错误的设计,去掉无用的设计,使得架构逐渐完善;当业务发生变化时,架构要扩展、重构,甚至重写。
![](https://static001.geekbang.org/infoq/9b/9b2fe688ceecf8591da0faac3f25311a.png)
《Evolutionary Architecture》给了我们一些启示,fitness functions 比较新颖,但我们对有效落地到现有团队持悲观态度,感兴趣的读者可以尝试。
2.1.3 基础设施债
代码债、架构债都同时与业务和技术相关,而基础设施债都只表现为技术属性,无效的 CI/CD、监控报警不完善不完备不准确、日志不完善、不具备可观测性、部署和上线问题、数据库连接池问题、中间件问题、陈旧的技术栈、错误的技术选型等,都属于基础设施债。
DevOps 平台通常会提供 API 可用率、性能、存活、报警、聚合日志、大报文、CPU Usage、线程池打满、net IO、内存、disk IO ratio、缓存大 key、data skew、慢 SQL 等 runtime 监控,可用于识别基础设施债。引入FMEA来管理基础设施债,是个被推荐的预防手段。
2.1.4 测试债
测试债表现为自动化测试不足和现有单元测试不符合FIRST原则。
通过覆盖率我们很容易识别出测试债,但可能存在面向覆盖率编程的风险:只追求高覆盖率,忽略验证强度。
When a measure becomes a target, it ceases to be a good measure.
─ Goodhart Law
这可以通过引入 mutation testing(Java 有pitest-maven插件工具)解决。但工具无法替代人类的深入交叉思考,应该在 code review 阶段强调关注单元测试的质量。此外,关键业务的并发测试缺失也属于测试债,JUnit5 本身就提供了并发测试机制。
需要注意的是,有时候表现为测试债的问题可能本质上是架构债:是设计的问题,代码不具备可测试性,untestable code,通过 mock 来 hack 会适得其反,应该从设计入手,让写单元测试容易,而不是硬着头皮硬写单测。
2.1.5 知识债
一个代码库,哪怕它有十几万行代码,如果一直是由最初始的小团队维护,后面不进新人,也无人离开,他们知道代码的来龙去脉。这时候外部一个人看了代码后说可维护性差、可理解性差,团队的人可能不会认同:虽然你读不懂,但我们早就习惯了,我们都懂,来了新需求我们也能很快交付,线上已经 xxx 天零事故了。那么谁对呢?虽然该团队不承认技术债,但从组织考虑,这是更大的债务:这个团队可能觉得技术债是 0,但团队变化时,技术债会陡增。技术债的确认不该从个人视角和小团体视角出发,应站在组织角度,技术债要有持续稳定性,不该陡增突降。
可理解性差,指的是从代码本身无法读懂,代码和现实割裂太严重,导致单纯依靠业务上的背景知识,并不能理解代码的实现方式,需要大量的上下文知识加在一起后才可能理解,上面的团队大脑里保存了完整的 context knowledge,而外部人缺失,当 context knowledge 信息量超过代码本身而且无法轻易获得时,就产生了知识债。
可以想象,当一个系统中只有一个服务,几万行代码,任何一个开发人员都可以花一段时间理解且记住逻辑的细节,当业务需求变更,开发人员可以不进行任何调研就设计解决方案,甚至可以马上和其他开发一起讨论方案的合理性。再试想,当业务不断增长,系统逐渐变的复杂,服务扩充到几十个甚至上百个,相信没有一个开发人员可以说的清所有的逻辑细节,甚至很难找到一个开发人员可以说清其中一个他最熟悉的服务的所有实现细节。因为当业务变得复杂,都需要多个服务配合才能完成,逻辑链条很长,而人的脑容量毕竟有限,系统不断增长的知识会逐渐超出人类所能记忆的范围。对于一直在这个软件系统上工作的开发人员尚且如此,那么对于新加入的开发人员,可想而知,业务和系统的知识会更少,更难理解原有逻辑设计的上下文和决策原因。项目人员的更替是无法避免的,这种更替会让团队人员对于上下文知识变的越来越少,这种趋势也是不可逆的。
从设计到代码,信息流失太过严重,Imperative Programming(例如 Java)关注如何实现,明确怎么一步步达到结果,它本身是琐碎的(相对于人的理解性而言),需要包含机器可执行的完整细节,却缺乏解决方案的具体化,而且实现同样效果的可能路径是无数的,因此不具备反向推导性,即无法直接从展现结果反向推导回源码。相比较而言,Declarative Programming(例如 DSL)直接描述最终效果,不关心如何实现,信息流失率更低。
设计与代码之间流失的信息就是上下文知识,注释是很好的弥补信息流失的方法,git commit message 是代码之外的补充注释(因此要强调它的重要性),但它们只能表达局部信息,无法看到设计全貌以及背后的决策过程和整体清晰思路。“代码本身就是文档(writing self-documenting code)”是理想状态,在复杂系统下难以实现,也无法 scale。因此,虽然代码本身是知识,但外部的文档类知识不可或缺(敏捷不是反对文档,是反对无价值文档)。知识债要解决的是知识的沉淀和知识的传播(传递)有效性问题。那么技术团队应该沉淀哪些类型的技术传文档呢?STC 是技术传播领域组织中最大的一个,他们把可交付的技术文档分成了四大类:
讲解产品、服务和政策的文档。例如:帮助文档、技术支持网站、使用指南、服务指南、参考资料,以及政策和程序等材料。
分享基础的科技信息的文档。例如:技术报告、科学文章、会议演示文稿,以及长如写一本书的项目等。
帮助用户提高技能的培训文档。例如:在线教程、快速查阅,以及面对面的教室或虚拟教室中使用的材料。
协助产品营销和服务的文档。例如:提案、营销网站、白皮书、产品目录、宣传册和简报等。
文档(知识)沉淀容易走向两个极端,要么文档少得可怜,要么无效(过期的和无价值的)文档泛滥。那么我们该怎么做?在每个代码库里至少要维护ADR(Architecture Decision Record),并纳入版本的动态管理(文档不再静止)。系统都是从简单走向复杂的,ADR[5]可以完整记录架构的演进过程,新人进入团队后可以快速掌握上下文知识,了解现状的来龙去脉,对于老人它也同样可以起到 Second Brain 作用。
2.2 治理原则
2.2.1 并非所有债都要还
![](https://static001.geekbang.org/infoq/a3/a3e49476feccbc056bce0e8d7f894c0d.png)
1.有些代码很少被修改,有的甚至很少人去读
2.债务产生的问题在可容忍范围内,处理债务的收益不高
3.因业务发展变化,产品生命周期结束,最终会下线
4.短命的产品,目的就是试错
5.一次性原型,例如 PoC
6.新技术的出现,硬件性能的提升,可以通过另外的方式缓解债务
7.已经技术破产的产品,偿还技术债倒不如推倒重来
Martin Fowler 在Strategic Design里指出:
a system can’t have the same high level of quality throughout the system
2.2.2 先偿还高息债
偿还高息债的收益最大。
不同业务域的技术债区别对待,优先处理核心域而且变更频繁业务域的技术债。
通过后面提到的 RICE 公式对技术债排序,确定优先级和排期计划。
2.2.3 应用童子军规则
童子军有一条规则:“永远保持离开时的露营地比你发现它时更整洁”。它的反面是破窗效应。
2.2.4 预防优先原则
降低技术债的感染性和破坏性,此外这也是让架构可演化的前提:及时对架构坏味道识别,查缺补漏。
2.2.5 开发与还债两不误
一边做有客户价值的工作,一边偿还相应部分的技术债,实践上通常拿出 20%的研发时间用于还债,如果本末倒置放着大量的 feature backlog 不顾只埋头处理技术债,势必得不到相关干系人的支持。
分期分迭代还债,而不是靠一次性。
2.3 最佳实践
2.3.1 推销给非技术干系人
对于 BOSS 和 PO(Product Owner)这两个重要的非技术干系人,他们对技术债的反应通常是这样:
![](https://static001.geekbang.org/infoq/fe/fe81c5463c44c74435e7759f1bace913.png)
要想实现 balance(business pressure, engineering pressure),向重要的干系人推销技术债是重要的,这样才能获取组织信任,技术债治理才可持续。
我们也曾经在团队中多次大刀阔斧地进行技术债治理的变革,团队以头脑风暴(Brainstorm)的方式收集技术债,添加到敏捷项目管理工具中统一管理,然后对于这些技术问题进行全局的优先级排序。在业务端我们也积极地进行了技术债相关理论知识的导入,陈述技术债的产生原因和危害。业务端负责人非常认同技术债治理的意义,甚至主动提出要把技术债放到每个迭代中治理、追踪。虽然在接下来的几个迭代中情况得到了一些好转,但是在 1~2 个月之后,排到迭代计划中的技术改进比例又恢复了原状,能够真正得到治理的少之又少。─ 来自一位技术债实践者
技术债的影响和收益是难以衡量的,对于这种代码级别的问题更是这样,对于没有技术背景的人来说,很难用数字量化代码重构的直接收益,技术债的消极影响有滞后性,重构的收益同样有滞后性,而且就像在【1.4 生命周期】里讲的一样,T3 无法准确评估。况且我们一直给客户承诺的是交付高质量、可工作的软件,除了性能、安全等非功能需求之外,代码质量本身也应该是内建的交付物之一。
以销售恐惧的方式推销技术债治理,往往不会奏效,技术债可视化是比较实际的方法。技术债不能仅在研发侧可见,也需要业务侧可见:技术债分类,建立预算,优先级排序,清晰的 roadmap,milestone,纳入敏捷迭代 backlog(backlog 分为两类:feature backlog, technical debt backlog),投入资源量化到人周,使用非技术语言解释等,都有助于获取信任,让他们看到希望和清晰路径,获得快速成就感(instant satisfaction),他们对技术债越理解就会越愿意支持。
2.3.2 优先级的 RICE 得分
如何评估技术债的优先级?使用 RICE 得分,它是Intercom使用的用来评估项目优先级的方法,该方法同样适用于技术债的优先级评估。它把优先级分为 4 个维度,并公式化,RICE 代表影响范围(Reach)、影响(Impact)、信心(Confidence)和努力程度(Effort)。
2.3.3 把比较重要的技术债务当作项目
重要技术债当做单独的项目进行管理。
2.3.4 积极引入自动化识别工具
光靠捋代码来挖掘技术债,对于复杂系统,是不现实的,无法 scale。要积极发现工具,或者自行开发。
三、预防技术债
预防技术债是必要的,不从源头抓起的后果很可能是还债速度跟不上新增欠债速度,后果是技术债永远降不下去。
3.1 技术愿景和价值观
技术管理者需要为技术团队建立清晰的 technical vision,同时争取资源。
我们要交付是伟大的产品,而不仅仅是业务功能,而这需要时间
Take the pressure off developers coming from business/product teams
要工匠精神, 不要差不多先生!
3.2 强化架构约束认知
我们的架构约束体系,不该是束之高阁的东西,需要结合案例经常性宣讲,提升架构的 adoption,强化一线研发认知,提升开发能力,在开发时能唤醒约束意识,减少 blindly coding。
同时不断补充完善修订该体系,每次变更需要显性化,使得演化过程可追溯,否则容易与实践脱落,变得有价值却无用。
那么我们有哪些架构约束?下面这张图从战略设计、战略实施、战术设计、战术实施共 4 个层面完整列出了我们的架构约束。
3.3 Type1/2 决策
亚马逊在 2016 年发布给致投资者的长信里透露了 Jeff Bezos 对公司如何保持创新动力的看法,关键是 Type 1/2 Decision。决策一般分为两种:
Type 1 Decision:不可逆,一旦执行就不能回头,没有办法回到决策前的状态。这种决策要认真、谨慎地作出。
Type 2 Decision:可逆,可以回到决策前的状态。这种决策要依赖有判断力的个人和团队快速作出。
随着公司变大,大多数决策都会依赖 Type 1 Decision 的方法,觉得那样更可信赖,但是实际上大多数问题只需要 Type 2 Decision。
3.4 设计评审机制
设计评审机制,是解决 Code Review 滞后和过载问题的良药,它基于“专家比初级研发制造更少技术债”的假设,在项目早期低成本地把专家知识注入团队,抑制技术债的引入,详情见《有效的设计过程和机制》。
3.5 代码评审机制
如果设计评审可以有效运转起来,就可以大大提升代码评审的效果:它让 Code Review 更专注它擅长的局部问题,也避免了 Code Review 在开发周期中滞后而造成的 reviewer 让步(降低质量要求)问题。
Code Review 对技术债治理来说过于滞后,Reviewer 的关注点通常只关注具体的局部代码,往往无法识别更大范围的技术债,CR 是重要的保障代码质量的方法,但不能让它过载,否则适得其反,更详细的 CR 问题请参考《微软论文精华翻译:CR的预期、实际效果和挑战》和《3 Problems to Stop Looking For in Code Reviews》。简单地说,CR 时可以识别出哪些问题?按照难易程度可以分为 3 级:
easy issuesLint, 代码规范,注释,拼写错误,格式,风格一致性,语言或框架约定,该删除的代码没删(被注释掉),unused variables or imports
medium issues 可读性和可维护性,命名,覆盖率,日志,性能,常见陷阱,边界条件
hard issues 重新发明轮子,过度设计,架构问题,没有满足需求,复杂度
让代码评审产生效果,仅靠这一个评审是不够的,如何控制 Pull Request 大小,如何控制 commit 粒度,如何排除 diff 不必要的干扰都是前置条件,review 的关注点、范围和标准要确立,此外,需要对评审深挖,进行复盘总结。
《The Code Review Pyramid》给了我们代码评审的参考,每个团队可根据实际情况进行剪裁和补充。
3.6 识别后的复盘
这是一种正反馈机制,对识别出的新技术债进行复盘分析,总结出可以预防重复发生的结论。
四、管理技术债
4.1 原则
集中化,透明化,可视化。
根据技术债的标记自动生成技术债数据,技术债的数据集中化,集中化方便实现透明化。技术债需要对研发透明,对产品透明,对业务透明,真实反映系统现状,以便相关方做出合理的决策。
让业务人员看见产品的技术债状况很关键,不然业务人员就搞不清楚产品的真实情况,进而无法做出明智的经济决策。
4.2 具体管理方法
通过敏捷进行项目管理,维护 2 个 backlog 是核心:
feature backlog
technical debt backlog
更详细的敏捷管理内容,在此略过。
技术债管理的关键动作包括:记录技术债,度量成本,排优先级,排入 backlog,跟踪(包括现状 status,和过程),作为输入反馈给预防,重构开发。
![](https://static001.geekbang.org/infoq/c8/c876e56e901bbd65849b01d112d2e485.png)
4.3 成本度量参考模型
![](https://static001.geekbang.org/infoq/18/189fcb0a51a2699862a2da42831baf77.png)
五、如何使用本指南
建议认真、完整阅读整篇内容,它可以当做技术债治理的参考手册,和思维框架。
但具体到某一个具体的项目,它仍缺少 ACTION 环节。
ACTION = (WHO, WHEN, WHAT, DONE Definition)
具体地实施方法:
![](https://static001.geekbang.org/infoq/5f/5f4bce521cb65f46b839ffe55ba41d82.png)
六、结论
技术债治理是个复杂的系统工程,管理上需要获得 balance(business pressure, engineering pressure),需要向非技术干系人推销才可能持续,而这需要技术债的可视化。
让技术债控制在 budget 范围内,提升团队能力是根本,工具是辅助,流程和机制是保障。
技术债治理,成功的关键要素是:
![](https://static001.geekbang.org/infoq/21/2185dc2fc500c305f15e3d822a637d5c.png)
参考文献
[1] Martin Fowler 2019 “Technical Debt”
[2] InfoQ, 2013, Managing Technical Debt
[3] Gartner, 2020, Manage Technology Debt to Create Technology Wealth
[4] Martin Fowler, Technical Debt Tag
[5] Amazon,2022,Using architectural decision records to streamline technical decision-making for a software development project
评论