题图 | © Yunshan Xia
几年前在 O'Reilly 看到一本叫 Living Documentation 的书,可惜当时没读完。
最近图灵出版了该书的中文翻译版《活文档:与代码共同演进》,才想起来有这么回事......正好有时间,把这个坑了几年的东西给填上了。
简单来说,这本书基本可以叫 Living Java Doc (误。我们可以先看看日常开发中会涉及哪些文档:
需求文档
接口文档
业务词汇表文档
模块业务流程文档
系统间交互文档
系统架构文档
系统依赖文档
发布文档
示例文档(tutorial)
项目进度文档
案例文档
汇报文档
要写的类型文档还是不少的,无论哪种文档,都与项目的代码是脱节的。所以软件行业以往的文档编写方式有很多缺点:
文档易过时,因为与代码是分开的,改了代码忘改文档是很正常的,代码是新版本,而文档是历史版本
文档误导人,因为前一条,功能上的修改没有反映到文档上,读文档的人会被错误信息所误导
多余的工作量,工程师在写完代码之后还要再去写一遍文档(也可能是先写文档后写代码),基本是双倍工作量
文档的编写工具无统一的规范,大家喜欢不停地尝试新工具,从 Word 到 Markdown 到 Asciidoc 到 Notion
文档与真实的代码内容是脱节的,比如在 DDD 中常讲的业务词汇表,在文档与代码中可能用的不是同一个词,编码和看文档的时候需要脑内额外做映射
为了克服这些缺点,《活文档》提出了一些让代码变 living 的方法。既然代码和文档分开使得两者不易统一,那么我们就去追求把代码与文档写在一起,并通过技术手段把文档从代码中生成出来。
![](https://i-blog.csdnimg.cn/blog_migrate/f835b31b387a45826508d433afff87ec.jpeg)
上面提到的几种文档,挨个来看看。
需求文档
因为需求最终会变成 BDD 测试,所以可以直接用 BDD 测试来生成这些需求文档。
词汇表
用 DDD 来做业务的话,一般可以用一些一眼就能看出含义的词对类型和接口做注解:@DomainService,@DomainEvent,@BusinessPolicy,@AbastractFactory,@Adapter,@BoundedContext,@ValueObject。
同时为了强调一些业务概念,也可以用一些自定义的词汇,比如:@CoreConcept,@CoreBehavior,@StateMachine。
然后通过扫描代码,就可以直接把项目中的上述概念抽出,每次代码有更新,能够自动生成业务的词汇表。
自动接口文档
这个应该很多人也见过了,就是类似 swagger 的项目。框架做得好的企业基本也都会有基于类似 swagger 的手段来做自己的接口平台(其实做得好的没几个)。下面是在 beego 中使用 swagger 的例子:
![](https://i-blog.csdnimg.cn/blog_migrate/1d9ed9b69acd992d07304b77c2337218.jpeg)
因为 Go 语言不支持 annotation,所以这些在注释里写的注解没有语法层面的检查和约束,这点上可能比那些支持的语言算是个弱势。
业务流程文档
在代码中编写相应的流程节点和前后关联节点:
![](https://i-blog.csdnimg.cn/blog_migrate/7be576ac87c693573e51434423600971.jpeg)
通过扫描代码生成文档:
![](https://i-blog.csdnimg.cn/blog_migrate/78fdb787381b1c41b9e2eb72665c2149.jpeg)
跨系统的交互流程:
![](https://i-blog.csdnimg.cn/blog_migrate/10ec2f2d7cca0ff15280a8c399490218.jpeg)
扫描生成相应的文档,其实和前面的差不多:
![](https://i-blog.csdnimg.cn/blog_migrate/93305cacacb95a0d6e3cfeb41b7510e2.jpeg)
除了上面这些风格的注解,以前就已经存在从文本生成文档的手段,比如 plantuml、graphviz:
上图这样的 plantuml 的文本,本身就可以和代码一起在同一个代码仓库中进行管理。
在一些业务逻辑中,涉及难懂的逻辑,可以用 @Policy 或者 @BusinessConvention 来说明相应的业务决策原因。
发布文档
现在 GitHub 上的很多开源项目,已经可以根据 commit message 自动生成 release note 了,这归功于其 commit message 本身就有一定的规范:
![](https://i-blog.csdnimg.cn/blog_migrate/06eee553fa35d00c5b0af149971df8cf.jpeg)
只要用简单的 bash 脚本就可以将两个版本之间的 commit message 进行汇总,并写出完整规范的 release note。
国内不少开源项目现在还是人肉写 release note 的。
tutorial
新人入职需要看一些代码范例,如果项目内有写的规范的代码,可以专门挑出来作为例子:@Exemplar(“这个订单流程是一个很好的 CQRS 学习示例”)。扫描代码找到所有的 @Exemplar 注解就可以成为 tutorial 文档了。
稳定文档
稳定文档指的是那些基本不怎么变动的文档,这种文档和代码分开是可以接受的,为了使我们的文档稳定,应该遵循一些基本的编写原则,比如:
不要带公司的那些容易变化的信息:公司名、子公司、品牌、商标等
如果在文档中附带有链接,应该从非稳定文档指向稳定文档,比如你可以从自己的 blog 贴一个 wikipedia 的链接,但是如果你去编辑 wiki 并粘贴了个人独立 blog 的链接,那么可能就不太合适
不要复制粘贴,尽量用链接引用原有内容
汇报文档
如果是项目进度相关的内容,可以用 @wip 或 @pending 之类的注解。
如果是给老板展示的案例,老板忙得没时间,所以可以用 @keyexample 把这些需要单独展示的案例摘出来。
如果是给新人学习的,除了前面说到的 tutorial,还可以有 @normalcase、@specs 等。
架构文档
一种是单模块架构,常见的是 DDD 中的六边形架构,可以引入 DDD 中的关键词,并生成最终的文档,比如 @HexagonalArchitecture.DomainModel、@Adapter,最终生成的文档是下面这样的:
如果是跨系统的文档,比如 CQRS 之类的,本身也是 Given(event)、When(cmd)、Then(output event) 的形式,所以用 BDD 可以生成这样的文档,前文有提到。
运行时文档
作者认为 zipkin 和 dapper 这种 tracing 系统也是一种形式的文档。服务发现系统中绘制出的服务依赖也是一种文档。
声明式配置
声明系统配置、资源需求、依赖的,也可以生成文档。
从这个意义上讲 K8s 的 yaml 既是文档,也是代码。
架构全景图
当我们可以将所有文档都通过扫描代码生成之后,最好能够将这些文档管理在统一的架构全景图中,相应的全景图可能也可以用一些注解的辅助手段来生成,比如 @Layer(LayerType.INFRASTRUCTURE)、@Repository(aggregateRoot = Customer.class)。
| 图书推荐
作者:西里尔·马特雷尔
译者:黄晓丹
| 图书特色
写文档也可以像写代码一样有趣、省力
应用领域驱动设计,让代码与文档相辅相成
领域驱动设计布道师张逸作序推荐
QECon 大会发起人朱少民、格蠹科技创始人张银奎、ThoughtWorks 首席测试与质量咨询师刘冉联合推荐
《活文档:与代码共同演进》系统地阐述了计算机软件开发各个阶段文档写作的步骤、内容、方法、工具、特点和要求,详尽指导软件开发人员和文档开发工程师如何写出规范的文档,包括软件文档的概念和内容、软件文档编写的原则和步骤、软件文档的管理和维护,对可行性研究报告、软件需求报告、软件测试计划等文档的写作方法和写作技巧。
Amazon 读者给这本书打出了 4.4 星的好评,本书一上市就受到了国内读者的欢迎。文档问题是令每个程序员头痛的问题,希望大家通过这本书解决尽可能多的问题。
京东传送门(7.3 折)
图 灵 社 群
点个「在看」