前阵子接手一个遗留系统,仓库里文档不少,最有分量的是一张架构图。可我看完只得到三个信息:中间一个方框写着"系统",周围几个小方框写着"模块A、模块B、模块C",箭头方向还自相矛盾。我花了将近两周才把真实调用链摸清,最后自己重画了一张贴进 Wiki。画完那一刻我明白了一个道理:diagram-design(图设计)这件事,从来不是画得漂亮的问题,而是能不能让看的人快速做出正确判断的问题。
我在多个项目里反复看到同一个规律:绝大多数团队的默认状态是"能看懂就行"。但"能看懂"其实是一个很低的及格线,真正的图设计要回答的是"看完这张图的人,能不能据此做出正确决策"。这篇分享不打算教某款绘图软件怎么点按钮,而是把我在实践中沉淀出来的一套图设计方法拆开来讲:为什么要把画图当工程而不是美工、不同图型该怎么选、为什么我最终转向"图即代码"、以及用代码维护图之后踩过的一堆坑。适合正在做系统设计、写技术文档,或者被"画图一小时、改图一下午"折磨过的同学。
1. 为什么画图是工程设计,而不是美工活儿
1.1 图的第一职责是传递约束,不是展示美感
很多人画图的第一反应是打开画布、找图标、调颜色。我早期也这样,画一张系统拓扑图能磨两个小时,最后产出物确实好看,但同事看完问我的第一个问题是:"这个模块和那个模块之间,数据到底怎么走的?"我愣住了——图上有连线,但线没有方向,也没有标注协议。
后来我意识到,架构图、流程图这类技术图,本质上是"约束的表达"。它需要回答四个问题:系统边界在哪、模块间能不能互相调用、数据往哪个方向流、失败时由谁兜底。一张图如果答不上这四个问题,那它就是装饰画,应该挂在墙上,而不是放进技术文档。
我常用一个比喻:好的架构图应该像地铁线路图。地铁图一点都不写实,站间实际距离被随意拉伸,但它用颜色区分线路、用节点表示换乘、用方向标清走向,乘客只需要三秒钟就能做出"我该坐哪条线"的判断。反过来,那种照着真实地理位置画的轨道交通图,虽然"真实",却根本没法用于日常导航。
所以我在 diagram-design 里给自己定的第一条原则:每一条线、每一个框、每一处颜色,都必须有语义,没有语义的元素一个都不要放。与其追求"好看",不如追求"看完能直接做决策"。
1.2 我习惯把图分成三个层级来设计
不同读者、不同阶段需要的信息粒度完全不同,硬把三层信息塞进一张图是最常见的翻车现场。我通常把图拆成三个层级。
第一层是全貌图(landscape),一页讲清楚系统边界和外部依赖。它回答"这个系统站在哪里,和谁打交道",读者通常是新同学、评审会上的非技术角色。绘制要点是只画外部系统、网关、核心存储,不画内部模块细节。
第二层是模块图(module/component),展示系统内部有哪些模块、模块间的依赖关系、关键数据存储。它回答"改一个功能会影响谁",读者是开发同组的人,这是日常被引用最多的图。
第三层是链路图(sequence/process),针对一条具体业务链路,画清楚跨模块的时序调用。它回答"一笔请求到底经历了什么",读者是正在排查问题或实现需求的开发者。
这三层图不是一个项目只画一套,而是各自独立演进。全貌图半年更新一次,模块图每次架构调整都更新,链路图则跟着具体需求走。把它们硬塞进同一张图,要么全貌和细节互相遮挡,要么为了保持整体布局牺牲某一层的信息完整度,两头不讨好。
1.3 有些场景,我真的不建议你画图
并不是所有关系都值得画成图。我见过有人在文档里给一个只有三个模块、依赖关系一句话能说清的系统,硬画了一张架构图,结果图比文字还难读。
我现在的判断标准很简单:这张图的信息增益,是否大于它带来的理解成本?如果一个依赖关系用一行文字能表达清楚,就用文字;如果流程超过一屏还是说不清,说明流程本身需要先被拆分,而不是靠一张巨图硬撑。图是降低沟通成本的工具,不是文档的装饰品。图一旦需要读者反复去"猜",它的价值就是负的。
2. 图型选型:不同决策场景该用哪张图
2.1 常用图型的定位速查
在 diagram-design 的实践里,我经常被问到"该用流程图还是时序图""要不要画 ER 图"。我的经验是:图型不是越复杂越好,而是越贴合决策场景越好。下面是我最常用的几种图型定位:
| 图型 | 核心回答的问题 | 典型读者 | 我通常使用的时机 |
|---|---|---|---|
| 架构图 | 系统边界和模块依赖 | 新同学、评审会 | 项目启动、架构调整 |
| 流程图 | 业务分支与状态流转 | 产品、开发、测试 | 需求分析、异常分支梳理 |
| 时序图 | 一次调用跨模块的顺序 | 后端开发、排查问题 | 设计跨服务接口、排查线上故障 |
| ER图 | 数据实体间关系 | 后端开发、DBA | 数据库设计、字段变更评审 |
| 状态图 | 对象生命周期与合法迁移 | 后端开发、测试 | 订单、任务等有状态业务 |
| 部署图 | 组件与运行环境映射 | 运维、SRE | 环境搭建、容量规划 |
这张表不是拍脑袋定的,而是我从实际协作里总结出来的:每种图型天然对应一类"决策点"。比如评审接口设计,时序图比流程图好使,因为时序图强调消息顺序和返回路径,而流程图强调分支条件——评审接口时我最关心的是"调用顺序对不对、超时和失败怎么处理",这些恰恰是时序图的强项。
2.2 三个选型问题
如果你只想快速决定该画哪种图,我建议每次画图前先问自己三个问题。
第一,这张图给谁看?新同学入职看全貌,用架构图;同事讨论具体接口,用时序图;测试同学写用例,用流程图或状态图。读者的身份决定了图的抽象层级。
第二,读者拿到图要做什么决策?是要判断"该不该拆模块",还是要搞清"失败时走哪个分支"?决策不同,图型就不同。一张图如果同时想支撑多个决策,通常会一个都支撑不好。
第三,这张图的变更频率有多高?变更频繁的图,必须考虑用代码化方式维护,否则每次改图都是一次体力活;变更很慢的图,拖拽工具也能接受。
这三个问题回答完,选型基本不会跑偏。
2.3 一个真实场景的图型组合
举个我最近处理的例子:设计一个订单支付的跨系统调用。我没有只画一张"大全图",而是按决策拆了三张。
第一张,架构图,画订单服务、支付网关、账户中心、消息队列之间的关系,用于评审服务边界。第二张,时序图,画用户下单后订单服务如何调用支付网关、如何异步回调、超时怎么补偿,用于评审接口细节和异常路径。第三张,状态图,画订单从待支付到已支付、已取消、退款中的状态迁移,用于和产品确认业务规则。
三张图合在一起,覆盖了边界、交互、状态三个维度,每张图都足够简单,评审时一次只过一张,效率明显比一张"全家福"高。这也是我反复强调的:diagram-design 的核心是"拆分决策",不是"堆砌信息"。
3. 从拖拽画布到"图即代码",我为什么换了套路
3.1 拖拽工具的三个痛点
早些年我用过各种拖拽式绘图工具,界面友好、模板丰富,"画"的过程确实爽。但用久了,三个痛点越来越明显。
第一个痛点是版本管理。架构调整之后,我改了三个框的位置、两条线的颜色,同事根本看不出这次改动的意图。拖拽工具没有像样的 diff 能力,架构评审变成了"你截图对比"。
第二个痛点是协作门槛。图文件是二进制或私有格式,没法在代码评审的地方直接评论。每次都要导出图片、传到文档里、再圈圈点点,链路长到让人不想更新。
第三个痛点是"随手画的成本太低"。工具越方便,越容易让人不假思索就开始画,画到一半发现思路没想清,前面的功夫全废。图即代码最大的价值,其实是逼你在写 DSL 之前先想清楚结构——语法本身比拖拽要"笨",但这种笨强制了思考顺序。
3.2 Mermaid 的实践心得
Mermaid 是我用得最多的方案,因为它语法最接近自然语言,团队上手成本极低。一个时序图大概长这样:
sequenceDiagram participant U as 用户 participant O as 订单服务 participant P as 支付网关 U->>O: 提交订单 O->>P: 创建支付单 P-->>O: 返回支付参数 O-->>U: 返回支付链接这种写法特别适合放在代码注释或者 PR 描述里,我甚至会在接口定义的注释块里直接贴一段图源码,编译器没法校验,但至少文档和代码在同一个仓库里,不会走丢。
Mermaid 的坑我也踩过不少。最典型的是:流程图里节点文字一旦包含特殊字符,比如括号、冒号,就必须用引号包起来,否则渲染会莫名其妙失败。还有,时序图里异步消息的箭头写法是 ->>,同步是 ->>,少写一个箭头符号,语义就完全变了,但语法检查不一定能拦住你。我的习惯是一段图写完后立刻在本地预览一遍,别等到提交后才发现。
3.3 PlantUML 的实践心得
PlantUML 是我在时序图特别复杂时的备选。它的 DSL 表达能力更强,尤其适合画有分组(alt/opt/loop)的大型时序图:
@startuml actor 用户 participant "订单服务" as O participant "支付网关" as P 用户 -> O: 提交订单 activate O O -> P: 创建支付单 activate P P --> O: 返回支付参数 deactivate P alt 支付成功 O -> 用户: 返回成功页 else 支付失败 O -> 用户: 返回失败页 end @endumlPlantUML 的激活块(activate/deactivate)能把方法调用期间的资源占用画得很直观,这对排查连接池、事务问题特别有帮助。缺点也很明显:语法更繁琐,新手容易把精力花在调格式上;而且默认渲染出的样式比较"工程风",需要额外配主题。
3.4 我的选型建议
如果你也在纠结,我的建议可以浓缩成一张对比表:
| 维度 | Mermaid | PlantUML |
|---|---|---|
| 上手成本 | 低,接近自然语言 | 中,需要记关键词 |
| 时序图能力 | 够用,复杂分支略吃力 | 强,支持 alt/opt/loop 分组 |
| 流程图能力 | 好,方向控制灵活 | 中 |
| 渲染依赖 | 轻,前端即可渲染 | 需要本地工具或服务端 |
| 适合场景 | 文档、PR 描述、快速表达 | 复杂时序设计、正式设计文档 |
另外一个现实约束是团队的基建。如果团队的文档平台原生支持 Mermaid 渲染,我基本无脑选 Mermaid;如果团队有统一的 PlantUML 服务,那 PlantUML 也能用得很顺。工具本身不是关键,关键是"图能不能跟着代码一起走、一起被评审、一起被版本管理"。这两点才是 diagram-design 里最值钱的部分。
4. 一套让图"不过期"的图即代码工作流
4.1 图文件进仓库,目录怎么摆
我在团队里推行过一个习惯:把图的源文件放到仓库的 docs/diagrams 目录下,和代码一起提交、一起走评审。目录按"图的作用域"来分,而不是按画图的人来分:
docs/diagrams/ ├── landscape/ # 全貌图,命名带系统名 │ └── order-system-overview.md ├── modules/ # 模块依赖图 │ └── order-modules.md ├── flows/ # 业务流程图、时序图 │ ├── create-order.md │ └── payment-callback.md └── data/ # ER 图、状态图 └── order-states.md这种分层和代码分层保持一致的目录,让新同学找图就像找代码一样自然:想知道全貌去 landscape,想看订单链路去 flows,想看状态去 data。图和代码同构,是图能被持续维护的前提。
4.2 命名、状态与图例的规范
图文件命名我要求带场景名,禁止出现"新建文档 1""未命名图"这种名字。规范是:业务场景-图型.md,例如 order-payment-sequence.md 或 user-login-flow.md。
图文件内部,我建议在最顶部用注释标清三件事:这张图的读者是谁、最后一次校验的提交号、当前状态(草稿/待评审/已生效/已过时)。别小看这个注释,半年后回来看图,如果没有这些信息,你根本无法判断这张图还信不信得过。
每张图还应该有一个微型图例,至少说明框代表什么(服务/模块/外部依赖)、线代表什么(同步调用/异步消息/数据流)、颜色代表什么。如果一张图不需要图例就能看懂,说明它足够简单;如果它需要图例,那就必须把图例画全,不能只画图不画例。
4.3 渲染与文档集成:把图变成"活的"
图即代码如果只停留在源码阶段,价值会打折。我在团队里做了三件集成的事。
第一,把图渲染脚本接进持续集成。每次提交触发构建,脚本自动把文档里的图源码渲染成图片,作为构建产物存档。这样 PR 描述里可以直接引用渲染图,评审人不用本地装任何工具。
第二,文档站点自动收集。技术文档站点的导航从 docs/diagrams 目录自动生成,路径就是图名,新同学点进去看到的是渲染好的图,旁边附源码链接。想看细节就点源码,想快速理解就看图。
第三,代码注释反向链接。在关键服务类或配置文件的注释里,加上一句话"本模块的依赖关系见图 docs/diagrams/modules/xxx.md"。这等于给代码和文档之间拉了一条永久跳转链路。
这三件事做完,"图过期"的问题被极大缓解:因为图已经变成代码构建链的一部分,改代码的人会下意识地去看关联图是否也要改。
5. 我在 diagram-design 路上踩过的四个坑
5.1 坑一:把所有信息塞进一张图
我第一次负责一个中型系统的全貌图时,把消息队列、缓存、每个微服务、甚至几张核心表都画进去了。结果这张图打印出来需要放大镜,评审会上没人愿意看。后来我忍痛把它拆成六张图,每张只讲一个视角,讨论效率反而上来了。
拆图的标准很简单:读者在三分钟之内,能不能指着图讲清楚这张图的核心结论?如果不能,说明信息熵太高,必须拆。拆的时候不要按模块数量均分,而是按决策点切——每个子图对应一个"看完了能拍板"的问题。
5.2 坑二:颜色语义混乱
我见过不少图,默认"绿色=正常,红色=异常",但画到一半又用红色表示"重点模块",再用绿色表示"已通过评审"。等读者看图时,颜色已经没有统一含义了,只能靠猜。
颜色是图里最容易被滥用、也最容易被忽视的语义载体。我现在的铁律是:能够用形状和文字表达的,绝不用颜色表达;如果一定要用颜色,全图最多三色,并且必须在图例里写死含义。为了照顾色弱读者,我还要求关键信息不能只靠颜色区分,必须配合线型或文字标注。这条规矩执行之后,图的可用性提升非常明显。
5.3 坑三:图不更新,变成"历史文物"
最常见的问题是:架构图是三个月前画的,现在系统已经重构了两次,但图还是那张。新同学看完图照着去接接口,结果接口早没了,白白浪费半天。
我后来总结出一套"保质期机制":图文件头部标注"最后校验提交号",并且把图更新挂在代码评审的检查清单里。凡是涉及模块拆分、接口变更、依赖新增的 PR,评审人必须在清单里确认"相关图是否已更新"。机制很笨,但有效。图一旦失去时效性,比没有图更可怕,因为它会误导所有人。
5.4 坑四:过度美化,把时间花在刀刃外
我也有一段"画图先调图标"的日子:给每个模块配一个精致的图标,阴影、渐变、立体效果都安排上,然后被同事一句"这个框代表什么服务"问得哑口无言。
真实世界里,技术图的审美优先级应该排得很靠后。我的排序是:语义正确 > 结构清晰 > 命名统一 > 排版整洁 > 视觉美化。如果你发现自己在画图时花了超过三分之一的精力在调整视觉细节,大概率是语义还没想清楚,在用美化逃避思考。
6. 让图真正进入协作流:从"画完就扔"到"持续有效"
6.1 把图当作评审材料,而不是交付物
我过去有个错误心态:图是设计完成后的"产出物",画完了就存档。后来我改成:图是设计过程中的"思考工具",先画图,再讨论,最后才写代码。评审会上大家对着同一张图说话,分歧会提前暴露在编码之前,改图的成本比改代码低一个数量级。
现在我带新方案时,流程通常是:先在白板上画潦草的草图,和同事对齐关键决策;再把草图代码化成 Mermaid 或 PlantUML,放进 PR 描述里给评审人看;评审通过后,把图归档到 docs/diagrams 并标记"已生效"。这个流程里,图既不是一次性草图,也不是事后补交的作业,而是评审的对象本身。
6.2 图例和术语表:一张图的"README"
一张复杂一点的图,如果不配术语表,读者很容易被缩写打懵。比如"OMS""PGW""ACC"这些缩写,画图的人习以为常,新读者完全不知道是什么。所以我在 diagram-design 规范里加了一条:图文件内必须有一个缩略语表,第一次出现的缩写要给全称。这个成本和收益极不成比例——一句话的注释,能省掉别人大半天的猜测。
另外图里用到的线型、箭头、框线样式,我会在图的 README 里统一登记,例如"实线=同步调用,虚线=异步消息,带叉=失败补偿"。只有语义被白纸黑字写下来,图才不会在不同人的理解里逐渐失焦。
6.3 我现在的个人工作流
落到每天的行动上,我的个人 diagram-design 工作流其实只有五步。
第一步,想清楚读者和决策点,回答"给谁看、看完做什么决定"。第二步,选图型,按第 2 节的三问来确定架构图、时序图还是状态图。第三步,画草图,在白板或纸上用最丑的线条把结构画出来,只求语义正确。第四步,代码化,用 Mermaid 或 PlantUML 把草图转成 DSL,放进仓库。第五步,评审和维护,把图挂进 PR、在评审清单里确认更新、给图标注状态和校验提交号。
这套流程看起来很朴素,但它把我从"画图一小时、改图一下午"里彻底解放出来了。我现在画一张模块图,从想清楚到提交,通常不超过半小时;更重要的是,半年后任何人再打开这张图,都能在五分钟内判断它还信不信得过、该不该改。
如果你现在还被"画图"这件事困扰,我的建议是:不要去买更多模板,不要研究更多渲染技巧,先拿一张你最常被别人问"这里是什么"的图,按照上面的三层拆分、三问问诊、代码化、保质期这套方法重新走一遍,我猜你会立刻感受到差别。