如何写好技术文档?-程序员宅基地

技术标签: DevOps  如何写好  技术文档  

在大多数软件工程师对编写、使用和维护代码的抱怨中,一个常见的问题是缺乏高质量的文档。缺乏文档有什么副作用呢?当遇到一个bug时,这个缩写是什么意思?这份文件是最新的吗?在整个职业生涯中,每个软件工程师都抱怨过文档的质量、数量或者完全缺乏文档。

01

为什么需要写文档?

高质量文档对工程组织有巨大的好处。代码和api变得更容易理解。当他们的设计目标和团队目标被清楚地陈述时,项目团队会更加专注。当步骤被清晰地列出时,手动流程更容易遵循。如果过程被清楚地记录下来,那么让新成员进入团队或代码库所花费的精力就会少得多。

但是,由于文档的好处有一定的滞后性,通常不会给作者带来直接的好处。不像测试,编写完测试用例,跑一遍就有结果。毕竟,你可能只编写了一个文档,但之后它将被阅读数百次,甚至数千次;它的初始成本将摊销给所有未来的读者。文档不仅可以随着时间的推移而扩展,而且它对组织的其他部分的扩展也很关键。它有助于回答以下的问题:

  • 为什么会做出这些设计决策?
  • 为什么要以这种方式实现这段代码?
  • 为什么大多数工程师不喜欢写文档?

虽然文档可以带来不少好处,为什么工程师通常认为它是糟糕的?其中一个原因,正如我们提到的,是好处不是立竿见影的,特别是对作者来说。另外还有以下几点:

  • 很多工程师习惯将写代码和写作割裂开,不仅仅是在工作上,而且在思想上就认为它们是完全不相关的两项工作,这就导致好多人重代码不重文档。
  • 也有很多工程师认为自己不善写作,索性就不写了。这实际是个偷懒的借口,写文档不需要华丽的辞藻、生动的语言,你只需要将问题讲清楚即可。
  • 有时候工具不好用也会影响的文档写作。如果没有一个很好的写作工具将写文档嵌入到开发工作流程中的话,写作确实会增加工作的负担。
  • 大多数人将写文档看做是工作的额外负担。我代码都没时间写,哪有时间写文档!,这其实是错误的观念,文档虽然前期有投入,但能让你代码的后期维护成本大幅降低,磨刀不误砍柴工这个道理相信大家都还是能理解的。

02

写文档的重要性

另外一个原因是文档被视为需要维护的额外负担,而不是使现有代码的维护更容易的东西。

写文档同样对作者也有非常大的好处:

  • 它有助于审视API。编写文档是确定API是否有意义的最可靠的方法之一。通常,编写文档本身会使工程师重新评估设计决策。如果你不能解释它,不能定义它,那你可能还没有设计好它。
  • 它提供了维护的历史记录。在任何情况下,代码中的技巧都应该避免,但是当你盯着两年前编写的代码,试图找出错误所在时,好的注释将提供很大的帮助。
  • 它使你的代码看起来更专业。开发人员自然会认为文档完备的API就是设计得更好的API。虽然情况并非总是如此,但它们通常是高度相关的。虽然这个好处听起来像是表面文章,但事实并非如此:一个产品是否有良好的文档通常是一个很好的指标。

这将减少其他用户提出的问题。对于编写文档的人来说,随着时间的推移这可能是最大的好处。如果你必须向某人解释某件事不止一次,那么记录该过程通常是有意义的。

03

像管理代码一样管理文档

软件工程师使用单一的、主要的编程语言来编写程序,他们仍然经常使用不同的语言来解决特定的问题。工程师可能会编写shell脚本或Python来运行命令行任务,或者他们可能会用c++编写大部分后端代码,但用Java编写一些中间件代码,等等。每种语言都是工具箱中的一种工具。

文档也应该如此:它是一种工具,用不同的语言编写,以完成特定的任务。编写文档与编写代码没有多大区别。与编程语言一样,它有规则、特定的语法和样式决定,通常是为了实现与代码中类似的目的:加强一致性、提高清晰度和避免理解错误。

文档通常与代码紧密耦合,因此应该尽可能地将其视为代码。也就是说,文档也应具有如下的属性:

  • 有明确的责任人维护;
  • 有统一的内部规范;
  • 定期更新;
  • 有变更和评审机制;
  • 有问题反馈和更新机制;
  • 明确文档的读者是谁

工程师在编写文档时犯的一个最重要的错误是只为自己编写文档。这样做是很自然的,并且为自己编写代码并非没有价值:毕竟,你可能需要在几年后查看这些代码,并试图弄清楚你曾经的想法。或者说在团队较小的时候,大家的工作交集很大,因此也能看懂你的文档,但是随着组织的发展,问题就逐渐凸显,新人会有这不同背景,团队大了以后,工作内容开始细化,交集减少,这时候之前写的文档对他们来说可能就很难理解了。

因此在开始写作之前,应该(正式或非正式地)确定文档需要满足的受众。设计文件可能需要说服决策者。教程可能需要为完全不熟悉代码库的人提供非常明确的说明。API可能需要为该API的任何用户(无论是专家还是新手)提供完整和准确的参考信息。

好的文档不需要修饰或完善。工程师在编写文档时所犯的一个错误是认为他们需要成为更好的作者。按照这个标准,很少有软件工程师会写。记住,我们的观众站在你曾经站过的地方,但没有你新的领域知识。所以你不需要成为一个伟大的作家;你只需要找一个像你一样熟悉这个领域的人。

04

文档类型

作为工作的一部分,工程师会编写各种不同类型的文档:设计文档、代码注释、操作文档、项目页面等等。这些都可以算作文档。但重要的是要知道不同的类型,不要混合类型。一般来说,文档应该有一个单一的目的。正如API应该做一件事并且做好一样,避免在一个文档中做几件事。

软件工程师经常需要编写几种主要类型的文档:

  • 参考文档,包括注释
  • 设计文档;
  • 教程;
  • 概念性文档;

1.参考文档

参考文档是工程师最常编写的文档类型;事实上,他们经常需要每天写一些参考文档。代码注释是工程师必须维护的最常见的参考文档形式。这些注释可以分为两个基本阵营:API注释和实现注释。这两种用户之间的区别:API注释不需要讨论实现细节或设计决策,也不要假设用户和作者一样精通API。但是实现注释可以假定读者有更多的领域知识,但是要注意不要假设得太多。

2.设计文档

大多数公司在项目开始之前都需要有设计文档。软件工程师通常使用团队批准的特定设计文档模板来编写建议的设计文档。然后还需要在特定的团队会议上讨论或评论设计的细节。

一个好的设计文档应该涵盖:

  • 设计目标
  • 实现策略
  • 利弊权衡和具体决策
  • 替代方案
  • 各方案的优缺点

一份优秀的设计文件,一旦获得批准,不仅可以作为历史记录,还可以作为项目是否成功实现其目标的衡量标准。大多数团队会将他们的设计文档进行归档,以便在后续的时间查看。在产品发布前检查设计文档通常是很有用的,以确保编写设计文档时所陈述的目标在发布时仍然是所陈述的目标(如果不是,那么文件或产品都可以进行相应的调整)。

3.教程

每个软件工程师,当他们加入一个新的团队时,都会想要尽可能快地跟上进度。拥有一个引导人们完成新项目设置的教程是非常有价值的。

通常情况下,编写教程的最佳时机是你第一次加入一个团队的时候。拿个记事本或其他方法做笔记,写下一路上你需要做的所有事情,假设没有领域知识或特殊的设置限制;完成之后,可能会知道在这个过程中所犯的错误和原因,然后可以编辑你的步骤,以获得更精简的教程。重要的是,写下一路上你需要做的一切;尽量不要假定任何特定的设置、权限或领域知识。

这类型的文档写作中,要求写作者尽可能站在用户的视角上思考,极力避免出现和用户的认知偏差,力争每个步骤做到明确无歧义,每两个步骤之间做到紧密衔接。

4.概念性文档

有些代码需要更深入的解释或见解,而不是仅仅通过阅读参考文档就能得到的。在这些情况下,我们需要概念性文档来提供api或系统的概述。概念性文档处理可能是API的库概述、描述服务器中数据生命周期的文档等。概念性文档是用来扩充而不是替换参考文档集的。有时候这和参考文档会有些内容重复,,但主要还是为了更深层次的说明某些问题、解释清楚某个概念。概念性文档没有必要涵盖所有边缘情况。在这种情况下,为了清晰度而牺牲一些准确性是可以接受的。概念性文件的要点在于传达理解。

概念文档是最难编写的文档形式。因此,它们通常是软件工程师工具箱中最容易被忽视的文档类型。而且还有另外一个问题,没合适的地方放,参考文档可以写代码里,落地页可以写项目主页里,概念性文档似乎也只能在项目文档里找个不起眼的角落存放了。

概念文档需要对广泛的受众有用:无论是专家还是新手。此外,它需要强调清晰性,所以它通常需要牺牲完整性(最好留作参考)和(有时)严格的准确性。这并不是说概念性文件应该故意不准确;这只是意味着它应该关注常见的用法,而将罕见的用法或副作用留作参考文档。

05

文档Review

在一个组织内,光靠个人去维护文档是不行的,必须得借助群体的智慧。在一个组织内部,文档的变更也应该像代码的变更一样,需要被其他人Review,以提前发现其中的问题并提升文档的质量。

技术文档得益于三种不同类型的review,每种审查都强调不同的方面:

  • 专业的视角来保证准确性:一般由团队里比较资深的人负责,他们关注的核心点是文档写的对不对,专不专业。如果Code Review做的好的话,文档的Review也属于Code Review的一部分。
  • 读者视角保证简洁性:一般由不熟悉这个领域的人来Review,比如团队的新人,或者文档的使用者。这部分主要是关注文档是否容易被看懂。
  • 写作者视角保证一致性:由写作经验丰富或者相关领域比较资深的人承担,主要是为了保证文档前后是否一致,比如对同一个专业术语的使用和理解是否有歧义。

06

文档写作的哲学

下面的部分更多地是关于技术写作最佳实践的论述。

5W法则相信大家已经听的多了,分别是WHO, WHAT, WHEN, WHERE, WHY,这是一个广泛被用在各行各业的法则,写文档当然也不例外。

WHO:正如前面所说,文档的针对对象是谁,读者是谁。

WHAT:明确文档写作的用途,通常仅需说明文档的用途和目的就能帮你搭建起整个文档的框架。

WHEN:明确文档的创建、Review和更新日期。因为文档也有时效性,明确相关日期可以避免阅读者踩坑。

WHERE:文档应该放在哪!建议一个组织或者团队有统一的永久文档存放地址,并且有版本控制。最好是方便查找、使用和分享。

WHY:为什么要写这篇文档, 你期望读者读完后从文档中获得什么!

1.三段式文档

所有文档,文档的所有部分都有开头、中间和结尾。尽管这听起来非常愚蠢,但大多数文件通常都应该有这三个部分。不要害怕在文档中添加章节;它们将流程分解为逻辑部分,并为读者提供文档所涵盖内容的路线图。

通常开头部分代表问题,中间部分介绍推荐的解决方案,结尾部分总结结论。但这也并不以为着文档应该有三个部分,如果文档内容比较多,可以将其做更细致的拆解,可以适当增加一些冗余的信息帮助读者理解文档内容。虽然很多工程师都讨厌冗余 极力追求简洁,但写文档和写代码不同,适当的冗余反而可以帮助读者理解。

2.好文档必备的属性

好的文档通常有三个方面:完整性、准确性和清晰度。很少在同一个文档中同时获得这三种信息;例如,当你试图使文档更完整时,清晰度就会开始受到影响。如果您试图记录API的每个可能的用例,您可能会得到一个难以理解的混乱。对于编程语言来说,在所有情况下都做到完全准确(并记录所有可能的副作用)也会影响清晰度。对于其他文件,试图明确一个复杂的主题可能会微妙地影响文件的准确性;例如,您可能决定忽略概念性文档中的一些罕见的副作用,因为文档的目的是让人们熟悉API的用法,而不是提供对所有预期行为的武断概述。

在每种情况下,一个好的文档都被定义为正在执行其预期工作的文档。因此,您很少希望文档执行一项以上的工作。对于每个文档(以及每个文档类型),确定其重点并适当调整编写,写概念性文档可能不需要涵盖API的每个部分,写一个参考可能想要这个完整,但可能必须牺牲一些清晰度等。所有这些加起来就是质量,不可否认的是,质量很难精确衡量。

07

结论

公平地说,对文档的处理与对测试的处理并不一定相同。对测试来讲可以进行原子测试(单元测试),并遵循规定的形式和功能。在大多数情况下,文件不能。测试可以自动化,但通常缺乏自动化文档的方案。文件必然是主观的;文档的质量不是由作者来衡量的,而是由读者来衡量的,而且通常是异步的。

最后总结下本文几个关键点:

  • 随着时间推移,团队的壮大,文档会越来越重要;
  • 文档应该是开发人员工作流程的一部分;
  • 每篇文档专注于一个目的;
  • 文档是给读者看的,而不是你自己写作。
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u012294613/article/details/123892015

智能推荐

JWT(Json Web Token)实现无状态登录_无状态token登录-程序员宅基地

文章浏览阅读685次。1.1.什么是有状态?有状态服务,即服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求的处理,典型的设计如tomcat中的session。例如登录:用户登录后,我们把登录者的信息保存在服务端session中,并且给用户一个cookie值,记录对应的session。然后下次请求,用户携带cookie值来,我们就能识别到对应session,从而找到用户的信息。缺点是什么?服务端保存大量数据,增加服务端压力 服务端保存用户状态,无法进行水平扩展 客户端请求依赖服务.._无状态token登录

SDUT OJ逆置正整数-程序员宅基地

文章浏览阅读293次。SDUT OnlineJudge#include<iostream>using namespace std;int main(){int a,b,c,d;cin>>a;b=a%10;c=a/10%10;d=a/100%10;int key[3];key[0]=b;key[1]=c;key[2]=d;for(int i = 0;i<3;i++){ if(key[i]!=0) { cout<<key[i.

年终奖盲区_年终奖盲区表-程序员宅基地

文章浏览阅读2.2k次。年终奖采用的平均每月的收入来评定缴税级数的,速算扣除数也按照月份计算出来,但是最终减去的也是一个月的速算扣除数。为什么这么做呢,这样的收的税更多啊,年终也是一个月的收入,凭什么减去12*速算扣除数了?这个霸道(不要脸)的说法,我们只能合理避免的这些跨级的区域了,那具体是那些区域呢?可以参考下面的表格:年终奖一列标红的一对便是盲区的上下线,发放年终奖的数额一定一定要避免这个区域,不然公司多花了钱..._年终奖盲区表

matlab 提取struct结构体中某个字段所有变量的值_matlab读取struct类型数据中的值-程序员宅基地

文章浏览阅读7.5k次,点赞5次,收藏19次。matlab结构体struct字段变量值提取_matlab读取struct类型数据中的值

Android fragment的用法_android reader fragment-程序员宅基地

文章浏览阅读4.8k次。1,什么情况下使用fragment通常用来作为一个activity的用户界面的一部分例如, 一个新闻应用可以在屏幕左侧使用一个fragment来展示一个文章的列表,然后在屏幕右侧使用另一个fragment来展示一篇文章 – 2个fragment并排显示在相同的一个activity中,并且每一个fragment拥有它自己的一套生命周期回调方法,并且处理它们自己的用户输_android reader fragment

FFT of waveIn audio signals-程序员宅基地

文章浏览阅读2.8k次。FFT of waveIn audio signalsBy Aqiruse An article on using the Fast Fourier Transform on audio signals. IntroductionThe Fast Fourier Transform (FFT) allows users to view the spectrum content of _fft of wavein audio signals

随便推点

Awesome Mac:收集的非常全面好用的Mac应用程序、软件以及工具_awesomemac-程序员宅基地

文章浏览阅读5.9k次。https://jaywcjlove.github.io/awesome-mac/ 这个仓库主要是收集非常好用的Mac应用程序、软件以及工具,主要面向开发者和设计师。有这个想法是因为我最近发了一篇较为火爆的涨粉儿微信公众号文章《工具武装的前端开发工程师》,于是建了这么一个仓库,持续更新作为补充,搜集更多好用的软件工具。请Star、Pull Request或者使劲搓它 issu_awesomemac

java前端技术---jquery基础详解_简介java中jquery技术-程序员宅基地

文章浏览阅读616次。一.jquery简介 jQuery是一个快速的,简洁的javaScript库,使用户能更方便地处理HTML documents、events、实现动画效果,并且方便地为网站提供AJAX交互 jQuery 的功能概括1、html 的元素选取2、html的元素操作3、html dom遍历和修改4、js特效和动画效果5、css操作6、html事件操作7、ajax_简介java中jquery技术

Ant Design Table换滚动条的样式_ant design ::-webkit-scrollbar-corner-程序员宅基地

文章浏览阅读1.6w次,点赞5次,收藏19次。我修改的是表格的固定列滚动而产生的滚动条引用Table的组件的css文件中加入下面的样式:.ant-table-body{ &amp;amp;::-webkit-scrollbar { height: 5px; } &amp;amp;::-webkit-scrollbar-thumb { border-radius: 5px; -webkit-box..._ant design ::-webkit-scrollbar-corner

javaWeb毕设分享 健身俱乐部会员管理系统【源码+论文】-程序员宅基地

文章浏览阅读269次。基于JSP的健身俱乐部会员管理系统项目分享:见文末!

论文开题报告怎么写?_开题报告研究难点-程序员宅基地

文章浏览阅读1.8k次,点赞2次,收藏15次。同学们,是不是又到了一年一度写开题报告的时候呀?是不是还在为不知道论文的开题报告怎么写而苦恼?Take it easy!我带着倾尽我所有开题报告写作经验总结出来的最强保姆级开题报告解说来啦,一定让你脱胎换骨,顺利拿下开题报告这个高塔,你确定还不赶快点赞收藏学起来吗?_开题报告研究难点

原生JS 与 VUE获取父级、子级、兄弟节点的方法 及一些DOM对象的获取_获取子节点的路径 vue-程序员宅基地

文章浏览阅读6k次,点赞4次,收藏17次。原生先获取对象var a = document.getElementById("dom");vue先添加ref <div class="" ref="divBox">获取对象let a = this.$refs.divBox获取父、子、兄弟节点方法var b = a.childNodes; 获取a的全部子节点 var c = a.parentNode; 获取a的父节点var d = a.nextSbiling; 获取a的下一个兄弟节点 var e = a.previ_获取子节点的路径 vue