活文档:与代码共同演进
上QQ阅读APP看书,第一时间看更新

1.1 一则来自活文档世界的故事

我们先讲一个故事。想象一个软件项目:你需要开发一个新的应用程序,作为公司大型信息系统的一部分。假设你是该项目的开发人员,任务是为新的忠实客户提供一种新折扣。

1.1.1 为什么需要这个功能

你见到了营销团队的Franck和专业测试员Lisa。你们三个人开始讨论这个新功能,提出问题,并找了一些具体的例子。Lisa问:“为什么需要这个功能?”Franck解释说,根本原因是为了奖励新的忠实客户,以游戏机制提高客户留存率。同时,他提供了维基百科上关于这个主题的链接。Lisa记下了主要观点和场景。

这一切都进行得很快,因为大家围坐在一起,沟通很容易。此外,因为使用了具体的例子,所以那些一开始不太清晰的需求也更容易理解和阐明了。所有需求都清楚之后,每个人回到自己的办公桌前开始工作。接下来轮到Lisa写下最重要的场景并发送给每个人了(上次,这是Franck的工作)。收到场景后,你就可以开始写代码了。

在你以前的工作经历中,工作流程不是这样的。以前,团队之间的沟通靠一些难以理解而且描述模糊的文档。现在,看着这些场景,你笑了。你很快将第一个场景转化为自动验收测试,观察它是否失败,然后根据测试结果修改代码,一直到测试结果显示为绿色(表示成功)为止。

你有了一种美好的感觉:能将宝贵的时间花在真正重要的事情上了,而不是其他事情上。

1.1.2 明天你就不再需要这个草图了

那天下午,两位同事Georges和Esther向团队询问需要做出的设计决策。你们围着白板,一边画着草图,一边快速评估每个选项。这时,你不需要用到太多UML(Unified Modeling Language,统一建模语言),只需要一些自定义方框和箭头来确保当下每个人都能理解。几分钟后,你们选定了一个方案。因为要彻底分开收到的订单和装运请求,所以你们计划在消息传递系统中使用两个不同的主题。

晚些时候可能会有人擦掉白板上的内容,所以Esther用手机对着白板拍了一张照片。但是她知道,不出半天,白板上的内容就会实现,之后就可以放心地删掉手机里的照片了。一小时后,当提交创建新的消息传递主题时,她在提交注释里提供的理由是“分开收到的订单和装运请求”。

第二天,前一天缺席的Dragos看到了新代码,想知道为什么代码变成了这样。他只要在线上运行git blame就能迅速得到答案。

1.1.3 抱歉,我们没有营销文档

一周后,新来的营销经理Michelle接替了Franck。Michelle比Franck更重视客户维系。她想知道应用程序中实现了关于客户维系的哪些功能,所以想看看相关的营销文档。结果,她很惊讶地发现根本没有营销文档。

“你说的不是真的吧?”她问道。但是,你很快向她展示了网站上在构建时生成的所有验收测试。页面顶部有一个搜索区域,她只要输入customer retention就能搜索到相关信息。她点击提交并看到了以下结果:

1  为了提高客户留存率
2  作为一名营销人员
3  我想为新的忠实客户提供一个折扣
4
5    Scenario: 新的忠实客户再次下单享10美元优惠
6    ...
7
8    Scenario: 新的忠实客户在过去一周内购买3次
9    ...

该结果列表展示了对新的忠实客户使用特殊折扣的场景。Michelle笑了。她甚至不需要浏览营销文档来查找所需的信息。这些场景描述的精确程度远远超出了她的预期。

Michelle问:“用欧元购买时也能享受同样的折扣吗?”你回答道:“我不确定代码能否完成不同货币间的转换,但我们试一下就知道了。”你打开IDE(Integrated Development Environment,集成开发环境),修改了验收测试中的货币类型,再次运行测试,结果测试失败了。所以,你知道还需要再加工一下代码让它支持货币转换。Michelle几分钟内就得到了她想要的答案。她开始想,与以前的工作环境相比,你的团队真有些特别。

1.1.4 你一直在用这个词,但并非其本意

第二天,Michelle又提出了一个新问题:purchase和order之间有什么区别?

按以前的做法,她会要求开发人员查看代码并解释其中的差异。但是,你的团队已经预见到了这个问题,所以这个项目的网站上包含一个词汇表。她问道:“这个词汇表是最新的吗?”“是的,”你回答,“每次构建过程中,这个词汇表都会根据代码自动更新。”她惊呆了。为什么不是每个人都这样做呢?尽管你很想详细地向她介绍Eric Evans的《领域驱动设计:软件核心复杂性应对之道》一书中的通用语言,但只是简单地回答道:“你需要让你的代码与业务领域保持一致。”要知道,你非常热衷于通用语言。

看着那个词汇表,Michelle发现有些词的定义混淆了,但之前没有人发现。她建议用正确的名称修复词汇表。但是,在这个团队里,修复操作有点不一样。你首先要做的是在代码中修复名称,接着重命名这个类并再次运行构建,然后……看!词汇表也一起修复了。所有人都很开心,你也新学了电子商务方面的一些业务知识。

1.1.5 给我看看完整的图,你就知道哪里有问题了

现在,你想删除两个模块之间不恰当的依赖关系,但是并不熟悉完整的代码库。因此,你让Esther提供一个依赖关系图,因为她最清楚模块之间的依赖关系。但是,即使是她也记不住每一个依赖关系。“我会从代码中生成一个依赖关系图。我一直想做这件事。这个过程可能需要几个小时,但是生成图之后就一劳永逸了。”Esther说。

Esther知道她可以使用一些开源库,从类或包中轻松地提取依赖关系,然后她很快将其中一个连接到Graphviz(一个能自动完成布局的神奇图表生成器)。几小时后,这个小工具就生成了依赖关系图。你得到了你想要的,很开心。她则又花了半小时将这个工具集成到构建中。

有趣的是,当Esther第一次看到生成的图表时,她发现了一些有意思的东西:两个模块之间不应该存在依赖关系。通过比较她脑中的图和根据实际系统生成的依赖关系图,很容易被找到了设计中存在的问题。

在下一个项目迭代中,设计问题就被修复了,而且在下一个构建中,依赖关系图也自动更新了。关系图变得更加清楚了。

1.1.6 活文档属于未来?不,是现在

这个故事描述的事情并不是发生在未来,它已经发生了,而且多年前就已经发生了。引用小说作家William Gibson的一句话:“未来已来,只是分布不均。”

工具是现成的,技术也是现成的。多年来,人们一直在做这些事,但这还不是主流。多么遗憾啊,因为这些概念对于软件开发团队来说是非常有用的。

在后续章节中,我们将介绍所有这些方法和许多其他方法,你将学习如何在项目中使用它们。