随便说说 living documentation
几年前在 oreilly 看到一本叫 《living documentation》的书,可惜当时烂尾了。
最近图灵出版了该书的中文翻译版,才想起来有这么回事。。。正好最近有时间,把这个坑了几年的东西给填上了。
简单来说,这本书基本可以叫 living java doc(误。我们可以先看看日常开发中会涉及到哪些文档:
- 需求文档
- 接口文档
- 业务词汇表文档
- 模块业务流程文档
- 系统间交互文档
- 系统架构文档
- 系统依赖文档
- 发布文档
- 示例文档(tutorial)
- 项目进度文档
- 案例文档
- 汇报文档
要写的类型文档还是不少的,无论哪种文档,都与项目的代码是脱节的。所以软件行业以往的文档编写方式有很多缺点:
- 文档易过时,因为与代码是分开的,改了代码忘改文档是很正常的,代码是新版本,而文档是历史版本
- 文档误导人,因为前一条,功能上的修改没有反映到文档上,读文档的人会被错误信息所误导
- 多余的工作量,工程师在写完代码之后还要再去写一遍文档(也可能是先写文档后写代码),基本是双倍工作量
- 文档的编写工具无统一的规范,大家喜欢不停地尝试新工具,从 word 到 markdown 到 asciidoc 到 notion
- 文档与真实的代码内容是脱节的,比如在 DDD 中常讲的业务词汇表,在文档与代码中可能用的不是同一个词,编码和看文档的时候需要脑内额外做映射
为了克服这些缺点,这本书提出了一些让代码变 living 的方法。既然代码和文档分开使得两者不易统一,那么我们就去追求把代码与文档写在一起,并通过技术手段把文档从代码中生成出来。
上面提到的几种文档,挨个来看看:
需求文档
因为需求最终会变成 BDD 测试,所以可以直接用 BDD 测试来生成这些需求文档。
词汇表
用 DDD 来做业务的话,一般可以用一些一眼就能看出含义的词对类型和接口做注解:@DomainService,@DomainEvent,@BusinessPolicy,@AbastractFactory,@Adapter,@BoundedContext,@ValueObject。
同时为了强调一些业务概念,也可以用一些自定义的词汇,比如:@CoreConcept,@CoreBehavior,@StateMachine。
然后通过扫描代码,就可以直接把项目中的上述概念抽出,每次代码有更新,能够自动生成业务的词汇表。
自动接口文档
这个应该很多人也见过了,就是类似 swagger 的项目。框架做的好的企业基本也都会有基于类似 swagger 的手段来做自己的接口平台(其实做的好的没几个)。下面是在 beego 中使用 swagger 的例子:
因为 Go 语言不支持 annotation,所以这些在注释里写的注解没有语法层面的检查和约束,这点上可能比那些支持的语言算是个弱势。
业务流程文档
在代码中编写相应的流程节点和前后关联节点:
通过扫描代码生成文档:
跨系统的交互流程:
扫描生成相应的文档,其实和前面的差不多:
除了上面这些风格的注解,以前就已经存在从文本生成文档的手段,比如 plantuml,graphviz
上图这样的 plantuml 的文本,本身就可以和代码一起在同一个代码仓库中进行管理。
在一些业务逻辑中,涉及到难懂的逻辑,可以用 @Policy 或者 @BusinessConvention 来说明相应的业务决策原因。
发布文档
现在 Github 上的很多开源项目,已经可以根据 commit message 自动生成 release note 了。这归功于其 commit message 本身就有一定的规范:
只要用简单的 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)。