Blog | Phodal - A Growth Engineerhttp://www.phodal.com/blog/2021-09-25T07:07:30.575591+00:00BlogAPI 库的文档体系支持:主流编程语言的文档设计2021-09-25T07:05:20+00:002021-09-25T07:07:30.575591+00:00Phodal Huanghttp://www.phodal.com/blog/author/root/http://www.phodal.com/blog/api-ducumentation-design-dsl-base/去年,我们在那篇《[编程语言的 IDE 支持](https://www.phodal.com/blog/language-in-ide/)》详细讨论了在不同 IDE、编辑器里,它们是如何提供对于编程语言的支持。在这一篇文章里,我们将不那么详细地讨论一下:不同的编程语言如何提供文档支持?如此一来,也能在未来为 [Datum Lang](https://github.com/datum-lang/datum) 提供相关的理论体系支持。这里所指的编程语言的文档体系,主要是指**语言标准库中的文档**。
## 新一代编程语言的文档体系
作为一个程序员,和诸多人一样,我并不热衷于在代码中写文档/注释。只是呢,诸多的情况下,我们依旧不得不写文档,如编程语言的标准库、API、SDK 等。在各个语言中,它们有各自不同的实践,有各自不同的特点。
起先,我只分析过 Rust 语言中的 `rustdoc`,分析它是如何自动化处理相关文档的。后来,联想到了其它语言的文档形式是不是也会类似。接着,便尝试性的整理自 2012 年后“比较”(相对,至少是我听得多的)主流的语言的文档方式。
于是,便花了点时间,从相关的代码库中**快速**梳理而来。因时间有限,加之不擅长某些语言。所以,结论上的可能并不是这么完整,欢迎各位读者指正。
| 语言 | 文档编写方式 | 文档代码一致性 | 源码 |
|--------|---------------------------------------------------------------|------------------------------------------|----------------------------------------------------------------------------------|
| Rust | 定制 markdown <br/> 1.对语法部分处理(默认 Rust 语言) | 文档测试 执行不报错 断言使用 assert | https://doc.rust-lang.org/rustdoc/ |
| Julia | 定制 markdown 借助 <br/> 1. index.md 来编排 <br/> 2. 自定义语法:note <br/> 3. 自定义类型:@contents | 文档测试 <br/> 1. 自定义标签:jldoctest <br/> 2. 使用 #output 进行断言(可选) <br/> 3. 内建 setup 方法 | https://github.com/JuliaDocs/DocumenterTools.jl |
| Dart | 定制 markdown <br/> 1. 使用 [xx] 来链接元素 | 不支持文档测试 | https://github.com/dart-lang/dartdoc
| Kotlin | 定制 markdown <br/> 1. 使用 [xx] 来链接元素 2. 自定义标签(如 @param) | 没有 | https://github.com/Kotlin/dokka/ |
| Swift | 定制 markdown <br/> 1. 自定义 callout 类型的系列标注(如 copyright、date、important 等) | 过去支持文档测试 | https://github.com/SwiftDocOrg/swift-doc <br/> https://github.com/SwiftDocOrg/SwiftMarkup |
从文档编写方式上来看,Rust、Julia、Dart、Kotlin、Swift 的文档工具都是相似的:基于 markdown 语法的基础上,进行了一系列的定制。如 Swift 文档中添加的一些特别的标准,如关于复杂度(`Complexity`)的特别说明,便使用了类似的方式来实现。而像 Julia 语言里的 DocumenterTools 则做了大量的定制,则可以基于 `index.md` 对内容进行特别处理,并添加大量的自定义语法。
从文档代码一致性上来看,从我初步阅读代码的情况来看,Rust、Julia 的文档工具都会校验文档中的代码是不是正常的,并能对其进行测试。如 Rustdoc 会编译、运行文档中的代码,可以通过 Rust 自身提供的断言语法(如 `assert_eq!`)进行测试。而像 Julia 里的 DocumenterTools 则做了更多的定制,如可以为文档添加 `setup` 方法,还能进行一系列相关的断言等。
除此,在 R 语言中提供的 R Markdown 也比较有意思,它提供了一种可执行文档的新思路,即让 markdown 与代码有机的结合在一起,结合 pandoc 构建了新的文档可能性。后续,可能会在文档体验设计相关的文章中,进行详细的介绍。
### Rust 的文档测试示例
说了那么多,让我们先简单看个示例。如下是一段 Rust 中的注释代码:
```
# Examples
```rust
assert_eq!(2 + 2, 4);
```
```
Rust 在遍历了语法树之后,会:
1. 解析 markdown,寻找 Rust 语言的语法块(如果没有标注语言类型,默认是 Rust)
2. 根据语法块,做一些简单的处理,生成可编译的代码
3. 编译上述的测试代码 (如果编译失败,则说明测试失败)
4. 运行这些测试 or 文档
如下是 Rustdoc 中将上述的代码生成测试代码的测试用例:
```rust
#![allow(unused)]
fn main() {
#[allow(non_snake_case)]
fn _doctest_main__some_unique_name() {
assert_eq!(2 + 2, 4);
}
_doctest_main__some_unique_name()
}
```
一旦上述的代码编译并运行通过,则说明文档中的注释是正确的。
详细见 Rustdoc 相关源码:[librustdoc](https://github.com/rust-lang/rust/tree/master/src/librustdoc)
## 构建文档体系:markdown 即 DSL
基于上述语言的文档体系,我们就能浮现出基本的原型。核心便是:基于 markdown 而扩展,将 markdown 视为可扩展的 DSL。如此以来,便可以按需添加功能,并根据不同需要进行可视化展示。
### 0. 基于 markdown
这一点倒是没有啥说的,markdown 在今天已经成为了事实上的开发文档标准。
### 1. 为扩展设计:文档 DSL
从做法上可以分为:
**让语法块可运行**。让 markdown 中的代码块包含更多的含义,根据不同的代码块来对语言进行特殊处理。如 Julia 的 Documenter.jl 便定义了一系列的如可执行的 `eval` 、REPL 环境输出的 `repl`、文档测试 `jldoctest` 等。除此,一旦我们使用特定的符号来标记时,我们就可以在代码中添加更多的可能性。
**添加自定义符号**。添加一些自定义的语法,以支持更多的高级功能。如 KotlinDoc/KDoc 中使用的 Dokka,使用 `[]` 来链接元素,这种方式和 markdown 比较贴进。
**文档编排**。通过自定义元素,实现在 markdown 对于内容的“编程”(管理、引用等)。如 Julia 的 Documenter.jl 中创建的 `@ref`、`@docs`、`@meta`、`@content` 等语法。
而除了上述的内容,我们还可以做更多的事,诸如于对于“代码的引用”等。
### 2. 为准确性设计:文档测试
为了确保文档与代码保持一致,又或者是文档中代码的准确性,我们需要引入文档测试的方式来检查 API。以 Rust 和 Julia 这两个语言来分析的话,主要可以分为多种情况:
- 代码编译通过。
- 代码运行不报错。可以在其中支持语言本身的断言。
- 与输出一致。文档需要输出程序的输出,它可以通过运行对应的测试,并保持两者是一致的。
主要的实现步骤可以参见上述的 Rust 语言实现。
### 3. 构建开放协作平台:开放协作
作为一个代码库的文档体系,它应该是开源的,能让所有的人为之做贡献,才会吸引到更多的开发者。
## 其它
人家苦短,让我们用 markdown 编程。