资讯详情

diagram-design:如何让图表像代码一样可维护

📅 2026/10/18 10:15:02 | 华诺云谱 👁 阅读
diagram-design:如何让图表像代码一样可维护
1. 从一张草图到一套系统diagram-design 到底在解决什么问题第一次听到 diagram-design 这个词很多人会下意识觉得它就是个“画图工具”或者“图表模板库”。但真正在项目里被图表折磨过的人会明白它要解决的根本不是“怎么画”而是“怎么让图在项目里活下来”。我做过一个跨平台系统的技术方案前后改了七版架构图。第一版用某在线工具画的导出 PNG 贴进文档第二版换了另一个工具因为要加时序图第三版发现两张图的配色和字体完全对不上又全部重画。到最后图是画完了但没人愿意维护——因为改一个模块名要同时打开三个工具、导出四次、替换五个文件。这就是典型的“图能画出来但设计没跟上”。diagram-design 的核心价值是把图表从“一次性产物”变成“可维护的设计资产”。它关注的不是单张图好不好看而是一整套图在同一个项目里能不能保持一致的视觉语言、能不能被版本管理、能不能在需求变更时低成本地同步更新。换句话说它更像是一套“图表的工程化规范”而不是一个画图软件。这套东西适合谁如果你只是偶尔画一张流程图发群里那确实用不上。但如果你在维护一个持续迭代的项目文档里有架构图、时序图、状态机、ER 图、部署图而且这些图会随着代码一起变那 diagram-design 的思路就非常值得参考。它尤其适合技术文档维护者、系统架构师、以及需要长期输出设计文档的开发者。我下面要拆的就是这套思路背后的选型逻辑、核心细节、实操流程以及我在实际落地时踩过的坑。内容会涉及工具链选择、样式系统设计、版本管理策略、以及如何让图表和代码保持同步。不堆概念直接讲怎么落地。2. 整体设计思路为什么“统一规范”比“画得好看”更重要2.1 图表失控的三个典型信号在讲方案之前先说说图表失控通常是怎么发生的。我观察下来有三个信号特别明显。第一个信号是同一份文档里出现三种以上字体。架构图用了一个字体时序图用了另一个ER 图又是第三个。单独看每张图都没问题放在一起就像三个人拼凑的文档。这个问题在多人协作的项目里尤其突出因为每个人习惯用的工具不同默认字体也不同。第二个信号是修改一个模块名需要动三张图。比如把“用户服务”改成“账户服务”架构图里要改时序图里要改部署图里还要改。如果这些图是独立文件那就得逐个打开、逐个修改、逐个导出。改完还要检查有没有漏掉的地方。第三个信号是图没有版本历史。文档在 Git 里代码在 Git 里但图是二进制文件改了什么、什么时候改的、为什么改完全看不出来。想回滚到上一版架构图只能靠“最终版2”、“最终版3”这种文件名。diagram-design 的思路就是针对这三个信号来设计的。它的核心主张是图表应该像代码一样被管理。这意味着图表的源文件应该是文本格式、应该纳入版本控制、应该有一套统一的样式规范、应该能通过工具链自动生成最终产物。2.2 为什么选择“文本描述 渲染引擎”的方案实现这个思路有几种路径。一种是直接用绘图软件的模板功能把样式固定下来另一种是用代码生成图表比如各种 DSL领域特定语言还有一种是混合方案用文本描述结构用样式文件控制外观。我最终选择的是“文本描述 渲染引擎”的方案具体来说就是用 Mermaid 或 PlantUML 这类工具来描述图表结构再通过统一的样式配置来控制输出效果。这个选择背后有几个考量。首先是可版本管理。文本文件天然适合 Git每次修改都能看到 diff谁改了什么一目了然。这比二进制文件强太多。其次是可批量处理。如果项目里有二十张图用文本描述的话改一个全局样式只需要动一个配置文件所有图重新渲染即可。最后是可自动化。文本描述意味着可以在 CI 流程里自动检查图表语法、自动生成图片、自动部署到文档站点。当然这个方案也有代价。文本描述的学习成本比拖拽式工具高复杂图表的布局控制不如手动调整灵活。但对于需要长期维护的项目来说这些代价是值得的。我自己的经验是前期多花两个小时学语法后期能省下几十个小时的重复劳动。2.3 样式系统的分层设计diagram-design 的另一个核心是样式系统的分层。我把样式分成三层基础层、语义层、场景层。基础层定义的是最底层的视觉变量比如主色、辅色、字体族、字号阶梯、圆角半径、线条粗细。这些变量不直接用在图里而是作为其他层的输入。语义层定义的是“这个颜色代表什么”比如“成功状态用绿色”、“警告状态用橙色”、“外部系统用灰色”。场景层则是针对具体图表类型的样式比如架构图的节点样式、时序图的参与者样式、状态机的状态样式。这样分层的好处是当品牌色调整时只需要改基础层的一个变量所有图自动更新。当某个语义需要调整时比如把“警告”从橙色改成黄色也只需要改语义层。场景层则保持相对稳定因为它引用的是语义层的变量而不是具体的色值。我试过不分层直接写死样式结果就是每次调整都要全局搜索替换还容易漏掉。分层之后维护成本至少降低了一半。3. 核心细节解析从工具选型到样式落地的关键决策3.1 渲染引擎的选型对比文本描述图表这个方向下有几个主流选择。我实际用过的是 Mermaid、PlantUML 和 Graphviz下面这张表是我自己的对比总结。工具语法友好度图表类型覆盖样式控制能力生态集成适合场景Mermaid高中中极好文档内嵌、快速迭代PlantUML中极广高好复杂系统图、正式文档Graphviz低偏图论中一般自动布局、大规模关系图Mermaid 的优势是语法简单Markdown 编辑器基本都支持适合放在 README 或技术文档里。但它的样式控制相对有限复杂布局有时候不太听话。PlantUML 的图表类型最全从用例图到部署图到甘特图都能画样式控制也更细但语法学习曲线陡一些。Graphviz 适合那种节点特别多、需要自动布局的场景但手动控制能力弱。我的实际选择是以 Mermaid 为主PlantUML 为辅。日常的流程图、时序图、状态图用 Mermaid因为它和文档的集成最顺滑。遇到 Mermaid 表达不了的复杂图比如带分组的部署图或者复杂的 ER 图再用 PlantUML 补位。两者都输出 SVG保证清晰度。注意不要为了统一而强行用一种工具。我见过有人非要用 Mermaid 画复杂部署图结果布局乱得没法看。工具是服务于表达的该换就换。3.2 样式变量的定义与命名规范样式系统的落地第一步是定义变量。我用的是一套基于 CSS 自定义属性的方案因为这样既能被 Mermaid 引用也能被 PlantUML 通过预处理引用。变量的命名我遵循“类别-语义-变体”的格式。比如--color-primary、--color-primary-light、--color-primary-dark。字体则是--font-family-base、--font-family-mono。字号用--font-size-sm、--font-size-md、--font-size-lg这样的阶梯。这里有个细节值得说字号阶梯不要太多。我一开始定义了七八个字号结果用的时候根本分不清该用哪个。后来精简到四个小号用于注释中号用于正文大号用于标题特大号用于图标题。这样选择成本低一致性也容易保证。颜色方面我建议主色不超过三个加上中性色和语义色。主色用于强调中性色用于背景和边框语义色用于状态标识。颜色一多图就会花反而降低可读性。3.3 图表类型的标准化模板有了样式变量之后下一步是为每种图表类型建立标准模板。模板的作用是让任何人画同类图时都从同一个起点出发避免“每次都要重新想怎么布局”。我目前维护的模板包括架构图模板、时序图模板、状态机模板、ER 图模板、部署图模板、流程图模板。每个模板里预置了常用的节点样式、连线样式、分组样式以及一些占位内容。以架构图模板为例它预置了“客户端层”、“服务层”、“数据层”三个分组每个分组里有示例节点节点之间的连线样式也定义好了。用的时候只需要替换节点名称和连线关系样式自动继承。模板的维护有个技巧把模板放在独立目录用脚本生成新图。我写了一个简单的脚本输入图表类型和文件名就自动从模板目录复制一份并替换占位符。这样比手动复制粘贴更不容易出错。3.4 版本管理与协作流程图表源文件纳入 Git 之后协作流程也需要相应调整。我的做法是图表源文件和代码放在同一个仓库但放在独立的docs/diagrams目录下。每次修改图表都走正常的 PR 流程需要至少一个人 review。Review 的重点不是“好不好看”而是“结构对不对”、“命名是否一致”、“有没有引入新的样式变量”。如果引入了新变量需要同步更新样式定义文件。这里有个坑渲染产物要不要入库。我一开始把生成的 SVG 也提交了结果每次改源文件都要重新生成再提交diff 里既有源文件变更又有产物变更很乱。后来改成产物不入库由 CI 自动生成并部署到文档站点。这样仓库里只有源文件干净很多。提示如果团队里有人不熟悉 Git可以提供一个简单的 Makefile 或 npm script让他们用一条命令完成“渲染 预览”。降低门槛比强制规范更有效。4. 实操过程从零搭建一套图表设计系统4.1 环境准备与工具链搭建假设你现在要从零开始搭建这套系统我按实际操作的顺序来说。首先需要准备的是运行环境。Mermaid 的渲染我推荐用mermaid-js/mermaid-cli它是基于 Node.js 的命令行工具安装简单输出质量稳定。npm install -g mermaid-js/mermaid-cliPlantUML 则需要 Java 环境下载 jar 包即可。Graphviz 如果要用也需要单独安装。我建议先只装 Mermaid跑通流程之后再按需扩展。目录结构我建议这样组织docs/ diagrams/ src/ # 图表源文件 architecture/ sequence/ state/ styles/ # 样式定义 variables.css mermaid-config.json plantuml-theme.puml templates/ # 模板文件 output/ # 渲染产物不入库样式定义文件是核心。variables.css里定义所有基础变量mermaid-config.json里引用这些变量并配置 Mermaid 的主题plantuml-theme.puml则是 PlantUML 的主题文件。4.2 样式变量的具体定义过程定义变量时我建议从实际项目里提取而不是凭空想。具体做法是先找出项目里已有的图表看看用了哪些颜色、字体、字号然后归纳出一套最小集合。比如我当时的项目里图表用到了三种蓝色、两种灰色、一种绿色、一种橙色。归纳之后主色保留一个蓝色灰色保留深浅两档绿色和橙色作为语义色。字体统一用一个无衬线字体字号定为四档。:root { --color-primary: #2563eb; --color-primary-light: #dbeafe; --color-neutral-900: #1f2937; --color-neutral-100: #f3f4f6; --color-success: #16a34a; --color-warning: #ea580c; --font-family-base: Inter, Noto Sans SC, sans-serif; --font-size-sm: 12px; --font-size-md: 14px; --font-size-lg: 18px; --font-size-xl: 24px; --radius-sm: 4px; --radius-md: 8px; --stroke-width: 1.5px; }这些变量定义好之后Mermaid 的配置文件里就可以引用。不过 Mermaid 本身对 CSS 变量的支持有限实际做法是在mermaid-config.json里直接写对应的值但保持和variables.css同步。我写了一个小脚本从variables.css读取变量并生成mermaid-config.json避免手动同步出错。4.3 第一张标准图的完整绘制流程环境准备好之后画第一张标准图。我以架构图为例走一遍完整流程。第一步是确定图的层级结构。架构图通常分三层接入层、服务层、数据层。每层里有哪些模块模块之间怎么调用这些先想清楚再动手。第二步是写 Mermaid 源文件。用flowchart或者graph语法配合subgraph来分组。graph TB subgraph client[接入层] web[Web 前端] mobile[移动端] end subgraph service[服务层] gateway[API 网关] user[用户服务] order[订单服务] end subgraph data[数据层] db[(主数据库)] cache[(缓存)] end web -- gateway mobile -- gateway gateway -- user gateway -- order user -- db user -- cache order -- db第三步是应用样式。在 Mermaid 配置里指定节点样式、连线样式、分组样式。比如节点用圆角矩形、填充浅色、边框用主色连线用灰色、带箭头分组用虚线边框、浅灰背景。第四步是渲染。用mmdc命令把.mmd文件渲染成 SVG。mmdc -i docs/diagrams/src/architecture/system.mmd \ -o docs/diagrams/output/architecture/system.svg \ -c docs/diagrams/styles/mermaid-config.json \ -b transparent第五步是检查。打开 SVG 看看布局是否合理、文字是否溢出、颜色是否一致。如果有问题回到源文件调整。这套流程跑通之后后续画新图就是复制模板、改内容、渲染、检查效率会高很多。4.4 批量渲染与自动化集成当图表数量多起来之后手动逐张渲染就不现实了。这时候需要批量脚本。我写的是一个 Node.js 脚本遍历src目录下所有.mmd文件逐个渲染到output目录并保持目录结构一致。const fs require(fs); const path require(path); const { execSync } require(child_process); function walk(dir) { const files []; for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { const full path.join(dir, entry.name); if (entry.isDirectory()) files.push(...walk(full)); else if (entry.name.endsWith(.mmd)) files.push(full); } return files; } const srcDir docs/diagrams/src; const outDir docs/diagrams/output; const config docs/diagrams/styles/mermaid-config.json; for (const file of walk(srcDir)) { const rel path.relative(srcDir, file).replace(/\.mmd$/, .svg); const out path.join(outDir, rel); fs.mkdirSync(path.dirname(out), { recursive: true }); execSync(mmdc -i ${file} -o ${out} -c ${config} -b transparent); console.log(rendered: ${rel}); }这个脚本可以挂到 npm script 里也可以集成到 CI。我在 CI 里的做法是每次 PR 修改了diagrams/src下的文件就自动运行渲染脚本把产物部署到预览环境方便 review 时直接看效果。注意CI 里渲染需要安装 Mermaid CLI 和对应的浏览器依赖。如果 CI 环境是精简镜像可能需要额外安装 Chromium。我踩过这个坑后来改用带浏览器的 Docker 镜像才解决。5. 常见问题与排查技巧实录5.1 渲染结果与预期不符的排查思路用文本描述图表最常见的问题就是“渲染出来和想的不一样”。布局乱、文字重叠、连线交叉这些都很常见。我的排查思路是分三步。第一步是简化复现。把出问题的图精简到最小可复现的版本去掉无关节点和连线。很多时候简化之后问题就消失了说明是某个特定组合导致的。第二步是检查语法。Mermaid 的语法虽然简单但有些细节容易写错。比如节点 ID 里带特殊字符、连线标签里有引号、subgraph的嵌套层级不对。这些都会导致渲染异常。第三步是调整布局方向。Mermaid 支持TB、BT、LR、RL四种方向。有时候换个方向布局就正常了。另外节点的声明顺序也会影响布局把关联紧密的节点放在一起声明通常能得到更紧凑的布局。如果以上都不行那就考虑换工具。Mermaid 的自动布局能力有限复杂图确实画不好看。这时候用 PlantUML 或者手动调整坐标可能更省时间。5.2 样式不生效的几种原因样式不生效也是高频问题。我遇到过的情况有几种。一种是配置文件没被加载。Mermaid CLI 的-c参数指定配置文件如果路径写错或者文件格式不对样式就不会应用。检查方法是看渲染日志里有没有报错。另一种是样式被内联覆盖。Mermaid 源文件里如果直接写了style或者classDef会覆盖配置文件里的样式。这时候需要检查源文件里有没有硬编码的样式。还有一种是变量引用错误。如果配置文件里引用了不存在的变量Mermaid 会静默忽略不会报错。所以改完配置后一定要实际渲染一张图看看效果不能只看配置文件。提示我习惯在配置文件里加一个“测试图”包含所有节点类型和连线类型。每次改完配置先渲染测试图确认所有样式都正确再去渲染正式图。5.3 多人协作时的冲突处理多人协作时图表源文件的冲突比代码冲突更麻烦因为文本描述的 diff 不如代码直观。我的处理策略是按图表类型分工减少同一文件的并发修改。具体来说架构图由一个人负责时序图由另一个人负责避免两个人同时改同一张图。如果确实需要多人改同一张图那就约定好修改范围比如一个人改节点一个人改连线改完及时合并。另外我建议在 PR 里附上渲染后的截图。这样 review 的人不用自己渲染就能看到效果减少沟通成本。截图可以用 CI 自动生成也可以本地渲染后手动上传。如果冲突已经发生了解决方法是先保留双方的修改然后重新渲染看布局是否合理。如果布局乱了可能需要手动调整节点顺序。这个过程比较耗时所以预防比解决更重要。5.4 图表与代码同步的实用技巧图表和代码不同步是文档维护的老大难问题。我的做法是把图表更新纳入代码变更流程。具体来说如果某个 PR 修改了服务之间的调用关系那这个 PR 里必须包含对应架构图的更新。review 的时候如果发现代码改了但图没改就打回去。为了让这个流程可执行我做了两件事。一是在仓库里加了一个检查脚本扫描代码里的服务名和图表里的节点名如果不一致就报警。二是在 PR 模板里加了勾选项提醒作者检查图表是否需要更新。这个检查脚本不复杂就是读取代码里的配置文件提取服务列表再读取图表源文件提取节点列表做差集。虽然不能覆盖所有情况但能抓住大部分遗漏。注意检查脚本不要做得太严格否则会产生大量误报反而让人忽略。我一开始把连线关系也纳入检查结果误报太多后来只检查节点名效果好很多。5.5 常见问题速查表问题现象可能原因排查方法解决方案渲染报错语法错误看错误信息定位行号修正语法简化复现布局混乱节点过多或方向不对简化图换方向拆分图或换工具样式不生效配置未加载或被覆盖检查配置路径和源文件修正配置移除内联样式文字溢出节点宽度不够检查节点文本长度缩短文本或调整节点样式颜色不一致变量未同步对比配置文件和变量文件用脚本自动同步协作冲突多人改同一文件看 Git 冲突标记分工修改及时合并图表过期代码改了图没改运行检查脚本纳入 PR 流程强制更新这张表是我在实际项目中逐步积累的基本上覆盖了八成以上的问题。遇到新问题我会先查表查不到再深入排查排查完把新问题补进表里。这样表越来越全排查效率也越来越高。6. 我在这套系统上踩过的坑和最终沉淀这套 diagram-design 的思路我从最初的想法到最终落地前后折腾了大概三个月。中间踩的坑不少有几个印象特别深。第一个坑是过度设计。一开始我想把样式系统做得特别完善定义了十几层变量、几十个语义色。结果用的时候根本记不住每次画图都要翻文档。后来砍到只剩基础层和语义层变量数量减少到二十个以内反而好用多了。这让我明白规范的价值在于被执行而不是在于完备。第二个坑是工具绑定太深。我有一段时间把所有图都迁到 PlantUML因为它的样式控制最强。但后来发现团队里其他人不熟悉 PlantUML 语法改图意愿很低。最后还是回到 Mermaid 为主因为它的语法门槛低大家愿意用。工具选型不能只看能力上限还要看团队的实际接受度。第三个坑是自动化过度。我写过一版 CI每次 push 都自动渲染所有图并部署。结果渲染时间太长CI 经常排队。后来改成只渲染变更的图并且只在 PR 时触发效率高了很多。自动化要适度不是越多越好。最终沉淀下来的其实就三样东西一套精简的样式变量、一组标准模板、一个批量渲染脚本。这三样东西加起来不到五百行代码但解决了图表维护的绝大部分问题。剩下的就是坚持执行——每次改代码时顺手改图每次 review 时检查图有没有过期。这个系统后续还可以扩展的方向比如把图表和 API 文档联动从 OpenAPI 定义自动生成时序图或者把图表和数据库 schema 联动从建表语句自动生成 ER 图。这些我还在摸索等跑通了再另开一篇聊。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑