跳转至

教,而非告知

本文讨论的是技术文档写作——更具体地说,是关于编程语言和库的文档编写。

作者:Steve Losh

发布:2013 年 9 月 3 日

原文:Teach, Don't Tell

本文讨论的是技术文档写作——更具体地说,是关于编程语言和库的文档编写。

我热爱阅读优秀的文档。当我有疑问时,如果文档几乎像作者提前预知了我的问题一样给出了答案,我会感到一种温暖而愉悦的情绪。我甚至能感受到与作者之间某种奇妙的连接,这让我忍不住微笑。

我也喜欢撰写文档。能够通过文字重新“布线”他人脑中的神经元,让他们理解此前无法理解的事物,是一件极其令人满足的事情。每当看到(或听说)某个概念突然“咔哒”一声在别人脑海中清晰起来,我的一天都会因此变得美好。

这篇文章将探讨我对“好文档”的理解,以及我认为你应该怎样去写它。我并非完美无缺,所以请对文中观点持保留态度。但我希望即使你不完全认同我的看法,也能从中获得启发并有所思考。

特别感谢 Craig Zheng 和 Honza Pokorny 对本文的校对。


阅读前建议

在你继续阅读之前,有两篇文章我认为你应当先读一读。

第一篇是 Jacob Kaplan-Moss 的《Writing Great Documentation》系列。他比我更有资格谈论这个话题,如果你还没看过,强烈推荐。本文很多观点都与他的想法一致,并在此基础上进一步展开。

第二篇是 George Gopen 与 Judith Swan 合著的《The Science of Scientific Writing》。不要因为它是为科学家撰写期刊论文而写的就望而却步——其中所有内容同样适用于程序员撰写技术文档。请通读全文,绝对值得。


我们为何要写文档?

首先,我们需要明确:我们为什么要为编程语言或库写文档?你可能有很多目标,但我想将其归结为一句话:

技术文档的目的,是把一个从未接触过你项目的人,培养成该项目的专家用户,并在他们成为专家后持续提供支持。

乍看之下,这句话似乎平淡无奇。但其中有一个词至关重要——它构成了我对文档写作的全部视角:

“教”(teach)。


教学的本质

如果你想让一个从未碰过吉他的人成为吉他大师,你该怎么做?

你得教他。

如果你想让一名高中生变成计算机科学家,你该怎么做?

你得教他。

如果你想让一位程序员从零开始掌握你的库,并最终成为专家用户,你该怎么做?

你得教他!

吉他课通常是一对一、面对面的教学;计算机科学通常由教授在课堂上传授;而编程库的使用,则通常由文档来“教学”。

既然文档的目标是把新手变成专家,那么它就必须承担起“教学”的职责。你应该把文档视为一堂课(或一系列课程),因为它本质上就是如此。

当然,写技术文档时,你通常无法与学习者进行一对一的对话。这确实增加了难度,但只要你足够用心,依然可以做到。你的文档必须同时扮演“面对面授课”和“教科书”两个角色。

本文余下的内容,几乎全部围绕如何将“文档即教学”这一理念应用于编程文档的写作。


七幕剧:糟糕文档的寓言

接下来,我将用一段段小剧场来吐槽那些糟糕的文档形式。如果你不想看这些牢骚,可以直接跳过。

每幕剧中都有两位角色:一位刚满 16 岁的青少年,和一位家长。这位青少年想学开车,以便能自己出门和朋友聚会,不再依赖父母接送。

每一幕都将讽刺一种典型的、无效的文档形式。我希望这些比喻能帮助你理解:为什么某些“文档”其实是偷懒的借口,以及为什么你应该认真写真正的文档。


第一幕:“去看源码”

早餐桌上,儿子正吃着麦片,父亲则低头看着 iPad。

儿子问:“爸,你说好今天放学后教我开车,还作数吗?”

父亲头也不抬:“当然,车就在车库。我还把一套扳手放在工作台上。你先把车拆开,看看每个零件,再装回去。做完这些,我就带你去考驾照。”

儿子默默继续吃麦片。

如果你用过不少开源库,你一定见过 README 里写着“去看源码”。每次看到这种话,我都心碎一地。

源码不是文档。

你能通过反复听一首曲子就学会弹吉他吗?你能靠参观美术馆就成为画家吗?显然不能!

我要澄清一点:阅读源码本身非常有价值。但那是在你已经会用之后。

就像画家在掌握技法后研究大师作品大有裨益,司机在学会驾驶后了解刹车构造可能救命。但如果你只给用户一个成品,却不解释背后的思路和设计,他们很难真正理解。

这就是文档存在的意义。


工具箱

写好文档其实不需要太多工具。

比如:你不需要词典或同义词库。不要为了避免重复用词而刻意替换为生僻词。就像你在现实中跟人说话一样自然就好!

读者可能根本不会注意到你同一个词用了十遍,但他们一定会注意到你莫名其妙塞进来的奇怪词汇。

不过,我推荐两样东西:

第一,不是工具,而是一项技能:打字能力

写文档时,你经常会发现自己走进死胡同,需要推倒重来。如果你打字慢,可能会舍不得删掉上千字的废稿。所以,练好打字,让你能毫不犹豫地扔掉不合适的内容。

Steve Yegge 的文章《Programming's Dirty Little Secret》对此有精彩论述。

第二,买一把好键盘。好键盘不会让你自动变成好作家(就像好吉他不会让你自动变成好乐手),但它会让你更愿意写作——仅仅因为使用一件精良工具本身就是一种享受。

我换了新吉他后,练琴时间明显增加。花 100 到 300 美元买个好键盘,如果因此让你更爱写文档,这笔钱就值了!

(顺便说一句:幸好好键盘只要几百美元,不像好乐器动辄上万。)


第二幕:“去看测试”

母亲接女儿放学。

女儿问:“妈,今天还教我开车吗?”

“当然!上车吧。”

十分钟后,她们停在雪佛兰工厂门口。

女儿一脸困惑:“我们来这儿干嘛?”

母亲笑着说:“真巧!我朋友 Jim 在这儿工作,他答应让你看几场新款 Malibu 的碰撞测试!看完我们就去考驾照。”

另一种常见的“文档”是 README 里写着“去看测试”。

测试不是文档。

再次强调:一旦你已经会用某个库,阅读测试确实很有帮助。但前提是你得先通过文档成为专家用户!

你不可能通过看车祸测试学会开车。但当你已经会开车后,了解车辆在碰撞中的表现或许能救你一命。

有人常辩解说:“测试里用了这个库,所以是很好的使用示例!”

这在表面上没错,但完全抓错了重点。大多数测试处理的都是 边界情况——而普通用户很少遇到这些(否则就不叫“边界”了)。

就算你幸运地找到一个测试验证了“正常输入”,那也只是用户日常使用的一小部分。测试无法指导用户如何在日常场景中有效使用你的库,更无法教会新手成为专家。


如何教学?

如果你接受“文档即教学”这一理念,下一个问题自然是:我该怎么教?

我曾有幸半正式地教了六七年舞蹈,也非正式地教过各种技能。教别人是提升教学能力的唯一途径。

没有什么能替代面对面地教一个人。如果你想写出更好的文档,请多练习教学。

不必搞什么教案或正式课程。你有编程之外的爱好吗?如果有,找个周末花几小时教朋友入门。你练了教学,他们学了新东西,双赢。

(如果你没有任何非编程爱好……也许该找一个?)

喜欢摄影?教他们曝光和构图基础。 会跳舞?教几个基本步伐。 玩乐器?教一首简单歌曲。 爱露营?介绍装备用途。

别过度。你不需要授予学位,只需练习“用语言重塑他人认知”这项艺术。

当你尝试教别人时,你会意识到:自己会做,和教会别人,完全是两回事。

面对面教学时,反馈是即时的。比如你教朋友按 C 大调和弦,结果只听到刺耳的杂音——你就知道得放慢节奏,解释手指怎么按弦。

但写文档时,我们几乎得不到这种反馈。我们看不到读者因某个“显而易见”的遗漏而彻底迷失。而面对面教学能帮你预判这些问题,从而在写文档时更好地照顾读者。

说到这里,我想分享一个关于“如何教学”的经典描述,出自波利亚(George Pólya)的《怎样解题》:

最好的教学方式,是自然地帮助学生。教师应设身处地站在学生角度,理解学生当前的思维状态,并提出一个学生自己也可能想到的问题或步骤。

这就是教学的核心。 人们不是靠被动接收一堆零散信息学会东西的。你不能靠朗读西班牙语词典来教人西班牙语。

教学应该是:

  1. 弄清学生已知什么;
  2. 明确你想让他们最终掌握什么;
  3. 找出一个能将现状向目标推进一小步的概念;
  4. 轻轻推动学生朝那个方向前进;
  5. 重复,直到达成目标。

可惜,太多文档只精心设计了第 2 步,然后直接把结论扔给读者——这不是教学,这是 告知(telling)。而人不是靠“被告知”学会的,是靠“被教会”的。


第三幕:“文学化编程”(Literate Programming)

女儿在 16 岁生日前夕对妈妈说:“如果还没买礼物,我想要开车课。”

妈妈笑答:“放心,都安排好了。”

生日当天,她拆开礼物——是一张《How It’s Made》(《制造的奥秘》)DVD,里面有一集讲她那款车的工厂生产线。

妈妈说:“看完这集,我们就去考驾照!”

最近一个糟糕的趋势是:用 Docco, Rocco 等“文学化编程”工具生成代码注释文档,然后让用户“去看这个”。

编程语言和库是工具。知道工具怎么造的,不等于知道怎么用。

学吉他时,你不会跑去制琴师那里看她如何用梣木雕刻 Telecaster 吉他。

了解汽车如何制造,在你会开车后确实有帮助;了解吉他如何制作,在你会弹奏后也有价值。但在那之前,这些信息毫无意义。

贯穿这几幕剧的主题是:源码、测试、文学化编程等,都是好东西——但前提是已有真正的教学型文档。

否则,它们反而有害:它们让你误以为“我已经写了文档”,任务完成了(Jacob Kaplan-Moss 在他的系列中也提到这点)。但你的任务只有在用户真正成为专家后才算完成。到那时,他们才能从这些“附加材料”中受益。


优秀文档的解剖结构

接下来,我将介绍构成优秀文档的几个关键组成部分。我的观点与 JKM(Jacob Kaplan-Moss)非常接近,所以如果你还没读他的系列,请务必补上。

在我看来,优秀文档大致包含四个部分:

  1. 初次接触(First Contact)
  2. 黑三角(The Black Triangle)
  3. 乱麻(The Hairball)
  4. 参考手册(The Reference)

这四部分不一定对应四个独立文件。“初次接触”和“黑三角”通常可合并为一个文件;“乱麻”和“参考手册”则应拆分为多个章节。但每一部分都是不可或缺的。

让我们逐一来看。


初次接触(First Contact)

当你向世界发布一个新的编程语言或库时,用户的初始状态是 一片空白。他们最需要知道的是:

  • 这是什么?
  • 我为什么要关心它?
  • 值得花时间学吗?

你的“初次接触”文档就该回答这些问题。

不必从第一性原理讲起。试着站在用户角度思考。教青少年开车时,你不必解释“轮子”是什么——他们可能已经用过割草机、高尔夫球车,甚至玩过赛车游戏。

同理,如果你开发的是 Web 框架,大多数访客应该知道 HTML 是什么。宁可稍作解释,也不要假设过多。

“初次接触”文档应用通俗语言说明:

  • 你的项目能做什么;
  • 为什么用户应该关心(省时?更稳定?有趣?);
  • 加分项:主动说明 为什么不该用 你的项目。几乎没人提及使用自己项目的权衡取舍,能做到这点会让人耳目一新。

最后,用户需要判断是否值得投入时间。你应该明确列出:

  • 项目许可证(是否可用于商业?);
  • Bug 跟踪地址(可查看问题);
  • 源码位置(可判断是否活跃维护);
  • 文档入口(可快速评估学习成本)。

第四幕:“去看 docstring”

父亲终于兑现承诺,要教女儿开车。

女儿问:“我从没开过车,从哪开始?”

这时,一位四十多岁的女士走进来。

“这是你的驾驶教练 Smith 小姐,”父亲说,“她会坐在副驾,陪你开两小时去看爷爷。路上你有任何问题,都可以问她。”

在支持 docstring(文档字符串)的语言中,很多人写了一堆 docstring 就宣称“文档已完成”。

docstring 的问题在于:它没有组织结构(除了按命名空间排列)。用户必须 先知道函数名,才能看到 docstring——而他们怎么可能知道?除非你先教会他们。

再次强调:docstring 在你熟悉项目后非常有用。但教学新手时,你需要引导他们,而不是坐在旁边等他们猜中“魔法词”再答疑。


黑三角(The Black Triangle)

下一部分是“黑三角”——一个简短的入门指南,帮助用户快速运行你的项目并动手尝试。

它有两个作用:

  1. 验证可行性:确认这个项目真的能在用户机器上跑起来,没烂掉。
  2. 立即上手:让用户“在画布上涂点颜料”(get some paint on the canvas)。

想象一下:第一堂吉他课,老师说:“我们先学 150 个和弦,六个月后再弹歌。”——没有老师会这么做。他们会教你三个和弦,让你立刻弹几首流行歌。这让学生感受到“当吉他手是什么感觉”,并保持兴趣。

你的“黑三角”文档应简明扼要地指导用户:

  • 获取项目;
  • 安装依赖;
  • 运行并简单交互。

“简短”是相对的。有些项目确实需要复杂配置。只要收益足够,这没问题。但请尽量缩短——先让东西跑起来,再深入。


第五幕:“去看 API 文档”

一年后,父亲对儿子说:“我知道你因姐姐的事故害怕开车,但我解决了问题。”

他递给儿子一本一英寸厚的手册:“上次边开边问显然不行,所以我让 Smith 小姐把车上每个零件都写了一两段说明。你先通读整本手册,再去探望爷爷吧。”

(注:上一幕的女儿因不知安全带而车祸身亡。Smith 小姐系了安全带,幸免于难。)

API 文档就像汽车用户手册。轮胎爆了时,它是救命稻草。但学开车时,它毫无用处——因为人不是靠读“按字母排序的零散信息列表”学会东西的。

如果你真的面对面教人用你的库,你会发现讲解顺序是跳跃的:先讲 A 命名空间,再跳到 B 讲相关概念,再回到 A。学习不是按字母顺序走直线,而是在他人思维迷宫中的曲折探索。


乱麻(The Hairball)

现在进入“乱麻”阶段。用户已通过“初次接触”和“黑三角”被吸引,但仍属新手。

“乱麻”是一团看似混乱、实则精心设计的教学路径,目的是将新手逐步塑造成专家。它要一点点重塑用户的认知结构,直到他们对你的项目有扎实理解。

通常,你会将“乱麻”分成若干章节(除非项目极小)。章节大致对应公共 API 的命名空间,但如有必要,应灵活调整。

不要怕写得多。简洁固然好,但宁可稍作冗余解释。程序员擅长跳过已知内容,但若你漏掉某个关键连接点,用户就会迷失在森林中。

每个“乱麻”章节都应有目录,主文档也应有总目录。目录能让读者鸟瞰全局,为阅读做好心理准备,也方便导航。

此时,你之前练习的“教学”经验和《怎样解题》中的理念就派上用场了:站在用户脑中,预判他们每一步需要的微小认知跃迁。


第六幕:“去看 Wiki”

母亲给儿子报名了课外驾驶班。

第一天:老师发 syllabus(教学大纲),讲评分标准,提前下课。 第二天:概述汽车部件和重要交规。 第三天:老师病假,代课老师讲了第五天一半内容,又因事离开,让他 19 岁女儿代讲第四天前半内容。 第四天:教室贴通知“课程取消,TODO:后续内容待定”。 第五天:老师带病复课,但喝了半瓶 NyQuil(感冒药),口齿不清,把“car”(车)说成“cat”(猫)。

全班驾照考试挂科。

Wiki 是文档的灾难。

首先,即便运作正常,Wiki 也 没有统一的声音。你上过多个老师同时授课的课吗?通常不行(双人舞等特殊场景除外)。

更糟的是:你上过那种总有个人不停插嘴、给出错误观点的课吗?Wiki 就是这样——它鼓励陌生人打断教学,插入自己的(常是错误的)见解。

我仿佛听到反对声:“但 Wiki 能让任何人修错别字啊!”

拜托!“方便修错别字”是使用 Wiki 最烂的理由。

首先,如 JKM 所说,你应有编辑或校对人员,能处理大部分拼写错误。

其次,错别字是最不重要的问题。“their” 拼错不会严重影响教学效果,但由三人跨六个月写出的混乱教程会。

而且,Wiki 很难与代码一起纳入版本控制——文档本该与代码同步演进。

但以上都不重要,因为除维基百科和游戏 Wiki 外,Wiki 根本不好用

维护者建个 Wiki,往椅背上一靠:“我搭好了平台,让别人替我干这无聊的文档活儿。坐等成果。”

结果呢?

  • 一两人修了错字;
  • 一个自以为懂的人写了完全错误的内容(可能被回滚,也可能没被发现);
  • 项目更新后,新用户读到过时文档,抱怨时却被怼:“这是 Wiki,你自己改啊!”

学生的责任不是修正破损的教案! 老师存在的意义,正是因为他们知道学生需要学什么——而学生不知道!

你可以请学生反馈“哪里难懂”,这没问题。但让 他们替你写教案,就是另一回事了。

说真的:去他妈的 Wiki。它们又烂又糟。花点时间和精力,写真正的文档吧。


参考手册(The Reference)

最后一部分是“参考手册”,面向已穿越“乱麻”、成为专家的用户。它应在日常使用中提供支持,包括:

  • 项目所有公开接口的 API 文档
  • 完整的 变更日志(尤其注意不兼容变更);
  • 项目 内部实现细节
  • 贡献指南(如接受外部贡献)。

像 JavaDoc 这类工具能生成看似“API 文档”的东西,但我同意 Jacob Kaplan-Moss 的观点:

自动生成的文档几乎毫无价值。 最好情况也只是略优于直接看源码,多数时候还不如直接读代码。自动生成文档唯一用途,大概是在合同要求交付“若干页文档”时凑数。每次点进“文档”链接却看到自动生成页面,我都怒火中烧。

手写、组织、编辑的文档无可替代。

是的,你可以用工具从源码“拉屎”般吐出一堆 HTML,甚至包含 docstring。但我仍强烈建议 手写 API 文档。虽然多打些字,但好处多多:

  • docstring 和 API 文档目的不同:前者为 REPL 编程时快速查阅,后者可提供更详尽解释、交叉链接,且适合 Google 搜索。
  • 常见反对意见:“重复劳动!怎么同步更新?”
    • 我的回答:如果你的公共 API 频繁变动,用户已经很痛苦了。你至少该多花点力气,提供最佳文档来缓解他们的痛苦。
  • 自动文档 没有统一声音,机械拼凑,无视整体结构和愿景。

你可以偷懒用自动生成,也可以为自己的作品骄傲,亲手写出最好的文档。


第七幕:“新的希望”

终幕场景:周日下午,商场停车场。

一对父母正在教儿子开车。

他们先把车开到空旷处。儿子坐上驾驶座,父母简要介绍主要控制装置,让他绕场开几圈感受车辆。

停车时,儿子挂 P 挡、解安全带。母亲提醒:“还有个叫‘手刹’的东西。” 儿子恍然大悟,从此牢牢记住停车必拉手刹。

此后,父母多次带他开车,每次都确保挑战在他能力范围内。他先后学会了:

  • 在普通道路行驶;
  • 平行泊车;
  • 上高速公路。

途中他不断提问。有时父母立即解答;有时问题暴露出更深层的知识缺失,他们便及时纠正。

日积月累,他考取驾照,开始独自驾驶。

  • 轮胎漏气时,他翻阅车主手册自行更换;
  • 出于好奇,他看了《How It’s Made》中关于自己车型的那集,了解上周救了他一命的刹车系统如何工作;
  • 雨刷坏了,他打开发动机盖,自己修好;
  • 一次被酒驾司机撞上,他仅受轻伤——他从未看过工程师做的无数次碰撞测试,但那些测试设计的气囊系统救了他。

最后一幕:多年后,头发微白的儿子坐在车里,正教自己的 teenage 女儿开车。

评论