[译] 真相就在代码中

真相就在代码中

购物应用模型,requirementsascode 的示例代码前端

迟早有一天,每一个程序员都会听到这样一句话:java

“真相只能在一处找到:代码中。”react

—— Robert C. Martin,代码整洁之道android

但这句话是什么意思呢?ios

敏捷宣言中指出“可工做的软件赛过繁琐的文档”。git

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

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

相反,代码始终都能表达软件的行为。由于正是它定义了这些行为。数据库

这就是为何说真相存在于代码中。编程

为阅读你代码的人考虑

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

代码的读者多是一个编译器,解释器或是其余开发人员。

因此你的代码仅能编译经过是不够的。你还要保证其余开发人员可以读懂它。将来他们须要在你代码的基础上工做,修改它,扩展它。

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

整洁的代码应该能表达意图:使用者经过调用该方法能作什么。而不是怎么作

猜想一下这个方法是作什么的:

BigDecimal addUp(List<BigDecimal> ns){..}复制代码

若是是这么写呢:

BigDecimal calculateTotal(List<BigDecimal> individualPrice){..}复制代码

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

知识共享的重要性

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

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

X:咱们不能继续开发 foo 特性了。

Y:为何?

X:由于 Z 是惟一了解这块代码的人。咱们要改动的代码就是他开发的。

Y:好吧,为何不去问问他呢?

X:由于他病了/休假了/在开会/离职了。

Y:额……

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

有这方面的技术。结对编程就是一个不错的选择。或者是和其余开发人员坐下来,一块儿过一遍你写的代码。

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

故事

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

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

我不知道。

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

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

手套商店的例子

做为一个软件的用户,我想要达到指望的目的。好比,我想拥有一双新手套在冬天能够给个人手保暖。

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

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

几天之后,我收到手套。

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

第一章: 用例

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

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

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

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

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

用例的基础零件是使用户离指望结果更近一步的步骤。好比:“系统展现一个手套的列表。”

不是全部用户均可以执行某一步骤,只有特定用户组的成员(“行为者”)才能够这么作。例如,终端消费者买手套。销售人员向系统中添加新款手套的报价。

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

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

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

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

要理解代码,你须要一个简单方法来了解几件事情。

对于一个用例(例如“买手套”):

  • 步骤流程

对于每一个步骤:

  • 哪些行为者有执行的权限(也就是哪一个用户组)
  • 在哪些条件下,系统能够响应
  • 该步骤是自发的仍是基于用户交互
  • 系统响应

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

  • 用户事件(例如“用户输入配送信息”)
  • 伴随事件而来的数据

一旦我知道在哪里能够找到用例以及它的零件以后,我就能够深刻研究了。

第二章: 经过组件分解步骤

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

一个组件多是:

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

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

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

当阅读代码时,我但愿能了解如下这些内容:

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

第三章:组件作什么

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

这一般出如今领域模型中。领域模型使用和业务领域相关的术语。

举例来讲,手套能够是一个术语。订单也能够是一个术语。

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

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

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

讲述你的故事

你的故事可能看起来和上面提到的故事很类似,也可能彻底不一样。不论你的故事是怎样的,编程语言都给了你极大的自由来说述你的故事。

这是件好事情,由于它容许开发人员适应不一样的情景与需求。

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

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

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

模型用于放置领域数据

视图是客户端用户界面,好比 HTML 页面。这是用户事件的来源。

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

所以,若是多个开发人员使用 Rails,他们就知道在哪里能找到和故事中特定部分相关的代码。

他们能够在分享他们的看法时找出缺失的东西。而后,他们就能够进一步的就约定在哪里放置故事的模块达成一致。

若是这些适用于你,那就行了。但我想比这更进一步。

代码即需求

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

在敏捷开发中,如何建立软件维护文档?

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

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

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

可是在几年前,我开始思考:

若是真相在代码中,那么代码也应该能讲述真相。

换句话说:若是你很是当心的在代码中讲述你的故事,为什么还要再说一遍呢?

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

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

惟一可靠的来源:代码自身。

在许屡次尝试以后,我有了一些成果。我把它们发布在一个名为 requirementsascode 的 GitHub 项目中。

它是如何工做的

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

所以,基于 UseCaseModel,UseCaseModelRunner 控制了对用户可见的软件行为。

经过 requirementsascodeextract, 你能够从同一个配置运行程序的用例模型中生成文档。这样,文档就能够始终表达软件的行为了。

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

你的反馈能够帮我改进这个项目

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

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

到目前为止,requirementsascode 一直都是个人业余项目。

这就是为何我须要大家的帮助。请给我一些反馈。

你以为这个想法怎么样?你能想象它在你的软件上下文中有效吗?还有其余的反馈意见吗?

你能够在评论区给我留言或是在 TwitterLinkedIn 和我联系。

你能够 clone 这个项目并亲自尝试一下。

也能够帮助在代码中记录真相。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOSReact前端后端产品设计 等领域,想要查看更多优质译文请持续关注 掘金翻译计划

相关文章
相关标签/搜索