探索敏捷方法
在构建 Wordz 时,我们将采用一种迭代方法,将应用程序构建为一系列用户可以使用的功能。这种方法被称为 敏捷开发。它之所以有效,是因为它允许我们更早地、按计划定期向用户交付功能。作为开发者,它让我们能够在开发过程中更多地了解我们正在解决的问题,以及一个好的软件设计应该是什么样子。本节将比较敏捷开发与瀑布式方法的优势,然后介绍一种称为 用户故事(user stories) 的敏捷需求收集工具。
敏捷开发的前身被称为 瀑布式开发。之所以称为瀑布式,是因为项目阶段像瀑布一样流动,每个阶段完全完成后才开始下一个阶段。
在瀑布式项目中,我们将开发分为连续的阶段:
-
收集需求
-
分析需求
-
创建完整的软件设计
-
编写所有代码
-
测试代码
理论上,每个阶段都完美执行,一切正常,没有问题。但实际上,问题总是存在的。
我们会发现一些遗漏的需求,设计文档可能无法完全按照编写的方式编码,设计中可能会缺少某些部分,编码本身也可能遇到困难。最糟糕的是,最终用户直到最后才能看到任何可运行的软件。如果他们看到的结果与他们的预期不符,我们将面临非常昂贵的修改和返工。
这是因为人类的预见能力有限。尽管我们尽力而为,但我们无法准确预测未来。我可以坐在这里,拿着一杯热咖啡,准确地知道它会在二十分钟后变冷。但我无法告诉你三个月后的天气会是什么样子。我们预测未来的能力仅限于短时间范围内,且针对具有明确因果关系的过程。
瀑布式开发在面对不确定性和变化时表现非常差。它基于所有事情都可以提前了解和规划的理念设计。更好的方法是拥抱变化和不确定性,使其成为开发过程的积极部分。这就是 敏捷开发 的基础。其核心在于一种迭代方法,我们选择一个用户关心的小功能,然后完全构建该功能,并让用户试用。如果需要更改,我们再进行一次开发迭代。当我们的开发过程积极支持变化时,变化的成本会低得多。
专业的敏捷开发流程依赖于维护一个单一的代码库,该代码库始终经过测试,并代表我们软件迄今为止的最佳版本。这段代码始终可以部署给用户。我们一次一个功能地扩展这个代码库,并在过程中不断改进其设计。
TDD 等技术在其中发挥了重要作用,它确保我们的代码设计良好并经过全面测试。每次我们将代码提交到主干时,我们已经知道它通过了许多 TDD 测试。我们知道我们对它的设计感到满意。
为了更好地支持迭代开发,我们选择了一种迭代的技术来捕捉需求。这种技术称为 用户故事,我们将在下一节中描述它。
阅读用户故事 —— 规划的构建块
由于开发是迭代的,并且包含 重构 和 返工,传统的需求规范方法不再适用。我们不再需要成千上万页的固定需求文档。更好的方法是每次处理一个需求,构建它并从中学习。随着时间的推移,我们可以优先考虑用户想要的功能,并更多地了解一个好的设计应该是什么样子。
通过敏捷技术,我们不需要提前知道未来;我们可以与用户一起发现它。
支持这种变化的是一种新的需求表达方式。瀑布式项目从一个完整的需求文档开始,详细说明每个功能。完整的需求集——通常是成千上万的条目——用正式的语言表达,例如 “系统应……”,然后详细说明对软件系统的更改。而在敏捷开发中,我们不希望以这种方式捕捉需求。我们希望遵循两个关键原则来捕捉需求:
-
需求一次只呈现一个,且独立于其他需求
-
我们强调对用户的价值,而不是对系统的技术影响
实现这一目标的技术称为 用户故事(user story)。Wordz 的第一个用户故事如下:

用户故事的格式始终相同——它包含三个部分:
-
作为一个[使用软件的人或机器],……
-
我想要[从该软件中获得特定结果],……
-
……以便[完成一项重要任务]。
这三个部分的写作方式是为了强调敏捷开发以向系统用户交付价值为中心。这些不是技术需求,它们不(实际上,绝不能)指定解决方案。它们只是说明系统的哪个用户应该从中获得什么有价值的结果。
第一部分总是以 “作为一个……”(As a
) 开头,然后命名该故事将改进的用户角色。这可以是系统的任何用户——无论是人还是机器。唯一不能是的是系统本身,例如 “作为一个系统”(As a system)。这是为了在我们的用户故事中强制执行清晰的思维;它们必须始终为系统的某些用户提供一些好处。它们本身从来不是目的。
以拍照应用为例,作为开发者,我们可能希望进行一项技术活动来优化照片存储。我们可能会写一个故事,例如 “作为一个系统,我希望压缩图像数据以优化存储。” 与其从技术角度写作,我们可以重新构建它以突出对用户的好处:“作为一名摄影师,我希望快速访问我存储的照片,并最大化新照片的存储空间。”
“我想要……” 部分描述了用户期望的结果。它始终以用户术语而非技术术语描述。这再次帮助我们专注于用户希望我们的软件为他们实现的目标。这是捕捉需求的最纯粹形式。在这个阶段,没有尝试建议如何实现任何内容。我们只是捕捉用户想要完成的任务。
最后一部分 “……以便……” 提供了背景。“作为一个……” 部分描述了谁受益,“我想要……” 部分描述了他们的受益方式,而 “……以便……” 部分描述了为什么他们需要这个功能。这构成了开发该功能所需时间和成本的合理性。它可以用来确定接下来开发哪些功能的优先级。
这个用户故事是我们开始开发的地方。Wordz 应用程序的核心是它能够评估和评分玩家对单词的当前猜测。值得一看的是这项工作将如何进行。
将敏捷开发与 TDD 相结合
TDD 是敏捷开发的完美补充。正如我们在前几章中学到的,TDD 帮助我们改进设计并证明我们的逻辑是正确的。我们所做的一切都旨在尽快向用户交付无缺陷的工作软件。TDD 是实现这一目标的绝佳方式。
我们将使用的工作流程是典型的敏捷 TDD 项目流程:
-
选择一个优先考虑影响范围大的用户故事。
-
稍微思考一下目标设计。
-
使用 TDD 编写核心应用逻辑。
-
使用 TDD 编写连接核心与数据库的代码。
-
使用 TDD 编写连接 API 端点的代码。
这个过程会重复进行。它形成了在单元测试下编写核心应用逻辑的节奏,然后逐步扩展应用程序,将其连接到 API 端点、用户界面、数据库和外部 Web 服务。通过这种方式工作,我们在代码中保留了很大的灵活性。我们还可以快速工作,将精力集中在应用程序代码最重要的部分上。