本文作者 zzzz,使用 CC-BY-ND 4.0 协议授权,亦即:
- [👍] 你可以在署名本人的同时使用任何形式转载分发本文章。
- [👎] 你不能在转载分发本文章的同时修改本文章。
- [👍] 你可以在商业目的下使用本文章。
1 引言
这篇文章算是“教程的教程”。本人也算是写过几年教程的人了,因此在写教程这一块也积累了一些东西,现在拿出来分享给大家。本人并不是什么名师大家,也没有读过什么教育学的书籍,因此一定程度的闭门造车在所难免,不过本人还是希望能够通过这篇文章,给所有想要写或者已经在写教程,尤其是系列性技术类教程的人一些帮助。
2 为什么要有教程
对于一篇特定的教程而言,它可不可以没有呢?当然可以。难道说教程的作者在编写该教程之前,人们就没有办法学到相应的知识了吗?但是,在此之前,人们学习特定领域的知识的过程,往往充满着弯路和误导性的见解,因此教程起到的,恰恰是整理归纳,帮后来者少走弯路的过程。
2.1 教程不是手把手
教程通常情况下是以图文或者视频的形式展现给大家的,因此往往很难产生实时性的回馈。当然,对于连载型的教程,读者的回馈一定程度上能够引导作者在接下来的教程编撰过程,不过实质上这方面的影响微乎其微。
因此,虽然教程读者可能很容易找到教程中的事实性错误(比方说哪里哪里写得不够全面,或者干脆就是写错了),但我们不能期望教程的受众能够从字里行间看到一些教程在设计编排和行文思路等方面存在的一些问题。
这恰恰是写教程的一大难点所在,因为我们要面对各种各样的读者:有的读者从未接触过相关领域,学得很慢;而有的读者可能已经是这个领域的熟手,只是想通过教程看看能不能学到一些新的东西。能够让大部分期待受众满意,应当是教程作者自始至终孜孜不倦所追求的目标。
2.2 教程不是开发文档
很多人倾向于把教程写成开发文档。这也难怪,因为这些人在编写教程的时候,市面上可能真的一个同类的教程都没有,因此这些人完全是通过开发文档甚至一定程度自己的摸索进行学习的。
但是,我们为什么会需要第三方开发者编撰的开发文档?请在编写教程时务必牢记:我们不是像流水账一样把知识点介绍完,而是应考虑到读者的学习规律,合理地划分知识点,从而引导读者完成一系列的学习过程。
2.3 教程相当于用户指南
本人很喜欢“用户指南”这一比喻。教程在系统性地介绍知识点的同时,也会把读者领入相关领域的大门,并尽可能地减少未来在该领域会遇到的阻碍。唯一的区别是:用户指南是官方的,而教程通常都是第三方的。
3 教程的受众
在写教程的时候,可不要犯搞不清“阳春白雪”和“下里巴人”的错误。把复杂的内容教给初学者去听,那显然是对牛弹琴;但如果把简单的内容教给熟练工去学,那只会让读者觉着太啰嗦。
但是,如果在某个知识点搞不清是什么样的人会看到,那应该写得精炼一些还是啰嗦一些呢?个人感觉还是要写得啰嗦一点,因为写得精炼会直接把新手拒之门外,而写得啰嗦顶多会让老手看得烦一些,通常不会把老手劝退。
3.1 遣词造句的调整
为了让读者在阅读时搞清楚自己在作者期望中的位置,我们可以在编写时采用一点语言上的小技巧。传言孔仲尼在编纂《春秋》时以一字为褒贬,我们虽然达不到孔老夫子的思想深度,但是一点点小的尝试还是可以有的,比如:
- 如果对编程知之甚少,那么建议使用广受好评的
VSCode
入门。 VSCode
在开发者中广受欢迎,同时也是编程初学者的上佳之选。
这两句话的含义几乎是一样的,但前者在表述中有默许编程初学者阅读本教程的倾向,而后者却没有这样的意思。如果教程不希望编程初学者阅读,那么使用后一个表述则更为合适。
3.2 遇到新手怎么办
很多时候,即使教程作者尽力了,还是会有超出作者预期的读者来阅读,尤其是新手。虽然每个人其实都是新手过来的,但整个社区由于处在浮躁的大环境下,因此很少能够产生相当的共情能力。幸运的是,编写教程本身不需要特别强的共情能力,因此我们应尽量避免新手在阅读教程遇到困难里,接触到来自社区内部缺乏共情能力所带来的恶意。
我们可以通过设定目标的方式来让新手完成一次自我筛查(比如提醒读者读完某本教材便有阅读当前教程的能力等),从而让超出作者预期的新手读者转而先去完善其他的知识。
3.3 遇到老手怎么办
有的时候,会有一些在特定领域十分熟练的人作为教程的读者。他们可能已经掌握了教程大部分的内容,因此教程中的很多东西对于他们是可有可无的,甚至是啰嗦的。对于这部分读者,本人的建议是在教程中分阶段编写总结性的内容(比如添加专门的“本章小结”),这样熟手便可直奔小结而去,从而节省他们阅读教程的时间。
4 教程整体结构的编排
很多人会像开发文档一样安排教程的整体结构。文档读者大多会直奔主题而去,因此文档的章节顺序是按照内容关注度排序的:最常用的东西放在最前,而不常用的东西放到偏后的位置。但如果我们编写教程时这么想,那是万万不可的。因此,设计开发文档的时候,往往可以想到什么写什么,而设计教程的时候,往往需要在一开始就把教程的整体结构设计完毕,如果之后再作调整,教程将会变得非常难改。
设计教程的整体结构时,考虑到教程的目标读者通常会从头开始,一章一章地读完,因此一定要顺应目标读者的学习过程。如果一个概念本身很常用,但是相对较难掌握,那么它可能很适合放在文档的最开头几章,而放在教程的最开头几章是极为不合适的。
4.1 前言怎么写
前言,又称引言、序,主要起到提纲挈领的作用。通常情况下,教程会在前言里提到教程本身大概写了什么,教程本身的适用背景,以及教程期望读者本身达到什么样的门槛,等等。个人感觉,前言的写法相对比较自由,但十分不建议不写前言。这是因为,如果一篇教程不写前言,尤其当标题写得不那么清楚的时候,很多读者上来便会云里雾里,不知道教程的重心是什么。
4.2 第一章怎么写
对于偏实践的教程而言,第一章通常会写一些工作环境的配置,Hello World 等。读者本身会通过模仿的方式在第一章形成对整个领域最朴素的认知,而且写 Hello World 什么的,是整个教程最基础的部分。一方面,编写该部分时应当尽可能准确,同时也应该与时俱进(这里不一定要求采用最新版本,但往往需要保证在当前的环境下教程里使用的版本也能正常工作)。这块写起来倒是没什么难度,而且开发文档的第一章往往也是类似的,因此流水账通常也能胜任。
对于偏理论的教程而言,第一章通常会介绍一些基本概念。在这一方面,教程作者就应当十分小心了。这一部分相当于后续章节的根基,因此给读者的印象需要经过极其细致的斟酌。此外,由于后续章节往往依赖于这一章,因此该部分一旦落成,后续便会极难更改。作者在编写时,应当把整个教程最核心,最精炼的地方拿出来。
有的教程会同时看重理论和实践,因此上面提到的两方面都会有,而一些篇幅偏短的教程,两方面都没有也是可行的。
4.3 后续章节怎么写
一个领域的知识点往往十分复杂,并且相互盘根错节,而两个章节的知识点相互引用,更是十分常有的事。那读者更容易接受的是靠前章节引用靠后章节的知识点,还是靠后章节引用靠前章节的知识点呢?显然是引用靠前的知识点。
因此,我们在尽量使各章相互保持松散的同时,如果万不得已,一定要尽可能地保证靠前的章节所介绍的知识的完备性。我们在编写教程的时候应该能注意到,教程越往后越好写,而越往前越难改,以上便是原因。
4.4 最后一章怎么写
最后一章通常介绍的是总结和展望性的内容,具体形式通常是资源链接和参考文献。和开发文档不同,教程实际上并不需要把一个领域的所有知识点都介绍完,而对于没有介绍到的知识点,我们便可以放到最后一章来加以简要介绍。最后一章还适用于一些很难嵌入整个教程但却相对重要的知识点。
4.5 后记怎么写
后记通常反映的是作者的心路历程,有的作者也会把这一部分放到前言去写,或者合并到最后一章。说白了,就是作者觉着一些东西不吐不快,但又和整个教程涉及的知识点没什么直接关联,因此放在最后可有可无的碎碎念什么的(笑)。
4.6 附录怎么写
附录记录的通常是流水账,一些杂七杂八的知识点有的作者也不愿意放到正文,而是放到附录来写。由于很多流水账其实是可以通过官方文档等途径,或者干脆通过直接搜索便可得到的,因此近年来也不是很时兴在教程里加附录。
5 教程的具体行文
在上面的内容中我们提到,和开发文档不同,教程通常情况下是会一章一章地依次读下来的。这意味着教程中,具体文段和文段之间的衔接非常重要,因为读者在阅读教程的时候,思维本身应当是连续的。
那什么情况下读者的思绪会被打断呢?
5.1 前后无联系的知识点
之前有过担当节目主持人的经历的应该知道,在两个节目切换的时候,主持人通常需要来到台前说一些转场词,以保持节目与节目之间的连贯。在教程中,这样的“转场词”也是需要精心设计的。
如果同一节引入了两个知识点,那么它们应能够对应读者思考问题的前后顺序,如果有两个知识点无论如何也很难联系到一起,那么它们就应当拆到不相干的两节,即使这两个知识点是作者当初一同学习的。
大部分场合,知识点之间的前后联系是显然的,而有的时候,知识点之间的联系却是深层次的。如果我们编写教程的时候发现两个看起来显然应该放在一起的知识点之间却找不到任何联系,那便要花时间设计引导读者的思维过程了。
这里我们以 Java 语言本身举例:
Java 中可直接构造的类型分为基本数据类型、类、和数组三种。
这三种类型听起来像是流水账,读者大可以在这里直接硬记住,而不去思考这三种类型之间的关联。而对于熟练的 Java 开发者而言,这看起来实在是太过于显然了,以至于 Java 开发者通常情况下压根不会去多花时间思考这三种类型之间是否真的有值得注意的关联。
为了让教程讲得更清楚,我们便需要亲自设计引导读者的思维过程。实际上,当我们使用“基础类型”和“复合类型”两个概念时,一切都迎刃而解了:基本数据类型是基础类型,而类和数组不过是复合类型的两种复合形式嘛。
当然,实际编写教程时,我们往往无法苛求所有的知识点之间都能够产生适当的联系,但是我们还是十分鼓励编写教程的过程中尽可能地把知识点串起来的。事实上,个人感觉,把知识点映射到读者思维过程的能力,恰恰体现了教程作者水平的高低。
5.2 使读者分心的知识点
另一个极其经常出现的原因是教程中掺入了各种虽然能够自然联想到,但会使读者分心的无关知识点。这通常和教程作者过去的学习途径有关:因为学习过程中注定要出现各种弯路,而这会让一个人在学习时掌握各种杂七杂八的知识点;当然,对于他自己而言,知识体系已经整体上掌握了,但如果读者照搬这样的方式去学习,那自然是没有必要的。
对于教程作者来说,这其实是比较大的挑战。我们刚刚提到过,一个领域的知识点是相互盘根错节的,作为初学者必须一样一样地掌握,就像收拾背包的时候必须将物品一件一件地整理一样。教程作者做的正是将知识点“压平”的过程。
当知识点遇上不同的历史阶段时,事情本身将变得更复杂。这里我们仍然以 Java 语言本身作为例子:
Stream<T>
接口的findFirst
方法返回了一个Optional<T>
,为什么不直接返回一个T
呢?
相信熟悉 Java 的开发者能够很快答上来:
Optional<T>
代表T
可能存在,可能不存在,而findFirst
方法代表找到第一个元素,如果Stream<T>
为空那当然不存在,否则当然存在。
但我们很快就能发现另一个问题:
Map<String, T>
的get
方法返回的值也可能存在可能不存在,但为什么不返回一个Optional<T>
呢?
作为 Java 开发者我们当然知道,设计 Map
时还压根没有 Optional
这一概念,而 Java 后来添加 Stream
的时候同时添加了 Optional
,因此新的接口返回了 Optional
,而旧的接口由于历史遗留原因,就只能维持原样,直接返回了。
道理我们自然都懂的,但如果我们想要把它写成教程,该怎么向读者解释呢?这其实便是写教程的难点所在。如果教程事先没有详细介绍过 Map
,同时这里读者只想关心 findFirst
方法返回 Optional
究竟是怎么一回事,而作者却把上面的因果关系直接全套照搬进教程里,我敢打包票大部分读者肯定一时半会看不懂,而且大概率会因为 Map
等无关知识点发懵。
这类问题的解决方案有很多,比如说只介绍 Stream
而忽略 Map
,或者把和 Map
相关的部分放到另外一节甚至另外一章,或者说把 Map
相关的部分单独提出一个“小贴士”提醒读者。不管怎么做,单纯全套照搬个人是极为不提倡的。
5.3 糟糕的排版和错别字
很多人写教程的时候很不注重排版。但个人觉着,排版就如同一个教程的脸面,一个排版良好的教程会大大激发阅读欲望,而一个排版糟糕的教程,即使写得再好,也不会有多少人兴致盎然地读下去。希望所有编写教程的人都能够使自己的教程排版干净整洁。
通常情况下,一个人很难保证自己的教程里没有错别字,但是把错别字降低到一个肉眼很难察觉的范围,个人感觉还是大多数人能够做得到的。当然有些错别字已被大众广泛使用(比如“下划线”的正确写法是“下画线”),个人认为不必过分关心。
5.4 在教程中过分玩梗
很多人喜欢把一些模因(meme)引入教程中。我个人并无意批判大多数情况下的玩梗行为,但是如果过量玩梗的话,很可能导致对梗不熟悉的读者迷失在猜谜的海洋里。当然,很多教程本身会带一定程度的冷幽默,这个个人认为是可取的,当然在提高教程趣味性这一点上,本人也还有很长的路要走。
6 教程的后续维护
教程写完了,自然会有后续维护。个人认为,向教程中添加东西是相对容易的工作,而删改东西则相对困难得多。以下主要分更新和修正两部分具体谈谈教程的删改工作。
6.1 更新教程
很多时候,读者是希望教程能够与时俱进的。但是教程更新版本所冒的风险是非常大的,至少相较开发文档大很多。这是因为开发文档通常是模块化的,每次更新通常只涉及开发文档的数个模块,而教程为了使读者更容易读懂,往往会将知识点揉碎了讲,因此经常容易出现“牵一发而动全身”的现象。
为了减少更新教程带来的不一致性,在最开始编写教程时,我们可以在一些地方稍加留意:一方面我们可以减少插图的数量,因为插图中的不一致性往往很难在更新教程时发现;另一方面我们可以尽量采用不会随版本发生变化的示例进行讲解,这样更新时概念的更新换代往往不会很严重。
当然,如果版本更新实在变动了太多太多的东西,抛弃旧包袱重新写一份新的教程也是值得考虑的。
6.2 修正教程
教程的作者经常能够发现教程的某一章或者其中的某一节讲得不够好。由于教程本身极容易出现不一致性,因此个人建议在修正教程的某一章或某一节时,直接将该章或该节重写,从而避免这一章或这一节内知识点相互引用错位导致的麻烦。
尽量不要移动教程的某一章或某一节到其他位置。因为不管是向前移动还是向后移动,都有可能出现某一章依赖的知识点跑到后面去的情况。因此,这里再次强调教程整体结构事前编排的重要性。
7 总结
因为本人自己写过一些教程,本身作为版主也看过一堆花花绿绿的教程,因此本人很早就有写这样一篇文章的想法了。但是因为本人拖延症发作(笑),以及这篇文章中途删了又改,改了又删,直到现在才和各位见面。
以上纯属个人观点,希望各位能够带着批判性的眼光看待这篇文章。