随着软件系统的不断发展,它们往往会变得不那么灵活,也更难使用。我们通常把这种情况归咎于猖獗的“技术债”,但却没有讨论导致技术债的原因。
笨拙的编程不是造成技术债的主要原因,因此我们不能指望仅依靠更熟练的编程就能解决技术债。相反,技术债是沟通不畅的三阶效应。这是缺乏适当抽象的症状,而这反过来又源于对问题领域建模的不足。这意味着没有进行充分的沟通;为解决歧义并做出明智的权衡而进行的讨论和决策已经被掩盖了。
我们所观察到并被标记为“技术债”的是这一功能失调开发过程的副产品:代码中缺乏解决方案的具体化。为了解决不断积累的技术债问题,我们需要修复这个被破坏的过程。
造成技术债的主要原因
技术债的比喻是由 Ward Cunningham 引入的,它用来描述开发人员有意识地决定将具有已知限制的代码交付到生产环境中的过程。提前交付的目的有两个:快速进入市场,以及实现从生产到进一步开发和改进的反馈循环。它很快就流行起来了,因为它允许开发人员通过技术解决方案将“看不见的”问题传达给管理层和其他利益相关方。
然而,在技术债这个比喻变得广泛而流行的过程中,它的含义也被稀释了。在生产环境中运行的任何有局限性或者存在质量问题的代码都可能会被贴上技术债的标签。这是不幸的,因为它破坏了比喻的实用性和丰富性。很多被认为是技术债的东西都是随着时间的推移而无意中产生的,并且没有明确的偿还策略。
技术破产
令人遗憾的是,技术债这个比喻的含义已经被以这种方式稀释了,但是在语言中,就像在生活中一般,实用主义胜过意图。这就是我们的处境:所谓的“技术债”在很大程度上只是正常软件开发的副产品。当然,没有人希望代码问题以这种方式累积,因此问题就变成了:为什么我们似乎总会无意中导致如此多的技术债呢?我们进行软件开发的方式是什么样的,它为什么会导致这种我们不想要的结果呢?
这些问题很重要,因为如果我们陷入了技术债,那么我们就会在技术上资不抵债并在技术上破产。事实上,这似乎是许多软件开发工作中正在发生的事情。Ward Cunningham 指出,“整个工程组织可能会因为未整合实施的债务负担而停滞不前”。这种停滞就是技术性的破产。这意味着你的组织无法再继续前进了。
对软件进行合理的变更需要花费不合理的时间来实现。质量问题就成了永久性的问题;如果没有引入新 bug 的机会,就无法修复 bug,从而导致问题之间的某种共振。
实践中的技术债
如果我们要理解无意中导致技术债积累的力量,我们必须要看下代码,并看看“技术债”是如何体现出来的。
我的观察结果是,代码中往往有很多的“如果”和“但是”,但很少能传达意图并帮助理解。我的意思是,代码中有许多 if
和 else
分支,还有大量的布尔标志来控制这些分支之间的执行流。缺少的是能理解这一切的有用抽象和边界。这使得隔离出与单个功能相关的代码变得十分困难,因为该功能的代码在任何意义上(或明显意义上)来说都不是孤立的。很难预测变更后的影响,因为变更某个布尔标志可能会在整个代码库中产生连锁反应。
当我们对我们试图用软件来解决的问题有了一个不成熟且不充分的心智模型时,代码的结果是这样的。软件肮脏的秘密在于,我们可以对我们无法清晰表达的问题实施解决方案。如果我们的软件是“错误的”,那么正确的行为总是只需一个 if
分支。我们只需要用某种方法来注入正确的标志,就可以在执行流中转向正确的行为,而非错误的那个。在实现中,我们确实可以通过使用 if
分支来补偿我们糟糕的领域模型。但这正是我们因疏忽而造成技术债问题的原因:随着时间的推移,我们把自己逼到了绝境。它使软件变得难以理解了——技术破产。
在不破坏现有功能的情况下,我们不能再以这种方式添加功能了。
修复模式,而不是代码
如果我们的头脑中没有正确的概念,就很难写出简单而又精确的代码。我们不仅需要这些概念来构建我们的解决方案,而且需要在一开始时就能清楚地思考这个问题。只要我们缺乏正确的概念,我们的思维以及我们与他人的交流就会变得笨拙而迂回。想象一下,在不知道狗(dog)这个单词或者甚至不知道动物(animal)这个单词的情况下,试图给某人讲一个关于狗的故事。“它是一种急切的、摇尾巴的、有四条腿的生物”。这听起来很傻,但我在项目中多次遇到这种情况。
在我参与的一个项目中,我们在处理信用卡模块时遇到了困难。代码复杂且难以理解,而且每当我们谈到这个模块时,我们的讨论效率就会变得很低且令人沮丧,但我们无法真正弄清楚原因。直到我们意识到我们缺乏一个概念来描述信用卡是如何与信用卡交易相关联的(一种“关联机制”),才使得一切变得合理。突然间,我们的头脑清醒了,我们的讨论也清晰了,而且我们可以非常直接地实施它。我们删除了我们之前所编写的所有笨拙的代码,并代之以一些简单易懂的代码。
这一经验为处理复杂代码提供了一种启发式的方法;为那些往往会在一段时间后被贴上“技术债”标签的代码寻找缺失或尴尬的概念。在团队的设计讨论中寻找受挫的模式。可能是领域想告诉你一些事情。试图“修复”没有正确概念的代码很可能会失败,因为错误的概念没有优雅或干净的组织。
我想说的是,我们的问题是源于我们试图用软件来解决的问题具有不成熟且不充分的心智模型。对于团队协作开发的软件来说(也就是大多数的软件),该心智模型需要在软件开发人员之间共享。不然的话,毫无疑问,不一致和极端情况就会咬着我们不放。如果我们没有就问题和建议的解决方案达成一致的话,那么我们应该期望看到这些对齐失败的后果能在代码中体现出来。我们也确实是如此。
开发一个足够丰富且灵活的共享心智模型的关键是沟通和协作。当软件被技术债压得喘不过气来时,这表明开发软件的组织可能需要查看其沟通和协作模式了。
业务与 IT 之间的分歧
Ward Cunningham 发明了“技术债”这个比喻,使得开发人员能够与业务人员交流一些前者看得见、后者看不见的东西;虽然我们现在发布的代码满足了业务需求,但我们在这方面做得太多了。这样做让我们失去了平衡,我们需要花一些时间来恢复这种平衡。否则我们最终会摔倒,很难再爬起来。但从某种意义上说,这是一个很容易解决的问题:可以说,慷慨地给开发人员一点时间,让他们时不时地打扫一下自己的房子。业务人员所需要的只是一点等待开发人员迎头赶上的耐心。
不幸的是,我认为这不会奏效。如果我的观点是正确的,即我们所说的技术债实际上是源于业务领域建模的不足,并且最终是由沟通和协作问题引起的,那么这不是开发人员可以自行解决的问题。事实上,认为开发人员能够并且应该单独处理技术债是导致技术债的另种思维症状。对于开发人员和业务人员来说,这都是一个令人不安的观点。对于业务人员来说,将技术债视为 IT 需要处理的事情是很方便的,而对于开发人员来说,认为他们所需要的只是一点时间来把事情做好就更舒服了。但如果我们要解决技术债的根本原因,这是一种我们无法承受的便利和安慰。
减少技术债
对于一个发现自己正接近技术破产的软件组织来说,主要的问题不是债务本身,而是该组织在其当前状态下无意中产生了大量难以管理的复杂代码。如果我们继续以与以前相同的速度产生新的债务,那么削减已发生的债务几乎没有任何用处。试图理清濒临破产的代码可能是非常昂贵、耗时和冒险的。通常,最好是找到某种方法,用其他以更健康的方式生成的代码来替换债务繁重的代码。我能给出的最好的建议是尽量减少我们目前的债务,也就是说,首先要减少我们必须要减少的技术债。
随着时间的推移,减少我们所谓的“技术债”的最佳方法是解决根本原因,那就是我们如何共同协作的方式。改变软件组织的文化可能很困难。自上而下的举措往往会遇到困难,因为它们无法解决实地出现的问题。也许最好的自上而下的举措是给那些处于底层的人留出余地和自主权,因为我相信自下而上是有可能带来积极改变的。
我的经验是,作为一个团队进行软件开发(即集成编程)不仅可以更快地为问题提供更好的设计解决方案,而且还可以创造一种向更开放、更具同理心和更坦诚的沟通文化的转变。这反过来意味着,随着时间的推移,进行集成编程的团队不太可能会陷入技术债的泥潭中。此外,在经历了团队内部沟通的改善之后,团队成员也不太可能适应跨团队边界或组织中不同角色的人员之间的沟通不畅。如果这是真的,那么团队合作可以对组织的沟通模式产生积极的连锁反应。
结论
在软件行业中,无意中不断积累不受控制的技术债是一种普遍现象。造成这种趋势的根本原因是我们的沟通模式不够完善。这导致了心智模型的不成熟,开发人员通过堆积布尔标志和分支控制流来近似解决表达和理解欠佳的问题。
随着时间的推移,以这种方式构建的软件变得难以理解。打破这种趋势的方法是改变我们构建软件的方式:通过更好的协作和交流。正在集成的工作可能会朝着正确方向迈出一大步,因为它将协作和沟通置于软件开发的核心了。
作者介绍
Einar W. Høst 是挪威公共广播公司 NRK 的软件开发人员,在该公司,他帮助建立了电视流媒体服务。他的主要兴趣是领域建模、API 设计和计算机编程。他拥有奥斯陆大学计算机科学博士学位。你可以在推特上@einarwh找到他,或者读他的博客。
出处:https://www.infoq.cn/article/kj9cKsuxBAMhPDz8QWAL