☰
diagram-design:用代码思维管理图表,提升可维护性与一致性
2026/10/19 0:52:57 网站建设 项目流程

1. 从一张草图到一套系统:diagram-design 到底在解决什么问题

很多人第一次听到 diagram-design 这个词,会下意识觉得它只是“画图”的另一种说法。但真正在项目里被图表折磨过的人知道,画图本身从来不是难点,难点在于图表的可维护性、一致性和表达效率。我见过太多团队,需求评审时随手画一张流程图,开发阶段又重画一张架构图,上线后运维再补一张部署图,三张图各说各话,改一个模块要同步改五个地方,最后谁也不敢保证哪张图是最新的。

diagram-design 要解决的,正是这个“图与人、图与代码、图与文档之间脱节”的问题。它不是一个单纯的绘图工具,而是一套以设计系统思维来管理图表资产的方法论加工具链。核心思路是把图表当成代码来对待:用文本描述结构,用版本控制追踪变化,用统一的样式规范保证视觉一致,最终让图表能够像函数一样被复用、被组合、被自动生成。

这套东西适合谁?如果你是技术文档写作者、系统架构师、产品经理,或者任何需要频繁输出结构化图表的人,diagram-design 能帮你把“画图”这件事从手工劳动变成工程实践。如果你只是偶尔画一张示意图发群里,那可能用不上这么重的方案,但了解它的思路对提升图表表达质量依然有帮助。

我最初接触这个方向,是因为一个跨平台系统项目里,架构图每隔两周就要更新一次,每次更新都要手动调整几十个框和箭头,错一个连线就要返工半小时。后来我把图表描述抽成文本,用脚本渲染,改结构只改几行配置,样式自动套用,效率提升了不止一个量级。下面我把这套实践中沉淀下来的方法拆开讲,包括工具选型、结构设计、样式规范、自动化集成和踩过的坑。

2. 为什么“用代码画图”比拖拽更靠谱

2.1 拖拽式绘图的三条死胡同

先说清楚为什么我不推荐在需要长期维护的项目里用传统拖拽工具。第一条死胡同是版本对比几乎不可用。你改了一个框的位置,导出的图片二进制文件在 Git 里只显示“文件已更改”,根本看不出改了什么。第二条是样式漂移。三个人画三张图,边框粗细、字体大小、颜色深浅全不一样,拼在一起像三个不同项目。第三条是复用困难。一张子模块图在五张总图里出现,子模块改了,你得手动找到五张图逐一修改。

这三条死胡同的本质,是拖拽工具把“结构”和“表现”绑死在了一起。你拖动一个矩形,既改变了它的逻辑位置,也改变了它的视觉呈现,两者无法分离。而 diagram-design 的核心洞察就是:结构应该用文本描述,表现应该用样式规则控制,两者解耦。

2.2 文本描述结构的实际收益

把图表结构写成文本,最直接的好处是可以用 Git 做 diff。比如下面这段用 Mermaid 描述的一个简单流程:

flowchart LR A[用户请求] --> B{鉴权通过?} B -->|是| C[查询缓存] B -->|否| D[返回 401] C --> E{命中?} E -->|是| F[返回缓存数据] E -->|否| G[查询数据库] G --> H[写入缓存] H --> F

这段文本在 Git 里的变化一目了然:新增了一个判断节点,或者调整了某个分支的走向,review 的人一眼就能看懂。而如果这是一张 PNG,你只能看到“二进制文件不同”。对于需要严格评审的架构设计来说,这种可审查性是刚需。

另一个收益是批量生成。当你有二十个微服务,每个服务的调用关系都需要一张图时,手动画二十张是灾难。但如果你把服务依赖写成结构化数据(比如 YAML 或 JSON),用模板渲染,二十张图可以一键生成,改一个公共样式全部生效。

2.3 什么场景不适合代码化

当然,我不是说所有图都该代码化。高度艺术化的信息图、需要精细排版的海报、一次性演示用的概念草图,这些场景用专业设计工具更合适。代码化绘图的优势区间是:结构清晰、需要频繁更新、需要多人协作、需要嵌入文档流水线的图表。判断标准很简单:如果这张图三个月后还要改,而且改的时候你希望知道改了什么,那就值得代码化。

3. 选型实战:Mermaid、PlantUML、Graphviz 怎么挑

3.1 三者的定位差异

市面上主流的文本绘图方案不少,我重点对比三个最常用的:Mermaid、PlantUML 和 Graphviz。它们不是互相替代的关系,而是各有擅长的领域。

工具最擅长学习曲线渲染依赖样式控制
Mermaid流程图、时序图、甘特图低浏览器/Node中等,支持主题
PlantUMLUML 全系、架构图、思维导图中Java强,支持皮肤参数
Graphviz复杂关系网络、自动布局中高本地二进制弱,偏自动布局

Mermaid 的优势是上手快、生态好。GitHub、GitLab、Notion 等平台原生支持 Mermaid 渲染,你写完直接就能看到效果。它的语法接近自然语言,非技术人员也能读懂。缺点是复杂布局的控制力有限,节点位置基本靠自动排布。

PlantUML 的优势是表达力强、图表类型全。从类图、时序图到部署图、思维导图,几乎覆盖了软件工程所有标准图表。它支持丰富的皮肤参数,可以精细控制颜色、字体、间距。缺点是渲染需要 Java 环境,在纯前端场景下不如 Mermaid 方便。

Graphviz 的优势是自动布局算法强大。当你有一百个节点、关系错综复杂时,Graphviz 的布局引擎能自动算出相对清晰的排布,这是手工调位置做不到的。缺点是它的 DOT 语言偏底层,样式控制不够直观,适合做数据分析类的关系图,不太适合做演示用的架构图。

3.2 我的组合策略

在实际项目里,我通常这样组合:日常流程图和时序图用 Mermaid,因为快且平台支持好;正式架构文档用 PlantUML,因为样式可控、图表类型规范;数据关系分析用 Graphviz,因为自动布局省心。三者可以共存于同一个文档体系,通过统一的样式变量来保持视觉一致。

这里有个关键细节:如果你要在同一份文档里混用多种工具,一定要统一颜色变量和字体。我的做法是定义一个theme.yaml,里面存主色、辅色、字体族、圆角半径等,然后写脚本把变量注入到各工具的配置里。这样即使工具不同,出来的图也是一个视觉体系。

3.3 一个容易忽略的选型因素:渲染环境

选型时很多人只看语法,忽略了渲染环境。Mermaid 在浏览器里渲染,适合嵌入网页和在线文档;PlantUML 需要服务端或本地 Java 进程,适合 CI 流水线里批量生成;Graphviz 需要本地安装二进制,适合脚本化处理。如果你的文档平台不支持某种工具的渲染,那语法再好也用不上。所以选型第一步应该是确认你的发布环境支持什么,再倒推工具选择。

4. 把图表当代码管:目录结构与命名规范

4.1 为什么目录结构值得单独设计

当图表数量超过二十张,目录结构就开始影响效率了。我见过把所有.mmd文件堆在一个文件夹里的项目,找一张图要翻半天,改完也不知道影响了哪些文档。好的目录结构应该反映图表的逻辑归属,而不是文件类型。

我常用的结构是这样的:

diagrams/ architecture/ system-overview.mmd >flowchart TD req[用户请求] --> auth{鉴权服务} auth -->|通过| cache[缓存层] auth -->|拒绝| deny[返回错误]

这样 ID 稳定,改显示文本不影响引用关系;显示文本清晰,读图的人不用猜。如果节点需要在多处引用,ID 一定要语义化,比如auth-service而不是s1。

5. 样式系统:让一百张图看起来像一个人画的

5.1 样式漂移的根源

图表视觉不一致,根源在于样式定义分散。每张图各自定义颜色、字体、间距,改一个品牌色要改一百个地方。解决办法是把样式抽成单一数据源,所有图表从同一个地方读取。

以 Mermaid 为例,它支持通过%%{init}%%指令注入主题配置:

%%{init: {'theme':'base', 'themeVariables': { 'primaryColor':'#e8f0fe', 'primaryBorderColor':'#4a6fa5', 'lineColor':'#5a5a5a', 'fontFamily':'system-ui, sans-serif', 'fontSize':'14px' }}}%% flowchart LR A[模块A] --> B[模块B]

把这段init指令抽成一个公共文件,每张图开头引入,就能保证所有图共用一套视觉变量。PlantUML 类似,用!include引入皮肤文件。Graphviz 则通过-G参数或配置文件注入。

5.2 颜色语义化:不要用“蓝色”思考

样式系统里最重要的不是选什么颜色,而是建立颜色与语义的映射。我通常定义这几组语义色:

  • 主流程色:正常路径的节点和连线
  • 决策色:判断节点的强调色
  • 异常色:错误分支和告警节点
  • 外部色:第三方系统或边界外的组件
  • 数据色:存储和数据库相关节点

定义好语义后,画图时想的是“这是一个异常节点”,而不是“这个框要涂红色”。这样即使品牌色调整,只要改语义映射表,所有图自动更新。而且不同人画图时,对同一类节点会用同一种颜色,一致性自然就有了。

5.3 间距和字体的隐形影响

很多人只关注颜色,忽略了间距和字体。实际上,间距不一致比颜色不一致更显眼。两张图并排,一张节点间距 20px,一张 50px,看起来就像两个不同团队做的。我的做法是在主题里固定几个间距档位:紧凑 16px、标准 24px、宽松 40px,按图表密度选用,不随意写数值。

字体同理。中英文混排时,如果字体族没配好,中文用宋体、英文用 Arial,看起来会很割裂。我通常指定system-ui, -apple-system, "Segoe UI", sans-serif这样的字体栈,让系统自动选择最合适的中英文字体,保证渲染一致。

6. 自动化流水线:从提交到渲染产物

6.1 为什么需要自动化

手工渲染图表有三个问题:忘记渲染导致文档和源文件不同步、不同人本地环境渲染结果有差异、批量更新时重复劳动。自动化流水线解决的就是这三个问题:提交即渲染、环境统一、批量处理。

6.2 一个可复用的渲染脚本

我用 Node.js 写了一个渲染脚本,核心逻辑是遍历diagrams目录,根据文件扩展名调用对应的渲染器,输出到_build/output。简化版如下:

const fs = require('fs'); const path = require('path'); const { execSync } = require('child_process'); const SRC = path.join(__dirname, 'diagrams'); const OUT = path.join(__dirname, '_build', 'output'); function walk(dir) { const entries = fs.readdirSync(dir, { withFileTypes: true }); for (const entry of entries) { const full = path.join(dir, entry.name); if (entry.isDirectory()) { if (entry.name.startsWith('_')) continue; walk(full); } else if (entry.name.endsWith('.mmd')) { renderMermaid(full); } else if (entry.name.endsWith('.puml')) { renderPlantUML(full); } } } function renderMermaid(file) { const rel = path.relative(SRC, file); const outFile = path.join(OUT, rel.replace('.mmd', '.svg')); fs.mkdirSync(path.dirname(outFile), { recursive: true }); execSync(`mmdc -i "${file}" -o "${outFile}" -t neutral -b transparent`); console.log(`rendered: ${rel}`); } function renderPlantUML(file) { const rel = path.relative(SRC, file); const outFile = path.join(OUT, rel.replace('.puml', '.svg')); fs.mkdirSync(path.dirname(outFile), { recursive: true }); execSync(`plantuml -tsvg -o "${path.dirname(outFile)}" "${file}"`); console.log(`rendered: ${rel}`); } walk(SRC);

这个脚本的关键点是:跳过下划线开头的目录(比如_shared和_build),保持输出目录结构与源目录一致,输出 SVG 而非 PNG(SVG 矢量、体积小、可搜索文本)。

6.3 接入 CI 的注意事项

把渲染脚本接入 CI 时,有几个坑要注意。第一,渲染工具版本要锁定,不同版本的 Mermaid 渲染结果可能有细微差异,导致 CI 产物和本地不一致。第二,渲染失败要阻断流水线,不能静默跳过,否则文档里会出现缺失的图。第三,产物要缓存或提交,如果文档平台不实时渲染,就需要把 SVG 提交到仓库或发布到静态资源服务。

我的做法是在 CI 里跑渲染脚本,把产物提交到一个gh-pages分支或对象存储,文档里引用产物的 URL。这样源文件和产物分离,仓库不会因为二进制文件膨胀。

7. 踩坑实录:那些文档没写的细节

7.1 Mermaid 的自动布局陷阱

Mermaid 的自动布局在简单图上表现很好,但节点一多就容易出现连线交叉和节点重叠。我遇到过一次,一张有三十个节点的流程图,Mermaid 渲染出来连线乱成一团,完全没法看。排查后发现是节点之间的依赖关系太复杂,自动布局算法找不到好的排布。

解决办法有两个:一是拆图,把一张大图拆成几张子图,用超链接关联;二是手动指定方向,用flowchart TD或flowchart LR控制主方向,减少交叉。如果还是不行,就换 PlantUML 或 Graphviz,它们的布局算法对复杂图更友好。

7.2 中文字体在 SVG 里的丢失问题

用 Mermaid 渲染 SVG 时,如果主题里指定的字体在渲染环境里不存在,SVG 会回退到默认字体,导致中文显示异常。我踩过一次坑:本地渲染正常,CI 渲染出来中文全是方框。原因是 CI 环境没有安装中文字体。

解决办法是在 CI 环境里安装中文字体包,或者在主题里指定通用字体栈,让渲染器有多个回退选项。更稳妥的做法是渲染时把字体转成路径,但这样会失去文本可搜索性,需要权衡。

7.3 版本升级导致的语法不兼容

Mermaid 和 PlantUML 都在持续迭代,新版本可能修改语法或默认行为。我有一次升级 Mermaid 后,之前能渲染的图报错了,原因是某个语法在新版本里被废弃。教训是:锁定渲染工具版本,升级前先在分支上全量渲染验证。可以在package.json里固定版本号,不要用^或~。

7.4 图表与文档的同步问题

最后一个坑是图表更新了但文档没更新。文档里引用的还是旧图,读者看到的是过时信息。解决办法是在文档里嵌入图表源文件而非图片,让文档平台实时渲染。如果平台不支持,就在 CI 里加一步校验:检查图表源文件的修改时间是否晚于文档,如果是就报警提示更新。

8. 从单张图到图表库:规模化后的组织思路

8.1 建立图表索引

当图表超过五十张,光靠目录结构已经不够了。我建议建一个图表索引文件,用表格列出每张图的名称、用途、所在路径、关联文档。这个索引可以手工维护,也可以用脚本从文件注释里自动生成。索引的价值是让新人快速了解项目里有哪些图、分别在哪、什么时候用。

8.2 图表的复用与组合

规模化的另一个问题是重复画相似的图。比如十个微服务,每个都有一张调用关系图,结构类似只是节点不同。这时候可以把公共结构抽成模板,用变量替换节点信息。Mermaid 本身不支持模板,但可以用脚本预处理:读一个 YAML 数据文件,套模板生成多张.mmd。

8.3 定期清理与归档

图表库和代码库一样,会积累废弃的图。我建议每个季度做一次清理:删除不再引用的图、归档历史版本的图、合并重复的图。归档的图移到_archive目录,不参与渲染,但保留在 Git 历史里,需要时可以找回。

9. 我在这套方法里最看重的三个习惯

第一个习惯是先写结构再调样式。很多人一上来就纠结颜色和间距,结果结构改来改去,样式白调了。我的做法是先用最朴素的样式把结构画对,确认逻辑无误后再套主题。这样返工成本最低。

第二个习惯是每张图都写注释。在图表源文件顶部用注释写明这张图的用途、作者、最后更新原因。过三个月回头看,这些注释能帮你快速回忆上下文,比看图猜意图高效得多。

第三个习惯是图表和代码同仓库。图表描述的是系统结构,系统结构变了图表就该变。把图表放在代码仓库里,改代码时顺手改图,评审时一起看,能最大程度避免图表和实现脱节。如果图表放在独立的文档仓库,很容易出现代码改了图没改的情况。

这套 diagram-design 的方法我用了两年多,最大的感受是:画图这件事,前期多花时间建规范,后期省下的时间是指数级的。一开始可能觉得写文本比拖拽慢,但当你需要改第十张图的时候,就会庆幸当初把样式抽出来了。如果你正在被图表维护折磨,不妨从下一张图开始,试试用文本描述结构、用变量控制样式,慢慢把这套习惯沉淀下来。

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

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

立即咨询