4

文档同构:如何实现文档与代码的双向绑定?

 3 years ago
source link: https://www.phodal.com/blog/isomorphism-document/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Posted by: Phodal Huang Sept. 13, 2021, 8:01 p.m.

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

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

引子 1:文档与注释的痛点

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

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

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

  1. 文档可能得不到维护
  2. 文档可能没有人阅读
  3. 由于有多个信息来源,文档可能达不到简化复杂性的目的

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

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

引子 2:正向生成与反馈设计

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

  1. 编写、设计 DSL。设计描述这个领域的 DSL(领域特定语言)。
  2. 编写代码。编写编程工具提供的 DSL 描述系统 。
  3. 工具生成代码。即工具解析相关的 DSL,并生成目标系统上的代码。

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

  1. 自动化分析。寻找合适的工具对开发人员编写的代码进行自动化分析。
  2. 设计比对。将分析完的数据结构与 DSL 生成的数据结构进行对比。

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

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

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

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

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

  1. 高亮核心。即区分核心域与通用域,将重要精力投入到系统的核心部分设计。
  2. 代码与文档双向绑定。即上一部分所说的正向生成与反馈设计。
  3. 文档代码化。即设计领域特定语言来描述用描述,通过结构化的形式来实现与代码的同构。

高亮核心:区分核心域与通用域

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

  • 概念词汇表。可以基于简易的表达方式来维护,诸如 Markdown 里的列表,又或者是 CSV 形式。
  • 精炼文档。从概念上来说,我偏向于使用 DSL 来设计。但是使用 YAML 或者 CSV 的形式,它在解析和维护上会比较简单。

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

代码与文档双向绑定

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

  1. 编程语言的解析。即生成代码的定制数据模型,记录关键的概念所在行数、文件、位置等相关的信息,以便于自动修改。
  2. 代码与文档的显示与更新机制。即我们是否显示文档,是否需要对文档进行校正等。

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

文档代码化:领域特定语言设计

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

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

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

  • 使用领域特写语言编写内容。如 markdown
  • 可以通过版本控制系统进行版本控制。如 git
  • 与编程一致的编程体验。

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

文档同构模式

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

文档同构文档模式:文档测试

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

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

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

文档同构模式:可执行的文档

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

// exemd-deps: colored;version=1.8.0 
extern crate colored;

use colored::*;

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

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

文档同构模式:代码注释分离

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

文档同构模式:IDE 自动呈现注释

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

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

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

欢迎围观 Forming:https://github.com/inherd/forming


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK