软件开发复杂性解决之道:浮现和脚手架方法概述

2022-04-19 11:06:29 浏览数 (1)

作者 | Greg Brougham

译者 | 王强

策划 | 丁晓昀

本文概述了一种基于脚手架(Scaffolding)和浮现(Emergence)的系统开发新方法。我们使用了三个关键原则,但我们所做的基础改变则是基于采取一种名为浮现的方法并引入脚手架概念:

  • 我们专注于业务需求,并且不再安排产品负责人,因为他们通常只是业务方的代理人。我们承认,客户的需求往往是突然浮现的,而且人们很难理解这些需求,因此我们更愿意达成一个大方向的共识,并共同探索下一步该做什么才有意义。
  • 我们从一个稳定状态逐步前进到下一个稳定状态。在这样做的过程中,我们做出了一系列承诺,朝着期望的方向前进,并且如果结果不理想,我们也有能力调整或停止步伐。这些稳定状态使我们能够“加快步伐”并交付具有业务价值的下一个稳定状态。
  • 我们希望利用脚手架来加速学习和交付过程。除了支持交付过程外,我们还设法构建知识框架,因为这样做也是有帮助的。重点不仅在于技术,还在于知识。

拥抱浮现方法让我们能够以协作的方式探索潜藏的需求。如前所述,我们在这里不会设置有特权的“产品负责人”,而是尝试直接处理业务需求并从业务方的角度探索他们的需求。业务价值可以是直接的或间接的。直接是指它是销售或服务的一部分,直接向客户收费。间接层面上,业务价值可能体现在加速交付或支持公司所提供服务的维护工作上。直接与业务方交流可以帮助我们探索具体的业务价值类型和业务收益。

使用脚手架不仅让我们能够更快交付,它还让我们可以延迟对问题域的显式知识的开发工作。事实证明,这种“知识”脚手架非常有用,让我们有时间深入了解那些与示例用例的问题域相关的底层技术和通信协议。

我们认为这些实践对其他开发项目也很有用、有相应的价值。我们将这种工作方式的各个方面视为对其他一些方法(如连续架构)的补充。

浮 现

人们对浮现概念不怎么熟悉,但在基本层面上,我们需要承认我们一开始并不是什么都知道的。只有通过互动和探索,我们才能了解哪些东西才是有用的,这就是我们所说的浮现。虽然需求可能是浮现出来的,但我们往往会对自己想要什么东西和 / 或我们想要前进的方向有一些想法和判断,它们可以用来设置指导性的约束。

如果拿它与首先定义一组需求的传统方法对比的话:后者定义的需求通常基于一系列未经证实的假设,因此可能是不正确的,也可能是不完整的。确实,在开始写代码之前,我们的确需要知道自己正在构建什么东西。因此,我们需要能够弥合这些差距,同时仍然允许客户在开发过程中浮现之前未明确的需求。这些需求只有通过对系统的实际使用过程才能明确下来。

我们需要在开始开发工作之前就有一个“完整”的概念,其具体内容需要根据情况具体分析。有一句老话是,有了明确的定义,问题就解决了一半。精益有一种“完整”工具包的理念,说的是在所有相关信息和材料可用之前不开始任务。

隐性知识可以自然获取,而显性知识需要人们隐性地理解和应用才行。因此,所有知识要么是隐性的,要么植根于隐性知识。完全显性的知识是不存在的。

我们也可以从需求的角度来看待这一点。我认为有三种类型的知识可以映射到 Cynefin 所说的清晰、复杂和非常复杂的领域。它们分别是:

  • 已知的知识,或已知有人理解的知识
  • 尚不清楚的内容,或已知还没人了解的内容
  • 还没被发现的未知数

有些人会说,已知的知识应该是易于处理并且很容易解释清楚的,但即使在这里也需要谨慎小心。我记得有一次,我正在开发一个新的卡片结算系统,当时我们需要处理列入黑名单的卡片。我们的假设是一张卡片要么被列入黑名单,要么不在黑名单上。但我们被告知当前系统可能会返回“是”“否”或者“可能是”,没有人能解释最后这一条是怎么来的。我们错误地假设这个问题是非常清晰明了的,但它其实是一个复杂的问题,解决起来既费时又费钱。

我们在解决第二种需求——已知还没有人了解的内容——方面有着大量经验,你可能会说很多敏捷实践可以帮助表达这些需求,相关实践(例如创新游戏)在这里也是很有用的。大体上就是这种情况,迭代开发也很有用,因为它使我们能够清晰地表达这些元素并将它们融合在一起。

当处理不明确的需求或还没人发现的未知数时,挑战就来了。如果我们不知道某些东西是未知的,那么我们就需要一种方法来处理它们的浮现过程;这是传统开发方法难以应对的问题。我们还需要承认,实现需要明确的需求,因为我们需要有一个方法或解决方案的大纲,这样才能编写代码。在这里,我们看到了 Cynefin 临界状态的价值所在。在这种状态下,我们对各种选项持开放态度,探索哪些东西才是有用的,只有在我们达成共识后才投入开发工作。在实践中,我们发现客户往往从他们的角度很清楚他们要求什么,但我们需要明确一些事情来确认实现的情况,并在作出承诺之前取得共识。这不仅需要对需求作出确认,而且明确了支持“完成”这个定义的需求。

与此相关的是,我们对界面有着好奇心,但并不会一门心思放在界面上面,因为我们的重心是放在功能领域上,这也是业务重点。脚手架在这里可能会有些用途,它可以提供需要观察的预定自然边界。功能区域可能成为问题域,但这不是提前定好的。

从交付的角度来看,我们会参与很多小块的工作,这些工作都能帮助我们探索系统架构和需要支持的功能。这些都是价值步骤,我们还会使用史诗(epic),因为敏捷社区理解这些概念并提供了合适的粒度级别。这些工作由很多合适的故事和任务组成。一个故事可能是查看一个支持所需功能要用到的特定界面,我们还要承认这两者可能会随着时间的推移而发展,而我们几乎无法预测它们的发展方向。史诗的一个例子可能是对系统的监控,这些不一定是关于功能方面的,也可能包含非功能元素。

在故事级别,我们可能会探索很多提供某些功能或能力的选项。我们想的是以低成本和快速的方式探索它,从而实现价值。我们不会将其加载到特性本身中,因为我们希望在承诺将其添加到史诗之前充分了解如何支持和交付它。这样以来,系统架构在每一步都会不断探索和完善,每一步都是一个“稳定状态”,可以让价值得到提升。

这样我们就不需要在前期事无巨细地做规划,同时能够专注于对业务有价值的内容,并对需求保持开放的态度。我们不会维持大量积压,这也有助于确定优先级,让业务真正敏捷起来,并对那些未曾预料到的客户需求负起责任。

脚手架

为了支持使用浮现方法,我们还使用了脚手架,它有助于引导系统并提供最开始的稳定性。有很多不同类型的脚手架可用,但在高层次上,它们可以是内部的或外部的,临时性的或永久性的。内部脚手架通常会提供一个结构让你在其上构建。根据定义,脚手架可能成为结构的一部分,因此本质上是永久性的。外部脚手架往往会约束系统,但支持系统向下一个稳定状态移动。但由于它是外部的,因此通常是临时的。从长远来看,它往往会变成多余的部分,因为内部结构可以自己支持自己。

人们通常会认为脚手架只是让你推迟工作的东西,但经常被忽视的是脚手架允许我们推迟知识探索过程,因为它已经包含了领域知识的“编码”。这里说的不仅仅是关于我们如何构建的知识,而且还有直接的业务知识,让我们不需要再花时间来获取它们。如果是这种情况,那么我们可以利用它来尽早交付业务价值,而无需提前了解业务领域(我们正在推迟这个过程并留出时间来开发隐性知识)。

我们还可以使用脚手架来绑定或桥接系统的各个元素。在这里,我们可以使用一些能够以合适的规范结构交换信息的现成工具,因为它们比某些常见格式更有效。这些会成为系统的组成部分并且是长期存在的。

此外,脚手架允许在交付周期的早期集成核心元素,从而支持对已做出的任何假设的探索过程。结合基于主干的开发过程,这意味着每个更改都会以整体方式进行隐式评估,因此我们不需要显式集成测试。

这里的脚手架是很多开源实现的集合,这些实现是基于对高级业务需求的简单功能分解和底层技术领域的适当抽象级别选择出来的。如前所述,这意味着我们不需要对业务需求进行详细或广泛的分析,并且往往可以从与业务方的简短对话中获得足够的信息。

一旦我们对业务需求有了基本的了解,我们就可以查看那些支持引导系统的选项。这可能还需要多个组件;我们可能会找到一些解决特定业务需求的代码,但仍然可能需要一种存储和管理数据的方法。

我们在选择选项时的关键点(这里先不管语言偏好)是问题域的适当抽象级别,这又回到了脚手架知识这个点上。我们需要简单直观的界面,以避免开发隐性领域知识的需求,除非我们已经有了这种需求。在我们熟悉特定功能(例如数据表示)的情况下,我们可以使用它来指导技术或代码库的初始选择过程。

案例示例——KNX 监控

这个案例示例要求开发一个 KNX 监控系统,该系统可用于呈现和分析安装中部署的所有设备。KNX 是二进制协议,定义了那些支持自动化和监控家庭和办公室所有元素的 ISO 标准。在这个案例中,我们有一个基本的功能分解框架,使我们理解我们想要发展的方向,但这里缺少各种领域知识。定下来的大体方向是,我们需要从部署中收集和呈现指标,然后发出警报,最后形成循环,让指标能够引发动作。我们还需要支持大规模扩展,因为可能会有大量部署。

这些功能都是针对这个场景的,因此在其他案例中,你也需要经历类似的过程。值得注意的是,这些功能领域是从讨论中总结出来的,是随着时间的推移而确定并浮现出来的。我们并没有打算进行长期或详细的分析,而是在我们确定了初始特性和第一个价值步骤后立即开始前进。这些初始特性包括了收集和演示,因为它们提供了所需的可见性并涵盖了大多数技术问题,例如连接性、设备事件的收集和演示。随着时间的推移建立的关键能力总结如下:

  • 集合——基于多个位置的线程和 KNXETS 文件的集成,因此设备类型是已知的并且可以映射。
  • 演示——构成安装的设备和组的事件、指标和状态的可视化。
  • 处理——分析事件并根据条件发出警报。
  • 代理——发送警报以支持动作并自动执行系统中的动作。

基本的脚手架支持是通过利用一个开源实现来完成的,这个实现用于 KNXDPT(数据点类型)元素和主要的数据存储和处理过程。如果出现了业务或性能问题,这些元素可以在以后更换掉。我们还采用了“事件溯源”方法,确保所有事件都能被捕获。这不仅支持了可观察性,还确保我们可以重新创建所需的任何特定视图,从而推迟特定的设计决策。

在开源方面,我们主要对 MIT 许可感兴趣,因为它提供了灵活性。但我们也愿意利用 Apache 许可来隔离不太可能需要更改的部分。这里的想法是在短时间内构建系统架构,同时降低关于 KNX 的复杂性及其设备和组地址结构的学习成本。我们通过用于数据收集和处理的 Influx TICK 堆栈对此做了补充。随着时间的推移,我们可以切换堆栈的元素或在它们成为约束时重新编码它们,但核心堆栈每秒可以处理几千个事件,并且我们看到典型的 KNX 部署每小时处理 800-1000 个事件。这意味着一个位置的当前速率平均低于每秒一个事件。

如上所述,我们没有一开始就预先定义一系列需求,而是以允许学习的协作方式探索这些需求。我们基于史诗构建了 WoE(参与方式),这些史诗是一些短期工作(几天到一周),用于交付一些新特性。这些特性反映了客户的需求,这也意味着我们可以考虑新的需求,也可以让客户在下一步开发工作之前就体验系统。这本质上是迭代的,意味着开发工作始终专注于需要的东西。它还让我们可以学习正在使用的堆栈和库的知识,目前来说这种做法是很有效的。

这些实践不必纯粹是功能性的,一个例子是对安装好的连接的监控和恢复工作。基本的史诗涵盖了出错连接的恢复工作,但我们添加了一个挂起连接的故事来解决我们观察到的状态。解决这个问题意味着系统在很大程度上是自我修复的,并且在很大程度上是自动运行的。

我们还简化了代码和测试方法,使用主 / 主干(现在通常称为“主”)分支开发模型,因此当我们进行特性开发时,每天都会提交和测试所有代码。如果需要,我们可以使用特性切换来限制某个特性的使用,但这些通常是附加的需求。我们用特性切换来支持从 InfluxDB V1 到 V2 的迁移过程。

总 结

我们相信,我们探索出来的支持浮现和脚手架的实践对系统交付过程具有普遍的适用性。围绕浮现的实践避免了对正式要求的需求,并提供了一种简单的工作方式和与客户互动的方式。使用脚手架通过开源方法解决技术需求,意味着我们最初不需要深入了解 KNX 技术,这直接转化为早期的业务收益。这意味着当业务需求变得更清晰时,我们既可以节省时间,又可以响应业务需求。

参 考

Cynefin 框架(https://cynefin.io/wiki/Cynefin)

脚手架的 Cynefin 类型(http:// https//cynefin.io/wiki/Scaffolding)

作者介绍:

Greg Brougham 是一位经验丰富的开发人员、架构师和技术领导者。近年来,他一直担任一家区块链初创公司的工程总监,并为一家电信公司的数字化转型计划定义了架构。他还写了一本关于 Cynefin 复杂性框架的小著作,并且他在时间允许的情况下仍然很喜欢编写代码。

原文链接:

https://www.infoq.com/articles/emergence-scafolding-complexity/

0 人点赞