引言
写过不少技术文章,以及给不少技术思路手绘示例配图之后,在这方面有了一些心得,本文给出一些关于配图的见解,仅供大家参考。
关于技术配图
对于理工科出身的同学,对于可以量化的事情,总是很习惯根据量化差异来做出判断,比如一个程序性能优化之后,对比优化之前快出多少,都能很容易的通过一个量化的数字来说明。
但是对于那些不能量化的东西,就很难说出具体好在哪里了。
本文主题要讨论的“技术配图”就属于这种很难量化的领域,很难有一个标准来量化说明两幅图之间差别在哪里。我也是画了很多图,以及看了别人的很多配图之后,才慢慢有一些心得,本文权当个人的一些的总结,抛砖引玉,欢迎交流探讨。
本文并不是一个画图工具的对比说明,尽管现在各种绘图工具已经很多,也各有自己的优缺点,但是在这里并不讨论具体工具的使用,会把更多的文字放在配图的一些注意事项上。但是,也总有人问我文章的配图是用什么工具做的,在这里再回答一次:OmniGraffle,一款目前仅有 Mac 版本的工具软件。
配图的重要性
在开始交代具体的配图注意事项之前,有必要先说说配图的重要性。
绘图,某种程度来说也是辅助自己思考某个技术点的手段之一,以我个人的体会来说,有时候讲不清楚一个技术点的时候,就手绘图出来,比朴素的文字更容易说明问题。其中的原因,有可能是:图片可以有多维的信息,而文字通常只有一维,遇到文字表达能力不太好的人,这仅有的一维能力可能还不好发挥出来。
所以,在交代技术细节、沟通交流的时候,尽量多画图。反向的,图画多了,也自然慢慢会找到感觉,更好的通过图示表达思路。
顺便一提,还有比朴素的文字表达更差的技术沟通方式,就是简单粗暴的贴一大段代码上去。这种做法,其实更多时候是对作者的思路没有太多个人的整理,纯粹偷懒的方式,往往最后回头再看写过的文字,可能连自己都看不懂了。
如果产出某些内容的时候,能假设自己未来就是这些输出的读者、维护者,那么输出起来会更完善一些。比如写的代码、文章、甚至于提交代码时候的信息,如果能考虑是写给未来的自己看的,会更清晰、尽可能留下更多的信息。我最开始要在文章里大量配图,也是为了将来自己回看的时候能看懂。
总之,尽可能多画图来表达技术思路。
下面开始正题,以下会以简单的几个原则及示例来说明。
区分、联系、组合
配图中,应该尽量将不同的模块、组件等区分开来,“区分”的方式有很多,常见的有:
- 使用不同的颜色。
- 使用不同的形状。
- 使用箭头、曲线等表示数据的走向、趋势。
等等,所有的这些手段,概括起来就是尽量在图中,将不同的元素区分开来,“有区分”意味着至少有一个维度的不同,这样能给读者更加清晰的观感。可以结合下面的例子来理解区分、联系和组合的绘图表达。
分组
一个模块里,可能由多个组件构成,可以把这些组件分组到一个更大的模块中。
分组是非常常见的一种手段,这里多举几个例子。
上图中,每个 CPU Core 中有 L1、L2 缓存,于是把这些组件合并在一起放在 Core 组件中,周围使用一个正方形包裹起来,同时这个正方形左上角有一个 Core 的说明文字,这样一目了然:Core 模块,由 L1、L2 缓存构成。
上图出自 Raft 论文,整体上划分为了 Client、Server 这两大部分。而每个 Server 又有以下三部分组成:
- 一致性算法模块。
- 状态机。
- 持久化的日志。
所以,图示中将这三部分合在一起放在同一个矩形里,表示一个Server有这三个组件。
另外还需注意的是,一般这种分组中外围的矩形,有这样的讲究:
- 一般使用斜面矩形,即四个角是圆角的矩形,这样圆润一些的边角看起来会更舒服一些,如上图。
- 如果这个组合,是一种逻辑上的组合,那么线的形状一般用虚线;否则就一般用的实线。
在分组时,有时候可以将相同类型的模块层叠起来,这样会更加简洁,如下图:
上图是出自 Raft 论文中的状态机模型,其中想要表达的一个点是:
- 有多个 client 向 server 发起请求。
- server 要达成一致,需要将日志在 server 之间同步。
但是上图中,并没有把这些同类型的组件分开表达,而是巧妙的使用层叠的方式,简洁得表达了有多个 client、多个 server 的情况。
趋势
下图是描述不同层次存储的访问速度,于是用了两个方式来表达访问速度的变化趋势:
- 左边的箭头表达速度和成本的变化。
- 不同大小的多边形表达了这些存储空间的变化:越往上访问速度越快,但是对应的存储空间也更小。
再比如,下图中,是说明 sqlite 中 btree 页面的数据组织的。其中的两部分内容,Cell 地址数组以及 Cell 内容区为变长大小,前者从地址低位向高位生长,后者反之,于是在图中,就用箭头示例出地址的高低位区别,以及两者的增长方向:
(出自 sqlite3.36版本 btree实现(五)- Btree的实现 - codedump的网络日志[1])
联系
这在涉及:
- 状态切换。
- 数据流向。
等场景下是非常常见的手段,比如经典的 TCP 状态机切换:
以及 TCP 三次握手流程,也是典型的“状态切换”:
需要说明的是,以上的图示中:
- 箭头代表的状态切换走向中,同时也配以文字说明是什么动作导致的状态切换,这样这个图示就更清晰了。
- 箭头也分为实线和虚线,一般而言,虚线表示数据的走向,实线表示状态的走向。
禁止
需要禁止或者错误的行为,可以用特殊的符号,如带颜色的“×”符号示意出来;反之,可以用带颜色的“√”符号示意出来,而且表示禁止的时候,一般用红色会更显眼,下图就是一个示例:
(出自 Memory Barriers in .NET · Nadeem Afana’s Blog[2])
说明
如果不好说明问题,可以在图示中搭配简短的说明文字。注意:这类型文字一定要足够的简短,否则可能会喧宾夺主。
比如下图中,有两部分蓝色注解的文字来说明不同的表类型:
(出自 sqlite3.36版本 btree实现(五)- Btree的实现 - codedump的网络日志[3])
再比如下图中,使用注解文字来说明查找数据的两步流程:
(出自 sqlite3.36版本 btree实现(五)- Btree的实现 - codedump的网络日志[4])
分类
有时候需要使用类似“{”这样的符号,对一类元素做一些说明,例如:
下图中,是说明 sqlite 中 btree 页面的数据组织的,最右边的以“{”包起来的文字,对每部分做了简要的说明。
(出自 sqlite3.36版本 btree实现(五)- Btree的实现 - codedump的网络日志[5])
下图中,将页面划分为不同的部分,这些不同的组成部分,既使用了颜色进行区分,也使用了向下的“{”辅以文字说明。
(出自 sqlite3.36版本 btree实现(五)- Btree的实现 - codedump的网络日志[6])
步骤
如果配图是需要讲解某个操作的步骤的,可以配以数字来辅助理解整个流程。
下图中,表达的是根据帧数查找页面编号的两个步骤:
(出自 sqlite3.36版本 btree实现(四)- WAL的实现 - codedump的网络日志[7])
下图中的步骤就更多了,并没有显得很乱,大概原因在于:
- 最左边表达了每一步的步骤。
- 每一步写入数据之后,显示 WAL 文件在写入之后的内容。
- 最右边使用“{”表达修改之后的数据。
(出自 sqlite3.36版本 btree实现(四)- WAL的实现 - codedump的网络日志[8])
展开
如下图中,是用于展示 wal index 索引文件格式的:
- 左边示例每部分内容的大小,想说明的是,那个索引块大小为 32 KB,而第一块的头 136 字节为索引文件头。
- 于是,在右边图中,将左边不同模块的具体格式继续展开说明。
(出自 sqlite3.36版本 btree实现(四)- WAL的实现 - codedump的网络日志[9])
总结
以上简单总结了一下个人技术配图的一些心得,总的大原则是:
- 区分:将组件、流程、趋势等之间的“区分”尽可能在图示中通过各种手段(如不同的颜色、形状、箭头)表达出来。
- 联系:组件之间的数据流动、状态切换等,都是它们之间的联系,也需要通过各种手段表达出来。
- 说明:可能的话,要在图中加上一些说明文字,如步骤说明、分类说明,等等。