
diagram-design这个项目往小了说是“用代码画图”往大了说是把图表从一次性的静态产物变成可以版本的、可复用的工程资产。我最早碰这个东西纯属被鼠标拖拽折磨出来的——画个架构图先是对齐框体就花掉半小时等需求一变又得手动删线、挪位置、重新截图最崩溃的是图上内容和线上系统的真实情况早就对不上了但根本没人记得更新。换成代码化设计之后图表跟着文档一起走改起来就是改几行文本渲染交给工具几乎不用操心排版。这篇就围绕diagram-design的完整工作流来聊涵盖方案选型、语法细节、实操案例、踩坑记录和团队落地经验适合要写技术文档的开发者、需要梳理系统方案的架构师以及任何想把复杂逻辑一次性讲清楚的从业者。1. 先搞清楚思路diagram-design到底在设计什么1.1 为什么鼠标拖拽画图总是不靠谱很多人以为画图难在“会不会用工具”其实难在“图是要跟随逻辑变化的”。用拖拽式工具画图你画的不是逻辑而是一堆坐标。坐标是视觉概念逻辑是结构概念这两者一旦错位图就成了死图。我自己常见的翻车现场是这样的流程图里新增一个判断节点手动把后面的连线一根根拆掉重新接然后把整块区域往右推害怕挤到旁边的子图子图内部的节点改了名字外层的框线又得跟着拉伸拉伸完布局全乱。这个过程至少重复三遍以上等到第N次微调时我已经不关心内容了只想赶紧结束。还有一层更隐性的成本图片和文档是分离的。代码仓库里的README嵌了一张架构图但架构图早已过期方案文档里放了一张时序图时序图对应的代码却是上一版逻辑。时间一久图不仅没有辅助理解反而在误导新人。diagram-design的思路恰好绕开了这些问题。图的本质是结构化的信息而结构化信息天生适合用文本描述。一旦用文本描述就可以进版本库、可以做diff、可以被程序校验、可以被团队review。图形只是这个文本结构的一种“渲染视图”而已。1.2 方案选型背后的核心逻辑代码化画图这条路不同工具解决的是不同层次的痛点。我简单梳理下当前几个主流选择。Mermaid是目前最轻量的方案语法接近自然语言对Markdown生态的侵入性最小。写博客、写GitHub仓库、写内部知识库的时候直接内嵌一个mermaid代码块就能渲染不需要额外服务。它对流程图、时序图、状态图、甘特图支持得都不错社区活跃度也高。PlantUML更贴近UML语义写类图、用例图、活动图这种专业建模场景时表达力更强适合技术体系比较重的团队沉淀设计文档。代价是渲染通常依赖Java运行环境在自己电脑上没问题但在线平台和某些文档系统上接入成本会高一些轻量场景下反而有点重。Graphviz靠的是自动布局引擎擅长处理依赖关系图、调用链、数据流这类结构复杂的图。你只需要声明节点和边的连接关系布局算法自动安排位置这一点对“节点特别多”的图来说极其好用。但它的默认样式确实寒碜而且想做到视觉好看需要花功夫调参数。我的选型原则很简单图的类型决定工具而不是反过来。日常做业务流程图、系统架构图、时序图优先用Mermaid涉及UML类图和状态机建模考虑PlantUML处理几十个节点以上的依赖关系、血缘关系就交给Graphviz。那diagram-design这个项目具体怎么落地我后面就用Mermaid作为主线因为它最容易上手也最能说明“代码驱动设计”的核心思路。PlantUML和Graphviz的思路完全一致差异只是语法形态和使用场景。2. 设计图表前必须搞懂的基础元素与原则2.1 节点、连线、分组图表就是结构化的表达我习惯把一张图拆成三类基础元素来理解。节点代表实体可以是一个服务、一个人、一个状态、一个步骤。Mermaid里节点常用方括号写例如A[用户]就是名为“用户”的节点。连线代表实体之间的关系箭头表示流向或依赖标签表示关系的语义。比如A --|发起请求| B意思是A把请求发给B。分组代表系统的边界和模块归属Mermaid里用subgraph来声明子图把同一层级的服务放在同一个框里这样图的层级感立刻就有了。这三类元素的关系可以类比成一篇文章节点是名词连线是动词分组是段落。图要表达的不是“有哪些东西”而是“这些东西之间发生了什么、谁依赖谁、谁在哪个边界内”。表格式对比一下不同工具的元素表达能力图表元素MermaidPlantUMLGraphviz节点形状方框/圆角/菱形/圆形类/用例/状态等多种通用节点形状连线类型箭头/虚线/加粗/双向继承/实现/依赖/关联有向/无向/虚线/实线分组subgraph子图package/namespace/包cluster子图布局控制方向和间距参数基础布局、偏强制多种算法可切换无论用哪个工具设计阶段都建议先把节点、关系、分组这三张清单列出来再动手写语法。清单列清楚了图已经在脑子里成型了写码只是翻译过程。2.2 布局方向与阅读顺序直接影响表达效果布局方向不是随便选的。横向排布LR适合表达时间线、处理流程、串行步骤因为人的阅读习惯是从左到右箭头天然指向“下一步”。纵向排布TB适合表达层级、包含关系、从上到下的命令分发比如网关到服务的调用链就非常适合自上而下展开。我踩过的一个典型误区是把所有节点一股脑往上放指望工具自动给出好布局。实际上方向参数只是给出一个倾向还是要靠你在设计时规划主路径。主路径永远是图的核心阅读线索次要关系可以放在两侧避免连线交叉。另外节点的ID要有语义。Mermaid里的ID是最终连接关系的锚点起得乱七八糟过两个星期连自己都看不懂。我习惯用业务代号加下划线命名比如order_svc、user_frontend既简短又能看出含义。ID和显示文字是分开的显示文字可以用中文ID保持英文两全其美。2.3 颜色和样式是语义的一部分diagram-design对样式的基本要求只有一个颜色要表达“类别”而不是表达“喜庆”。一组图里相同语义的节点头颜色应该一致比如外部系统用灰色、核心服务用主色调、数据库用另一种色、异常路径用红色虚线。Mermaid里可以通过classDef来统一定义一组节点的样式再用class把某个节点归入这组。例如下图中的order_svc会被应用上名为core的样式背景变成蓝色字体变成白色。classDef core fill:#1f6feb,color:#fff,stroke:#1f6feb,stroke-width:2px; class order_svc core;这样做的核心价值在于视觉信息与逻辑语义绑定读者扫一眼颜色就能判断“这句话是讲主要流程还是旁路分支”不需要逐个读文字。全图一塌糊涂全是高亮就等于全图没有高亮。颜色种类建议控制在四种以内。字体的处理同样关键。尤其中文环境下不同机器的默认字体不同渲染出来的效果天差地别严重的会变成豆腐块。在Mermaid里可以通过初始化配置统一指定字体%%{init: {theme: base, themeVariables: {fontFamily: Microsoft YaHei, PingFang SC, Noto Sans CJK SC}}}%%我一般会在靠前的位置就把这个配置写好后面所有图表共用同一个主题基座整体风格才统一。3. 从零到一设计一张完整架构图的实操过程3.1 动手写代码前先画一张“信息手稿”很多人在代码化画图上手失败是因为跳过了信息梳理这一步直接开写语法。结果写到一半发现漏了一个服务、少了一条关系回头补的时候布局全乱。我的习惯是先在纸上或者白板上把信息结构画个粗糙版本不需要好看只需要把以下四件事定下来有哪些实体需要出现在图上它们之间是什么关系谁调用谁谁依赖谁谁的数据流向谁哪些实体属于同一个模块或子系统主阅读方向应该是什么拿一个常见的“用户下单流程”来举例。先列出实体用户、前端页面、API网关、订单服务、库存服务、支付服务、消息队列、数据库。关系大概是用户操作前端前端请求网关网关分发到订单服务订单服务依次调用库存和支付服务同时读写数据库系统把事件发到消息队列。分组显然是把订单、库存、支付放进一个“业务服务”的子图里外部实体放在外面。方向选择TBA表示从上到下符合“用户从顶部进来、系统在下方响应”的直觉。3.2 从信息结构到Mermaid语法逐行写出来信息手稿一旦成型写代码就是翻译过程。下面是一版可以直接跑起来的示例%%{init: {theme: base, themeVariables: {fontFamily: Microsoft YaHei, PingFang SC, Noto Sans CJK SC}}}%% flowchart TB U[用户] F[前端页面] G[API网关] subgraph 业务服务 O[订单服务] I[库存服务] P[支付服务] end DB[(数据库)] MQ[(消息队列)] U --|访问| F F --|提交订单| G G --|路由请求| O O --|校验库存| I O --|发起支付| P O --|持久化| DB I -.-|扣减完成事件| MQ P -.-|支付成功事件| MQ逐行解释一下flowchart TB声明这是一张从上到下的流程图框架贯穿全图。节点定义里[数据库]前面加了一对双括号[( )]这是圆柱体表达数据库语义。箭头写法分两种--实线箭头代表直接调用-.-虚线箭头代表异步事件。箭头后接|文案|给关系本身加上语义标签这是图读起来通顺的关键。观察这张图的结构主流程从上到下依次是用户、前端、网关、订单服务之后再分叉到库存和支付。信息层次比一开始手稿更清晰因为分组让“哪些服务属于业务域”一目了然。3.3 样式定制用主题变量和classDef控制全局风格默认渲染效果虽然能看但在正式文档里通常不够精致。Mermaid的定制入口有几个层次第一层是全局主题变量可以改背景色、字体、阴影等基础属性。第二层是classDef批量定义节点样式适合给某类节点统一配色。第三层是linkStyle可以单独设置某条连线的颜色、粗细和虚线属性。比如我想把订单服务突出显示同时把两条异步事件链路做成虚线弱化可以这样追加classDef core fill:#1f6feb,color:#fff,stroke:#1f6feb,stroke-width:2px; class O core; linkStyle default stroke:#888,stroke-width:1px;这里的逻辑是大部分连线设为浅灰色只让核心调用路径保持正常整个图的视觉重心就会自然落在关键结构上。样式调整的边界是“每一步改动都要有理由”如果只是为了花哨建议全部不要改。3.4 渲染、导出、嵌入文档完整闭环写完语法后到真正“出图”还有一段路。我通常用的流程是先在Mermaid Live Editor里粘贴代码看渲染效果。这里迭代最快改完秒出图适合日常调试。确认没问题后如果要输出成SVG或PNG就用mermaid-cli完成mmdc -i architecture.mmd -o architecture.svg -b white --scale 2参数含义-i指定输入文件-o指定输出文件-b设置背景色为白色避免透明背景在文档里显得突兀--scale 2输出两倍分辨率的图像插入文档后放大也不会糊。如果图要嵌进Markdown直接用三个反引号包住mermaid代码就行GitHub、GitLab、语雀、Notion这类平台基本都可以原生渲染。如果图要放进Word或PPT就导出PNG或SVG再插入。SVG的优势是矢量和可编辑缺点是部分老版本办公软件支持不好这点要自己权衡。整个流程走下来从信息手稿到最终成图大概不超过十分钟。改版时同样从文本改起重新导出即可不再有“手动挪坐标”的工序。4. 高频问题与排查技巧实录4.1 箭头方向画反了而且自己反复看不出来问题是这样的明明在代码里写的是A -- B再去看渲染图总感觉箭头指向不对。原因在于箭头语义是“从A指向B”如果你对“谁调用谁”的定义和对箭头的默认预期刚好相反就会觉得反了。排查思路很简单先确认箭头方向约定。A -- B箭头的尖锐端在B这一侧意思是A访问B。然后再确认业务语义比如“前端调用网关”和“网关接受前端请求”是同一件事写F -- G就对写G -- F就画反了。建议是动手写码前统一一个约定全项目一律使用“调用方指向被调用方”。这样任何时候看到箭头就能直接读成“左边调用右边”不用每一条连线重新理解语义。4.2 同一份代码本机渲染正常线上变成豆腐块这种情况几乎都是字体问题。线上渲染环境里没有你本地安装的中文字体渲染系统回退到默认字体而默认字体又不支持中文字符集结果就是一堆方框。解决手段就是塞fontFamily配置在前面已经给过示例。关键是配置里的字体列表要覆盖几种主流系统否则只写一个“Microsoft YaHei”在macOS和Linux服务器上还是回退。如果走mmdc导出图片还可以用css注入的方式强制指定字体mmdc -i input.mmd -o output.svg --css text { font-family: Noto Sans CJK SC !important }这个方法胜在精确适合线上环境没办法改初始化配置的边界场景。4.3 节点一多连线交叉图变成蜘蛛网单图超过四五十个节点的时候Mermaid的布局引擎容易在密集交叉的连线上失去优雅感。这时候不要试图用参数微调解决那是错误的方向。我用过最有效的三个办法把大图拆成多张小图每张图只讲一个主流程小图之间用文档的章节逻辑串联。增加subgraph分组让相互关联的节点先成小簇再让小簇之间连线。子图结构能显著减少跨区域连线的交叉概率。调整方向参数比如LR换成TB或者反过来。有时候布局密密麻麻并不是结构问题只是方向选择不适合这张图的形态。实践下来一张图控制在二十到三十个节点之间可读性和可维护性最好。4.4 导出的图片不够清晰插入文档有锯齿我最初遇到过从Live Editor截图出来的图片插到PPT里一放大就糊成一片。后来换成mmdc导出问题就解决了。关键参数就是--scale需要多高的清晰度就设多大的倍率常用2到3就够。如果对清晰度有极致要求可以考虑输出SVG而不是PNG。放大到任何程度都不会失真也方便后续二次编辑。缺点是GitHub等平台上预览SVG不如PNG稳具体情况需要你自己按平台取舍。4.5 常见问题速查表问题现象主要排查方向建议解法箭头方向和预想相反箭头定义的方向约定统一“调用方指向被调用方”统一使用A -- B写法中文显示为方块字体缺失或字体未指定在init中配置multi-fontFamily导出时用css强制指定节点多、连线乱节点数量与布局参数不合理减少单图节点数、增加subgraph、切换方向导出的图模糊图片分辨率不足mmdc加--scale 2/3业务上改用SVG子图框位置奇怪subgraph与节点定义顺序先定义节点再写subgraph归属别交叉引用语法报错但看不出错在哪某个特殊字符没转义检查标签里是否含引号、括号必要时用引号包裹这几条是我实际跑diagram-design项目时遇到最频繁的问题基本覆盖了从语法到渲染、从字体到导出的全部高发故障。5. 团队协作时diagram-design怎么落地5.1 图表进代码库让图和逻辑同源个人用图很大程度上是给自己看团队里用图就得考虑“图有没有人维护”。diagram-design最大的优势此时才真正显现图是文本文件可以放进代码仓库跟着项目代码一起走。这意味着改图就像改代码一样有修改记录有diff检查有review过程。某一个服务下线了删掉它对应的节点定义和连线PR里一目了然某条链路新增了依赖加上节点和箭头reviewer可以看到改动范围。过去那种“文档图过期了三个月没人管”的问题在代码化图表面前不存在——图改不动的地方和代码没法编译是一个道理。我现在的做法是每个系统模块的docs目录下放一个diagrams文件夹里面按图的功能命名比如checkout-flow.mmd、payment-seq.mmd。Markdown文档里用相对路径引用渲染后的SVG或者直接内嵌mermaid代码块保证图和文档永远跟随主分支同步演化。5.2 沉淀团队自己的diagram-design规范用了一段时间之后我强烈建议每个团队沉淀一份简易规范不用长就几页纸但能省掉大量协作成本。规范至少应该包含这几项规范项推荐约定理由节点命名英文小写加下划线编码稳定不随显示文字改动而断链拼音英文业务名词优先用常见英文缩写避免中文ID在部分渲染环境出问题方向默认值流程图用LR层级图用TB符合阅读习惯减少随意性颜色语义蓝色核心绿色成功橙色告警灰色外部颜色承载语义避免风格分裂子图命名统一用业务域名称区分层级归属便于检索标签风格箭头上的文字用动词短语表达“关系”而非“状态”这些规范看起来琐碎但真正运行几周后会体会到它的价值别的新人接手看图不用猜颜色什么意思你回看自己三个月前画的图也不用努力回忆当时想表达什么。另外可以顺带在CI里加一道mermaid语法校验用mmdc的--check参数在提交时跑一遍语法错误直接拦在合并之前。这跟代码检查一样把低级错误挡在门口。5.3 从“画图”到“设计”的思维转变我用diagram-design的时间越长越觉得“design”这个词才是重点。它不只是把图画出来而是让你在设计阶段思考清楚信息层级、关系类型、阅读路径和风格一致性。写代码画图这个动作本身没什么稀奇真正有价值的是背后这套流程想清楚结构写成文本让文本可维护。当你习惯了这种工作流再回头用拖拽工具画复杂的架构图会特别痛苦于那些无法自动对齐的框、无法批量修改的样式、无法进版本库的图片。最后分享一个小技巧如果你手头有一段要反复调整的图表代码先在Live Editor里调好整体结构和样式确认逻辑没跑了再落到本地仓库。本地仓库永远只保留“可发布的、被review过的”版本别把调试过程中的各种半成品全塞进去。这样你的diagram-design资产才会越积累越干净而不是又变成一个存放混乱图片的新仓库。