主页

索引

模块索引

搜索页面

技术债

定义:

技术债是指在当前以某种方式做某件事的好处,以换取在将来某个时候以另外的方式做这件事的成本。
https://img.zhaoweiguo.com/knowledge/images/architectures/techDebtQuadrant.png

技术债4象限(Technical Debt Quadrant)参见 这儿 轻率(reckless)还是谨慎(prudent);有意(deliberate)还是无意(inadvertent)

备注

交付第一次代码就像陷入债务。 债务是可以加快开发速度,只有通过重写代码,及时偿还债务。如果不偿还债务,就会发生危险。 把时间花在写一些不正确的代码上的每一分钟都算作该债务的利息。 整个软件项目可能在未合并代码的部署,面向对象设计或其他方面的债务问题而陷入停顿。 —— Ward Cunningham(沃德・坎宁安)

技术债是无法避免的,只是产生技术债或多或少的问题,不还技术债付出的代价更高。
Technical debt(技术债 / 技术负债)= design debt(设计负债)= code debt(代码负债)

抬头看天是为了找到正确的方向,有时因为一些原因(如开发时间紧)往别的方向上拉了一会
一定要记得这是临时行为,而且这些临时行为会导致「技术债」
https://img.zhaoweiguo.com/knowledge/images/architectures/technical_debt.png

The Future of Managing Technical Debt 这张全景图主要从两个方向来分析技术债对于软件的影响:可维护性(Maintainability)、可演进性(Evolvability),同时结合问题的可见性(Visibility)分析技术债对于软件开发过程的影响。

可维护性(Maintainability) 主要指的是狭义上的代码问题,即代码本身可读性如何、是否容易被他人所理解、是否有明显的代码坏味道、是否容易扩展和增强。

可演进性(Evolvability) 指的是系统适应变化的能力。 在生物学中它指的是种群产生适应性的遗传多样性,从而通过自然选择进化的能力。对软件系统来说,可演进性(Evolvability)本质上一种架构的元特征(Meta-Characteristic),描述的是软件架构趋于目标演进的能力,演进目标并不仅局限于支撑功能快速迭代(Iteration)的灵活性(Flexibility),也可以是其他的架构属性(Quality Attribute),比如高可用性、可扩展性。

针对 可见性 的分析可以依赖于外部视角:

对于最终用户来说:
    软件功能、设计和用户体验等方面的缺陷,导致用户无法顺利完成既定的业务流程,
    那么对于用户不可见的代码问题就升级为了可见的质量问题;
对于需求提供方来说:
    臃肿的技术架构、散落各处的业务逻辑导致产品无法快速响应需求变化,导致交付延期,
    那么对于无技术背景的业务人员来说,难以理解的、不可见的架构问题就升级为了可见的软件交付风险。

分类

  • 技术债务

  • 运营债务

技术债大概分为三大类:

文档负债(包括需求分析负债,开发文档负债,测试文档负债)
代码负债(包括架构负债,编码负债,业务负债)
管理负债(包括工期负债,人员负债,协同负债,成本负债)

编码负债:

1. 代码命名规范:代码命名没有规范,存在大量杂乱不堪的命名代码。
2. 代码复杂度:条件语句过多,流程控制过于复杂,代码嵌套过多。
3. 代码耦合度:代码中参数,类,接口高耦合,需要大量修改代码。
4. 代码行数:存在大量未使用的代码。

[极客]软件工程之美

技术债务,就是软件项目中对架构质量和代码质量的透支:

技术债务也有金融债务的一些特点,比如有利息
    债务的 “利息”,就是在后面对软件做修改的时候,需要额外的时间成本。
技术债务不一定都是坏的
    在软件项目中,也经常会刻意的欠一些技术债务,提升短期的开发速度,让软件能尽快推出,从而抢占市场;
    还有像快速原型开发模型,通过欠技术债务的方式快速开发快速验证,如果验证不可行,这笔技术债务直接不用偿还
技术借债也一样不能是无限制的:
    因为借债越多,利息越大,当收益抵不过利息时,就会陷入恶性循环,导致开发效率低下,进度难以保障
对于项目中的债务:
    我们要清楚的知道有哪些技术债务,以及它给项目带来的收益和产生利息,
    这样才能帮助我们管理好这些债务

识别技术债务:

1. 开发速度降低
2. 单元测试代码覆盖率低
3. 代码规范检查的错误率高
4. Bug 数量越来越多

其他指标:
用的语言或者框架的版本是不是太老,早已无人更新维护了
开发人员总是需要加班加点才能赶上进度,如果架构良好、代码质量良好,这些加班本是可以避免的。

处理技术债务策略:

1. 重写:推翻重来,一次还清
    优点:
        你可以针对当前的需求和业务发展特点,重新进行良好的设计,精简掉不需要的功能和代码。
    缺点:
        重写通常工作量很大,在新系统还没完成之前,同时还要对旧系统维护增加新功能,压力会非常大;
        另外新写的系统,重新稳定下来也需要一段时间
2. 维持:修修补补,只还利息
    优点:
        相对成本低,不用投入太大精力
    缺点:
        如果项目不需要新增功能,只需要维护还好,如果项目还持续要新增功能,越到后面,维护的成本就越高了
3. 重构:新旧交替,分期付款
    每次只是改进系统其中一部分功能,在不改变功能的情况下,只对内部结构和代码进行重新整理,
    不断调整优化系统的结构,最终完全偿还技术债务
    优点:
        很多,例如不会导致系统不稳定,对业务影响很小。
    缺点:
        整个过程耗时相对更久

算账:

无论选择哪种策略,都是要有投入的,也就是要有人、要花时间,而人和时间就是【成本】;
同样,对于选择的策略,也是有收益的,比如带来开发效率的提升,节约了人和时间,这就是【收益】。
如果【收益高于投入】,那这事可以考虑做,否则就要慎重考虑。

预防才是最好的方法:

1. 预先投资
    好的架构设计、高质量代码就像一种技术投资,能有效减少技术债务的发生
2. 不走捷径
    大部分技术债务的来源都是因为走捷径,
    如果日常能做好代码审查、保障单元测试代码覆盖率,这些行之有效的措施都可以帮助你预防技术债务
3. 及时还债
    有时候项目中,因为进度时间紧等客观原因,导致不得不走捷径,
    那么就应该把欠下的技术债务记下来,放到任务跟踪系统中,
    安排在后续的开发任务中,及时还债及时解决,就可以避免债务越来越多

技术债治理的四条原则

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

核心域(Core Domain)
支撑子域(Supporting SubDomain)
通用子域(Generic Subdomain)

备注

The Core Domain should deliver about 20% of the total value of the entire system, be about 5% of the code base, and take about 80% of the effort.

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

技术债导致的可演进性问题大多和架构相关,比如服务和服务之间的循环依赖、模块和模块之间的过度耦合、缺少模块化和服务边界的 “大泥球” 组件等,在添加新的功能时,这些架构的坏味道会给产品功能的迭代造成不少麻烦。比如服务之间如果存在循环依赖的问题,当你对系统进行少量更改时,它可能会对其他模块产生连锁反应,这些模块可能会产生意想不到的错误或者异常。此外,如果两个模块过度耦合、相互依赖,则单个模块的重用也变得极其困难。

可演进性问题可能会直接导致开发速度滞后,功能无法按期交付,使项目出现重大的交付风险。而且问题发生的时候往往已经 “积重难返”,引入的技术债务没有在合适的时间得到解决,其产生的影响会像 “滚雪球” 一样越滚越大。在我所经历过的项目中有一个不太合理的模型设计,由于错过了最佳的纠正时间,为了实现新的业务功能最终不得不做服务拆分时,发现需要修改的调用点竟有 1000 多处,而且这些修改点很难借助于 IDE 或者重构工具来一次性解决,不但增加了团队的负担还直接导致了后续功能需求的交付延期。

和可演进性问题相比,高复杂度、霰弹式修改等代码级别问题也很重要,但是相对来说我们更加关注软件适应变化的能力,通过提升软件系统的适应性减少软件最终交付价值的前置时间,快速收集真实用户的反馈,持续不断迭代产品、完善设计。

所以我们在治理技术债时坚持的另外一个原则是 “可演进性优于可维护性” 。如果把上文提到的可维护性和可演进性使用不同的颜色来标识的话(红色表示可演进性问题、蓝色表示可维护性问题),我们可以得到这样的结果:

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

  • 刻意设计(Intentional Design)

  • 浮现式设计(Emergent Design)

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

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

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

在技术债治理的过程中,实践可以剪裁,甚至原则也可以妥协,因为比这几条原则更重要的是获得关键干系人的支持。作为技术人员或者技术领导者,不仅要有前瞻性的技术洞察力、锐意变革的魄力,还需要以 “旁观者” 视角,置身事外地观察自己所处的环境,思考技术改进究竟对于自己、他人、团队、公司和客户究竟产生了什么价值。

减少债务的方法

1. 评估你的处境, 弄清楚你欠了多少

这一步最关键。一旦团队决定必须偿还他们的技术债务(这不是一个容易的决定 —— 而且必须与业务一起做出),他们就必须弄清楚他们实际上欠了多少债务。最好的方法是进行自上而下的设计和代码审查。

步骤:

首先看看你的设计文档。
    它是最新的吗?
    它是否准确地描述了设计中最重要的点?
然后,你可能想首先审查下代码的哪些部分给你带来了最大的麻烦
    哪些部分最难修改?
    哪些地方出错率最高?
    那些部分对你的业务来说最重要?
找出这些问题的答案,可以帮助你对你需要做的事情进行排序,找出方法改善你的处境。

2. 停止引入新债务

最难的部分是学会组织文化变革,这样你就不会让积累的债务超过合理的服务能力。在用户故事中包含债务偿还活动。

为了修复发现的问题,你必须花时间来实现修复,这意味着你在纠正问题时会搁置新的开发。关于这一点,没有什么完美的方法,无论你采取什么方法,你都需要与业务协商如何平衡技术债务偿还和新功能开发。下面是一些我们认为有效的策略。

3. 选择债务偿还策略

策略:

“最高利率优先”
    首先考虑承担影响最大的任务
“最低余额优先”
    首先处理最小的修复

4. 按计划行事!

关键是,让偿还债务成为你长期活动的一部分。确保你能在合理的时间内偿还,而不是让它越积越多。

5. 跟踪和评估进展

  • 你需要能够报告你在债务偿还活动中取得的进展。

  • 采集一些指标,用于向管理和业务证明,花费在这些活动中的时间是值得的,这点特别重要:

    例如,很多时候你需要重构代码来提高性能,这时,手上有正确的统计数据来显示用户体验的改进是很重要的。
    
    同样,当你在改进一个简单的代码库时,添加新特性的速度是另一个向业务证明价值的重要指标。
    

实例

假设你正在领导一个团队创建一个内容管理系统。这个内容管理系统需要能够展示内容。根据你所属组织的类型,你将通过头脑风暴来讨论你想要以各种形式展示的内容的分类。

也许你们是一个大型组织,拥有营销团队或用户体验调研员,要捕捉关于用户想要什么相关的数据。也许你们是一个比较小的团队,没有那些资源,但是正与屈指可数的几个客户沟通,以便直接根据他们的具体需求定制你们的软件。或者,你是一个单独的开发者,正在从事一个业余项目或者做兼职。无论如何,你都将在某个时候到达那种状态,规范在手且准备开工。

因此,现在开始开发这些内容组件,当它向后端请求要展示的内容后在前端进行渲染。

假设你已经确定了两类用户想要展示的内容 —— 博客帖子和图片帖子。博客帖子是长文本条目,而图片帖子是一些短条目,带有一张图片和图片下方的一两句描述性的文本。

你是否应该将它们创建为各自独特类型的组件?你可以简单地创建一个 “blogPost” 对象和一个 “imagePost” 对象,各自具有一些属性。这看起来很简单来发布到产品 —— 博客帖子有一个段落数组来渲染,而图片帖子有一个图片字段和一个紧随其后渲染的段落字段。或者你应该创建一个 “masterPost” 对象,用一个 “type” 字段来决定如何渲染它?或者你可以根据对象中存在的字段来动态生成一个 “masterPost” 对象,然后在渲染内容时以某种方式对其进行解构?

目前来看,创建两种不同类型的对象,blogPost 和 imagePost,是最简单的。在你完成调研之后,利益相关者的规范是非常清楚的。如果你使用更通用的 masterPost 方法来存储内容,那么你必须测试各种额外的情况,没有实际的立马可见的好处。我们必须尽快将这个产品推向市场 —— 它需要为下个季度的营销计划做好准备,该计划会在两个月内生效。因此,这显然是一个开和关的情况。让我们将这些票放入队列并进行分配,我们将在下一个工作阶段完成!

然而,一两个月后,你的利益相关者现在要求你开发一个 “recipe post”、一个 “tutorial post”、一个 “life event post” 等等…

不管怎样,你应该明白了。需求已经变更。你需要增加这些新发现的功能。虽然你之前花了一点儿时间来制作各自类型的对象,但似乎创建 “masterPost” 对象更适合一些。你可以用一种比现在更动态的方式来渲染它们。这个方案的工作量有一点儿多,但它现在更适合。而且,其最大好处之一就是你的设计能经得起未来的考验,当新类型的内容被要求时,你只需要为新的字段添加新的渲染方法就可以了。

最初,你花了两周来构建你单独的帖子类别。它只投入生产一个月左右,但已经有足够多的用户生成了足够多的数据,现在你必须将这些数据转换成新的 masterPost 格式。(你难道不想让这些数据保持原样,为特定时期的数据构建独特的渲染实例吗!你敢不敢!)当然,你还需要构建 masterPost 相关功能。转换用户数据必须等到这些功能就绪并经过了测试。如果你一开始就考虑点儿未来的话,这本来应该只花费一个月时间,但现在将额外花费两个月时间。这甚至会超过新的营销活动的开始时间,使得营销团队不得不将他们的活动再延后一个月。总共,你目前在整个项目的这个部分已经花费了三个月时间,而一开始的一点儿远见会将这个时间减少大约三分之二!

谁来负责?

这真的是技术债吗?需求变化是技术债吗?谁应该为这类 “失败” 负责?

(提示:这并不是真正的失败。这只是一个做生意的组织的本性。这都是事后诸葛亮。)

当然,这是一个很有争议的话题。在这个决策过程中,似乎有许多力量导致你们没有更好地利用开发资源。你可能在一直修复已知的错误!

无论这个设计缺陷的真正原因是什么,请放心,所有相关方都会指责其他人。用户调研团队没有问对问题;开发团队没有充分讨论产品设计的可预见性;市场营销团队的计划安排对产品团队的工作优先级有太多影响;谁能预测到用户需求的变化呢;CTO 打太多高尔夫球了,没有管理好这些团队和相关个人之间的协作等。

好吧,别再指责别人了!技术债不是任何特定人群的责任。你们整个组织都对此负责。这是因为人类天性中的低效率和缺点,它们在组织框架内相互起作用,因此导致了债务。

组织内不同团队的专家之间的实际的有意义的沟通和信任,是战胜技术债的关键。虽然每个公司都是独特的,但它们都有一个共同点,不论他们做得怎么样 —— 组织内部的人员对彼此的项目和实际需求更坦诚。每一项业务都有独特的需求和需要解决的特殊问题,从而改变讨论的性质。

主页

索引

模块索引

搜索页面