ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

图表即代码:用Mermaid和diagram-design打造可维护的技术图表

图表即代码:用Mermaid和diagram-design打造可维护的技术图表 1. 项目概述与设计初衷1.1 从一次混乱的架构评审说起我一直觉得团队里最容易被低估的技术环节不是写代码而是“把想法讲清楚”。上个季度我们做一次核心链路重构架构评审会上后端同学投屏一张手工拖拽的架构图连线上标注还是上一个版本的接口名。负责存储的同事问“这部分缓存是怎么穿透的”画图的同学愣了三秒然后指着其中一个方框说“这个我待会儿再改”。散会之后两个方案因为一张图没画清楚被否掉等于白开了两小时会。那次之后我认真想了一件事日常开发里我们到底花了多少时间在“画图”和“看图”上需求文档里要放流程图架构评审要出部署拓扑接口设计要画时序图数据库建模要出ER图连写季度总结都免不了来一张链路示意。图表从来不是“锦上添花”的东西它几乎就是技术沟通的通用语言。但问题在于绝大多数团队画图的方式还停留在“打开绘图软件手动拖拽导出PNG贴到文档里”——这套流程听起来没什么问题真正跑起来全是坑。diagram-design我最初接触这个词的时候以为它只是又一种绘图工具的代号。真正研究下来才明白它代表的不只是某个软件而是一整套“把图表当代码来设计、管理、维护”的工作方式。简单来说就是用文本标记语言描述图表的节点和连线图表本身作为工程的一部分存在跟随代码仓库统一版本管理既可以被渲染成图片也可以被自动化流程复用。这就像是把“用Word手调格式”升级成了“用Markdown写文档”。格式交给工具处理内容本身变成纯文本可追踪、可比较、可协作谁改了什么一目了然。把图表的生命周期纳入软件工程的轨道里而不是让它散落在某个人电脑里的绘图文件中这才是diagram-design真正想解决的问题。1.2 这个方案能解决什么痛点先抛几个场景你看看是不是似曾相识文档里的架构图是照片级别的PNG图上某个服务已经下线半年了图还是老样子新来的同学照着图排查问题顺着一条已经不存在的数据链路找了一整个下午。代码评审里改了一个接口的入参但时序图没有同步更新前后端联调阶段才发现理解的版本不一致返工成本远高于画图的成本。团队里每个人都装了自己的绘图软件有人用付费工具有人用开源工具导出格式不统一画风难看复制粘贴到文档里清晰度还会被压缩。这些问题的共性在于图表没有跟上代码和业务的演进节奏也没有被当作“一等公民”纳入工程管理流程。而diagram-design这种代码化图表方案恰好能同时解决以上几个问题。文本描述图表的天然优势就是差异化对比。无论谁改了图的哪个节点通过Git就能看到准确的行级变更记录。图存成.dot、.puml或.mmd格式的文本文件代码评审平台可以直接展示diff不需要生成图片再人工对比。而且当图表写法和代码并存于同一个仓库提交历史天然把“代码变更”和“图表变更”关联在一起后续追溯信息流时顺着提交记录就能找到当时的上下文。另外还有个容易被忽略但实际很重要的点写图的输入门槛。传统拖拽式绘图操作本身不复杂但精细对齐、统一配色、规范排版需要大量手动工作量。文本绘图则把“画”变成了“描述”思维负担小很多圈复杂度高的流程也能很快铺开。对于开发同学来说这几乎是无缝衔接——写图画图都是写文本从思路到产出的路径更短了。2. 工具选型解析代码化绘图的主流方案对比2.1 四种主流方案的定位与能力边界如果决定走“图表即代码”这条路首先得选一个趁手的工具。现阶段社区里最常用的方案有四类Mermaid、PlantUML、GraphvizDOT语言和云厂商自研的图表DSL。它们各有侧重选错方向会在后面写大图的时候非常难受。先说我个人使用频率最高的Mermaid。它最大的特点是语法极简几乎不需要学习成本比如画流程图flowchart TD开头A -- B就是一条线半小时不到能上手。Markdown文档里可以直接嵌代码块GitHub、GitLab等平台原生渲染团队协作零摩擦。它的短板也很明显复杂布局的掌控力偏弱尤其当节点数量超过50个时默认布局算法的结果经常不理想手动干预排版的手段也比较有限。PlantUML定位则更偏向软件工程场景内建了时序图、用例图、组件图等类型语法语义跟UML绑定得很深。早期团队做接口设计评审时常用它出时序图participant和message的关系书写方式很直观。但它的渲染效果跟Mermaid比稍显老气而且环境依赖Jave在轻量化场景里略重。Graphviz则走得是另一条路线。它使用DOT语言描述图结构核心优势在于图布局算法极其强大几十上百个节点的复杂关系它能自动算出一个相对合理的排布适合做链路拓扑、依赖分析这类图。不过DOT语言的语法灵活度太高表达能力越强入门曲线就越陡峭用它画业务流程图反而有点杀鸡用牛刀。云厂商的自研DSL这里暂不展开因为多数绑定自家平台迁移成本高。如果是个人项目或者团队内部工具链还不确定我更建议先在Mermaid和PlantUML之间二选一大部分场景这两者覆盖足够了。2.2 不同场景下的选型建议我自己的经验是不要只押注一种工具而是按图的类型划分工具边界。流程图、架构图、状态图、饼图、甘特图这类偏“展示逻辑”的图优先用Mermaid。原因有两个一是渲染格式干净在Web端展示效果好视觉负担低适合放进文档和PPT二是生态足够开放主流Markdown编辑器、协作文档平台都已经内置了Mermaid渲染器别人拿到你的源文件也能顺利生成。时序图、部署图、活动图这类偏“软件工程规范”的图用PlantUML更顺手。PlantUML的时序图语义非常严谨消息的同步异步、激活状态、返回箭头都表达得很直接跟UML模型能一一对应适合在技术设计文档中作为正式交流语言。而且通过PlantUML Server还可以在不需要本地搭环境的情况下直接生成图片接入CI流程做自动化文档更新也方便。Graphviz不是用来“画图”的它是用来“算布局”的。假设你要展示微服务之间所有调用链路的依赖关系节点上百个连线复杂人工排版根本没法维护这时候用DOT描述节点关系Graphviz的dot算法能在几秒内产出一个相对不乱的布局。它处理的是图论意义上的图不是人类阅读意义上的图这两者差别很大上手前务必明确自己的需求。另外补充一个判断维度项目协作对象的属性。如果你的图表最终要交给产品、运营等非技术角色阅读Mermaid这种简洁风格更友好如果主要面向研发团队内部评审PlantUML更严谨如果是自动化分析的附属产出Graphviz更合适。2.3 轻量协作实践我为什么推荐从Mermaid起步考虑到我这次项目的场景是“日常设计沟通为主、自动化辅助为辅”最终选型是以Mermaid作为主力一份图至少要能被三个人在五分钟内看明白。对于刚接触diagram-design概念的同学我也建议从Mermaid入手。Mermaid的语法对新手极度友好最大的心理门槛其实不是“不会写”而是“不知道原来还可以这么写”。比如画一个最简单的流程图三个节点加两个箭头就已经形成可读的图了哪怕一开始写得丑只要结构对渲染出来就是规整的。这种“描述即所得”的反馈循环会让入门阶段非常愉快。另一个理由前面其实提过生态。Mermaid的兼容性几乎是事实标准级别的Notion、GitHub、GitLab、Jekyll、Vitepress、Docusaurus等文档工具通通原生支持这意味着你写的代码块放到哪里都能渲染换工具或者换平台不会产生迁移成本。对于长期维护的文档项目这一点决定性优势是无法替代的。还有就是自动化扩展。Mermaid的文本形态非常容易被程序解析配合mmdc命令行工具可以批量生成图片配合CI平台能实现文档自动更新后续想玩出更多花样也有足够空间。先用它跑通“描述图表”的工作流再按需引入其他工具这个路径我觉得最平滑。3. 核心细节解析与实操要点3.1 Mermaid语法的关键知识点与应用场景Mermaid的语法体系可以分为“图类型声明”和“元素定义”两部分。图类型声明决定了整张图的呈现形式常用到的包括flowchart流程图描述过程、分支、循环最常见sequenceDiagram时序图描述对象间消息交互顺序classDiagram类图描述类结构及关系stateDiagram-v2状态图描述状态流转erDiagramER图描述实体关系gantt甘特图描述项目进度计划pie饼图描述占比分布gitGraphGit分支图描述提交演进历史以流程图为例基础结构非常直观。关键字flowchart指定图类型方向选项TD表示从上到下LR表示从左到右RL和BT则对应相反方向。定义一个节点只需要写方括号内的文本之后用箭头符号连接不同节点flowchart TD A[接收请求] -- B[参数校验] B --|校验通过| C[调用订单服务] B --|参数错误| D[返回错误码] C -- E[返回订单详情]这段描述渲染出来就是一个非常清晰的分支流程图。这里有个容易被忽略的小细节--代表普通箭头---代表无向连接代表加粗箭头.-代表虚线箭头。不同箭头语义对应不同的业务含义比如主流程用加粗异步回调用虚线异常分支用普通线这样图的表达力会大幅提升。时序图的语法也不复杂。participant声明参与交互的对象-表示同步消息--表示异步消息Note over用于在某个对象上方添加说明文字sequenceDiagram participant Client participant Gateway participant OrderService Client-Gateway: 创建订单请求 Gateway-OrderService: 转发订单数据 OrderService--Gateway: 返回订单ID Gateway--Client: 返回成功对于研发团队来说把接口调用链路的时序图画清楚比写一长段描述文字高效得多。代码评审时贴这张图资深同事扫一眼就能判断哪一步设计有问题。3.2 状态图与ER图的实操要点状态图在业务系统设计中的出镜率其实被很多人低估了。尤其是订单、支付、审批这类状态机驱动的核心模块把状态流转图画明白很多“边界情况没考虑清楚”的问题在设计阶段就能暴露。我用stateDiagram-v2的频率也很高stateDiagram-v2 [*] -- 待支付 待支付 -- 已支付: 用户完成支付 待支付 -- 已取消: 用户取消订单 已支付 -- 已发货: 仓库发货 已发货 -- 已完成: 用户确认收货 已支付 -- 退款中: 用户发起退款 退款中 -- 已退款: 退款成功这里强烈建议团队在设计状态机前先画这个图。很多资历浅的同学在建表时只给订单表加一个status字段根本不梳理状态间的合法跳转后续加需求的时候各种“不合法状态”出现兼容代码越堆越烂。画状态图的过程其实就是在逼自己重新思考状态机的完备性。ER图在Mermaid里同样有内置支持虽然它的数据库设计专业度不能跟专门的建模工具相比但用于设计评审前期已经足够erDiagram CUSTOMER ||--o{ ORDER : 下单 ORDER ||--|{ ORDER_ITEM : 包含 PRODUCT ||--o{ ORDER_ITEM : 被选购实体间的关系符号需要稍微花点时间记忆比如||--o{表示“一个实体对应零到多个另一方实体”“一对一”是||--||“多对多”是}o--o{但这个表意体系学习曲线很平缓半小时就能掌握。画完ER图再建表字段设计的思路会清晰很多。3.3 主题定制与控制布局Mermaid默认渲染效果只能算中规中矩想让图表真正融入文档体系的视觉风格需要掌握主题定制的基本操作。最直接的方式是通过%%{init: {...}}%%在代码块内注入配置%%{init: {theme: neutral, themeVariables: {primaryColor: #4F46E5, lineColor: #334155}}}%% flowchart LR A[登录] -- B[鉴权] B -- C{有无权限} C --|有| D[业务逻辑] C --|无| E[拒绝访问]theme有default、neutral、dark、forest等预设themeVariables可以细粒度控制节点颜色、连线颜色、字体大小等。这里有一个新手经常踩坑的地方Mermaid配置项的键名严格区分大小写比如primaryColor中间的大写C写错了渲染器不会报错而是选择默默忽略配置最终效果跟预期完全不一样排查起来很费时间。还有一个常用技巧是给节点加class实现批量样式管理flowchart TD A[成功] -- B[继续] C[失败] -- D[重试] class A,B okNode class C,D errNode配合CSS定义class的样式在大型图表里能大幅减少重复代码并且保证同类元素的视觉一致性。一开始就规划好装备后面维护会轻松很多。4. 实操过程从零搭建一套diagram-as-code协作工作流4.1 本地环境准备与CLI工具链这一节我按实际搭建流程来讲读者可以跟着一步步操作。前提是电脑上已经安装了Node.js 18及以上版本。我们要用到的核心工具是mermaid-cli它的作用是把.mmd格式的Mermaid源文件渲染成SVG或PNG图片方便嵌入常规文档。安装命令很简单npm install -g mermaid-js/mermaid-cli安装完成后验证是否可用mmdc --version如果显示版本号说明安装成功。这里有一个我在Windows环境踩过的坑安装完成后执行mmdc命令如果提示“无法加载文件ps1因为在此系统上禁止运行脚本”需要在PowerShell中以管理员身份修改执行策略。执行Set-ExecutionPolicy RemoteSigned然后确认即可。接着创建一个项目目录作为图表仓库的试验田mkdir diagram-design-demo cd diagram-design-demo npm init -y后续所有源文件和生成图片都组织在这个目录下整体结构可以这样规划diagram-design-demo/ ├── diagrams/ │ ├── order-flow.mmd │ ├── payment-state.mmd │ └── system-architecture.mmd ├── output/ │ ├── order-flow.svg │ └── payment-state.png ├── package.json └── README.md4.2 编写第一张可维护的架构图先用架构图作为热身项目。假设我们在设计一个电商系统的下单链路需要体现客户端、网关、核心服务、消息队列和数据库之间的交互。创建一个system-architecture.mmd文件写入以下内容flowchart TB subgraph Client[客户端层] App[移动端APP] Web[Web浏览器] end subgraph Gateway[接入层] Nginx[负载均衡] API[API网关] end subgraph Service[核心服务层] Order[订单服务] Pay[支付服务] Inv[库存服务] end subgraph Middleware[基础设施层] MQ[(消息队列)] DB[(数据库)] end App -- Nginx Web -- Nginx Nginx -- API API -- Order API -- Pay API -- Inv Order -- MQ Pay -- MQ Inv -- DB用mmdc渲染成图片mmdc -i diagrams/system-architecture.mmd -o output/system-architecture.svg这里涉及几个值得注意的设计细节subgraph语法用于构建分组同组节点自动拥有一个统一背景色块非常适合表达“分层”概念。子图之间默认不可互连在实际语义中它们反而应各自独立。节点ID用简短有意义的英文单词不要用中文或特殊字符。ID只是索引真正的展示文本写在方括号内这样如果后面想改显示名称不用改任何连接关系。大写开头的字符串会被识别为英文小写开头的字符串在部分版本中会报错。为保险起见节点文本一律加方括号包裹特殊类型节点用圆括号。4.3 批量渲染与自动化集成单张一张张渲染效率太低更实用的是写一个自动化脚本。在package.json里添加批量渲染脚本{ scripts: { render: mkdir -p output for f in diagrams/*.mmd; do mmdc -i \$f\ -o \output/$(basename \$f\ .mmd).svg\; done } }Windows环境如果跑不了bash循环语法可以用Node.js写一个简单的批量脚本const { execSync } require(child_process); const fs require(fs); const path require(path); const srcDir ./diagrams; const outDir ./output; if (!fs.existsSync(outDir)) { fs.mkdirSync(outDir, { recursive: true }); } const files fs.readdirSync(srcDir).filter(f f.endsWith(.mmd)); for (const file of files) { const outFile file.replace(.mmd, .svg); execSync(mmdc -i ${path.join(srcDir, file)} -o ${path.join(outDir, outFile)}, { stdio: inherit }); console.log(已生成: ${outFile}); }这套脚本只是起步真正实用的是接进CI流程。比如GitHub Actions的workflow里增加一个job当diagrams目录下文件发生变化时自动执行渲染并把产物提交到仓库或上传到构建物name: render-diagrams on: push: paths: - diagrams/** jobs: render: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - uses: actions/setup-nodev4 with: node-version: 20 - run: npm install -g mermaid-js/mermaid-cli - run: npm run render - uses: actions/upload-artifactv4 with: name: diagram-outputs path: output/这样团队里任何人更新了图表源文件推送代码后图片就会自动重新生成保证文档永远是最新的。让机器去做重复性的渲染工作人力只关注真正的设计内容。注意依赖puppeteer的mermaid-cli在CI环境中需要安装Chrome依赖。如果容器基础镜像比较精简渲染时经常会遇到“Could not find any Chrome executable”之类的报错。建议在CI脚本中增加npx puppeteer browsers install chrome来预装环境。4.4 与文档平台和代码仓库的协作实践图表源文件放进Git仓库之后还有几件事值得做好。第一在项目根目录添加.gitattributes文件确保.mmd文件按文本格式处理。Git默认对文本文件做diff没有问题但如果某些二进制相关内容混入diff会变得不可读。显式声明可以避免这种问题*.mmd text *.svg text第二约定提交信息规范。当提交涉及图表修改时在commit message里加上对应的业务描述比如“更新订单超时状态的流转逻辑”“调整支付回调异常分支”。这样做不是为了好看而是让未来的自己通过git log就能定位每次改图的业务动机。第三在README中建立图表清单表写明每个文件的作用和适用场景方便新成员查阅文件路径图表类型内容说明对应业务模块diagrams/order-flow.mmd时序图下单主链路交互逻辑订单中心diagrams/payment-state.mmd状态图支付状态合法流转关系支付中心diagrams/system-architecture.mmd架构图核心分层架构总览系统整体5. 常见问题与排查技巧实录5.1 千奇百怪的渲染报错与排查思路凡是代码化绘图肯定会遇到渲染报错。Mermaid的错误信息通常还算友好但仍然有一些常见场景值得拿出来专门说。错误类型一语法解析失败。这类报错一般会附带行号和解析位置比如“Expecting END”或“Parse error on line 5”。排查思路是先看报错位置附近是否有中文字符缺少引号包裹、方括号是否闭合、箭头符号是否误用中文全角标点。我见过一个特别隐蔽的坑在节点文本里用了英文双引号但没有转义整个图就渲染失败。解决办法要么换成全角引号要么使用实体表示。错误类型二方向标识混乱。flowchart TD声明之后局部子图又想改变方向这需要通过direction关键字在子图内部指定。很多新手以为方向的修改作用于全局结果节点排列完全不符合直觉。错误类型三特殊节点渲染后消失。节点ID如果是end、start这类保留字会导致解析异常。遇到这种问题把ID改成endNode这类规避即可。5.2 文本绘图方案的地板与天花板文本绘图并不适合所有图识别这个边界可以少走很多弯路。Mermaid对复杂布局的掌控力有限它擅长的是“线性流程”“树状层级”“明确分组的网状结构”。但当你需要精细控制每个节点的绝对位置、任意角度曲线、复杂的重排逻辑时它的自由度明显不足。举个例子画一个微服务全链路调用拓扑图节点和连线按真实机房间的网络路径排列这种图用Mermaid会非常痛苦——它不会严格按你的预期排列位置。这类场景更适合用专业的可视化编辑器人为排布或者接受Graphviz的“自动布局但相对合理”。反过来如果你想画非常标准的需求流程图节点的顺序已经通过文字描述清晰表达那Mermaid就是最佳选择。每个人的需求不同我最后的建议是先想清楚图要服务什么角色再选择表达工具不要反过来让工具限制表达。5.3 跨平台渲染差异与规避策略Mermaid的渲染效果在不同平台之间存在细微差异尤其是字体和间距。同一个.mmd文件在GitHub上渲染和在本地mmdc渲染字体不同导致节点宽度发生细微变化图形出现轻微的错位感。规避策略其实很简单如果图表最终要嵌入正式文档并保持视觉稳定建议用mmdc渲染成PNG或SVG固定图片格式再嵌入文档。如果只是轻量分享用途直接依赖平台的实时渲染即可。千万别一个图既在Markdown里嵌代码块又同时引用生成图片同一个源文件产生两套视觉样式会给人不专业的感觉。另外如果生成的图片要放在深色页面中建议显式设置背景色。默认的transparent背景在某些Office和PDF工具里会显示成黑色或灰色块处理方案是在mmdc命令里加-b white强制白底mmdc -i diagrams/payment-state.mmd -o output/payment-state.png -b white5.4 从“画图”思维到“设计”思维的转变最后想特别聊一个不太常见但在实践中很关键的问题很多人接触diagram-design第一个冲动是赶紧学语法但我建议先花时间想清楚“图是给谁看的希望他看完之后做什么决策”。有一次我帮一个团队评审支付系统的状态图他们把支付流程画得非常完整各种失败重试分支全画出来了图变得特别复杂。单看局部是对的一张图但从阅读效率角度讲这张图信息量过载了。评审会上一堆人在看细节没有人关注核心状态机的设计是否合理。后来我建议他们把图拆成两层一层是“主流程极简图”只画成功路径和两个最核心的异常分支用于评审沟通另一层是“完整状态流转图”把所有分支细节全部列出作为开发时的参考文档。这就用到了“设计图表”而不是“画图表”的思路。好的图表设计不是把系统里所有的关系都画上去而是根据受众的信息需求做取舍。一个能让人在10秒内抓住主线的图表远胜过一个信息完备但需要10分钟才能看懂的图表。diagram-design的核心也正是在这里——用工程化的方式管理图表同时用设计的思维规划图表的信息层次。我自己在实践中最受益的一条经验是每次画图之前先用三句话在纸上写下这张图试图传达的核心信息。写完这三句话再落笔图的结构会清晰很多后续改图的次数也会大幅减少。如果你也在考虑把图表纳入工程化管理我建议你先从一个小模块的流程图开始试水跑通“写文本、推仓库、自动渲染”的闭环再逐步扩大应用范围。这套工作流真正实践下来你会发现文档的过期率变低了评审沟通的效率上去了团队协作的摩擦也随之少了很多。
返回列表