代码化制图指南:用Mermaid实现可维护的架构图与流程图
2026/9/9 10:26:20 网站建设 项目流程

画图这件事,做了十年软件我都绕不开它。方案评审要画架构图,代码注释里要放时序图,README 里得补一张流程图,连给运营同学讲数据链路时都得靠 diagram。diagram-design 说白了,就是“把脑子里那幅图,变成别人能看懂的一幅图”。这句话很朴素,但能做到的人真的不多。我见过太多团队,图是画了,画完自己都改不动,协作时更是灾难——有人用 Visio,有人用 draw.io,有人拿 PPT 拼方块,最后文件全变成“最终版_v3_修改2”躺在网盘里。

这一篇我会用实际踩坑换来的经验,把从设计思路、工具选型,到落地实现、常见坑位的完整路径都走一遍,重点说说为什么我最终选择了代码化制图这个方向,以及怎么让你的图能持续维护、能跟着代码演进。内容适合正在给项目画图、想用代码管理图表、或者刚接手一个没有文档的技术团队的人。不需要你有图形设计基础,能写 Markdown 基本就能跟上。

1. 先想清楚:你画的到底是什么图

1.1 图表不是装饰,它是信息压缩的效率工具

先给图找一个正确的定位。有人把图当交付物,觉得画得越满越有工作量,于是架构图里塞 80 个方块,评审时没有一个人愿意认真看。有人把图当思维整理,想到哪画到哪,最后只有画图的人自己能看懂。还有少部分人把图当沟通媒介,一切设计都围绕“对方能否在 20 秒内看懂核心结构”展开。我建议你采用第三种心态。

为什么是 20 秒?因为现代人的注意力和耐心都非常有限。评审会上,大家扫一眼图,能建立初步心智模型,才有可能对细节提出有效问题;如果这张图第一眼就是密密麻麻的节点和箭头,绝大多数人只会礼貌性点头,然后继续低头看手机。好的 diagram-design,本质上是在做信息压缩:把一段需要两千字才能说清的结构关系,压缩成一张 20 秒能读完的图。但压缩一定有损失,有损失就必须做取舍,只保留目标读者当前最需要的信息层次。

所以,动手前第一件事不是打开工具,而是判断这张图存在的理由是什么。是帮助新人理解主链路?是评审时讨论模块边界?还是上线前确认部署关系?理由不同,图的内容和抽象粒度完全不同。理由不清晰,图画得再漂亮也只是自我感动。

1.2 常见图型与适用场景速查

在动手之前,先知道自己要画的是哪一类图。不同图型解决的问题不一样,选错图型,表达效果直接打折一半。

  • 流程图(flowchart):主要表达“先做什么,再做什么,判断后走哪条路”。典型场景是业务流程、接口处理流程、部署发布流程。判断标准很简单:如果文字描述里反复出现“如果、否则、然后”,大概率就是流程图。
  • 时序图(sequence diagram):表达“谁在什么时候调用谁,按什么顺序传什么参数”。典型场景是多系统交互、分布式链路追踪、SDK 调用过程。如果核心是多个参与者之间的先后顺序,而不是流程判断,就用时序图。
  • 状态图(state diagram):表达“一个对象在生命周期里有哪些状态,事件触发后怎么转移”。典型场景是订单状态、微服务实例状态、审批状态。
  • 类图与 ER 图:表达“数据结构与关系”。类图偏代码层,ER 图偏数据库层,两者侧重点不同,但很容易混淆。
  • 架构图(architecture diagram):表达“系统由哪些模块组成、模块之间如何依赖、流量如何流转”。它其实是多种图型的组合,也最考验设计师的抽象能力。
  • 数据图(饼图、柱状图、折线图):表达量化信息,一般用于汇报和指标分析。
图型核心问题典型场景选错时的表现
流程图接下来按什么顺序执行业务流程、CI 流水线画成架构图后看不出先后顺序
时序图参与者之间怎么交互接口调用、事务过程画成流程图后看不出调用方
状态图对象如何迁移订单状态、实例生命周期大量箭头交叉,可读性差
类图/ER 图类和表之间如何关联领域模型、库表设计信息过载,评审无人看
架构图模块与依赖边界系统设计、部署方案忽略流量方向,看不出主链路
数据图数值关系变化报表、指标分析用流程图硬套,信息失真

选型时还有一个常见误区:以为一张图能承担所有职责。实际上,越是复杂的系统,越需要多种图型配合使用,例如用一张架构图展示全貌,再用若干张时序图补充关键链路的交互细节,各司其职,比硬把所有信息塞进一张图里要清晰得多。

1.3 设计之前先回答 5 个问题

我每次画图前都会在脑子里过一遍这 5 个问题,简单但非常有效。

给谁看?这决定了术语密度和信息分层。给技术团队看,可以放心画 JVM、数据库连接池、消息队列;给业务方看,只画“入口、能力、出口”就够了,连节点命名都要换成业务语言。

想表达什么?一张图只讲一个核心观点。常见陷阱是“想在一张图里既展示全貌又展示细节”,结果两个都没讲清。如果发现自己正在补充很多旁支信息,说明该拆图了。

信息层级有多少?如果一张图里超过 30 个节点,我会强烈建议拆图。节点越多,布局越难、连线越乱、改图越累,读者接收信息的效率反而越低。

布局方向是什么?流程类一般从上到下,时序类从左到右。方向在代码化工具里只是参数,但在设计时要提前统一,混用方向会让人迷失。

这张图要活多久?如果是临时涂鸦,画多丑都行;如果是长期文档,就必须考虑可维护性。这个问题直接影响下面的工具选型。

2. 工具选型:为什么我最终选了代码化制图

2.1 主流方案横向对比与适用边界

市面上的画图工具五花八门,按照“是否代码化”可以粗分成两大类:拖拽类和代码类。拖拽类代表有 draw.io、ProcessOn、Excalidraw、Visio;代码类代表有 Mermaid、Graphviz/DOT、PlantUML。两类我都深度用过,下面这张表是我个人的直观感受。

方案学习成本版本管理自动布局维护成本适合场景
拖拽类(draw.io 等)手动一次性草图、白板讨论
Mermaid极好自动文档、README、团队规范图
Graphviz/DOT复杂关系图、自动生成图
PlantUML自动UML 语义强、代码建模

拖拽类工具的共同问题是:图的背后没有一个清晰的数据模型,你保存的只是一个视图工程文件。一旦结构发生变化,要增加节点或者改连线,就必须手动拖拽、重新对齐、调整跨线,改一次至少 20 分钟。代码化制图则不一样,你改的是声明式文本,布局算法会自动重排。对高频变化的项目文档来说,这个维护成本差异非常明显。

Graphviz 的强项在于节点关系非常复杂时,它的自动排版算法仍然能给出相对稳定的结果;PlantUML 则更贴近软件工程语义,类图、时序图、用例图都有专门语法。两者都有学习曲线。Mermaid 虽然功能不算最全,复杂的美化效果也做不了,但它是“文本 + 图”工作流里最顺滑的,语法简单到像写 Markdown 一样,这也是我日常选用它的原因。

2.2 代码化给团队协作带来的三个隐性收益

选代码化制图,不只是自己改图方便,更重要的是它改变了整个团队的协作方式。我有三个很深的体会。

第一个是 diff 可读。拖拽图保存后通常是二进制或私有格式,代码评审时根本没法逐行 review,别人只能看到“哦,图变了”,但说不清哪里变了。Mermaid 这类纯文本图可以直接出现在 PR 的 diff 里,改没改、改了什么,一眼就能看出来。这一点对技术团队非常重要,因为图常常是评审的重点对象。

第二个是可复用、可生成。既然图是文本,就可以在模板、脚本里动态生成。我做过从数据库元信息自动生成 ER 图,也做过从接口清单自动生成依赖图,这在拖拽类工具里几乎不可能实现。数据变化时,图跟着重新生成,永远比手工维护的图准确。

第三个是和文档系统无缝集成。GitHub、GitLab 对 Mermaid 有原生渲染支持,Markdown 文件里直接写代码块就能出图;mkdocs、VitePress 也有相应插件。这意味着图不需要单独维护一个文件,它可以作为文档的一部分提交和版本化,图的“居住地”和代码在一起,天然不会失联。

3. 核心语法拆解:以 Mermaid 为例画结构图与流程图

3.1 最小可运行示例与基本方向

Mermaid 是代码化制图方向上一个很有代表性的工具,也是我日常用得最多的一个。虽然它不是功能最全的图工具,很多复杂效果做不到,但它在“文本 + 图”的工作流里体验最顺滑。先看一个最基础的 flowchart。

flowchart TD A[解析请求] --> B{校验权限} B -- 通过 --> C[执行业务逻辑] B -- 拒绝 --> D[返回错误]

第一行flowchart TD是图声明,TD 表示 Top to Down,从上到下。要改成左右布局,就写flowchart LR,也就是 Left to Right。后面的每一行都是一个节点或一条连线:A、B、C、D 是节点 ID,方括号表示普通矩形,花括号表示菱形判断,-->是带箭头的实线。这就是 Mermaid 的核心模型:声明节点,再声明节点之间的边。剩下所有的语法,都是在这两个动作上做扩展。

方向的选择也有一点讲究。从上到下适合流程步骤相对线性、判断分支不复杂的图;从左到右更适合表达“调用关系”,因为调用顺序天然是从左到右逐层深入。我的习惯是:业务流程图用 TD,系统调用图用 LR,这样第一眼的信息方向就符合直觉。

3.2 节点写法与常用形状

节点是图的基本单元。Mermaid 提供了多种形状,每种形状在语义上都有对应角色,我常用的几种如下。

  • A[文本]:矩形,普通节点,最常用。
  • A(文本):圆角矩形,适合开始或结束节点。
  • A((文本)):圆形,适合子流程入口或出口。
  • A{文本}:菱形,适合判断分支。
  • A>文本]:非对称形状,适合输出或外部实体。
  • A[[文本]]:子程序或模块的表示。
  • A[/文本/]:平行四边形,适合输入输出节点。

这些形状不需要硬记,做图时按角色选用即可。我的习惯是:菱形只用来表达判断,矩形只用来表达数据处理,圆角矩形表达起止点,这样读者只需要看一眼形状就能猜到节点性质。节点 ID 建议只用英文和数字,显示文本放在语法符号的括号里就好,中文标签可以直接放进去,但千万别在 ID 里放中文或空格,否则解析很容易出问题。

3.3 连线语义:箭头、标签、虚线、子图

图的信息量其实大部分来自连线,而不是节点本身。连线的类型直接影响读者对依赖关系的判断。

  • -->:实线箭头,表达流向或依赖方向,最常用。
  • ---:无箭头实线,表达关联或相邻关系。
  • -.->:虚线箭头,表达异步、间接或弱依赖。
  • ==>:加粗箭头,表达强流程或重点链路。
  • -- 文案 -->|文案|:给连线加标签,说明关系内容。
A -- 创建订单 --> B A -. 异步通知 .-> C A ==> 核心链路 ==> D

需要给模块分组时,用 subgraph 语法。凡是属于同一个模块的节点,放进同一个 subgraph 里,图的层次会清晰非常多。

subgraph 订单模块 O1[创建订单] O2[订单查询] end

需要注意的是,subgraph 的 id 不能带空格,标签要写在 id 后面,某些旧版本写法差异会导致渲染失败,团队里最好固定一种写法。另外,连线标签不要写得像代码注释一样长,控制在六到八个字以内,否则线上文字比节点还显眼,干扰视线。

3.4 样式与主题定制

样式层面的定制主要有两个入口:classDef 和主题变量。classDef 是为某一类节点定义统一样式,比如给“核心服务”和“外部依赖”用不同填充色。

classDef normal fill:#e8f4f8,stroke:#333,stroke-width:1px class A,B,C normal

主题变量则在图开头用%%{init: ...}%%设置,可以调整整体配色、字体、连线曲率等。

%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#fafafa", "fontSize": "14px"}}}%%

我的建议是:颜色是用来传达语义的,不是用来好看的。定一套固定色板就够用了,比如核心服务用蓝色系、外部依赖用灰色系、异常链路用红色系,全图颜色种类不超过四种。颜色一旦太多,图就变成了彩虹,读者反而抓不住重点。团队规范图尤其要统一配色,防止每人画一个版本。

4. 实操案例:给“在线订单系统”画一张可持续维护的架构图

4.1 场景设定与最终效果目标

假设你刚接手一个订单系统,新同事问主链路时,总不能从数据库表讲起。此时需要的是一张能让不熟悉系统的人在 1 分钟内看懂“用户请求是怎么进来、经过哪些模块、最终落在哪些存储上”的架构图。这个目标决定了抽象粒度:只画主链路上的模块,不画内部实现细节,更不画每一行的具体处理逻辑。

这个例子我选在线订单系统,是因为它是典型的后端业务系统,几乎所有读者都能理解它的业务背景。最终效果应该包含:客户端入口、网关、核心服务、异步消息、数据库与缓存,以及日志告警这些横切能力。不要一上来就追求完整,先画出主干,再逐步补充依赖。

4.2 分步实现:从文本草稿到完整图

第一步,把节点先列出来。这一步不排版、不连线,只把概念打散到桌面上。比如:Web 端、Nginx 网关、订单服务、库存服务、支付服务、消息队列、MySQL、Redis、日志系统、告警系统。这个环节如果发现概念粒度不统一,比如既有“订单服务”又有“订单超时状态机”,说明信息层级混了,要先想清楚再继续。

flowchart TD Web[Web端] Gateway[Nginx网关] Order[订单服务] Stock[库存服务] Pay[支付服务] MQ[消息队列] DB[(MySQL)] Cache[(Redis)] Log[日志系统] Alert[告警系统]

第二步,把主链路连出来。先画最关键的调用顺序:Web 端请求到网关,网关到订单服务;创建订单需要同步调用库存服务和支付服务,再发一条消息到 MQ。这一段不需要追求完整,先把主干理清楚,让它能表达“一条订单请求是怎么走完主流程的”。

Web --> Gateway Gateway --> Order Order --> Stock Order --> Pay Order --> MQ

第三步,加依赖存储与旁路。订单服务读写 MySQL 和 Redis,库存和支付也有自己的存储;MQ 的消费方属于旁路,比如订单超时关单,它不是用户请求的直接路径,但确实依赖消息队列。日志和告警是横切能力,用虚线连接到相关服务即可。到这一步,我开始意识到“Order 到 MQ”只画一条线很难表达“发送事件”和“消费事件”两个角色,于是拆解成两个节点:MQ 生产者侧和消费者侧。

第四步,用子图分组并调整视觉层次。把网关集群、订单域、基础组件各放进一个 subgraph,再给核心服务加色,外部依赖用灰色。完整代码基本长这样:

flowchart TD subgraph Client[客户端] Web[Web端] end subgraph GatewayLayer[接入层] Gateway[Nginx网关] end subgraph OrderDomain[订单域] Order[订单服务] Consumer[关单消费者] end subgraph Deps[依赖服务] Stock[库存服务] Pay[支付服务] end subgraph Middleware[中间件] MQ[(消息队列)] DB[(MySQL)] Cache[(Redis)] end Web --> Gateway Gateway --> Order Order --> Stock Order --> Pay Order --> MQ MQ --> Consumer Order --> DB Order --> Cache classDef core fill:#e8f4f8,stroke:#1a73e8,stroke-width:2px classDef dep fill:#f5f5f5,stroke:#999,stroke-width:1px class Order,Gateway core class Stock,Pay,Consumer dep

到这一步,图已经能说明问题了。如果还想再严谨一点,可以把“订单超时关单”这个异步链路的标签写清楚,比如Order -- 发布超时事件 --> MQ,再把MQ -- 投递事件 --> Consumer补上,读者就不会产生“这消息是发给谁”的疑问。

4.3 把图接进文档工作流:README、CI 与团队协作

图画完只是开始,真正难的是让它一直保持更新。我见过不少项目,架构图在评审当天很漂亮,半年后和代码已经没有关系了。要解决这个问题,必须把图嵌进文档和开发流程。

首选做法是放在仓库根目录的docs目录下,命名成architecture.md,在 README 里加一个链接。由于 GitHub 和 GitLab 都能直接渲染 Mermaid 代码块,图不需要导出成图片文件,直接以源码形式存在仓库里。每次代码结构变化,顺手更新这个文档;如果依赖关系改了但图没改,代码评审时其实很容易发现,因为 diff 里能看到图文件没有变化。

如果需要导出图片给非技术同事,或者放进对外方案书,可以用 mermaid-cli 的命令行工具,例如:

npx @mermaid-js/mermaid-cli -i input.mmd -o output.png

这条命令会启动一个无头浏览器完成渲染,导出的效果和本地预览一致。还可以把它写进 CI,在文档变更时自动生成 PNG 并作为构建产物。团队协作时再补两条约定:一张图只有一个 owner,改动由 owner 负责;提交 PR 时如果涉及模块依赖,必须在同一个 PR 里更新图。这些约定不需要额外工具,写在 CONTRIBUTING 文档里就够了。

5. 常见问题与排障技巧实录

5.1 渲染与语法问题速查表

我整理了一张速查表,都是实际使用中遇到频率最高的问题。遇到图渲染不出来,先对照这张表排查一遍。

症状原因解决办法
节点内容变成纯文本,没有渲染成图Markdown 代码块语言标识写错在文档里把代码块语言标识为 mermaid,本地 CLI 用 mmdc 处理
图渲染时报语法错误节点 ID 里放了空格或中文ID 只用英文、数字、下划线,显示文本放在括号里
中文标签乱码旧版本 Mermaid 或导出环境缺中文字体升级工具版本;导出时安装中文字体并指定 font-family
箭头方向画反混淆了 A --> B 的方向语义记住箭头指向的就是流向目标,从源到目标书写
subgraph 没有显示成矩形分组subgraph id 含空格,或 id 大小写不匹配subgraph 的 id 不含空格,后续引用大小写保持一致
classDef 风格不生效class 语句写在了 classDef 定义之前先定义 classDef,再写 class 赋值
主题变量初始化报错init 里的 JSON 格式不对JSON 用双引号,不要加注释,写完检查括号
图很大时渲染卡顿单图节点过多拆成总览图加局部详图,一张图不超过三十个节点

5.2 我踩过的三个典型坑

第一个坑:本地渲染正常,推到 GitHub 上却报错。原因是不同平台的 Mermaid 版本不一致,旧版本对某些语法和新特性支持不好,尤其像&<>这类特殊字符,处理方式差异很大。后来我强制要求团队统一版本,文案里避免特殊字符,需要展示时用 HTML 实体写法,这个问题就很少再出现了。

第二个坑:用 mermaid-cli 导出 PNG 时中文全部变成方块。排查下来不是代码问题,是容器里没有中文字体。解决方法是给导出环境安装中文字体,比如 Noto Sans CJK SC,然后在 CSS 里指定font-family: "Noto Sans CJK SC"。这里要提醒一下,本地编辑器预览正常不代表 CLI 导出正常,两条链路要分开看。

第三个坑:一张图塞了 100 多个节点,布局算法跑出来交叉线多到没法看。我把锅甩给工具,后来才明白是设计问题。节点太多时,任何自动布局都救不了可读性。正确做法是拆图:总览图只画模块和主链路,局部详图单独展开,节点之间用 click 跳转关联起来,读者需要看细节时再进去。

5.3 团队落地阶段的三条建议

最后说三条团队落地建议,都是我实践过觉得比较有性价比的约定。

第一,单一图单一职责。一张图只表达一个核心观点,节点数量控制在三十个以内。如果超过三十个节点,说明这张图承担了两个以上职责,拆开反而更清晰。

第二,图随代码走。图文件放进代码仓库的 docs 目录,和 README、接口文档一起版本化。代码改了,图也必须跟着改,把“更新图”养成肌肉记忆。

第三,固定语义色板。全团队统一颜色规则,核心服务用什么色、外部依赖用什么色、异常链路用什么色。视觉一致性看似小事,长期维护时能省掉大量沟通成本。

我在实际项目里有个朴素习惯:每次画完图,会找一个不懂这个系统的人来看,只看 20 秒,然后让他描述自己看到了什么。如果他说的和我最初想表达的差不多,说明这张图合格;如果他张口就问“这是什么、那是什么”,说明图里的职责边界和信息层级还没理清楚。这个测试成本很低,但比任何工具技巧都管用。diagram-design 说到底不是艺术比赛,它是让团队少开几次无效评审、少写几段重复解释的技术动作。希望这篇文章能让你下次画图时,少走我走过的弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询