前不久有个同事拿着他刚画完的一张系统架构图找我,说“图也画了,节点也对了,为什么评审的时候大家还是看不懂”。我看了一眼,问题不在内容,而在Diagram本身的设计:节点大小参差、连线穿来穿去、颜色随心所欲、主次关系全无。那一刻我意识到,很多人把“画图”当成了“把信息堆上去”,但Diagram设计真正要解决的,是一个“信息如何被结构化地看见”的工程问题。
这篇文章想跟你聊的,是我在一套跨系统项目中沉淀下来的Diagram设计方法:从图型选型、视觉规范、工具链选择,到自动化输出和一整串踩坑记录。不管你是做架构设计、写技术文档、画产品流程图,还是给汇报材料配图,这套思路都能直接抄作业。
1. 为什么“会画图”和“设计Diagram”是两件完全不同的事
1.1 随手画的图为什么总被吐槽:从三个反例说起
先看最典型的反面教材。第一张图,业务流程图里挤了三十多个节点,没有分区、没有泳道、没有层级,阅读顺序完全靠猜,实际阅读体验就像在黑板上看一团钢丝球。第二张图,一张架构图用了十一种颜色,每个系统一个色,辅助线还比主连线粗,人眼的第一反应是“哪里都在强调,等于哪里都没强调”。第三张图更常见,用一张流程图去表达系统的分层架构,把“部署关系”画成了“先后顺序”,语义完全错位。
这三个反例其实是同一个根因:作者没有区分Diagram的语义信息与视觉信息。语义信息是节点、关系、方向、状态这些内容本身,视觉信息是分层、对齐、配色、线宽、间距这些结构手段。好的Diagram设计,本质上是让视觉结构主动服务于语义结构,而不是让两者互相打架。
1.2 Diagram是工程资产:可维护、可复用、可自动化
我一直坚持一个观点:Diagram不应该被当成一次性产物。架构图、业务流程图、时序图这些东西,会随着系统演进而反复修改。如果它们只是某个画板里的“死图片”,每次改动都要手工重画,团队很快就不愿意维护了,图也会变成“仅供参考”的废纸。
所以我会把Diagram当工程资产来管理,它必须具备三件事。第一,来源可控:图的源文件要存放在版本仓库里,和代码一样有历史记录。第二,视觉一致:所有图遵循同一套设计规范,观感统一,读者不需要反复适应。第三,生成自动:源文件变更后,最终产物能自动渲染出来,不需要手工导出再上传。
这三点听起来简单,但真要在团队里落地,需要把选图型、定规范、选工具、接流程这些事逐一做扎实。下面我按实际推进顺序来拆。
2. 先定图型再动手:架构图、流程图、时序图到底怎么选
2.1 图型选型矩阵:什么场景用什么图
很多人一上来就打开白板拖框拖线,这是最致命的。图型选型应该在画第一个节点之前就完成,因为它决定了整张图的语义语言。我把日常最高频的几种图型整理成了一张矩阵,直接用这张表对号入座。
| 图型 | 适用场景 | 关键元素 | 最容易踩的坑 |
|---|---|---|---|
| 架构图 | 系统的组成部分、部署边界、模块依赖 | 容器、边界、连接线、分层 | 把部署关系画成流程方向 |
| 流程图 | 业务流程、处理逻辑、决策分支 | 开始/结束、处理、判定、并行 | 判定分支条件不标注,读者靠猜 |
| 时序图 | 跨模块/跨系统的请求交互顺序 | 生命线、消息、返回、激活条 | 激活条和消息顺序对不上 |
| 状态图 | 对象的生命周期状态流转 | 状态、事件、转换条件 | 漏掉初始态和终态 |
| 类图/ER图 | 数据模型和实体关系 | 实体、属性、关系基数 | 关系基数缺失,语义模糊 |
| 甘特图 | 项目排期、任务依赖 | 任务条、里程碑、依赖线 | 依赖线交叉严重,阅读混乱 |
我的经验是,一张图只表达一种语义。如果你发现既要表达模块归属,又要表达调用顺序,别硬塞进一张图,拆成架构图加时序图两张,信息会干净得多。
2.2 架构图的细节:从C4模型里挑合适的层级
架构图最常被画成一堆方框加一堆线,但方框到底代表什么,连线到底是什么关系,往往没人定义。这里我推荐沿用C4模型的思路,把它当作一个“层级选择器”而不是一个必须完整执行的框架。
C4提供四个层级:系统上下文(Context)、容器(Container)、组件(Component)、代码(Code)。我参与过的项目里,绝大多数落在了系统上下文和容器两层:系统上下文图面向业务方,展示整个系统边界和外部依赖;容器图面向研发人员,展示应用、服务、数据库这些可部署单元的拓扑关系。组件层通常只在特定模块的设计文档里用,Code层基本不画,细节太多,图一出来就没人看得完。
画容器图时,我习惯用“分区”而不是“连线”来表达归属。每个子系统是一个带背景色的大容器,内部放它自己的模块,模块间的跨容器连线才单独画。这样“边界”和“依赖”两个语义就被有效隔离了,一张图不会信息爆炸。
2.3 流程图的细节:把判定和汇合画清楚
流程图看起来简单,但最容易被忽略的是“判定之后所有分支都必须收敛”。很多图分支出去之后各奔东西,再也不回来,读者会想当然地补脑,一补就错。
我的做法是,每次画完分支,先检查是否每个分支都有明确的汇合点或终态,并在判定节点旁写清判定条件,条件要互斥且完备。还有一个细节是泳道:当流程里涉及多个角色或系统时,用泳道把不同参与方的职责隔开,节点落在谁的泳道里,谁就为这一步负责。这个习惯能直接消灭一大半“这个动作到底是谁做的”的争论。
2.4 时序图的细节:生命线、消息与返回的边界
时序图表达的是“谁在什么时刻对谁做了什么”,它和架构图的区别在于强调顺序。我见过最乱的时序图是消息线全挤在中间,返回线还跟请求线同色,完全分不清方向。
画时序图时我会遵循几个约定:请求消息用实线箭头,返回消息用虚线箭头;生命线上的激活条必须和消息的时间顺序严格对齐;每个消息的标签要写动词短语,不要只写名词。另外,如果一条流程里有超过五个参与者,我会优先考虑拆分,把图按业务阶段切成两三张,而不是在一张图里把所有参与者都塞满。
3. 设计一套能落地的Diagram视觉规范:布局、配色、字体、连线
3.1 栅格与间距:用网格控制一切
Diagram给人“专业感”的捷径不是高深的配色,而是对齐。只要所有节点都落在同一个栅格上,图的观感就会立刻提升一个档次。我通常在绘图工具里设置8像素的栅格基准:节点之间的最小间隔是8的整数倍,节点对齐全部吸附网格,连线拐弯处也尽量走在网格线上。
实际参数可以这样定:普通流程节点最小尺寸160x48像素,节点间距不小于32像素;架构图容器间距不小于48像素。这个栅格体系不仅用于手工绘图工具,在代码化方案中同样可以用布局参数模拟。统一栅格之后,一张复杂的图即使换人维护,新节点也能“长在”老结构里,不会看着像硬塞进去的。
3.2 配色体系:控制色相数量,明确语义
我对Diagram配色的要求很简单:全图主色相不超过四个,分别承担“结构、连接、警示、注释”的身份。结构色用于节点填充和边界,连接色用于连线,警示色只用于异常路径、风险区域和需重点关注的部分,注释色全部留给旁注和说明。
具体到一套可复用的参数:中性背景用浅灰或纯白,节点主色用与项目品牌色一致的深色调,文字用深墨色,连线用中性灰。状态表达单独走语义色,例如成功绿、告警黄、失败红、信息蓝。需要特别注意,状态色不要同时充当节点底色,否则读者会把“系统A”误读成“系统A处于告警状态”。颜色比例上,主色覆盖约七成视觉面积,状态色只出现在极小面积里,重点才真正亮得起来。
3.3 字体规范:中英混排与字号层级
Diagram里最容易翻车的是中文字体。默认字体一旦回退到宋体或者系统默认黑体,整个图的气质会直接垮掉。我的项目里统一使用无衬线黑体系列,标题、正文用常规黑体,代码和命令用等宽字体。字体层级固定为三档:标题16号加粗、正文14号常规、注释12号浅灰,放在同一张图里使用。
还有一个容易忽略的细节:中英文混排时的基线对齐。很多工具默认英文字体和中文字体的行高不同,导致标签文字上下浮动。处理办法是给所有文本节点设置固定的行高参数,中文标签单独设置字体族,确保在同一个节点里中文字符串和英文字符串混合时不会高低错位。
3.4 连线规则:走线、桥接、箭头与标签
连线是Diagram的灵魂,也是大部分人画乱图的根源。我的规则是:优先使用正交走线,只有在强调直线最短路径时才用斜线;线宽统一为主连接线2像素、辅助线1像素;箭头使用实心三角,对箭头样式的一致性要求是“所有连接关系都必须带方向语义”。
当两条线不可避免交叉时,不要直接让线“穿过去”,要使用桥接符号或让其中一条线绕行栅格,以避免视觉粘连。连接线的标签统一放在线的上方或左侧,背景色与画布底色一致,保证文字压在线上时依然可读。如果一张图的连线交叉点超过三处,我会立刻拆图,连线的“可追踪性”比节点数量更影响理解成本。
3.5 一套可直接抄写的样式参数表
以下是我沉淀的默认参数,可以直接作为团队模板的起点:
| 对象 | 参数 | 推荐值 |
|---|---|---|
| 画布背景 | 底色 | 纯白或浅灰 |
| 节点 | 圆角 | 8像素(架构容器16像素) |
| 节点 | 描边色 | 主题深色 |
| 节点 | 描边宽 | 2像素 |
| 连线 | 主连线条 | 2像素 |
| 连线 | 辅助连线条 | 1像素 |
| 字体 | 标题/正文/注释 | 16/14/12号 |
| 栅格 | 最小对齐单位 | 8像素 |
| 间距 | 同层节点最小间距 | 32像素 |
这套参数未必是唯一解,但它的价值在于“统一”。参数一旦定下来,无论谁画图,出来的视觉结果都像一个模子刻出来的。
4. 从手绘到代码化:Diagram工具链选型与自动化输出
4.1 手绘工具的适用边界
先给手绘工具一个公道评价:它适合探索性和协作性场景。临时画一张讨论稿,几个人一块在白板上改来改去,手绘的随意感反而让沟通更高效。我自己也常用Excalidraw和draw.io这类工具做头脑风暴,但它们有一个致命弱点:当Diagram进入“交付物”阶段,手绘很难保证规范一致性。节点大小、配色、对齐、线型都依赖个人手感,即使同一团队,两个人画出来也是两套风格。
所以我的边界判断是:讨论阶段随手画,一旦确定要进文档、进评审、进知识库,就立刻转成代码化方案。这个转换节点定得越清晰,后面返工的成本就越低。
4.2 代码化方案横向对比
我实际用过并保留下来的是Mermaid、PlantUML、Graphviz(DOT)、D2这四种,适用场景差别很大。
| 方案 | 语法入门门槛 | 支持的图型 | 布局可控性 | 输出质量 | 维护建议 |
|---|---|---|---|---|---|
| Mermaid | 低 | 流程图、时序图、状态图、甘特图等 | 弱 | 中等,依赖主题变量的定制能力 | 适合嵌入Markdown文档,团队扩散成本最低 |
| PlantUML | 中 | 类图、用例图、时序图等 | 中等 | 中等,可调样式多 | 适合UML系图型存量较多的项目 |
| Graphviz(DOT) | 中高 | 节点关系图、架构拓扑、聚类图 | 强,布局引擎可控 | 高,算法成熟 | 适合复杂架构图,需要花时间调样式 |
| D2 | 低 | 架构图、流程图、时序图、类图等 | 中,可切换布局引擎 | 高,自带主题与现代风格 | 适合新项目起步,但生态迭代中 |
4.3 为什么我把主力压在“Diagram as Code”上
我在一次跨团队协作中吃过手工绘图的亏:架构图在文档里改了六个版本,每次都是人工导出再替换,到了第七次团队直接放弃同步,图彻底失真。那次之后我决定把所有关键Diagram切到“Diagram as Code”。
代码化的收益有三个层面。第一是版本管理,源文件和代码一样进仓库,每次改动都有diff可看,谁改了什么都清清楚楚;第二是复用能力,样式主题可以被所有图引用,新图只需要关注自己的语义;第三是自动化,只要源文件变更,渲染产物可以自动重新生成,根本不用手工导出。代价也很明确:初期学习成本,以及对复杂布局的控制力不如手绘直观。
我的建议是分类型落地:文档型图(流程、时序、状态)优先用Mermaid,因为它跟Markdown的集成体验最顺;架构拓扑图用Graphviz或D2,因为这两个对容器、分区、运行依赖的表达更丰富,布局更可控。
4.4 统一入口:用目录组织Diagram资产
代码化之后,另一个容易失控的地方是“图散落在各处”。我的仓库里会建一个专门的diagrams目录,内部按类型划分子目录,所有图都有固定归属:
diagrams/ flow/ arch/ sequence/ state/ assets/ theme/每张图文件头部必须有元信息注释,包含title、owner、created、updated四个字段。这一步在外人看来有点啰嗦,但当Diagram资产超过五十张之后,检索、归属、维护的体感会舒服很多。
5. 跑通自动化流程:从源文件到CI流水线产物
5.1 先统一模板,再谈自动化
自动化渲染的前提是模板统一。我不会让每张图自己定义配色和样式,而是把所有公共样式抽到统一主题文件里。以Mermaid为例,可以在图文件顶部加载统一主题变量:
%%{init: { "theme": "base", "themeVariables": { "primaryColor": "#1F3A5F", "lineColor": "#6B7280", "fontSize": "14px", "nodeBorder": "#1F3A5F", "clusterBkg": "#F3F4F6", "clusterBorder": "#D1D5DB" } }}%%Graphviz下则把所有节点、边的公共属性放进统一头部,比如:
digraph G { rankdir=LR; pad="0.5"; node [shape=box, style="rounded,filled", fillcolor="#EFF6FF", color="#1F3A5F", fontname="Arial", fontsize=14]; edge [color="#6B7280", penwidth=2, arrowsize=0.8]; }统一模板的意义在于,图与图之间的差异只剩语义结构,视觉语言完全一致。模板可以随团队审美迭代,一次修改,全局生效。
5.2 用命令行批量渲染SVG/PNG
自动化流水线的核心,是把“渲染”变成命令行操作。我日常使用的几个典型命令如下:
npx @mermaid-js/mermaid-cli -i flow/order.mmd -o dist/flow/order.svg -b white -s 2 dot -Tsvg arch/system.dot -o dist/arch/system.svg plantuml sequence/checkout.puml -tsvg -o dist/sequence d2 --theme 0 --layout dagre flow/checkout.d2 dist/flow/checkout.svg几条经验值得说明:一是导出格式优先SVG,文本内容可选中,在任何设备上缩放都不糊;二是如果必须输出PNG,把scale参数设置为2倍或3倍,避免在高分屏上发虚;三是渲染产物的目录结构要和源文件目录保持一致,否则产物根本找不到对应关系。
5.3 把渲染接入自动化流水线
单一的命令行适合本地调试,真正让Diagram长期活着的关键是把渲染接到自动化流水线里。我在团队里通常这样设计:当代码推送到仓库后,流水线自动扫描diagrams目录下的源文件,识别变更项,触发对应图型的渲染脚本,然后把SVG/PNG产物提交回仓库的dist目录。
这里有一个很容易被忽略的约束:渲染不是无条件的。我会在流水线里增加两道检查。第一道是渲染校验,任何一个源文件渲染失败,流水线直接标红,避免“图坏了但没人知道”的情况。第二道是产物校验,SVG文件大小如果异常(比如超过1MB,通常意味着某些元素异常膨胀),流水线也会告警,让我们在提交前就从布局维度审视一次。
5.4 验证产物:尺寸、字体与可读性
自动化渲染完成后,还需要做一次人工验证闭环。最基础的三项检查是:尺寸是否符合文档版面要求;SVG文本是否真的可选中、可搜索;字体是否完整嵌入,换个环境打开会不会丢字体。Graphviz对中文字体支持尤其需要确认,如果渲染环境缺中文字体,产物就会出现一排豆腐块。我的做法是为所有渲染命令指定一个明确的字体名,并在流水线环境里预装对应字体文件,确保每次渲染的产物字体一致、不依赖运行机器。
6. 实战中反复踩到的坑:渲染、布局、字体、协作
6.1 子图方向失控:你以为的LR并不是LR
用Mermaid画包含subgraph的架构图时,我最常踩的坑是子图内的方向和整图方向不一致。明明整图设置了LR(从左到右),子图里的节点却经常自动变成TB(从上到下),整个结构看起来非常别扭。
后来排查下来发现,这通常是因为子图内外的节点间没有足够明确的“链式连接”,或者子图自身的层级参数被默认值覆盖了。我的解决办法是在子图里至少保留一条贯穿的链式连接来“锚定”方向,必要时逐个子图显式声明direction。还有一个类似问题是子图之间相对位置不可控,这其实是Mermaid布局引擎的固有局限,真要精细控制位置,我就换用Graphviz或D2了。
6.2 节点一多布局就爆炸:拆图比调参更有用
有一阵子我为了让架构图“完整”,把二十多个服务和中间件全部塞进一张图,结果布局引擎把它们排成了一大团,连线上穿下跳,怎么调参数都救不回来。后来我带项目组复盘,结论是责任不在工具,而在我没控制信息量。
现在我的原则是:单张图的主节点数量超过十二个,就开始认真考虑拆分。拆法不是简单分成两张,而是按分层视角拆。比如把基础设施、应用服务、支撑系统分别画成独立视图,再补一张系统上下文图讲它们之间的关系。读者看多视图的成本,远低于在一团乱麻里找关键路径的成本。
6.3 中文乱码与SVG字体丢失
中文乱码问题主要出现在两个场景:一是本地渲染正常,流水线里渲染出来全是方框;二是SVG在文档平台里展示正常,下载到本地打开字体就变。前者通常是因为渲染环境缺少中文字体,后者是因为SVG没有做字体嵌入或没有正确返回字体样式。
我的处理是双管齐下:渲染环境里固定安装一套中文字体;SVG产物里通过样式显式声明font-family,不依赖阅读端的环境字体。如果平台允许,优先使用包含字形路径的方式,这样SVG无论去哪都是所见即所得。这个问题绕不开,但只要把它写进自动化脚本的约定里,就不会反复出现。
6.4 diff冲突来得比代码还频繁:如何降低冲突面
代码化Diagram也会遇到协作冲突,而且冲突频率可能比代码还高。原因很简单:两三个同事同时改同一张架构图,每个人都可能调整节点位置或者改标签,行级diff很容易互相撞车。我在某次迭代中亲眼见过一张流程图在合并时出现了十几个冲突块。
后来我定了几条约束:每张图只属于一个负责人模块;多人需要协同时,按模块拆文件,不要共用一张大图;节点ID统一用英文驼峰,显示文本放在label属性里,这样即使显示文本有改动,ID不变,diff的范围也会缩小很多。效果立竿见影,不过几周,Diagram相关的冲突率就降下来了。
6.5 路径、转义、符号导致的渲染失败
最后一个坑比较隐蔽:文件路径中包含空格、中文或特殊符号时,多个渲染命令会直接解析失败。我的流水线里曾经因为目录名带了中文,导致PlantUML在解析文件路径时打不开源文件,报错信息还特别晦涩。解决方案是约定所有diagrams目录和文件名一律使用英文小写加连字符,中文或其他说明文字放文件头注释里。另外要留意标签文本里的特殊符号,比如大于号、星号、竖线,在部分语法里需要转义,否则图会渲染成一个语法错误页面。我把常见的转义规则写进了团队内部的Diagram编写约定文档里,新人上手时少踩很多坑。
7. 维护Diagram资产的一点个人体会
Diagram设计和写代码有一点很像:写出来只是开始,长期维护才是真正的投入点。我现在的习惯是,把Diagram维护纳入和代码评审同样的节奏里。每次版本迭代,图跟着改,改完在评审里过一眼,确认图和实际系统没有漂移。同时每个季度对diagrams目录做一次盘点,把已经废弃的图归档掉,把还能用的图模板固化成可复用文件。
还有一个很受用的小技巧:给每张图配一个简短的README,写清楚这张图回答什么问题、给谁看、谁负责维护。听起来像是过度工程,但当团队里有人问起“这张图还在用吗”的时候,你翻开README就能立刻回答,省下大量确认时间。Diagram设计这条路不好走,但走顺之后,它给你的回报是全团队沟通成本的持续下降——这比任何一张“惊艳”的图本身都值得。