> * 原文地址：[The truth is in the code](https://medium.freecodecamp.com/the-truth-is-in-the-code-86a712362c99)
> * 原文作者：[Bertil Muth](https://medium.freecodecamp.com/@BertilMuth)
> * 译文出自：[掘金翻译计划](https://github.com/xitu/gold-miner)
> * 译者：[loveky](https://github.com/loveky)
> * 校对者：[sunui](https://github.com/sunui) [yzgyyang](https://github.com/yzgyyang)

# 真相就在代码中 #

![](https://cdn-images-1.medium.com/max/800/1*Fw8F2fRNVfkcE-0VGyDZhQ.png)

[购物应用](https://github.com/bertilmuth/requirementsascode/tree/master/requirementsascodeexamples/shoppingappjavafx)模型，[requirementsascode](https://github.com/bertilmuth/requirementsascode) 的示例代码

早晚有一天，每个程序员都会听到这样一句话：

> “真相只能在一处找到：代码中。”

> —— Robert C. Martin，[代码整洁之道](https://www.amazon.de/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882)

但这句话是什么意思呢？

[敏捷宣言](http://agilemanifesto.org/)中指出“可工作的软件胜过繁琐的文档”。

即便开发人员一直都在撰写繁琐的文档以描述软件的行为。代码也在做着相同的事。

代码注释、外部规范也在记录软件的行为，但是当代码被修改时它们可能不会被同步更新。然后它们很快就不再能表达代码的行为了。

相反，代码**始终**都能表达软件的行为。因为正是它定义了这些行为。

这就是为什么说真相存在于代码中。

### 为阅读你代码的人考虑 ###

代码是一种文档。任何文档都应该能够被它的读者理解。

代码的读者可能是一个编译器，解释器或是其他开发人员。

所以你的代码仅能编译通过是不够的。你还要保证其他开发人员能够读懂它。未来他们需要在你代码的基础上工作，修改它，扩展它。

一个关于使代码容易阅读的常见建议是编写整洁的代码。整洁的代码指的是使用易懂的语言命名变量和方法的代码。这也使得很多代码注释变得不必要了。

整洁的代码应该能表达意图：使用者通过调用该方法能**做什么**。而不是**怎么做**。

猜测一下这个方法是做什么的：

```java
    BigDecimal addUp(List<BigDecimal> ns){..}
```

如果是这么写呢：

```java
    BigDecimal calculateTotal(List<BigDecimal> individualPrice){..}
```

整洁的代码是一个好主意。但我认为这还不够。

### 知识共享的重要性 ###

当有一个新需求时，你需要评估实现该需求对现有代码的影响。

如果你的软件已经存在了一段时间，这可能会是个挑战。我经常听到这样的对话：

**X**：我们不能继续开发 **foo** 特性了。

**Y**：为什么？

**X**：因为 **Z** 是唯一了解这块代码的人。我们要改动的代码就是他开发的。

**Y**：好吧，为什么不去问问他呢？

**X**：因为他病了/休假了/在开会/离职了。

**Y**：额……

事情就是这样。要想知道你的代码是否容易被理解，至少要有个人去尝试阅读它。

有这方面的技术。[结对编程](https://en.m.wikipedia.org/wiki/Pair_programming)就是一个不错的选择。或者是和其他开发人员坐下来，一起过一遍你写的代码。

然而，如果参与一个项目的开发人员太多呢？如果一个研发团队的成员变化了呢？这使得编写容易被其他人理解的代码变得更困难了。

### 故事 ###

整洁代码带给你正确的**用语**。

问题是：你在代码中使用它们讲述怎样的**故事**呢？

我不知道。

但是对于一个典型的业务应用，我很清楚我希望在代码中读到怎样的故事。

在介绍一个简单的例子之后，我会简要描述那个故事。

### 手套商店的例子 ###

作为一个软件的用户，我想要[达到期望的目的](https://medium.freecodecamp.com/nobody-wants-to-use-software-a75643bee654?source=linkShare-a74297325869-1489339708)。比如，我想拥有一双新手套在冬天可以给我的手保暖。

因此我上网找到了一家新开的线上手套专卖店。该店铺的网站可以让我购买手套。“基本流程”（也被称为“正常用例”）大概是这样的：

- 系统以一个空购物车开始。
- 系统展示一个手套的列表。
- 我添加喜欢的手套到购物车。系统将这些手套加入到我的订单中。
- 我选择结账。
- 我输入配送信息和支付详情。系统保存这些信息。
- 系统展示一份订单的详细信息。
- 我确认信息。系统开始配送我的订单。

几天以后，我收到手套。

### 下面是我想在代码中读到的故事 ###

### 第一章: 用例 ###

故事的第一章是关于用例的。当我阅读代码时，我希望在代码中按照某个用例一步一步的达到期望的结果。

从一个用户的角度来看，我想弄明白当出现错误时系统是如何应对的。

我还想搞清楚流程中可能的分支。例如，用户企图从支付详情页回到配送信息页时会发生什么？用户可以这样操作吗？

我想知道每一个用例中的不同部分对应哪块代码。

#### 那么一个用例由哪些零件构成呢？ ####

用例的基础零件是使用户离期望结果更近一步的**步骤**。比如：“系统展示一个手套的列表。”

不是所有用户都可以执行某一步骤，只有特定用户组的成员（“**行为者**”）才可以这么做。例如，终端消费者买手套。销售人员向系统中添加新款手套的报价。

某些步骤由系统主动执行。例如在展示手套列表时。无需用户交互。

而有的步骤则是用户交互的结果。系统**响应**某些**用户事件**。例如：用户输入配送信息。系统保存这些信息。

我想知道这些事件中包含哪些**数据**。配送信息包含用户的姓名，地址等等。

用户在任何给定的时间点上只能执行一部分步骤。用户只有在输入配送信息后才可以填写支付详情。因此每一个用例中都有一个定义了该用例中所有步骤执行顺序的**流程**。以及一个根据系统当前状态，表示系统是否可以响应用户的操作的**条件**。

#### 要理解代码，你需要一个简单方法来了解几件事情。 ####

对于一个用例（例如“买手套”）：

- **步骤**的**流程**

对于每个步骤：

- 哪些**行为者**有执行的权限（也就是哪个用户组）
- 在哪些**条件**下，系统可以响应
- 该步骤是**自发的**还是基于**用户交互**
- **系统**的**响应**

对于每个基于用户交互的步骤：

- **用户事件**（例如“用户输入配送信息”）
- 伴随事件而来的**数据**

一旦我知道在哪里可以找到用例以及它的零件之后，我就可以深入研究了。

### 第二章: 通过组件分解步骤 ###

让我们把你软件中一个封装的，可替换的组成单位称为一个**组件**。一个组件的**职责**可以被该组件之外的世界访问。

一个组件可能是：

- 一个技术组件，比如数据库
- 一个服务，比如“购物车服务”
- 你的领域模型中的一个实体

这取决于你的软件设计。但不论你的组件是什么：你通常都需要若干个组件配合来实现用例中的某个步骤。

让我们来看看“系统展示一个手套的列表”这一步骤中的**系统响应**。你很可能需要开发至少两项**职责**。一个用来在数据库中查找手套，另一个用来把这些数据转变为一个页面。

当阅读代码时，我希望能了解以下这些内容：

- 一个组件的**职责**是什么。例如：对数据库来说是“查找手套”。
- 每个职责的**输入**/**输出**是什么。输入的例子：查找手套的规则。输出的例子：手套列表。
- 谁来**协调**这些职责。例如：首先查找手套，然后将结果转换成一个网页。

### 第三章：组件做什么 ###

组件的代码用来实现它的职责。

这通常出现在**领域模型**中。领域模型使用和业务领域相关的术语。

举例来说，手套可以是一个术语。订单也可以是一个术语。

领域模型用于描述每个术语的**数据**。每个手套都有颜色，品牌，尺码，价格等数据。

领域模型还用来描述基于这些数据的运算。一个订单的总价是该订单中用户购买的所有手套价格的总和。

一个组件还可以是类似数据库这样的技术组件。该组件的代码就要解决如何在数据库中创建、查找、更新、删除数据的问题。

### 讲述你的故事 ###

你的故事可能看起来和上面提到的故事很相似，也可能完全不同。不论你的故事是怎样的，编程语言都给了你极大的自由来讲述你的故事。

这是件好事情，因为它允许开发人员适应不同的情景与需求。

这也承担了由开发人员讲述太多不同故事（哪怕是针对同一个产品）带来的风险，使得理解其他人编写的代码变得更困难了。

解决这个问题的一个办法是使用设计模式。它们可以帮你合理的组织代码。你可以在团队中甚至是团队间就这种通用结构达成一致。

例如：Rails 框架就是基于众所周知的模型、视图、控制器模式的。

模型用于放置**领域数据**。

视图是客户端用户界面，比如 HTML 页面。这是**用户事件**的来源。

控制器在服务器端接收用户事件。它负责**流程**。

因此，如果多个开发人员使用 Rails，他们就知道在哪里能找到和故事中特定部分相关的代码。

他们可以在分享他们的见解时找出缺失的东西。然后，他们就可以进一步的就约定在哪里放置故事的模块达成一致。

如果这些适用于你，那就好了。但我想比这更进一步。

### 代码即需求 ###

很多客户问我如何处理长期的软件文档。

在敏捷开发中，如何创建软件维护文档？

到目前为止都实现了哪些需求？

在哪里能找到它们在代码中的实现？

很久以来我都没有满意的答案。当然，除了良好编写的自动化测试，整洁的线上代码，以及共同认知的重要性之外。

但是在几年前，我开始思考：

> 如果真相在代码中，那么代码也应该能讲述真相。

换句话说：如果你非常小心的在代码中讲述你的故事，为何还要再说一遍呢？

需要有更好的方法。它可以提取故事，并基于它生成文档。非技术相关方也能理解的文档。

始终保持最新状态的文档，因为正是它的来源定义了软件行为。

唯一可靠的来源：代码自身。

在许多次尝试之后，我有了一些成果。我把它们发布在一个名为 [requirementsascode](https://github.com/bertilmuth/requirementsascode) 的 GitHub 项目中。

### 它是如何工作的 ###

![](https://cdn-images-1.medium.com/max/800/1*rZAA0h24T9SdEZYdE7stIQ@2x.png)

- UseCaseModel 实例用于定义**行为者**，**用例**，它们的**流程**以及**步骤**。它讲述故事的第一章。在本文的开头你能找到一个这种模型的例子。
- 用例模型配置 UseCaseModelRunner 实例。每个用户都有自己的运行程序，因为每个用户选择执行的用例路径可能不同。
- 运行程序通过调用后端的**系统响应**来响应前端的**用户事件**。前端只能通过运行程序和后端通信。
- 但是运行程序只有当用户在**流程**中的正确位置且满足步骤的**条件**时才会响应用户。例如：运行程序只有在用户已经输入了配送信息之后才会响应“进入支付详情页”事件。
- **system reaction** 是一个单例方法。方法内部负责协调不同组件以实现该步骤，就像在第二章中描述的那样。
- 第三章已经超出了 requirementsascode 的范畴。它留给应用程序决定。这使得 requirementsascode 可以兼容任意的软件设计。

因此，基于 UseCaseModel，UseCaseModelRunner 控制了对用户可见的软件行为。

通过 [requirementsascodeextract](https://github.com/bertilmuth/requirementsascode/tree/master/requirementsascodeextract)， 你可以从同一个配置运行程序的用例模型中生成文档。这样，文档就可以始终表达软件的行为了。

Requirementsascodeextract 使用了 FreeMarker 模板引擎。它允许你生成任何你喜欢的纯文本文档。例如 HTML 页面。进一步的处理可以生成其它格式的文档，例如 PDF。

### 你的反馈可以帮我改进这个项目 ###

我从几年前就开始了 requirementsascode 这个项目，直到最近才将它公开。与最初相比，它已经得到了极大的改善。

为了了解这种方法是否具有可扩展性，我尝试在一个有数千行代码的项目中使用。被证明是有效的，我也在一些更加小型的应用中尝试过。

到目前为止，requirementsascode 一直都是我的业余项目。

这就是为什么我需要你们的帮助。请给我一些反馈。

你觉得这个想法怎么样？你能想象它在你的软件上下文中有效吗？还有其他的反馈意见吗？

你可以在评论区给我留言或是在 [Twitter](https://twitter.com/BertilMuth) 或 [LinkedIn](https://www.linkedin.com/in/bertilmuth) 和我联系。

你可以 [clone](https://github.com/bertilmuth/requirementsascode) 这个项目并亲自尝试一下。

也可以[帮助](https://github.com/bertilmuth/requirementsascode/blob/master/CONTRIBUTING.md)在代码中记录真相。

---

> [掘金翻译计划](https://github.com/xitu/gold-miner) 是一个翻译优质互联网技术文章的社区，文章来源为 [掘金](https://juejin.im) 上的英文分享文章。内容覆盖 [Android](https://github.com/xitu/gold-miner#android)、[iOS](https://github.com/xitu/gold-miner#ios)、[React](https://github.com/xitu/gold-miner#react)、[前端](https://github.com/xitu/gold-miner#前端)、[后端](https://github.com/xitu/gold-miner#后端)、[产品](https://github.com/xitu/gold-miner#产品)、[设计](https://github.com/xitu/gold-miner#设计) 等领域，想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)。
