Skip to content

ricear/document-writing-guide

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 

Repository files navigation

GitHub

输入是为了输出。只有分享知识,才能更好地创造知识。

作为分享知识的一种绝佳途径,文档写作不仅可以锻炼我们的思维,而且可以在检验我们理解程度的同时提升我们的认知。通过读者的反馈,进一步弥补认知缺陷,不断完善对知识的认知。文档写作是一项技术活,规律有迹可循。本篇文章在参考相关资料后将自己关于文档写作的所思所悟记录下来,期望能为以后的文档写作奠定一个良好的基础。

本文主要从文档写作的目标、步骤和规范三个方面进行阐述。

写作目标

少即是多。

写作不能为了写作而写作。如果一篇文章要公开,那一定要让读它的人感到有价值。衡量一篇文章是否有价值主要有两个维度,一方面是创新,前人没有写过;另一方面是系统全面,读者可以从文章中获取更高的视角。

写作步骤

写作主要分为三种思路:第一种是边查询资料边写作;第二种是先收集资料,资料收集完成并整理结束后再进行写作;第三种是随时记录所思所想,积累到一定程度后,将已有灵感汇聚成一篇文章。第一种方式容易打乱我们的心流,写作效率低;第二种方式可以集中注意力写作,便于提高写作质量;第三种方式侧重平时积累,自主驱动力更强,也是更推荐的一种写作方式。

无论采取哪种写作方式,都会经过构思、收集、整理、写作和发布这几个步骤。

构思

思先于行。

写作和其他事情一样,开始之前先进行缜密的思考,这样才能在后面少走弯路。毕竟「方向不对,努力白费」,如果一开始的方向就是错的,那么越努力可能离目标越远。开始的思考也许会陷入瓶颈,相比于「立即行动」的人来说产出较少。但是这些「慢」是值得的,先思后行的人会把大部分困难在开始解决,后面的推进便会顺风顺水;而先行后思的人可能开始会走的很快,但一旦发现错误就会返回修改,不亚于「蛇形走路」,最终会被先思后行者超越。

写作之前要明确文章的主题、结构,并列出文章的提纲。写作过程中紧紧围绕文章主题来写,把握好文章的边界。持续深入,切不可泛泛而谈。

收集

持续不断记录,意义自然浮现。 ---- 源自 flomo 创始人「少楠」

资料收集过程要遵循「收件箱思维」。收件箱思维的核心有两点:一方面是收集,一股脑丢进收件箱,不做任何处理;另一方面是整理,定期处理收件箱内的项目,并移出收件箱。

资料收集的工具使用的是 flomo。之所以采用 flomo,主要有两个原因,一方面是无压记录,功能简洁,一个小巧的文本框支持我们随时随地记录;另一方面是标签化管理,便于内容的分类和查找。当查到与主题相关且让自己思维产生触动的内容时,会记录到 flomo,并添加相应的标签。下图是本文在写作中使用 flomo 的部分截图。flomo 的使用技巧可以参考 flomo 101

image-20230605172242836

整理

资料收集完成后使用思维导图按照思路进行整理。之所以采用思维导图是因为思维导图调整节点非常方便。思维导图工具采用的是 ObsidianEnhancing Mindmap。下图为本文写作过程中使用 Enhancing Mindmap 整理后的部分截图。

image-20230605195102577

写作

写作工具采用 Typora,结合 PicGo 构建图床,方便图片管理。

发布

文章写作完成后发布在采用 MrDoc 构建的个人博客上。受限于客户端使用不便,写了「新建文档」和「更新文档」两个脚本,可以结合右键助手来使用,详见附录。

规范

文档体系

在介绍具体的文档体系结构之前,我们先来介绍一下文件的命名规则:

  • 文件的文件名不能含有空格;

  • 文件名必须使用半角字符,不能使用全角字符,这也意味着中文不能用于文件名;

    • 错误:名词解释.md
    • 正确:glossary.md
  • 文件名建议只使用小写字母,不使用大写字母;

    • 错误:troubleShooting.md
    • 正确:troubleshooting.md

    为了醒目,某些文件的文件名可以使用大写字母,例如 READMELICENSE 等。

  • 文件名包含多个单词时,单词之间建议使用半角的连词线分隔。

    • 不佳:advanced_usage.md
    • 正确:advanced-usaged.md

技术文档体系结构一般如下:

  • 简介(Introduction):[必备] [文件] 提供对产品和文档本身的总体的、扼要的说明;
  • 快速上手(Getting Started):[可选] [文件] 如何最快速地使用产品;
  • 入门篇(Basics):[必备] [目录] 又称「使用篇」,提供初级的使用教程;
    • 环境准备(Prerequisite):[必备] [文件] 软件使用需要满足的前置条件;
    • 安装(Installation):[可选] [文件] 软件的安装方法;
    • 设置(Configuration):[必备] [文件] 软件的设置;
  • 进阶篇(Advanced):[可选] [目录] 又称「开发篇」,提供中高级的开发教程;
  • API(Reference):[可选] [目录|文件] 软件 API 的逐一介绍;
  • FAQ:[可选] [文件] 常见问题解答;
  • 附录(Appendix):[可选] [目录] 不属于教程本身,但对阅读教程有帮助的内容;
    • Glossary:[可选] [文件] 名词解释;
    • Recipes:[可选] [文件] 最佳实践;
    • Troubleshooting:[可选] [文件] 故障处理;
    • Changelog:[可选] [文件] 版本说明;
    • Feedback:[可选] [文件] 反馈方式。

具体示例如下:

文档内容

标题

  • 文档标题可以分为四级:
    • 一级标题:文章的标题;
    • 二级标题:文章主要部分的大标题;
    • 三级标题:二级标题下面某一方面的小标题;
    • 四级标题:三级标题下面某一方面的小标题。
  • 标题要避免孤立编号(即同级标题只有一个);
  • 谨慎使用四级标题,保持层级的简单,防止出现过于复杂的章节。如果三级标题下有并列性的内容,建议使用列表。

段落

  • 段落的中心句子放在段首,对全段内容进行概括。后面陈述的句子为中心句子服务;

    Excel由许许多多的单元格组成,每个单元格可以包含不同的内容。我们可以将Excel想象成一个有行和列的二维表格,每一行代表一个独立的实体,每一列代表该实体的不同属性。如果你想使用Excel来记录图书信息,那么每一行代表不同的书本,每一列代表书本的属性,比如书的名称、价格以及出版社等等信息。

    读者读完上面第一句话后,可能还是很懵,需要读完整段话才能明白作者在本段中想要表达的意思。段落的开头语可以通过提炼段落内容得到,我们可以在段落写完之后回过头来提炼一句话作为本段的开头语,修改后如下所示。

    Excel提供一个组织数据的高效方法。我们可以将Excel想象成一个有行和列的二维表格,每一行代表一个独立的实体,每一列代表该实体的不同属性。Excel还具备数学功能,比如计算平均值和方差等数学操作。如果你想使用Excel来记录图书信息,那么每一行代表不同的书本,每一列代表书本的属性,比如书的名称、价格以及出版社等等信息。

    上面这段话的第一句已经明确了段落主题,即「Excel 能高效组织数据」。但是,这段话中间却穿插了一个不相干的句子,说「Excel 具备数学功能,能够做一些数学操作」,这句话显然与本段主题不符,我们需要将其去掉,修改后如下所示。

    Excel提供一个组织数据的高效方法。我们可以将Excel想象成一个有行和列的二维表格,每一行代表一个独立的实体,每一列代表该实体的不同属性。如果你想使用Excel来记录图书信息,那么每一行代表不同的书本,每一列代表书本的属性,比如书的名称、价格以及出版社等等信息。

  • 段落应遵循「单一职责原则」,一个段落只陈述一个主题;

  • 段落之间用一个空行隔开;

  • 引用第三方内容时,应注明出处;如果是全篇转载,应在全文开头显著位置注明作者和出处,并链接至原文;使用外部图片时,必须在图片下方或文末标明来源。具体示例如下:

    • One man’s constant is another man’s variable. — Alan Perlis
    • 本文转载自 WikiQuote。
    • 本文部分图片来自 Wikipedia。

句子

  • 避免使用长句;

    • 错误:本产品适用于从由一台服务器进行动作控制的单一节点结构到由多台服务器进行动作控制的并行处理程序结构等多种体系结构。
    • 正确:本产品适用于多种体系结构。无论是由一台服务器(单一节点结构),还是由多台服务器(并行处理结构)进行动作控制,均可以使用本产品。
  • 尽量使用简单句和并列句,避免使用复合句;

    • 并列句:他昨天生病了,没有参加会议。
    • 复合句:那个昨天生病的人没有参加会议。
  • 同一个意思,尽量使用肯定句表达,不使用否定句;

    • 错误:请确认没有接通装置的电源。
    • 正确:请确认装置的电源已关闭。
  • 避免使用双重否定句;

    • 错误:没有删除权限的用户,不能删除此文件。
    • 正确:用户必须拥有删除权限,才能删除此文件。
  • 尽量少使用被动语句。

    序号 被动语句 (可考虑)主动语句
    1 角色权限是由管理员控制的 管理员控制角色权限
    2 API 结果无法被系统正常解析 系统无法正常解析 API 结果
    3 图像特征是通过 CNN 逐步降维的方式提取的 CNN 通过逐步降维的方式提取图像特征
    4 这种检测效果无法被客户接受 客户无法接受这种检测效果
    5 经过研发排查发现,这个现象是正常的 经过研发排查发现,这个属于正常现象

词性

针对名词,有以下原则:

  • 专有名词正确使用大小写;
    • 正确:
      • 使用 GitHub 登录。
      • 我们的客户有 GitHub、Foursquare、Microsoft Corporation、Google、Facebook, Inc.。
    • 错误:
      • 使用 github 登录。
      • 使用 GITHUB 登录。
      • 使用 gitHub 登录。
      • 我们的客户有 github、foursquare、microsoft corporation、google、facebook, inc.。
      • 我们的客户有 GITHUB、FOURSQUARE、MICROSOFT CORPORATION、GOOGLE、FACEBOOK, INC.。
      • 我们的客户有 Github、FourSquare、MicroSoft Corporation、Google、FaceBook, Inc.。
      • 我们的客户有 gitHub、fourSquare、microSoft Corporation、google、faceBook, Inc.。
  • 不使用不地道的缩写;
    • 正确:我们需要一位熟悉 TypeScript、HTML5,至少理解一种框架(如 React、Next.js)的前端开发者。
    • 错误:我们需要一位熟悉 Ts、h5,至少理解一种框架(如 RJS、nextjs)的 FED。

文档书写时尽量减少形容词和副词的使用,用具体数值代替、或者调整句子表述。因为技术性文档追求的是一个「」字,应该减少主观表达。具体示例如下:

序号 形容词/副词 (可考虑)调整为
1 经过优化,接口响应速度提升明显 经过优化,接口响应速度提升 2 倍
2 很多人反映现场误报很多 数据统计发现,现场误报率为 11%
3 大部分客户投诉说系统很不好用 最近一个月有超过 50 个客户投诉说系统不好用
4 升级依赖库后,该函数运行很快 将依赖库升级到 2.3.1版本后,该函数执行时间缩短到 100ms 以内
5 研发同事很辛苦,每天加班很晚 研发同事很辛苦,每天 23:00 之后才下班

优先考虑使用方便读者阅读理解的动词(强势动词)和句式(主动语句)。强势动词是指听起来动作幅度大、冲击力强的那一类动词。具体示例如下:

序号 弱势动词 (可考虑)强势动词
1 走过去 跳过去
2 切肉 砍肉
3 出现异常 抛出异常
4 程序退出 程序崩溃
5 内存增长 内存泄漏
6 找不到日志文件 日志文件丢失
7 客户提出质疑 客户投诉
8 任务未完成 任务延期
9 角色权限是由管理员设置的 管理员控制角色权限
10 系统无法正常使用 API 返回的结果 系统无法正常解析 API 返回的结果

列表

文字相对来说是一种效率比较低的表达方式。当我们想要尽可能直观地陈述内容,又想包含尽可能多的细节时,可以考虑使用列表或表格。

列表简单来说就是将原来用段落呈现的内容改为用项目(item)的方式来呈现,主要用于枚举、过程描述或要点归纳等场合。列表中的各项可以是名词、短语,甚至是句子。各项目之间有严格顺序要求的是有序列表,没有严格顺序要求的是无序列表。具体示例如下:

段落描述 列表描述
白天在公司上班期间,小张一共修复了7个bug,做了3个代码合并(评审),并和项目经理讨论了前天提的新需求。晚上回到家后,小张先做饭,然后给儿子洗澡,23:30上床睡觉。 小张白天在公司:
  • 修复了7个bug;
  • 做了3个代码合并(评审);
  • 和项目经理讨论前天提的新需求。
晚上回到家后:
  1. 做晚饭;
  2. 给儿子洗澡;
  3. 23:30上床睡觉。

针对列表,有以下原则:

  • 列表中各项内容结构一致,要么都是名词,要么都是短语,要么都是句子;

    错误 正确
    影响系统检测准确性的因素有:
    • 模型;
    • 产品开通过程中,工程师对算法参数校准程度;
    • 应用现场是否有灯光照明。
    影响系统检测准确性的因素有:
    • 模型的复杂性;
    • 部署时对算法参数校准的程度;
    • 应用现场是否有灯光照明。
    影响系统检测准确性的因素有:
    • 模型类型
    • 校准程度
    • 环境亮度
  • 列表中项目内容为名词时,每项结尾处不使用任何标点符号

  • 每个列表前面尽量添加一个明确的「引入说明」,以冒号结束。

标点符号

全角和半角:

  • 使用全角中文标点;

    错误 正确
    嗨! 你知道嘛? 今天前台的小妹跟我说 "喵" 了哎!
    嗨!你知道嘛?今天前台的小妹跟我说"喵"了哎!
    嗨!你知道嘛?今天前台的小妹跟我说「喵」了哎!
    核磁共振成像 (NMRI) 是什么原理都不知道? JFGI!
    核磁共振成像(NMRI)是什么原理都不知道?JFGI!
    核磁共振成像(NMRI)是什么原理都不知道?JFGI!
  • 数字使用半角字符。

    错误 正确
    这个蛋糕只卖 1000 元。 -
    乔布斯那句话是怎么说的?「Stay hungry,stay foolish。」 乔布斯那句话是怎么说的?「Stay hungry, stay foolish.」
    推荐你阅读《Hackers&Painters:Big Ideas from the Computer Age》,非常的有趣。 推荐你阅读《Hackers & Painters: Big Ideas from the Computer Age》,非常的有趣。

句号:

  • 中文语句的结尾处应该用全角句号;
  • 句子末尾用括号加注时,句号应在括号之外。
    • 错误:关于文件的输出,请参照第 1.3 节(见第 26 页。)
    • 正确:关于文件的输出,请参照第 1.3 节(见第 26 页)。

逗号:

  • 逗号表示句子内部的一般性停顿;
  • 避免「一逗到底」,即整个段落除了结尾,全部停顿都使用顿号。

顿号:

  • 句子内部的并列词应该用全角顿号分隔,而不用逗号,即使并列词是英语也是如此。
    • 错误:我最欣赏的科技公司有 Google, Facebook, 腾讯, 阿里和百度等。
    • 正确:我最欣赏的科技公司有 Google、Facebook、腾讯、阿里和百度等。

分号:

  • 分号表示复句内部并列分句之间的停顿。

引号:

  • 引用里面还要用引号时,外面一层用双引号,里面一层用单引号。
    • 示例:鲍勃解释道:“我要放音乐,可萨利说,‘不行!’。”

括号:

  • 补充说明时,使用全角括号,括号前后不加空格。
    • 示例:请确认所有的连接(电缆和接插件)均安装牢固。

冒号:

  • 常用在需要解释的词语后面,引出解释和说明;
    • 示例:请确认以下几项内容:时间、地点、活动名称和来宾数量。
  • 表示时间时,应使用半角冒号。
    • 示例:早上 8:00

省略号:

  • 占两个汉字空间,包含六个省略点,不要使用。。。或...等非标准形式;
  • 不应与「等」一起使用。
    • 错误:
      • 我们为会餐准备了香蕉、苹果、梨…等各色水果。
    • 正确:
      • 我们为会餐准备了各色水果,有香蕉、苹果、梨⋯⋯
      • 我们为会餐准备了香蕉、苹果、梨等各色水果。

破折号:

  • 用于进一步解释;
  • 占两个汉字空间。如果破折号只占一个汉字的位置,那么前后应该留出一个半角空格。
    • 示例:
      • 直觉——尽管它并不总是可靠的——告诉我,这事可能出了些问题。
      • 直觉 — 尽管它并不总是可靠的 — 告诉我,这事可能出了些问题。

连接号:

  • 用于连接两个类似的词;
  • 以下场合使用直线连接号(-),占一个半角字符的位置;
    • 场合:
      • 两个名词的复合
      • 图表编号
    • 示例:
      • 氧化-还原反应
      • 图 1-1
  • 以下场合使用波浪连接号(~)或一字号(—),占一个全角字符的位置。
    • 场合:数值范围(日期、时间或数字)
    • 示例:周围温度:-20 °C 至 -10 °C

加粗

加粗可以使文章的重点一目了然,其使用有以下原则:

  • 写文档的时候,适当加粗重点,但要控制加粗的比例,毕竟全部加粗等于没加粗。加粗的内容可以是一句话里的关键词,或者一段话的中心句;
  • 加粗的目的是让读者阅读的时候能找到重心。理想的情况是通过加粗内容,就能知道文章在说什么。加粗的内容摘出来,就是文章的大纲。

空格

有研究显示,打字的时候不喜欢在中文和英文之间加空格的人,感情路都走得很辛苦,有七成的比例会在 34 岁的时候跟自己不爱的人结婚,而其余三成的人最后只能把遗产留给自己的猫。毕竟爱情跟书写都需要适时地留白。

—— 摘自 pangu.js

空格的使用有以下原则:

  • 中英文之间需要增加空格;
    • 错误:
      • 在LeanCloud上,数据储存是围绕AVObject进行的。
      • 在 LeanCloud上,数据储存是围绕AVObject 进行的。
    • 正确:
      • 在 LeanCloud 上,数据储存是围绕 AVObject 进行的。
      • 在 LeanCloud 上,数据储存是围绕 AVObject 进行的。每个 AVObject 都包含了与 JSON 兼容的 key-value 对应的数据。数据是 schema-free 的,你不需要在每个 AVObject 上提前指定存在哪些键,只要直接设定对应的 key-value 即可。
    • 注意事项:
      • 「豆瓣FM」等产品名词,按照官方所定义的格式书写。
  • 中文与数字之间需要增加空格;
    • 错误:
      • 今天出去买菜花了 5000元。
      • 今天出去买菜花了5000元。
    • 正确:
      • 今天出去买菜花了 5000 元。
  • 数字与单位之间需要增加空格;
    • 错误:我家的光纤入屋宽带有 10Gbps,SSD 一共有 20TB。
    • 正确:我家的光纤入屋宽带有 10 Gbps,SSD 一共有 20 TB。
    • 注意事项:度数/百分比与数字之间不需要增加空格。
      • 错误:
        • 角度为 90 ° 的角,就是直角。
        • 新 MacBook Pro 有 15 % 的 CPU 性能提升。
      • 正确:
        • 角度为 90° 的角,就是直角。
        • 新 MacBook Pro 有 15% 的 CPU 性能提升。
  • 全角标点与其它字符之间不加空格。
    • 错误:
      • 刚刚买了一部 iPhone ,好开心!
      • 刚刚买了一部 iPhone, 好开心!
    • 正确:
      • 刚刚买了一部 iPhone,好开心!

图像不仅是所有表达方式中最直观的一种,同时还可以提升读者的阅读兴趣。有人专门做过研究:在文档中增加图像能提升读者对文档的喜爱程度,不管这个图像和文档内容本身是否有关系。例如下面是一段描述双向链表的文字:

双向链表也叫双链表,是链表的一种。它的每个数据节点中都有两个指针,分别指向直接后继节点和直接前驱节点。所以,从双向链表中的任意一个节点开始,我们都可以很方便地访问它的前驱节点和后继节点。在应用双向链表时,我们一般构造双向循环链表,链表首尾相连。

熟悉双向链表的同学可以看出上面这段文字对双链表的描述已经非常具体,但是对于小白来说效果可能不会太好。如果我们在这段话后面添加一个插图,告诉读者双向链表长什么样:

双向链表也叫双链表,是链表的一种。它的每个数据节点中都有两个指针,分别指向直接后继节点和直接前驱节点。所以,从双向链表中的任意一个节点开始,我们都可以很方便地访问它的前驱节点和后继节点。在应用双向链表时,我们一般构造双向循环链表,链表首尾相连。下图是双向链表结构示意图:

在这里插入图片描述

上面的文字描述配合图片,可以让读者更加清晰地理解双向链表。当文档中的文本和图片同时出现的时候,读者大概率会先看图片,然后再结合文字去理解,加快文档阅读速度。

图像的使用有以下两个原则。

第一个原则是可抽象也可具体。技术型文档中的插图不一定都得是流程图、架构图或者结构设计图这种非常具体的技术相关图片,还可以是抽象的、能形象表达文档主题的图片。

示例一:

Gitlab中有Label和Tag两个概念。

在这里插入图片描述

为了便于区分,这里将Label翻译成“标签”,将Tag翻译成“标记”(在有些地方这两个单词翻译并没有严格的差异)。Gitlab中标签的作用是为了分类、快速地检索和过滤,用户能通过标签来直观的管理Issues,比如to-do、bug等等。标记的主要作用是为了归档,给Commit取一个形象的别名,后期快速定位和查找。 Gitlab中创建标记可以理解为“做记号”,建立索引。一般推荐为标记定义一个有意义的名称,比如以版本号为名,当我们要发布1.0版本,对应的标记名称可以是“v1.0”,如果我们要发布2.0预览版,那么对应的标记名称可以是“2.0-pre”。

示例二:

源码版本控制系统(Source Code Version Control System)主要负责对源代码版本进行管理,涉及到代码提交、撤销、比对、分支管理、代码合并等功能。源码管理是软件开发过程中非常重要的一个环节,它能有效保证软件代码质量。

在这里插入图片描述

源码管理并不是软件开发周期的全部,整个软件开发周期涉及到多个流程、多个团队(多人)协作完成,包括立项/结项、进度/任务管理、需求/设计、bug管理、测试、集成上线等环节。

第二个原则是突出图中重点。我们为文档添加图片时,单张图片包含的内容不宜太过复杂,图片应能准确表达意思。如果一张图太过复杂、或者包含了一些可能引起歧义的部分,可以尝试以下两种改进方式:一是将复杂的图拆开,一张图对应一个局部细节;二是在图片中将重点区域标记出来,让读者一眼就可以发现重点。

在技术型文档中插入复杂的系统架构图很常见,这种时候应遵循「先宏观,再具体」的原则,循序渐进。我们不要一上来就放一张大图,还想将所有的细节都包含进去,这种想法不太现实。这不仅对画图的技术要求很高,读者看完也容易一脸懵。下面这张图就太过复杂。

整个视频分析系统由3大服务组成,分别是Intelligent Video Analytics、Front Video Service以及Distribute Load Balance,这3大服务一共包含15个子模块。下面是视频分析系统结构:

在这里插入图片描述

上面这个例子中插入的图片既想描述 3 大服务之间的交互关系,又想描述各个服务内部子模块之间的交互关系。读者碰到这种情况可能会产生两个感觉:一是图太复杂了,很难看懂;二是我需要重点关注的点在哪里。如果遵循前面提到的「先宏观,再具体」的原则,上面这个例子可以调整如下。

整个视频分析系统由3大服务组成,分别是Intelligent Video Analytics、Front Video Service以及Distribute Load Balance。下面是视频分析系统中各服务之间的关系:

在这里插入图片描述

其中,Intelligent Video Analytics服务主要负责对视频解码、推理以及行为分析等结构化操作。该服务内部一共包含9个子模块,模块之间的关系见下图:

在这里插入图片描述

Front Video Service服务主要负责视频接入、分发、配置管理等功能。该服务内部一共包含3个子模块...

另外一种情况是插入的图片包含了不相干的内容,文档作者又没有给出醒目的标记,读者看完不清楚关注重点在哪里。下面是错误的示例。

Gitlab中的Release功能主要用来对仓库中的代码以及其他一些相关资料文件进行归档,通常用于版本发布。当有新版本发布时,用户可以基于对应的Commit创建一个Tag标记,给它一个合理的名字,比如“v1.0-pre”(代表发布1.0预览版),然后再基于该Tag发布版本。后期,其他人可以通过Release菜单快速浏览、检索项目版本发布记录以及对应时间点的相关代码和资料。用户可以在Gitlab主界面的左侧菜单中找到Release功能入口:

在这里插入图片描述

上面在介绍 Release 功能时给出的图片中包含的菜单项太多,为了让读者更直观看懂图片关注点,可以将图片调整如下(左右两种都可以)。

Gitlab中的Release功能主要用来对仓库中的代码以及其他一些相关资料文件进行归档,通常用于版本发布。当有新版本发布时,用户可以基于对应的Commit创建一个Tag标记,给它一个合理的名字,比如“v1.0-pre”(代表发布1.0预览版),然后再基于该Tag发布版本。后期,其他人可以通过Release菜单快速浏览、检索项目版本发布记录以及对应时间点的相关代码和资料。用户可以在Gitlab主界面的左侧菜单中找到Release功能入口:

在这里插入图片描述

表格

表格在使用时,需要控制每个单元格的文本长度。一般情况下建议单元格只使用短语,如果必须使用段落,应该控制段落中句子数量(建议不超过 2 ~ 3 句)。下面是一种错误的表格示范。

序号 语言 介绍
1 C C语言由贝尔实验室发明于1969至1973年,是一种编译型计算机编程语言。它运行速度快、效率高、使用灵活,常被用于计算机底层驱动以及各种语言编译器的开发。C语言是一种面向过程的编程语言,同时它的语法相对来讲较复杂,新人入门门槛比较高。
2 C++ C++语言发明于1979年,是一种编译型计算机编程语言。它衍生自C语言,继承了C语言的一些特性,比如使用指针直接访问内存,同时它也支持面向对象编程,提升了代码的可复用性、可扩展性以及灵活性。由于C++继承了C的大部分语法,再加上本身具备复杂的类型系统以及泛型编程等语言特性,新人入门门槛也比较高。
3 Python Python语言发明于1991年,是一种解释型计算机编程语言,因此运行速度相对要慢。Python除了支持面向对象编程之外,还支持函数式编程,它语法简单,更贴近人类自然语言,新人入门门槛较低。Python是目前人工智能领域最热门的语言,对应的工具库非常丰富。

上面是以表格的形式来介绍 C、C++ 和 Python 三种编程语言,但是在「介绍」那一列中的文本内容太长,可以换一种表达方式。

C C++ Python
由AT&T 贝尔实验室发明于1969至1973年 由BJarne Struistrup发明于1979年 由Guido van Rossum发明于1991年
语法比较复杂,新人入门门槛高 语法比较复杂,新人入门门槛较高 语法简单,贴近人类自然语言,新人入门门槛低
编译型语言 编译型语言 解释型语言
支持面向过程编程 支持面向过程、面向对象编程 支持面向过程、面向对象、函数式编程
偏底层、运行速度快、使用灵活 继承了C语言的一些特性,在其基础之上还支持面向对象等特性 语法简单,学习难度低
一般用于驱动、编译器、嵌入式或者其他偏向硬件层面的开发 一般用于游戏前后端、PC客户端的开发 一般用于数据科学、人工智能相关开发

上面表格还是 3 列,但是现在每列代表一种编程语言,列中的每个单元格是对该语言的描述,描述内容都比较精简。如果想继续补充内容,可以对应增加行即可。

英文处理

英文处理有以下原则:

  • 英文原文如果使用了复数形式,翻译成中文时,应该将其还原为单数形式;
    • 英文:...information stored in random access memory (RAMs)...
    • 中文:⋯⋯存储在随机存取存储器(RAM)里的信息⋯⋯
  • 表示中文时,英文省略号应改为中文省略号;
    • 英文:5 minutes later...
    • 中文:5 分钟过去了⋯⋯
  • 英文书名或电影名该用中文表达时,双引号应改为书名号;
    • 英文:He published an article entitled "The Future of the Aviation".
    • 中文:他发表了一篇名为《航空业的未来》的文章。
  • 第一次出现英文词汇时,在括号中给出中文标注,以后再次出现时,直接使用英文缩写即可;
    • 示例:IOC(International Olympic Committee,国际奥林匹克委员会)。这样定义后,便可以直接使用“IOC”了。
  • 专有名词中每个词第一个字母均应大写,非专有名词则不需要大写。
    • 示例:
      • “American Association of Physicists in Medicine”(美国医学物理学家协会)是专有名词,需要大写。
      • “online transaction processing”(在线事务处理)不是专有名词,不应大写。

争议

以下用法略带有个人色彩,即:无论是否遵循下述规则,从语法的角度来讲都是正确的。

  • 链接之间增加空格;

    未加空格 加空格
    提交一个 issue并分配给相关同事。 提交一个 issue 并分配给相关同事。
    访问我们网站的最新动态,请点击这里进行订阅! 访问我们网站的最新动态,请 点击这里 进行订阅!
  • 简体中文使用直角引号。

    • 传统引号:“老师,‘有条不紊’的‘紊’是什么意思?”
    • 直角引号:「老师,『有条不紊』的『紊』是什么意思?」

写作风格

  • 尽量不使用被动语态,改为使用主动语态;
    • 错误:假如此软件尚未被安装,
    • 假如尚未安装这个软件,
  • 不使用非正式的语言风格;
    • 错误:Lady Gaga 的演唱会真是酷毙了,从没看过这么给力的表演!!!
    • 正确:无法参加本次活动,我深感遗憾。
  • 不使用生僻、生造或者文言文的词语,而要使用现代汉语的常用表达方式;
    • 错误:这是唯二的快速启动的方法。
    • 正确:这是仅有的两种快速启动的方法。
  • 正确使用「的」、「地」和「得」;
    • 形容词 + 的 + 名词:她露出了开心的笑容。
    • 副词 + 地 + 动词:她开心地笑了。
    • 动词 + 得 + 副词:她笑得很开心。
  • 名词前不要使用过多的形容词。
    • 错误:此设备的使用必须在接受过本公司举办的正式的设备培训的技师的指导下进行。
    • 正确:此设备必须在技师的指导下使用,且指导技师必须接受过由本公司举办的正式设备培训。

总结

本文主要从写作目标、写作步骤和写作规范三个方面结合相关资料阐述了自己对文档写作的认知。即是对自己文档写作技巧的总结,也希望能为大家在文档写作方面带来一些灵感。受限于知识水平,本文可能存在些许不足。如果您有好的意见或看法,欢迎来信与我沟通交流

参考文献

附录

附录 A

附录 A 主要记录的是基于 MrDoc 部署博客系统发布文档 Python 脚本的使用方法。

首先介绍一下脚本里面的几个核心变量:

  • ARCHIVE_PATH:文档发布完成后需要归档,这里指移动后的目标路径;
  • DOMAIN:博客域名;
  • TOKEN:用户 token,获取方法参考这篇文档

脚本使用时会传递两个参数:

  • 第一个参数:OP_TYPE,即操作类型,其中 1 表示新增文档,2 表示更新文档;
  • 第二个参数:TARGET_PATH,即目标文件路径。

脚本使用示例:

python3 operate_mrdoc_doc.py 1 /path/文档写作技巧.md

脚本可以结合「右键助手」来使用,方法如下:

  1. 打开「右键助手」,依次点击「常用脚本」->「+」;

image-20230606210738904

  1. 在新打开页面完成如下配置,然后点击「确定」:

    • 脚本名称:新建文档

    • 脚本内容:python3 /Users/weipeng/Library/Mobile\ Documents/iCloud\~md\~obsidian/Documents/KnowledgeBase/doc/Shell/operate_mrdoc_doc.py 1 @rfpath

    • 勾选:「必须右键选中文件才能使用」

    • 文件格式:md

      更新文档和新建文档类似,知识脚本中的第一个参数 1 改为 2 即可,下面不再进行单独阐述。

      image-20230606211412686

      image-20230606211854327

  2. 配置完成后在要操作的文档上依次点击「右键」->「常用脚本」->「新建文档」即可。

    image-20230606212311073

最后附上文档发布的脚本。

import requests
import sys
import shutil
import os
import logging
from logging import handlers
from datetime import datetime
import urllib3
urllib3.disable_warnings()

NOW_TIME = datetime.now()
FORMAT_NOW_TIME = NOW_TIME.strftime('%Y%m%d%H%M%S')

OP_TYPE = sys.argv[1]   # 操作类型,1: 新增文档,2: 更新文档
TARGET_PATH = ' '.join(sys.argv[2:])   # 文件路径
TARGET_NAME = TARGET_PATH.split('/')[-1].split('.md')[0]    # 文件名
KNOWLEDGE_BASE_PATH = '/Users/weipeng/Library/Mobile Documents/iCloud~md~obsidian/Documents/KnowledgeBase'
LOG_BASE_PATH = '{}/doc/Shell/logs'.format(KNOWLEDGE_BASE_PATH)
ARCHIVE_PATH = '{}/archive/'.format(KNOWLEDGE_BASE_PATH)
ARCHIVE_FILE = '{}{}-{}.md'.format(ARCHIVE_PATH, TARGET_NAME, FORMAT_NOW_TIME)
DOMAIN = "https://notebook.ricear.com"   # 域名
TOKEN = "******"  # 用户 token

API_CREATE_URL = "{}/api/create_doc/?token={}".format(DOMAIN, TOKEN)    # 新增文档 API 地址
API_UPDATE_URL = "{}/api/modify_doc/?token={}".format(DOMAIN, TOKEN)    # 更新文档 API 地址
URL = ''

class Logger(object):
    level_relations = {
        'debug':logging.DEBUG,
        'info':logging.INFO,
        'warning':logging.WARNING,
        'error':logging.ERROR,
        'crit':logging.CRITICAL
    }#日志级别关系映射

    def __init__(self,filename,level='info',when='D',backCount=3,fmt='%(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s'):
        self.logger = logging.getLogger(filename)
        format_str = logging.Formatter(fmt)#设置日志格式
        self.logger.setLevel(self.level_relations.get(level))#设置日志级别
        sh = logging.StreamHandler()#往屏幕上输出
        sh.setFormatter(format_str) #设置屏幕上显示的格式
        th = handlers.TimedRotatingFileHandler(filename=filename,when=when,backupCount=backCount,encoding='utf-8')#往文件里写入#指定间隔时间自动生成文件的处理器
        #实例化TimedRotatingFileHandler
        #interval是时间间隔,backupCount是备份文件的个数,如果超过这个个数,就会自动删除,when是间隔的时间单位,单位有以下几种:
        # S 秒
        # M 分
        # H 小时、
        # D 天、
        # W 每星期(interval==0时代表星期一)
        # midnight 每天凌晨
        th.setFormatter(format_str)#设置文件里写入的格式
        self.logger.addHandler(sh) #把对象加到logger里
        self.logger.addHandler(th)

if __name__ == '__main__':
    logger = Logger('{}/shell.log'.format(LOG_BASE_PATH),level='debug').logger
    err_logger = Logger('{}/error.log'.format(LOG_BASE_PATH), level='error').logger

    logger.info('Target path: {}'.format(TARGET_PATH))

    try:
        f = open(TARGET_PATH)
        line = f.readline()

        splits = line.split('-')
        project_id = splits[1].split('/')[0]    # 文集 ID
        if len(splits) == 2:
            doc_id = 0
        else:
            doc_id = splits[2].split(')')[0]    # 文档 ID

        content = ""    # 文档内容

        line = f.readline() # 去除第一行下一行的换行
        while line:
            line = f.readline()
            content += line
        f.close()

        if OP_TYPE == '1':
            # 新增文档
            payload={   # 参数
            'pid': project_id,
            'parent_doc': doc_id,
            'title': TARGET_NAME,
            'doc': content,
            'editor_mode': '2'
            }
            URL = API_CREATE_URL
        elif OP_TYPE == '2':
            # 更新文档
            payload={'pid': project_id,   # 参数
            'did': doc_id,
            'title': TARGET_NAME,
            'doc': content}
            URL = API_UPDATE_URL

        files=[]
        headers = {}

        response = requests.request("POST", URL, headers=headers, data=payload, files=files, verify=False) # 更新文档
        response = response.json()

        logger.info('URL: {}'.format(URL))
        logger.info('Arguments: {}'.format(payload))

        if response['status'] == True:  # 操作成功后将文档进行归档
            if os.path.exists(ARCHIVE_PATH) == False:
                os.makedirs(ARCHIVE_PATH)
            if os.path.exists(ARCHIVE_FILE):
                os.remove(ARCHIVE_FILE)
            logger.info('Archive file: {}'.format(ARCHIVE_FILE))
            shutil.move(TARGET_PATH, ARCHIVE_FILE)
        else:
            err_logger.info(response.content)
    except Exception as e:
        err_logger.error(e)