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