如何解决技术债

一、什么是技术债

基本定义

关于技术债的定义,维基百科的解释是:由于现在选择简单(有限)解决方案而不是使用需要更长时间的更好方法而导致的额外返工的隐含成本。

二、技术债的影响

在项目中,你是否曾经遇到过这样的问题:

  • 随机失败的流水线
  • 无处引用的文件和方法
  • 令人费解的代码实现
  • 大跌眼镜的无效测试
  • 无处追溯的基础设施配置

在项目中,每一个人或多或少都遇到过以上问题。当开始新功能的交付时,每一个问题都可能成为快速且成功交付的阻碍。

如何尽可能地减少或避免这些问题?如何确保项目的稳定交付?除了遵循良好的实践和设计,统一规范之外,对技术债务的有效管理也是其中不可或缺的一环。

三、技术债产生原因

Martin Fowler 提出了著名的TechnicalDebtQuadrant

image

鲁莽/有意 - "我们没有时间去设计"

团队虽然意识到这样做会导致技术债的积累,但却不清楚具体欠下债务的后果。为了提醒团队,可以增加相关流程和责任人。

谨慎/有意 - "我们必须现在交付,之后再处理因为追求速度所产生的结果"

当团队面临业务压力时,例如在发布新产品时需要快速上线以占领市场时,快速积累用户的重要性常常超越了更好的实践。在这种情况下,团队往往会选择快速完成产品交付,然后再处理技术债务。团队清楚这样做会带来技术债务,也知道逾期还债的具体后果。

鲁莽/无意 - "什么是分层?"

这个维度技术债务产生的原因通常是由于人员技能的不足。由于缺乏相关技能,开发人员可能不清楚如何编写更优秀和精炼的代码,如何设计良好的架构或者什么是更佳的解决方案。因此,导致技术债的出现和持续积累。这种情况需要加强团队成员的技能培养。

谨慎/无意 - "我们现在知道应该怎么做了"

随着团队成员的能力和对系统的了解的不断提升,使得之前认为是最佳方案的解决方案现在看来并不完全准确。但在当时,团队中没有人知道更好的做法。

以上将技术债务产生的原因分为四类。我们通常认为,健康的技术债是右边的两个维度,不健康的技术债是左边的两个维度。基于此我们可以分析技术债产生的原因并制定相应的改进措施。

四、技术债类型

关于技术债的类型及常见问题主要有:

image

五、为什么要管理技术债

Martin Fowler 在 Is High Quality Software Worth the Cost? 一文中从添加功能 / 时间两个维度来对比高质量的软件低质量的软件在交付速率上的差别。

image

我们如何保持高质量的软件状态及交付速率?

技术债的持续累积是导致质量下降的关键原因,但技术债无法避免,因此技术债的有效管理和消除是我们保障高质量软件的必不可少的方式之一。

六、如何管理技术债

关于技术债管理的流程,主要分为六个步骤:

image

6.1识别

技术的持续改进离不开团队中每个人的努力,因此需要每个成员都积极参与。在日常交付中,团队成员应该持续识别和记录需要改进的问题并将其放入backlog中,以便在技术改进会议中与团队同步。此外,团队还可以定期组织头脑风暴,以收集技术痛点和改进建议。

当然也不仅仅限于团队内部,也要倾听用户的问题,有意识的建设研发效能度量体系,度量技术债和留意产品延迟和成本的上升。

image

针对上面这几个技术债象限产生问题的原因来分析如何避免和解决技术债。鲁莽的/谨慎的 可以理解在技术债产生时,团队是否做出来细致的分析,其结果可以对应到 无解决方案/有解决方案。而 故意的/无心的 表达的是对于技术债的产生是已知的,还是未知的,可以对应到 已知问题/未知问题。这样就可以将问题的解决也同样划到四个不同的象限,对于第一象限而言,已知问题,也有解决方案,只需要将技术债的解决列入计划实施起来就可以了。而对于其他象限,都无法直接列入计划解决,因此,我们需要通过一些手段让技术债从认知和方案层面向第一象限移动。第三象限的技术债,团队认知水平很低,只能通过提升团队的整体能力和认知水平,让问题逐渐清晰,向第二和第四象限转移。

image

6.2可视化

当进行技术债务管理时,我们必须始终以价值为出发点。这个价值不仅限于技术层面,最终还会反映在客户或用户所获得的业务价值上。因此,我们建议在评估技术债务和技术改进任务时,同时考虑技术价值和业务价值两个维度。我们可以参考以下"价值映射图"示例,根据架构债务中存在的技术问题以及解决这些技术债务所带来的技术价值和业务价值进行分析。之后为每个改进任务创建技术卡来可视化所有技术改进任务。

image

6.3分析优先级

我们时常会遇到的问题是,需要改进的地方太多,尤其是对于遗留系统。怎么办?先排优先级。我们可以基于价值/成本矩阵来评估改进任务的价值和成本。

image

基于以上价值-成本矩阵,我们会:

优先解决高价值+低成本的技术债,;

尝试将高价值+高成本 的技术债拆分为高价值+低成本的技术债,"尽早、频繁、小批"地进行PDCA(Plan/Do/Check/Adjust)的迭代解决;

在没有高价值+高/低成本 的技术债时,再来考虑低价值+低成本的技术债;

最后如果只剩下低价值+高成本 的技术债,还是先拆分,再解决,或可考虑直接移除

6.4计划执行

由于我们难以在一段时间内集中处理技术债务,因此我们认为将消除技术债务作为每个迭代交付目标的一部分是推进技术改进的有效方式。在对技术债务及相关改进任务进行优先级评估后,需要根据优先级将技术改进任务引入到每个迭代中。例如:每个迭代中引入20%的工作量用于技术改进任务,根据不同的问题类型要执行在识别中的不同策略,将技术债的状态进行转移:

6.4.1 明确技术规范,加强管理

对于已知且无解决方案的问题,只是没有深入思考对系统的影响,对于这部分技术债,产生的原因主要有两个:

  • 团队没有技术规范标准,即便发现问题,也没有方案

  • 缺少对架构修改的审查,开发人员自己思考解决方案,依赖个人经验

为了让这些技术债在产生前就避免,或者引导到可以快速解决的方向,首先,需要建立团队的技术规范和标准,让每个决策都有依据。其次,加强流程上的管理,建立架构评审委员会,对架构的修改进行评审,一方面规避问题,另一方面根据问题完善规范和标准。

在Neal Ford、 Rebecca Parsons 的著作《演进式架构》中提出了"架构适应度函数"(Architecture Fitness Function)的概念,当团队有了技术规范和标准,可以通过构建架构适应度函数,来检查和约束架构的变更或代码的修改,帮助我们发现和避免一些新技术债的产生。

6.4.2 技术债解决日常化

对于已知且有解决方案的问题,研发团队对于架构的守护要像产品人员对待产品一样,架构健康和产品质量一样重要。在项目日常开发中,除了产品的业务需求外,应该规划一部分工作用于架构的优化,修复那些已知且有解决方案的技术债,这样才能持续保证软件系统的响应能力和产品质量。对于这部分的技术债有几个原则:

1. 核心领域优于其他子域

识别领域、子域是DDD战略设计的重要步骤,在识别子域之后我们还需要进一步分析哪些是核心域(Core Domain),哪些是支撑子域(Supporting SubDomain)和通用子域(Generic Subdomain)。核心域在业务上至关重要,它提供了区别于行业竞争对手的差异化优势,承载了业务背后最核心的基础理念

2. 可演进性优于可维护性

可演进性问题可能会直接导致开发速度滞后,功能无法按期交付,使项目出现重大的交付风险。而且问题发生的时候往往已经"积重难返",引入的技术债务没有在合适的时间得到解决,其产生的影响会像"滚雪球"一样越滚越大。和可演进性问题相比,高复杂度、霰弹式修改等代码级别问题也很重要,但是相对来说我们更加关注软件适应变化的能力,通过提升软件系统的适应性减少软件最终交付价值的前置时间,快速收集真实用户的反馈,持续不断迭代产品、完善设计。

3. 明确清晰的责任定义优于松散无序的任务分配

如果我们深入分析一下技术债产生的过程,很容易发现"交付压力"是一个频繁被提及的原因,这也许也是技术债这个隐喻本身存在的一些问题,原本应该体现为内建质量的工作被当做可以取舍、可以之后偿还的债务,导致必要的工作被滞后、被遗忘。有时候浮现式设计(Emergent Design)反而成了一种心理安慰的借口:"我知道这里有问题,但是我觉得这个变更需要通过需求来驱动"。

诚然,在开发阶段彻底消除技术债是需要付出额外成本的,在真实的项目中也很难明确定义出这样的边界: 哪些部分应该是用刻意设计(Intentional Design),哪些应该是用浮现式设计(Emergent Design)?在 Bob大叔的新作《架构整洁之道》 中也提到这种类似的情形:在一开始的设计阶段,如果要划分出完美的架构边界,让两个组件在编译期和部署期相互独立,既要考虑动态的接口抽象、输入(Input)和输出(Output)数据结构定义,又需要做好组件之间的依赖管理,这些都增加了不少额外的工作量,可以采用一些妥协的做法比如共享组件、策略模式、外观模式(Facade)等。

那对应不同的组件、模块或者服务,谁可以来决定采用哪一种设计方法?两个模块之间应该采用完全隔离还是部分隔离?如果采用部分隔离应该采用哪一种方式?团队在针对技术债的治理过程中也经过一段很长时间的混乱期,团队中每个小组并不是一个独立的作战单元,而是一个个特性工厂(Feature Factory), 每个小组没有清晰的业务职责边界,分配功能特性的时候由各个小组根据兴趣、意愿等主观因素选择功能开发;针对于线上的问题分配也比较随机,更多是基于团队当时的忙碌程度和带宽,这些都导致了业务和技术上下文的割裂,原本在开发初期的架构设计原则逐渐退化,刻意选择要在未来偿还的技术债务在各个小组切换功能的过程中也逐渐被遗忘。

理想的情况下每个小组应该是一个价值趋向(Outcome Oridented)的团队,负责一个或者多个业务能力,原则上每个业务能力应该有且仅有一个团队负责。然而这种理想的情况在实际项目中又很难落地,即使业务能力和团队对齐,但客户对于不同业务能力的投资并不是均等固定的,如果客户一开始希望把百分之五十的预算花在某核心业务能力的构建上,而且要在3个月之内交付,之后的投资重心又转向了其他方面,在这种情况下要如何保持这种稳定的结构?不过这种业务能力和团队之间映射关系还是必要的,在多个小组合作开发时,可以由所负责的小组驱动必要的合作对话和相关的技术治理活动,触发关于业务上下文和技术上下文的跨团队分享,制定并推广代码规范、架构设计原则同时监督各个团队实施的效果。

但有时候业务能力和技术模块是无法一一对应的,现存的单体应用可能是横跨了多个上下文、提供了多种业务能力,在分配特性、技术债务或者线上问题的时候,高度抽象的业务能力无法提供有效的指导。所以我们采用了一种比较折中的方案 - 服务责任人制度(Service Owner), 一个小组负责一个或者多个微服务,每个微服务只由一个小组负责,在分配特性功能、技术债务和线上问题时,需要把服务责任人制度作为首要遵循的原则。由于业务知识和技术上下文的相对集中,在解决具体的软件缺陷时不再浮于表面,团队成员可以更加深入地从需求、技术方案、软件架构等方面着手解决根本问题;针对线上问题通过清晰、明确的责任制度,倒逼团队在开发阶段主动关注软件的内建质量,谨慎判断是否引入技术债。

4. 主动预防优于被动响应

这个原则本质上是缩短反馈周期,提前发现潜在问题,除了必要的代码审查流程(Code Review)、提升团队能力之外还可以借助于自动化工具来提前发现问题。

对于代码可维护性方面,很多比较成熟的静态代码扫描工具都可以自动识别这类问题,比如SonarQubecheckstyle 等,但是仅仅在持续集成上(Continuous Integration)运行还不够,需要和团队一起自定义扫描规则,并把检查代码扫描报告作为代码审查的一部分,逐步形成一种正向的反馈机制。

那我们应该如何提前发现不可见的可演进性问题哪 ? 在Neal Ford、 Rebecca Parsons 等合著的《Building Evolutionary Architecture》中提出了"架构适应度函数"(Architecture Fitness Function)的概念,可以给我们发现潜在的架构问题提供一些思路。

"适应度函数"这个概念来源于遗传算法(Genetic Algorithm),用计算机模拟仿自然界生物进化机制,适应度函数用于评价个体的优劣程度,适应度越大个体越好,反之适应度越小则个体越差。在软件系统的不断增量迭代过程中,我们可以基于架构的演进目标,定义出软件架构的适应度函数,来衡量增量的代码是否会导致架构偏离这个目标。

在工具方面使用方面,我们可以借助于 ArchUnitNDepend 帮助我们定义自己项目中的"适应度"规则,这是一个借助于ndepend自动识别组件循环依赖的例子:

image

6.4.3 持续关注技术发展趋势,提前规划架构的演进方向

对应未知且有或没有解决方案的问题,我们只能提升自己的能力,持续保持对技术发展趋势的关注,探索是否有更优的解决方案,在日常的开发和运维过程中,要做到以下几点:

  • 明确当前团队的技术选型优势和问题,深入理解团队面临的问题
  • 对于所选择的技术框架版本持续关注,定期升级到最新的稳定版本
  • 关注新技术的趋势和动向,探索是否有更优的解决方案,新的技术是否经过成熟的验证,提前规划架构的演进方向

关注新技术不代表一味追求新技术,首先要明确现有的技术选型到底有哪些解决不了的问题,而在选择新技术的时候也要思考它的弊端在哪里,是否有很大的影响。

6.4.4 保持技术优化相关投入

这个建议更多是给团队的高层管理者,在激烈的市场竞争中,产品的开发可谓争分多秒,管理者们一定更愿意花钱在看得见的产品上。一旦技术框架基础已经奠定,会逐渐缩减在技术侧的投入,这其实也是大部分产品的软件系统技术债逐渐增多的一个非常重要的原因,产品的快速演进,进度的压力无疑是技术债产生的最大元凶。

为了保证产品持续的竞争力,上面几点只是方法,需要一定成本上的投入,给技术领导者一定的时间和预算来解决问题并阻止新问题的出现。相信他们能够合适地使用资源。从整个产品团队,都要提升对技术的正确理解,技术的构建并不是一劳永逸的,是需要不断的成本投入来维护的。当然技术战略也要和商业战略协同工作,保证价值最大化。

6.5总结回顾

在技术债务管理中,可视化技术改进成果是不可或缺的一环。总结的目的不仅是梳理改进成果,更是与团队成员持续分享我们所创造的价值。通过可视化技术改进成果,能够增强团队成员的归属感和满足感,这对于提高团队士气非常重要。虽然有人开玩笑说"每一行代码都是改变世界的力量",我们可能还无法真正改变世界,但我们能够为客户和用户持续创造价值。

6.6展示成果

首先,了解客户需求至关重要,例如客户关心的系统稳定性、成本控制、交付效率等,除了向客户解释技术改进的价值和解决的问题之外,将技术改进的成果分享给客户也是至关重要的。我们需要持续跟进技术改进的成果,并向客户提供反馈。持续增强客户对技术改进的认可,才能真正确保技术改进的可持续性。

以上,就是我们在技术债管理方面的实践。当然,我们还在持续探索学习的路上。

参考整合:1、偿还技术债的六个步骤 - Thoughtworks洞见

2、 管理技术债:企业管理者不容忽视的问题 - Thoughtworks洞见

3、如何解决技术债 - Thoughtworks洞见

4、持续改进------优先攻克哪个点? - Thoughtworks洞见

5、技术债治理的四条原则 - Thoughtworks洞见

相关推荐
羊锦磊2 小时前
[ Mybatis 多表关联查询 ] resultMap
java·开发语言·数据库·mysql·mybatis
ZeroToOneDev4 小时前
Java(泛型和JUnit)
java·开发语言·笔记
迪尔~6 小时前
Apache POI中通过WorkBook写入图片后出现导出PDF文件时在不同页重复写入该图片问题,如何在通过sheet获取绘图对象清除该图片
java·pdf·excel
现在,此刻6 小时前
leetcode 11. 盛最多水的容器 -java
java·算法·leetcode
DKPT7 小时前
Java设计模式之开闭原则介绍与说明
java·设计模式·开闭原则
hyy27952276847 小时前
企业级WEB应用服务器TOMCAT
java·前端·tomcat
布朗克1687 小时前
Spring Boot项目通过Feign调用三方接口的详细教程
java·spring boot·feign
Arva .7 小时前
Spring基于XML的自动装配
xml·java·spring
帅得不敢出门9 小时前
Android Framework定制长按电源键关机的窗口
android·java·framework
fatfishccc9 小时前
循序渐进学 Spring (上):从 IoC/DI 核心原理到 XML 配置实战
xml·java·数据库·spring·intellij-idea·ioc·di