文档同构:文档与代码的双向绑定

  先说一下对于结论的定义:

  文档同构是一种将代码与文档保持一致的技术理念,它能读取格式化的文档,并将文档自动加入到代码中,如以注释的形式或者是只在 IDE 呈现;同时,还能将读取代码中的文档,自动更新到文档中,或是对文档进行测试和差异对比。

  最近,我边设计架构描述语言Forming,边围绕于这个概念体系编写新书。期间,在翻阅了一系列的架构书籍,如在《领域驱动设计》的 Highlighted Core 一节中提出了一个“精炼文档”的概念。

  精炼文档是编写 一个非常简短的文档(3 ~ 7 页,每页内容不必太多),用于描述核心域和核心元素之间的主要交互过程 。

  写文档的痛苦,我想大部分程序员是懂得的,它的痛苦主要体现在两方面:自己不想文档、自己想看文档的时候没有。而如书中所说,独立文档的常见风险主要是在两个方面:

  文档可能得不到维护

  文档可能没有人阅读

  由于有多个信息来源,文档可能达不到简化复杂性的目的

  同样的,对于代码中的注释来说,问题是相似的,可以说:注释即文档。而且,另外一个常见的问题是,项目中可能即有文档,还有注释,又有代码,三者的不一致性是一个更严重的问题。

  因此,在这篇文章里,我们将探讨一下文档同构,即如何实现注释与文档的自动化同步。事实上,我一直在想的是,注释-代码-文档的一致性检查,可能我会在下一篇文章里,结合契约式设计一起讨论。

  在设计时,诸如于 Forming 或者 UML 这样的架构设计工具时,我们所做的事情是:正向生成,诸如于通过 UML 直接生成相关的代码。从流程上,它一般是:

  编写、设计 DSL。设计描述这个领域的 DSL(领域特定语言)。

  编写代码。编写编程工具提供的 DSL 描述系统 。

  工具生成代码。即工具解析相关的 DSL,并生成目标系统上的代码。

  在演进时,诸如于采用 ArchGuard 或者 Guarding 这样的架构守护工具时,我们所做事情是:反向设计,即分析代码将其与系统原先的设计进行对比。从过程上,它一般是:

  自动化分析。寻找合适的工具对开发人员编写的代码进行自动化分析。

  设计比对。将分析完的数据结构与 DSL 生成的数据结构进行对比。

  对于文档来,它也应该如此,所以我们可以设计一个文档工具,用来进行注释的自动生成,并识别系统中的注释,从而与原来的文档进行比对。

  基于上述的两个基本的思想,我们就可以定义出文档同构的概念:

  文档同构是一种将代码与文档保持一致的技术理念,它能读取格式化的文档,并将文档自动加入到代码中,如以注释的形式或者是只在 IDE 呈现;同时,还能将读取代码中的文档,自动更新到文档中,或是对文档进行测试和差异对比。

  在起初我构思的时候,我只想把这概念用在注释与代码的自动化同步上,而随着我对于相关内容的进一步深入了解,我发现这是一个很有的东西,我将会在后续的模式上展开相关的介绍 。

  在我设计 Forming 实现时,我尝试着去总结了一些要点:

  高亮核心。即区分核心域与通用域,将重要精力投入到系统的核心部分设计。

  代码与文档双向绑定。即上一部分所说的正向生成与反馈设计。

  文档代码化。即设计领域特定语言来描述用描述,通过结构化的形式来实现与代码的同构。

  在一个系统中,它必然会充斥着大量的领域相关的概念,我们无法展开每一个的概念的讨论。所以,在设计的时候,我们向《领域驱动设计》一书所说,提炼出系统的核心部分。结合我们在进行统一语言相关设计时,会采取词汇表相关的概念。所以,在这部分的设计里,它由两部分组成:

  概念词汇表。可以基于简易的表达方式来维护,诸如 Markdown 里的列表,又或者是 CSV 形式。

  精炼文档。从概念上来说,我偏向于使用 DSL 来设计。但是使用 YAML 或者 CSV 的形式,它在解析和维护上会比较简单。

  由这两部分的文档,形成系统的代码与文档的映射。

  对于文档同构工具来说,它的难点依旧是:

  编程语言的解析。即生成代码的定制数据模型,记录关键的概念所在行数、文件、位置等相关的信息,以便于自动修改。

  代码与文档的显示与更新机制。即我们是否显示文档,是否需要对文档进行校正等。

  从实现来说,现有的技术都已经比较成熟了。

  最后,再回顾一下我对于文档代码化的定义:

  文档代码化,将文档以类代码的领域特定语言的方式编写,并借鉴软件开发的方式(如源码管理、部署)进行管理。它可以借助于特定的工具进行编辑、预览、查看,又或者是通过专属的系统部署到服务器上。面向非技术人员的文档代码化的一种常见架构模式是:编辑-发布-开发分离』

  在那篇《文档代码化的文章里,我们定义了文档代码化的三个主要特征:

  使用领域特写语言编写内容。如 markdown

  可以通过版本控制系统进行版本控制。如 git

  与编程一致的编程体验。

  在这种模式下,我们也可以支持起多个代码仓库,诸如于微服务架构的系统。

  在过去的一段时间里,在思考这个设计的时候,我便在思考文档和代码如何相处,便也顺便总结了一些模式。

  Rust 对文档的哲学,是不要单独写文档,一是代码本身是文档,二是代码的注释就是文档。Rust 不但可以自动抽取代码中的文档,形成标准形式的文档集合,还可以对文档中的示例代码进行测试。

  去年,我思考文档代码的主要原因是看到了 Rust 中的文档测试: rustdoc支持执行文档示例,作为测试,以此可以确保文档是最新的和有效的。

  嗯,我们所做的模式,就是在这的基础之上,做一些升级,即将业务概念文档同步到代码中。

  可执行的文档即文档是可编译、可直接运行的。这个是在看到 rustdoc之后,我尝试性地编写了 Exemd 项目。可以自动化 Markdown 文件中中的一些代码,支持:Rust、Ruby、JavaScript、TypeScript 等语言,并支持依赖的形式,即可以通过//引入了 Rust 的color依赖。

  // exemd-deps: colored;version=1.8.0

  externcrate colored;

  usecolored::*;

  fn main {

  println!("{} {} !","it".green,"works".blue.bold);

  }

  而这种模式是以文档为主的模式,在我最初的设计里,它还可以直接 import 可执行的源码。它适用于我在写文章和写书的模式下进行的。

  代码注释分离,即我们可以不需要在代码里写注释,注释是写在代码中的其它地方注释,是写给人看的,诸如于采用后续的 IDE 呈现的方式。

  这个模式之下,注释是以文档的形式存在的,但是不编写在代码中,是独立存在的。我们可以使用 IDE 插件方式加载注释。

  基于云 IDE 的理念之下与及 云研发架构模式,它就可以解决文档在传输中不存在的问题。

  自我开始研究“云研发”以来,我一直在研究对软件研发的代码化,从各类的自动化到各类的代码化,如设计各类的领域特定语言。文档也是其中的重要一环,我们的目的应该是:注释-代码-文档自动一致性。

posted @ 2022-02-15 17:13  ebuybay  阅读(91)  评论(0编辑  收藏  举报