Levix

Levix's zone

x
telegram

所有编写的代码最终都会变成技术上的持续负担(技术债务)

Ward Cunningham 最早提出的技术债务这个概念,指的是通过加速软件开发的进程以获得短期效率提升,但这样做的代价是拖慢未来的开发速度。

这很像向银行贷款。贷款可以让你比平常更快地完成某件事情。而技术债务,就像你在项目初期的开发过程中获得的推进力。但是一旦你贷款,你就必须支付利息。对于技术债务而言,这就相当于未来开发速度的放缓。当你归还了贷款本金,需要支付的利息也会随之减少。这类似于重构过程中,你投入时间改善代码质量,以期加快未来开发的速度。

就如同金融债务一样,技术债务也可以成为一种有用的工具。有时能够获得的短期收益会超过它带来的长期影响。但是,正如过多的金融债务可能导致破产一样,过多的技术债务积累可能会使你的产品开发陷入龟速,甚至停滞不前。

如果你有过从零开始创建新应用的经验,那你一定体验过那种没有任何技术债务,项目进展顺风顺水的愉快感觉。在开发初期,你可以快速迭代出新功能,不用担心会对现有用户产生影响,可以全身心投入到新功能的实现上。

但是,随着应用的不断成熟和壮大,开发速度不可避免地会逐渐放缓。在一个设计不太完善的产品上,开发速度可能会迅速减缓,而即使产品设计得足够优雅,但随着时间的推移,开发速度仍旧会慢慢下降。这是因为你向应用中添加的代码越多,开发就会变得越缓慢,因此,我认为所有的代码,从本质上来说,都构成了技术债务。

增加新假设导致技术债务增加#

在应用不断完善的过程中,会逐渐形成一系列基础性的假设。当你刚开始一个新项目时,由于还没有任何功能,代码库中也就不会有任何预设。这使得添加新功能变得非常简单,就如同功能的直接实现一样。但是,一旦你的项目有了第一个功能,之后开发的每一步都必须考虑到前面功能对未来开发工作的限制。

以我创建的社区活动平台 Doorkeeper 为例,它帮助组织者吸引参与者并管理注册。开始时,Doorkeeper 只是为了帮助一个本地网络活动 ——Mobile Monday Tokyo 简化他们的注册及签到流程。当时他们只需要一个简洁的注册流程,用户只能进行活动注册和取消。

当我们扩展 Doorkeeper 以适应更多组织者的需求时,我们发现许多活动是有固定参与上限的,这是 Mobile Monday 之前没有考虑过的。要满足这些新需求,我们决定增加一个限制参与者数量的功能。

我们基于一个假设构建了参与人数有限的活动:人们可以为活动注册。但我们还需要决定,当某个活动已满员时,尝试注册的人该怎么处理。最简单的方法是,当人满时直接禁止注册,但我们认为这会给想要参加的人带来失望,也无法反馈给组织者活动的受欢迎程度。所以,我们引入了候补机制:如某人想注册已满的活动,他们会进入候补名单,若有参与者取消,则候补名单上的首位将顶替其位置。

接下来我们添加的功能是活动预付费,如果我们的唯一假设是 “人们可以为活动注册”,那么实施起来将非常简单,但我们还得考虑其他已有的假设。

根据不同的活动,组织者可能允许也可能不允许预付费参与者取消。此外,他们可能想设置一些取消政策,比如根据取消的时间给予不同比例的退款。由于取消预付费注册涉及到许多特殊情况,而且大多数组织者也不希望让这一操作过于容易,我们决定不允许参与者自行取消预付费注册。取而代之的是,参与者需要联系组织者,然后由组织者决定如何处理。

另外,因为一场活动的注册人数有限,我们需要确保只有真正有机会参加的人才能预付费。为此,我们将注册流程分为两步:首先输入注册详情,然后进行支付。如果在用户填写初始表格和提交之间,活动名额已满,则该用户会被添加到候补名单中。

我们还得处理用户提交了初始表格却没有完成支付的情况。为此,我们引入了一个机制,在一定时间后自动取消这些未付款的预订。

另一个需要考虑的情况是,当有人从候补名单上被转正。对于需要预付费的活动,我们不能马上发放票券,须让用户返回网站完成支付。

正如你所见,随着新功能的不断增加,考虑和解决基于现有假设限制的问题也变得愈加复杂。

功能可能带来负面价值#

为了让一个功能对产品有所增益,它需要对用户确实有用。当一个功能带来的技术债务超过了它为产品增加的价值,那么这个功能实际上就具有了负面价值。

本地化就是一个往往会产生负面价值的功能。本地化在最简单的层面上,就是把你的应用翻译成多种语言。除了翻译工作,这一过程还涉及到更多挑战。就算只考虑最简单的本地化形式 —— 维护一个包含各种语言特定字符串的字典。

作为初步,这意味着你的应用中不能再包含硬编码的文本,这增加了一层额外的抽象。尽管这一层抽象并不会带来太多额外工作,但开发人员仍需时刻注意这一点。

你还必须在开发流程中增加翻译这一步骤。考虑到开发人员可能不是所有支持的语言的母语使用者,他们将不能独立完成文本部分,而需依赖译者。这无疑会让所有未来的开发进程都略显缓慢。

如果本地化能够产生巨大价值,那么这点额外工作不成问题。但我常常看到一些应用进行马虎的本地化,希望通过此吸引其他国家的用户。但这并非有效的工作方式。除非你愿意全力以赴地进行本地化和在支持的语言区域市场推广,否则本地化不会带来真正的价值。这样一来,你最终会有一个功能,它带来的技术债务超过了它本身的价值。

代码本身并不自带价值#

作为开发人员,我们容易陷入这样的错觉 —— 认为编写代码即是创造价值。然而,软件的价值在于它对用户的实用性,而非我们代码的质量。即便编写得不完美但实现了有用功能的代码,比起完成无用任务的漂亮代码来说,其价值要大得多。

由于这个原因,我们需要确保自己的开发工作聚焦于有价值的功能。虽然传统的开发过程中总假设有一个全能的产品主人,他们似乎能准确判断各项功能的价值,但实际情况往往并非如此。

希望你已经和其他利益相关者合作,并深刻理解到你手头工作的价值。但如果你并不这么认为,那么就应该提出质疑,试图去探究他们为何认为这个功能有价值,以及是否有任何验证工作被执行过。

确保我们实施的功能具有价值,这样我们就减少了它们带来的技术债务成为不可承受之重的可能性。

一旦添加功能,它大概会永久存在#

之所以我们需要对那些未能带来足够价值的功能保持高度警惕,部分原因在于一旦功能被加入,它通常就会永久地留存。

即便事后明显看出该功能未能如我们预期那样表现出色,常见的做法是不采取任何行动。这在一定程度上是由于沉没成本的误判,经常有种希望:即使功能目前未被使用,将来可能会有用武之地。

然而,不采取行动也有合理之处。移除一个功能也需要付出成本,不仅包括清除功能所需的开发工作,也可能会引起客户的不满。因此,一旦某个功能被加入到产品中,它几乎总是会存在。

要避免技术债务,最好不要编写代码#

避免技术债务的最可靠方法就是一开始就不写代码。作为开发人员,我们最初的直觉是通过编写代码来解决问题,但这并不总是最佳策略。很多时候,我们需要克制这种直觉。

举个我自己的例子,我经营的一个招聘网站,帮助国际开发者在日本找到工作。起初我只是随机发布我看到的职位信息,但随着听到越来越多的成功案例,我认为公司会愿意为这项服务付费。与此同时,我希望能在申请被发送给公司之前,对其进行一些基本的垃圾广告筛查。

为了实现这一点,我开始使用 Ruby on Rails 构建一个筛选系统。然而,开发大约一天之后,我发现创造一个比让候选人发送电子邮件表现更好的系统相当复杂。我需要复制电子邮件所具有的所有功能:附件、候选人与公司之间的通信等。更严峻的是,我需要保证系统的稳定运行,监控日志等。

然而,我真正需要的只是在邮件发送至公司之前,有办法对其进行适度地审核。因此,我没有继续走下去,而是选择了使用 Google 应用中的组功能为每个公司设立一个邮件列表。尽管这不是最好的技术方案,但我创建的价值并非仅在于技术本身。相反,我更应该把时间用于帮助公司和候选人之间建立联系。

在现有的框架内工作来实现新功能#

当我们增加新功能时,一个减少技术债务的方法是在现有的假设或条件约束内完成工作,而非增添新的假设。以我们在 Doorkeeper 中的实践为例来说明。

活动组织者可以给参与者发送信息。我们建议组织者利用此功能向参与者发送活动提醒。但有些组织者从来不发送这种提醒,或许是因为他们忘记了,或者实在太忙。同时,还有一些组织者会发送定制化的提醒,其中包括活动当天的具体说明。

要作为新特性的我们的目标是确保每个参与者总是能收到提醒,而不依赖于组织者手动发送消息。

增加自动提醒的功能似乎最简单的方法便是活动前一天自动向参与者发送电子邮件。这种实现方式很简单,但没能很好地考虑到那些需要发送临时指令的组织者的情况。如果组织者还要发送额外的消息作为补充提醒,那么参与者就会因为同一件事收到多封邮件,这会带来不太理想的体验。要应对这个问题,我们可以允许组织者自定义提醒内容。

考虑到自定义提醒的功能需求,我们意识到提醒和消息本质上是没有太大区别的。如果我们能自动安排一条消息在指定时间发送,那么组织者就能够修改这条消息,甚至完全停用该功能。

当时我们并没有安排消息在特定时间发送出去的功能。不过,允许组织者安排消息发送时间在更广的应用场景中同样也是一个很有用的特性。更为重要的是,把提醒保留为普通消息,这样我们就能为组织者展示通常的相关报告,比如多少人打开和点击了消息。

通过在已有的特性之上构建,而不是创造一个全新的独立功能,我们找到了一个更好的解决方案。它不仅提供了更优的用户体验,而且产生了更少的技术债务。

概要回顾#

所以来快速回顾一下,由于在产品中增加更多代码会导致开发速度变慢,我们应该把所有代码看作是一种技术债务。为了确保开发不至于逐步停滞,我们需要确保我们编写的代码能创造出更多的价值而非债务。

作为开发人员,我们应该成为产品的守护者,从不成熟的功能设计中保护它。我知道这不容易做到,但我们通过确保自己创造的产品有价值,而不仅仅是源源不断地产出代码,可以带来更大的好处。

原文链接:https://www.tokyodev.com/articles/all-code-is-technical-debt

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。