开源 群虽然是围绕开源软件建立起来的,但是建设繁荣的开源生态,依靠只读的源代码是不够的。建设繁荣的开源生态需要开源协同,而协同的基础是沟通。沟通不仅有论坛和聊天室的交互式形式,还有主动的内容生产和发布,包括不同受众的文档的撰写和翻译,以及技术文章的传播等等。这些内容创造都可以归类为技术写作,也就是说,围绕开源软件的技术写作,是建设繁荣的开源生态的重要一环。
开发过程
虽然大一统的低语总是想让我说“代码也是技术写作的一部分”,但是我还是克制了这个冲动。不过,软件开发的过程当中,为了提高软件的可理解性和可维护性而刻意写好的代码注释和提交信息,确实可算作是技术写作的一部分。毕竟,很有一些开源软件即使增长到一定的复杂度,也没有半行注释,提交信息里全是 、 或 这样不明所以的单词信息。
代码注释
代码注释的重要性在许多软件开发的最佳实践当中都有提及,总结下来我认为不外乎三点。
1.关键设计要有模块注释,接口和数据结构要有基本的说明注释。2.实现上的 trick 要有行内注释。3.清晰明白的代码不需要啰嗦的注释。
第一点可以分成两个部分,第一个部分是通过模块注释提纲挈领地说明模块的设计动机和思路。一个软件往往由多个模块构成,每个模块又由多个小模块构成,这样层层拆分下去,每一层都有模块设计的注释,对于有志于投入到代码开发或使用的参与者来说,就是一个循序渐进且每次都能处理适量信息的过程。
这一方面做得尤为出色的是 Rust 的标准库注释[1]。虽说是注释,但是在 rustdoc 渲染工具的加持下,这份注释其实直接就可以作为实现文档呈现出来。Rust 的标准库注释从最上层介绍了标准库的构成和各个模块的职能,到每个模块细分之后同样复刻最上层的介绍模式,直到每一个具体的数据结构和方法的接口定义。可以说,开发 Rust 标准库的程序员和使用 Rust 标准库的程序员,都反复多次阅读过这些注释文档。
由于标准库做出了好的示范,并且提供了好的注释文档化支持,Rust 生态的软件和 Java 生态的软件,大多能够考虑写好模块注释。语言对注释的支持是很重要的。
例如 Rust 按照模块拆分,支持了简单的模块注释手段和 Markdown 语法,程序员就很容易顺着这条预设路径写出好的注释。反观 Java 是以类来组织代码的,优秀的项目往往会在类注释内写好详实的注释,但是 的用例并不流行,甚至很多程序员都不知道有这么个东西,JavaDoc 生成出来的内容比起 rustdoc 更加难以阅读,尤其是因为缺乏模块的概要注释,以至于根本不知道应该看哪部分代码。所以 Java 程序员往往是直接阅读源码上的注释,而不是到对应的 JavaDoc 站上阅读。
第一点的第二部分是对第一部分的补充。除了大段的模块注释,组成软件对外契约的接口和公共数据结构,需要有基本的说明注释。也就是说,这个数据结构的用途和各个字段的定义,接口方法的用途和输入输出的约束等等。这些接口和数据结构往往被下游所引用和依赖,软件分层、抽象和封装的目的就在于使用者只需要阅读接口契约就够。
第三点某种意义上是对前两点的总结,也就是说除了公开定义的数据结构和接口和模块的注释,以及实现上必须澄清的细节,其他的注释能少则少。这也是《重构》[5]当中颇为反直觉的一个论断,书中给出的解释如下
当你感觉需要撰写注释时,请先尝试重构,试着让所有注释都变得多余。
我同意这个观点。技术写作不必卷帙浩繁,内容不是越多越好,简练准确地传达出完整的信息,是注释要达到的效果。
提交信息
代码注释之外,与开发活动密切相关的另一个技术写作的实例,就是撰写提交信息了。
现代开源软件大多有源码控制系统版本化的管理,每次变更都会经过提交补丁和代码评审,通过后方可合并到代码仓库中。提交上来的补丁会包含提交信息,如果用 Git 管理,那就是所谓的 Git Commit Message[6] 的内容。
关于如何写好提交信息[7],相关讨论和材料不少。总结下来,我认为有以下几个关键点。
第一点,参考 Conventional Commits[8] 的分类,根据项目特点突出每个提交的目的。同时,Conventional Commits 标准还给出了提交信息的基本格式指南。
第二点,为了突出提交的目的,标题采用祈使句而非陈述句,也就是说提交信息标题读起来形如“(应用这个补丁,将会)实现某个功能/修复某个缺陷”。
第三点,提交信息内容主要关注为什么要做变更和做了什么变更,而不是变更的细节或实现方式。如果项目使用 issue tracker 等工具记录需求和缺陷 告,通常可以简化为提供一个到 issue 的引用。如果实现细节 trick 是软件知识的一部分,也应该记录在案。这跟注释的分类方式是类似的,具体如何取舍详略,只能在实践当中根据直觉和同行评议交流积累经验了。
第四点,可能比较容易引起争议,我个人倾向于合入主分支的提交信息省略开发中间过程,保持主分支的线性提交历史,每个补丁提交的信息都是经过开发、评审、修改后的结论。Linus 设计 Git 的时候,其 git-merge 功能实际上会把开发分支的所有 commits 都合并到上游分支里,这就导致开发的中间过程被保留。往往多个分支互相反复 merge 以后,提交历史就变成一个复杂的有向无环图,难以阅读。当然,我认为开发的中间过程有其自身的价值所在,不过在 GitHub 式的平台接管开发流程的今天,这种在 pull request / merge request 交互过程中产生的中间状态,可以由代码托管平台记录下来,作为提交历史的外挂扩展信息存在。通常,只需要在提交历史里提及对应的 request 链接,即可关联上相关信息。
代码注释和提交信息,是在一线开发环境里产生的一手技术写作内容。实际上,如果这部分相对上游的内容生产被主动管理起来,生产出高质量的内容原材料,下游无论是文档还是宣传内容,都将受益匪浅。
文档
前文提到注释的时候,我经常会下意识地写成注释文档或者文档,这是因为注释可以理解成与代码共同演进的文档。其实,《活文档》[9]一书中就曾经提及,以开发过程中诞生的知识为源头活水,随软件开发不断进化的文档,或者叫“活文档”,才是可靠、高效、协同且有见地的文档。
虽然如此,为了编写描述实现细节、模块设计、开发流程和使用方式的文档,所需的技能集与代码开发还是有很多不同。尽管有不少开源项目的文档是由开发者顺便完成的,但是无论是在企业当中,还是组织复杂度达到一定程度的开源团队当中,都会形成专注文档内容输出的子团队。
如前所述,根据受众用途的差异,文档可以粗略地分成用户文档和开发文档;根据受众地域的差异和全球化的需求,文档团队又需要考虑国际化和本地化的问题。
开发文档要为生态开发者解决的问题,就是明确这些框架接口的定义和约定,让他们能够放心地依赖并创造出新的价值,构建出生态护城河。此外,开发文档 站可以罗列已有的生态开发项目,一方面能够激励生态开发者“名留青史”,另一方面对于新人来说,有一个天然的不用刻意编写的参考教程,即使是经验丰富的生态开发者,也有可能从其他人的作品当中汲取灵感。
除去展示阶段的趋势,另一个问题是内容翻译的专家如何参与进来。上面提到的 Docusaurus 框架,或者许多手工完成国际化的文档页面,往往采用一个页面写两次的手段,依靠人来维持不同版本之间的同步。这样很容易导致文档之间及时性不足不说,对于内容翻译者来说,他们更想关注的是内容的翻译,而非页面方式用于描述如何渲染和其他 站相关的元素。
GNU gettext[25] 项目提供了一种方案,能够提取出文件中的片段,翻译者只需要翻译相关片段的内容,通过成熟的套件支持,能够把原来文件当中的内容部分替换成翻译后的内容。
Python 的用户文档[26]多语言支持做得是公认的好,其背后就是这样一套体系在支撑。具体的内容可以参考 PyCon Taiwan 2016 的演讲《多语系 Sphinx 与 Python 官方文件中文化》[27],这也是我最初接触文档国际化和本地化的重要材料之一。

工具之外,开源共同体当中自发组成的内容翻译团队的力量也不容小觑。上面提到的 Python 官方文档的翻译,就是一个自发运行的小组。另一个颇有名气的翻译组,是 Linux 中国的翻译组[28]。他们翻译了大量 Linux 群的高质量文章,并且推进了许多实用工具文档的中文化,是一个很有行动力的组织。
书籍文章
按照距离开源软件生产中心的距离,文字内容创作依次分类成代码注释、提交信息和文档,再往下走就是本节要介绍的文章和书籍了。
例如,这篇文章就可以视作开源共同体当中讨论技术写作的重要性和必要性的内容输出。
例如,前面提到的 TiDB 群发布的每周速递、功能解读、源码阅读和路线展望等内容,就是具体到某个开源项目对自己的开发过程及结果的介绍和宣传。
这其中,自然也包括 TiDB 首席架构师 @siddontang[31] 在简书上发布的博客,核心开发者 @tiancaiamao[32] 的个人博客等等。
技术博客文章的价值在哪答这个问题之前,我想先抛出一个论点。
文章须言之有物。
这个观点来自于胡适《文学改良刍议》的第一条。为什么讲这句话为技术博客及文章,其价值体现首先依赖于内容言之有物。
例如,前不久登上 Hacker News 首页的 Retool 团队升级 4TB PostgreSQL 的经验[33]一贴,就引来了数十条评论。@tiancaiamao 在自己博客当中讨论到具体问题的时候,我自己和其他读者都曾经回复讨论过这些问题的解法和自己的经验。
技术博客文章的价值在哪认为第一点是自己想写,想记录,满足自己整理思路和外挂记忆的需求,再有第二点是内容言之有物,与同行切磋碰撞。
开源精神的重要内涵是分享的精神,写作文章就是向开源共同体分享自己的所见与所思。开源协同的本质是共同创造价值,通过文章输出自己的观点,把别人不知道的事情讲给人听,把别人隐隐约约知道的事情讲清楚,挑明问题,提出解法,为开源共同体创造价值,最后,自己和文章所依托的开源 群收获真知灼见与技术影响力,这是开源 群的内容创作的闭环。
要想改善内容输出的频率和质量,培训和引导是必要的。例如,PingCAP 为了提高员工的技术写作水平,优化 TiDB 群内容的质量,在公司内举行了多个技术写作培训计划。例如,Google 发布了自家关于技术写作的材料[34],这份材料本身就是其内容影响力矩阵的一部分。
一个好的例子是 Bytebase 团队,他们围绕开源软件 Bytebase 关注的数据库运维领域,输出了一系列相关技术文章,同时也包括团队的文化和运行管理方式。这很大程度上是由于创始人重视内容创作,并且以身作则示范好的内容应该如何生产的缘故。
ytebase – 重新定义 DBA么是数据库 Schema Drift读 Retool 团队升级 4TB PostgreSQL 踩坑
博客文章毕竟是一种比较口语化的内容输出形式,并且受限于文章篇幅,往往不能够完整地讨论一个问题。通常,博客文章的作用局限于对知识思路进行整理时的阶段性输出,或者即时地议论时事。如果相关内容在你的脑海中有比较完整的脉络,或者有具体的结论,首先可以写一篇文章再做一轮输出,然后就是把相关文章作为原材料,二次创作形成文档、论文或者书籍了。
论文的形式相对博客文章更加正式,并且经过同行评审后才会在相关期刊会议上发出。分布式系统的明星博主 Martin Kleppmann[36] 就实践过这种做法。
书籍相对于博客文章来说,容量更加宽裕。开源软件到达一定的流行程度以后,大多会发行相关的书籍来系统性的介绍自己的设计和使用场景,大幅度的提升自己的技术影响力。《Redis 使用手册》、《HBase 原理与实践》以及各种“权威指南”,均属于此类。
写作之外
随着时代的发展,多媒体内容逐渐占据了内容市场的重要比例。除去撰写文章、书籍和文档,以及开发过程当中涉及的技术写作内容以外,在内容创作这个大门类下面,还有其他的重要形式。
例如,越来越多的开源 群在 Bilibili 和 Youtube 等视频 站上开设自己的账 并上传视频和短视频内容。这可以理解成线上演讲的一部分,也包括线上课程和功能展示等等。目前视觉化地学习 Flink 和 Pulsar 最好的资源,就是它们在 Bilibili 上发布的视频内容。
ulsar: TGIP-CN 直播合集[37]pache Flink 系列教程入门篇[38]
例如,播客作为视频和文字之间的折衷,也越来越得到开源 群的青睐。ALC Beijing[39] 和 CHAOSS China[40] 这样的 群,会通过播客的方式邀请专业嘉宾访谈来输出自己的内容观点。
可以看到,开源软件的技术写作,到开源软件的内容创作,是一个内涵极其丰富的领域。即使最贴近软件开发的注释和提交信息的部分可以结合到软件工程实践当中由开发人员完成,但是针对受众群体特征的文档的写作,内容的国际化与本地化,文章与书籍的技术写作技巧,内容生产之后运营和推广的能力,与开发一个软件所需掌握的技术能力是有很大区别的。
实际上,我们尚未看到有任何一个创业公司,能够达到 Linux 这样的成就。同时,Linux 的协同方式与传统的公司运作方式有着许多不同。清醒客观地认识到开源 群建设与发展的复杂性和创新性,评估和应对其中包括软件开发、项目管理、内容创作、市场营销乃至资金支持的风险和相应的收益,才是应有的审慎的态度。
References
Rust 的标准库注释: https://doc.rust-lang.org/stable/std/
Java 的标准库注释: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/module-summary.html
Java 并发模块的注释: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/package-summary.html
讨论技术债问题的推特: https://twitter.com/tison1096/status/1521758171928223744
《重构》: https://book.douban.com/subject/30468597/
Git Commit Message: https://git-scm.com/docs/git-commit#_discussion
如何写好提交信息: https://cbea.ms/git-commit/
Conventional Commits: https://www.conventionalcommits.org/en/v1.0.0/
《活文档》: https://book.douban.com/subject/35372829/
Perl: https://github.com/perl/perl5
Spring 框架生态的文档矩阵: https://spring.io/
Quickstart: https://spring.io/quickstart
Getting Started Guides: https://spring.io/guides
Kubernetes 的交互式教程: https://kubernetes.io/docs/tutorials/hello-minikube/
Katacoda: https://www.katacoda.com/
TiDB 的文档: https://docs.pingcap.com/tidb/stable
Class Loading 的问题: https://nightlies.apache.org/flink/flink-docs-stable/docs/ops/debugging/debugging_classloading/
PostgreSQL: https://www.postgresql.org/docs/current/
MySQL: https://dev.mysql.com/doc/refman/8.0/en/
Python Developer’s Guide: https://devguide.python.org/
Guide to Rustc Development: https://rustc-dev-guide.rust-lang.org/
PostgreSQL Development information: https://wiki.postgresql.org/wiki/Development_information
TiDB Development Guide: https://pingcap.github.io/tidb-dev-guide/
Rust Forge: https://forge.rust-lang.org/
GNU gettext: https://www.gnu.org/software/gettext/
Python 的用户文档: https://docs.python.org/3/
《多语系 Sphinx 与 Python 官方文件中文化》: https://blog.liang2.tw/2016Talk-PyDoc-TW/
Linux 中国的翻译组: https://linux.cn/lctt
“开源之道”: https://opensourceway.community/
庄表伟的 Blog: https://zhuangbiaowei.github.io/
@siddontang: https://www.jianshu.com/u/1yJ3ge
@tiancaiamao: https://www.zenlife.tk/index
Retool 团队升级 4TB PostgreSQL 的经验: https://news.ycombinator.com/itemd=31084147
Google 发布了自家关于技术写作的材料: https://developers.google.com/tech-writing/one
《开源指南》: https://tisonkun.org/open-source-guides/
Martin Kleppmann: https://martin.kleppmann.com/
Pulsar: TGIP-CN 直播合集: https://www.bilibili.com/video/av87830398
Apache Flink 系列教程入门篇: https://space.bilibili.com/33807709/channel/seriesdetailid=1378455
ALC Beijing: http://xima.tv/1_mFtWulsonic=0
CHAOSS China: http://xima.tv/1_OWWef3sonic=0
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!