diagram-design 这个项目名字,听起来像是个画图工具,其实我把它做成了一整套“如何把复杂系统讲清楚”的设计规范。起因是我自己带团队时经常遇到一种尴尬:评审会上投出来的架构图非常漂亮,各种颜色、圆角、图标都齐全,可一旦开始讲,大家就会指着某个方框问:这个箭头是调用关系还是数据流?这个虚线是什么意思?为什么同一个服务在图上出现了三次?问到最后我自己也愣住了——因为我画图的时候只想着怎么把节点摆好看,压根没把“看图人需要什么信息”这件事想清楚。
我后来花了大半年时间把这段经历沉淀成一个独立项目,项目代号就叫 diagram-design。它不是一个产品,也不是某个开源库,而是一套可执行的画图方法:从制图前的信息梳理,到不同类型的图该用什么结构,再到布局、配色、箭头方向这些细节,最后还覆盖了图如何存入代码库、如何自动化校验。这篇文章就是把我在 diagram-design 里整理过的经验一次性写出来。适合给那些需要画架构图、流程图、时序图,又不想每次都被同事追问“这图什么意思”的人。
1. 为什么大多数技术图画完就没人看
1.1 作者脑子里有一张图,纸上却是另一张图
这是我做 diagram-design 时发现的第一个问题:画图的人和看图的人,信息量根本不对称。画图的人在脑海里已经跑完一整套流程,他知道哪个服务先启动、哪个模块是异步的、哪条链路只在异常时才会走,但这些内容几乎不会主动画到图里。结果就是,图纸上只有几个孤零零的方框和箭头,所有靠背景知识才能看懂的隐含关系,全部落到了“只可意会”的层面。
我举一个实际例子。某一次我画一个交易系统的架构图,放了订单服务、支付服务、消息队列、数据库,还加了一堆微服务网关之类的装饰性节点。我自己看的时候觉得特别清晰,因为我知道订单服务失败之后要发一条消息到队列,支付回调通过另一个 topic 回来。但看图的人不知道,他们看到的只是一堆方块之间有几条线,根本判断不出正常路径和补偿路径分别是什么。后来我把这张图拿给一位新同事看,他问的第一句话是:“这里哪条线是主要流程?”那一刻我就明白了,图纸的信息密度再高,如果作者没有把“看图人需要哪种心智模型”考虑进去,就等于没画。
所以我在 diagram-design 里立了第一条硬规矩:画图之前,先写下这张图试图回答的三个问题。回答不出来的话,最好先去把业务逻辑搞清楚,而不是打开画布工具拖着节点开始排版。
1.2 先回答“这张图给谁看”,再决定画多细
同一套系统,给不同的人看,需要的图完全可以是两张。给决策者看,他们关心的是哪些模块是自研、哪些依赖外部、预算和风险在哪里;给一起开发的同事看,他们关心的是接口怎么调、数据落在哪个库、异常分支有没有兜底;给新成员看,他们需要的是一张极简的全景图,先让大脑有个骨架,再去啃细节。
如果你试图用一张超大图涵盖所有人的需求,结果往往是每个人都只能看到对自己有用的那一小部分,剩下的全部变成视觉噪音。我在 diagram-design 里专门给受众做了一张判断表,每次画图前先选一列:
| 受众 | 关注重点 | 必须出现的元素 | 不必出现的元素 |
|---|---|---|---|
| 决策者 | 成本、风险、影响范围 | 系统边界、外部依赖、关键链路 | 服务内部实现、数据库字段、类名 |
| 协作者 | 接口协议、数据流向、部署关系 | 模块名、端口/队列名、存储名 | 装饰性图标、阴影、渐变 |
| 新成员 | 整体架构、入口、归属关系 | 分层、分组、命名规范 | 异常分支、重试链路、临时性节点 |
这张表不是说每次都要严格遵守,而是逼着画图的人做一次“面向读者”的思考。你要是在画布上放一个节点,就得想清楚它是不是核心信息;如果不是,请删掉。删掉一个节点,比给节点换十种颜色更能提升图的质量。
1.3 信息分层:用删减代替堆叠
diagram-design 里有个概念叫“信息层级”,简单说就是一张图只能有一层主信息、两层支撑信息,剩下的全部舍弃。很多图之所以乱,是因为作者试图在一张图里同时表达部署结构、服务依赖、业务时序和团队归属。这些内容不是不能画,而是要拆成四张不同的图,而不是压成一张。
我通常的做法是先在草稿纸上写三个列表:必须表达的信息、有帮助的信息、可画可不画的信息。画完图之后再对照一遍,凡是落在第三类的,能删就删。如果一个节点被删掉之后没人会质疑它的存在,那它就不该存在。这样留出来的空白,反而会让核心链路变得更加显眼。
2. 制图前的信息梳理:先用文字把关系说清楚
2.1 可执行的文本原型:关系优先于坐标
很多人打开画图工具的第一件事是拖一个矩形出来,然后想怎么摆放。这个习惯几乎必然导致灾难,因为在布局还没想好的时候,你已经被坐标和大小分散了注意力。diagram-design 的解决方法是:在进入画布之前,先用文本把节点、连线、分组全部描述一遍。
我给项目里定义了一个极简的文本格式,类似伪代码,连语法都算不上。比如要描述一个登录链路,我会这么写:
[用户] -> [前端页面] : 输入账号密码 [前端页面] -> [网关] : 提交登录表单 [网关] -> [认证服务] : 校验凭证 [认证服务] -> [用户数据库] : 查询用户记录 [认证服务] -> [前端页面] : 返回登录令牌这段文本看起来非常简陋,但它比一张花花绿绿的图画出来得还要快,而且更容易被人 review。你可以让同事直接看这段文字,问他们有没有缺环节。如果文字的因果关系大家都认可,再把它转成图,剩下的工作就纯粹是排版,而不是重新思考业务。
在我的实践中,用文本先描述一遍,至少能省掉一次返工。因为很多人画图时习惯性遗漏异常分支,比如登录失败、验证码过期、token 刷新失败,这些分支在图形界面里很容易被忽略,但在文本流程里写几行“异常返回”并不难。
2.2 元素分类:四类基础单元
diagram-design 梳理过制图语言里最常见的底层单元,最后归结为四类:实体、动作、状态、决策点。实体是名词,比如用户、订单、数据库;动作是动词,比如调用、返回、写入;状态是系统或流程在某一时刻的处境,比如“等待支付”“处理中”;决策点是产生分支的判据,比如“余额是否充足”。
不同形状的图,其实都是这四类元素的组合。画架构图时,实体占绝大多数,动作变成连线上的标签;画流程图时,决策点变成菱形,动作变成矩形,状态则可能体现为分支路径;画时序图时,实体变成纵向的生命线,动作变成横向的消息箭头。你可以把这种映射理解成一套翻译规则:
| 图形类型 | 实体 | 动作 | 状态/决策点 |
|---|---|---|---|
| 架构图 | 服务、数据库、组件 | 箭头上的调用说明 | 层与层之间的边界 |
| 流程图 | 输入/输出 | 处理步骤 | 菱形判据、分支泳道 |
| 时序图 | 参与对象 | 消息箭头 | 返回结果/例外情况 |
这套分类最大的好处是,在画出第一笔之前,你就能知道自己到底在画什么。如果一张图里四类元素混成一团,没有主次,基本可以断定画完也没人看得懂。
2.3 从关系图到布局图:两个阶段不要混着做
diagram-design 把制图过程拆成两个独立阶段:第一阶段叫关系建模,第二阶段叫布局渲染。关系建模阶段只关心谁跟谁有关系,什么方向,什么类型,完全不考虑节点的坐标;布局渲染阶段才去计算怎么摆得好看、怎么分配空间。
我在项目里早先犯过一个特别典型的错误:第一阶段还没完成,就开始手动拖拽节点,试图让连线不交叉。这么做的结果是,每多拖一个节点,已有的连线全乱,最后不得不推翻重来。正确的做法是先接受“初始布局一定很乱”,等所有关系都在图里之后,再走一遍自动布局或手动微调。现实中很多画图工具都有布局算法,但它们的输入是关系数据,而不是你手动摆出来的坐标。所以先把自己变成关系描述者,再把自己变成排版者,心情能轻松不少。
3. 三类常见图的实战设计要点
3.1 架构图:先画边界,再画依赖
架构图是团队里出现频率最高、也最容易被画废的图。diagram-design 里对架构图的一条核心建议是:边界优先于节点。也就是说,先把系统切成几个大的区域,比如接入层、应用层、数据层,或者前端、后端、基础设施,然后再往里放具体组件。没有边界,节点就会散落在画布上,读者看不出哪个服务属于哪块职责,等于把架构图退化成了脑图。
可以想象一套三层结构,我用 diagram-design 自己的 DSL 来描述大概是这样:
容器: 前端 容器: 后端 容器: 数据层 组件 网关 in 前端 组件 订单服务 in 后端 组件 库存服务 in 后端 组件 MySQL in 数据层 连接 前端 -> 网关 连接 网关 -> 订单服务 连接 网关 -> 库存服务 连接 订单服务 -> MySQL 连接 库存服务 -> MySQL这里的关键不是语法,而是“容器”把组件括住之后,读者的视线会先纵向理解三层边界,再横向看每个容器内部的连线,最后看跨容器的箭头。这种阅读顺序非常舒服,因为它是从整体到局部的天然递进。反过来,如果一开始就把十来个服务平铺开,读者只能被动地在节点之间跳来跳去。
另外,架构图里的依赖方向要跟现实调用方向保持一致。如果 A 调用 B,箭头可以从 A 指向 B,也可以从 B 指向 A,表示“B 被 A 依赖”,但一整张图里必须使用同一种约定。我在 diagram-design 里约定的是箭头总是从调用方指向被调用方,这样看箭头就知道请求往哪个方向走,不会产生歧义。
3.2 流程图:先画全路径,再裁剪
流程图最忌讳只画“完美路径”。所谓完美路径,就是用户输入正确、系统无异常、数据库返回及时的 Happy Path。真实世界几乎不可能按这条路径走,所以流程图必须包含异常分支和回退机制。diagram-design 的做法是先画出所有可能的分支,包括失败、重试、超时、降级,然后再从所有分支里挑出最重要的两条路径做视觉强调。
把正常路径加粗,把异常路径用普通粗细的线条表示,是性价比很高的做法。读者第一眼就能抓到主线,需要排查问题时再去看异常分支。另外,当流程图里的线条开始交叉得很厉害时,我倾向于用泳道把不同角色或不同子系统的区域分开。泳道本质上是把画布切成几列,每列对应一个责任方,每个步骤放在责任方对应的泳道里,连线自然会沿着横向或纵向走,交叉概率大大降低。
在我自己画过的支付流程里,泳道就特别有用。用户、商户端、支付网关、银行四个泳道一摆出来,哪一步该谁负责就非常清楚,开会讨论职责划分的时候可以直接对着泳道讲,比干巴巴的文字协议直观得多。
3.3 时序图:消息命名与责任分组
时序图是表达“按时间发生的交互”最合适的工具,但也是最容易被画成“乱七八糟的箭头叠罗汉”的图。diagram-design 里对时序图有两个刚性要求:第一,横向顺序必须等同于时间顺序,不能因为某个消息放在左边更好看就打破时间轴;第二,每一根生命线只能代表一个明确的角色,不能把用户和前端页面混成一根线。
还有一个常被忽略的点:消息箭头的名字应该尽量写成“动词+宾语”,而不是“处理”或“调用”这种模糊词。比如“查询用户信息”比“方法调用”好,“创建订单并锁定库存”比“执行业务逻辑”好。好的消息名,让读者不用进代码就能推测出系统的行为。我在项目里还规定,需要表示异步消息时,用虚线或半箭头;同步调用用实线;返回结果统一用带小圆圈的虚线。这个约定实现起来不复杂,但能省掉大量口头解释。
4. 布局与排版的核心约束:对齐、留白、统一
4.1 对齐:网格的隐性力量
我见过很多内容很准确的图,看起来仍然很乱,原因几乎都是不对齐。节点之间的间距忽大忽小,同一层级的组件高度参差不齐,箭头从矩形的任意一边伸出来。人眼对乱序非常敏感,一旦视觉上没有规律,注意力就会从信息本身转移到“这图怎么这么不舒服”上。
diagram-design 的应对方式是给画布加一层不可见的网格,所有节点的坐标和尺寸都对齐到网格的整数倍。这个规则听着很程序化,但效果立竿见影。举个例子,同一层服务的基础高度固定为一种值,宽度视文字长度变化但保持同一种宽度族,横向间距保持一致。这样做之后,即使没有背景色和阴影修饰,图也会显得干净利落。你可以回想一下那些让人舒服的架构图,几乎都是默默对齐的——你察觉不到对齐的存在,但它一直在起作用。
4.2 主方向:让大图可读
另一个经常被忽视的问题是图的整体走向。流程图通常从上到下,时序图从左到右,架构图则可以是多方向。但一旦图的规模变大,方向选择就成了可用性的关键。我画过一张包含四十多个节点的部署图,挤在一个正方形画布里,每个节点间距只有几个像素,别说打印,连投屏都看不清楚。
后来 diagram-design 里引入了一条规则:大图优先选择单一主方向延伸。如果信息流是横向的,就让图尽量往右扩展;如果信息流是纵向的,就让图尽量往下延伸。这样你在屏幕上滚动时,总能看到完整的一层,而不是在二维空间里反复拖动画布找节点。还要注意控制一张图的总节点数,通常不超过 25 个;超过这个量,我宁愿拆成多张图。拆图不是示弱,而是尊重读者的注意力。
4.3 颜色与字体:默认保持克制
颜色在图中承担的是分类和强调功能,不是装饰功能。diagram-design 默认的配色规则非常朴素:同一类元素尽量使用同一色系,强调点可以用高饱和颜色,但全图不超过三种主色。用于表示危险或异常的红色只能出现在真正需要警惕的地方,否则看图的人会对所有区域都产生不必要的紧张感。
字体方面,我建议整张图不超过两种字重:常规和加粗。节点名称使用加粗便于快速扫视,描述文字使用常规字重,大小设置到可以在会议室最后一排看清即可。很多人为了美观给每个节点加阴影、渐变、圆角,但这通常会让图显得商业味过重,反而拉低信息的可信度。保持克制的视觉风格,读者才会把注意力放在关系结构上。
5. diagram-design 踩坑清单:四个高频问题
5.1 箭头方向到底代表什么
箭头方向这个问题,几乎在每个项目里都会被反复争论。有些图里,箭头代表数据流,从数据源头指向目标;有些图里,箭头代表控制流,从调用者指向被调用者;还有些图里,箭头代表依赖方向。diagram-design 的建议是,一幅图里只用一种表示方式,并且在图例里写清楚。如果图上同时出现数据流和控制流,就用实线/虚线和箭头形状双重区分,而不是只靠颜色区分。
我自己踩过的最典型的坑是:把外部依赖画成从依赖方指向被依赖方,同时又希望表达“返回结果”,结果箭头底层指向很混乱。最后我规定:真实调用方向用实线箭头,返回或回调用虚线箭头,数据搬运用两端开口的箭头。做完这个区分之后,团队成员对图的理解速度明显提高,不再需要一边看图一边追问“这个箭头反过来看是不是也成立”。
5.2 颜色编码缺乏可访问性
团队里一旦有红绿色盲的同事,你的漂亮配色就会瞬间失去意义。diagram-design 早期的版本用红色和绿色区分正常和异常,后来我一位同事反馈他完全分不清,只能靠节点位置猜测。这件事之后,我要求项目里的所有状态标签必须配合形状或文字,而不是只靠颜色传递信息。比如正常状态可以加一个小圆点,异常状态加一个菱形,颜色只是辅助。这样不仅对色盲友好,对打印成黑白版也有效。
5.3 文字溢出与节点尺寸预估
文字溢出看起来是小问题,实际很影响图质量。很多工具会把很长的服务名直接塞进一个固定宽度的大方框里,文字要么被截断,要么和节点边框重叠。diagram-design 的实践是,节点里足够容纳完整内容,换行规则固定,不超过两行。对追求简洁的节点,名称之外可以再加一行小字说明;再多就得考虑是不是信息放错位置了。
尤其注意别把图里的文字全部堆在一行里。代码块里的长函数名、带查询参数的 URL、一段 SQL 查询语句,都不适合直接当作节点标签。我的做法是给它们一个可读别名,比如“用户积分明细查询”,再把真实接口名放进备注或另一个注释层。这样看图的人不会觉得头痛,想看真实名字的人也知道去哪里找。
5.4 图与代码脱节,改图救不了真问题
最后这个坑其实最严重,也最容易被忽略:很多图在写完的那一天就已经过期了。代码不断迭代,图却躺在某个文档目录里没人维护。diagram-design 给出的解法是,将图的源文件也纳入版本管理,与代码放在同一个仓库里;每当代码改动触发架构变化时,图的定义文件也要一并变更,否则 CI 就直接提示图校验失败。这听起来像项目管理问题,但实际是一个非常实在的技术问题:图必须有唯一的、可 diff 的源文件。如果它只是画布上的一张位图,那你根本没法自动化比对它是不是过期了。
6. 从静态图到交互式图表:diagram-design 的下一步
6.1 静态图的局限
静态图最大的问题在于信息密度不能自适应。你把一个高层的架构图缩小到一屏,读者能看到全貌但看不清细节;放大到细节可读,又丢失了整体结构。diagram-design 走到后期,我开始关注如何在同一个图里实现多级缩放和按需展开,而不是只发布一张静态 PNG。交互并不能替代好的布局,但可以让不同需求的人拿到同一套数据的不同视图。
6.2 三种低成本的交互模式
不用把交互想得很复杂,最低成本的做法有三种。第一种是节点可点击展开,把一个容器层展开成内部子图;第二种是悬浮提示,鼠标放上去才显示标签和描述,这样图上不用堆满文字;第三种是焦点聚焦,点击某个节点时,其他无关节点自动降透明度,让视线集中到当前链路周围。这三种模式做下来,交互的代码量并不大,但当它接在规范化定义的图数据之上时,能显著提升阅读体验。
6.3 自动化图表管线
更长期来看的是自动化。yaml 或 json 的结构化定义可以放进 git,每次提交都触发一次渲染,把图导出成 SVG 和 PDF,然后发布到内部知识库。这样图的来源是唯一的,生成过程是可复现的,不依赖任何人的手动操作。diagram-design 下一步的方向就是把这个管线彻底打通,让非技术同事也能通过修改配置文件的方式更新一张图,而不是拿着绘图工具重新画一遍。
我在实际尝试中发现,当图真正开始“从代码里长出来”之后,维护成本反而下降了。以前每画一张新图都觉得是负担,现在更像是在写文档,节点和关系都是现成的,操作只是改文本、跑脚本、看输出。
最后再说一个我自己的习惯。每次画完一张看似完美的图,我都会找一个人来“看图说话”,不看任何口头解释,只让他对着图把内容复述一遍。如果他复述出来的链路关系和我脑子里想的一致,这张图才算合格;如果不一致,我会立刻返回前面的步骤重新梳理关系。这个习惯帮我揪出了无数藏在漂亮外表下的逻辑漏洞。画图从来不是终点,让看图的人不用问你就懂,才是 diagram-design 真正想做到的事。