
做了这么多年开发和架构相关的工作有一个事情我感触特别深很多系统不是代码拖垮的是文档里那一张张说不清道不明的图拖垮的。业务流程图、系统架构图、时序图、ER图哪个都绕不开。更烦的是图一多、一改需求手动调整线条和位置能让人心态爆炸。所以我后来慢慢把手绘图全部淘汰了把所有图都收进代码仓库用文本方式去“设计”图。这个思路用今天的话说就是 diagram-design。简单讲它不是让你用鼠标拖一个图形而是用一套结构化的文本语法把图当作代码来写、来维护、来版本管理。这篇文章不和你聊概念我只讲我实际用下来觉得最值得复制的思路、方法、工具和踩坑记录。不管是给团队搭建架构文档还是自己写技术方案这套东西都能让你的图变得可维护、可复用、不再是一次性消耗品。1. 整体设计与思路拆解1.1 为什么代码驱动的图表设计能胜过手工画图先说一个很多人没想明白的点图和代码本质上是一回事。图表达的是节点和关系代码表达的也是实体和调用。既然代码可以 diff、可以 review、可以回滚那图为什么不行用代码方式设计图表最大的收益不是画图快而是它把图从一个静态文件变成了工程资产。我以前用 Visio 或 draw.io 画架构图其实画得也不慢。但真正痛苦的是后续维护。需求一变模块从 8 个变成 9 个就得手动拖拽、对齐、改连线一不小心就把一张整洁的图搞成一团乱麻。更别提团队协作两个人同时改一个文件交接全靠微信发文件版本对不上图里的信息和代码完全脱节。一旦换成代码驱动所有图都变成纯文本文件放在仓库里每次改动都有记录、都有评审。有没有画错review 代码的时候顺带就发现了。时间一长图就变成了组织里真正可信的技术资产。另一个被低估的点是自动化。代码生成的图可以做成流水线。文档推完流程图自动渲染成图片和PDF发布到内网。不用任何人工介入。这个体验一旦习惯你就再也不想回到手动画图的老路。1.2 从数据类型到图类型先定结构再动手很多新手画图容易犯一个错上来就打开画布想到哪画到哪。这其实反了。画图的第一步不是画而是搞清楚你到底想表达什么关系。数据关系不同该选的图类型完全不同。我自己习惯先把要表达的内容归类。如果节点之间有明确的父子层级关系比如组织结构、调用链、模块依赖优先考虑树状图或层次布局如果是一组实体之间的关系比如用户、订单、商品那就是ER图如果是交互过程随时间推进比如登录流程、支付流程那就是流程图或时序图。这中间有一个关键判断标准你希望读者一眼看到的是“结构”还是“顺序”。架构图让人先看整体结构流程图让人先看执行顺序。搞反了图的信息密度越高读者越懵。一次画图先拿半分钟想清楚这个问题后面会省很多返工时间。1.3 用文本描述图核心是“剥离坐标”手工画图的时候你的注意力大量消耗在“这个框放在哪”上。水平垂直对齐、间距、居中、线的走向这些其实都是排版问题不是表达问题。而图的价值在于内容在于节点和关系本身。代码驱动的 diagram-design 核心思路就是把坐标剥离出去让布局交给算法。你只管声明“模块A依赖模块B”而A和B在画布里具体放在哪个位置让布局引擎去算。这个思路带来的好处很明显内容变了结构变了图会自动重排你永远不需要手工挪框。代价也有就是布局的可控性不如纯手工。但实际用下来90% 的场景下自动化布局的效果完全够用剩下的 10% 也可以通过记录顺序、分组、不可见边这些方式去“调教”引擎。2. 设计规范与核心细节2.1 布局是有逻辑的层级、跨度与阅读顺序我发现很多人对布局的理解就是“整齐”其实布局的本质是“引导阅读顺序”。人看图天然从左上看到右下。你要顺着这个习惯把你的关系流摆出来。代码驱动的布局算法核心也遵循这个逻辑。比如最常用的 Graphviz它的 dot 引擎就是设计用来画有向层次图的。它会自动把起点放上面层层向下展开。所以写 DOT 代码的时候你的节点声明顺序、边的方向直接影响最终图的骨架。想清楚谁是源头谁是终点把它们按逻辑顺序写下来而不是随缘写是最基本的布局意识。另一个容易忽略的点是跨度控制。一张图里如果有一条线从左上角一路拉到右下角中间跨过七八个节点读者很难一下子看清这条线连的是谁。这种时候宁可引入中间节点或重复标注也别追求“一条线直连到底”。我在画跨服务调用链的时候经常会把“通过接口调用”这种逻辑关系单独拆成一个聚合节点让线条变短图的可读性会立刻上一个台阶。2.2 颜色与字体少即是多的可视化约束图不是海报信息表达优先。我见过太多把架构图画得跟霓虹灯似的文档五颜六色一通堆最后读者根本分不清重点。我自己用的配色原则总结下来是三句话背景尽量白同层级同色一个重点色点缀。具体到实操我一般会把图中元素按逻辑分组给色。比如 API 层统一用蓝色系业务服务用绿色系基础设施用灰色系。每个色系里再区分深浅表达不同的细分模块。这样读者不需要看字光靠色块就能感知到系统分层。但这里有一条铁律整张图中高饱和的强调色一定要控制在极少数节点上。你告诉读者“注意这两个节点”就真的只让两个节点醒目其余一律低调。否则满图皆重点等于没有重点。字体这一块很多语言生成的图默认字体不太友好尤其是中文环境经常出现豆腐块。这个我在后面实战部分会专门讲。但有一条原则可以先记下字体类型不要超过两种正文一种标题一种就够了。字号上最小字号要保证鼠标放在原图上能轻松看清否则就存在过度压缩。2.3 分组与标签用容器表达系统边界软件架构里有一个概念叫“边界”。图里表达边界最直观的方式就是容器。Graphviz 里的 subgraph、PlantUML 里的 package、D2 里的 container都是干这个用的。没有边界的话一个 30 节点的图就是一堆散沙有了边界读者一眼就能看出“哪些节点属于同一个子系统”。用分组的时候有一个细节值得注意图形容器之间的连线尽量画在容器层而不是内部节点层。比如你画两个微服务分组之间的交互如果连线从服务 A 内部的 nginx 一路连到服务 B 内部的 order-service这条线看起来就特别乱。合理的做法是让连线连到容器的边界或者用一个聚合节点表达“对外接口”。这种细节的处理恰恰是专业图和不专业图的分水岭。标签方面则要克制。节点标签最长不要超过一行能容纳的短句能在标签里放一个名词解决问题的就坚决不要放一句话。实在有细节要展开放进注释区不要在图上堆文字。图的空间有限字越多可读性下降得越快。3. 实操过程与核心实现3.1 工具选型Graphviz、Mermaid、PlantUML 该怎么选说到代码驱动画图绕不开工具选择。这块我三款都用过分别说下感受。Graphviz 是元老级工具核心优势是布局算法极其成熟尤其适合处理复杂层次结构几十个节点的大图也不容易乱。它的 DOT 语法上手稍微有点门槛但对于要量产架构图的人这个学习成本完全值得。我之前给团队搭了一套基础设施全景图几十个组件、上百条依赖就是靠 Graphviz 撑住的。Mermaid 是目前文档场景最火的最大的优势是轻量和 Markdown 集成做得特别顺滑。Github、GitLab、各种笔记软件都有原生支持。如果你需要的是能快速嵌入文档、供日常阅读的流程图时序图Mermaid 基本拿起来就能用。但它的短板也很明显复杂布局表达力弱节点多了容易混乱定制能力远不如 Graphviz。PlantUML 则是另一个方向的成熟选择它对软件开发场景的支持很深入。时序图、用例图、活动图都有专门的语法糖画架构图相对弱一些。它和 Graphviz 一样本地跑需要 Java 环境但好在各类插件都很齐全。我的建议是画系统关系图、架构全景图选 Graphviz写文档要快速出图选 Mermaid画 UML 软件设计图选 PlantUML。三款工具配合使用基本覆盖所有日常场景。3.2 用 DOT 语言从零画一张系统架构图这里我给你走一遍完整流程画一个最典型的场景一个小型 Web 应用的部署架构。需求是画出用户请求从浏览器到数据库的完整链路。首先定义节点。DOT 语言里节点声明很简单digraph architecture { rankdirLR; user [label用户, shapecircle]; nginx [labelNginx, shapebox]; web [labelWeb Server, shapebox]; app [labelApplication, shapebox]; cache [labelRedis, shapecylinder]; db [labelMySQL, shapecylinder]; }注意我用了rankdirLR就是让图从左向右排列符合用户请求从左往右走的阅读直觉。节点形状上circle 代表外部角色box 代表服务cylinder 代表存储。接下来是边也就是关系user - nginx - web - app; app - cache; app - db;保存为arch.dot命令行执行dot -Tpng arch.dot -o arch.png一张图就出来了。这就是最基本的 workflow。Graphviz 会自动算好每个节点的位置让连线清晰可读。如果多个服务在同一个逻辑层级比如 Web 服务有多个实例可以写在一起web1 [labelWeb Server 1]; web2 [labelWeb Server 2]; user - nginx; nginx - web1; nginx - web2;让 nginx 同时连接多个 web 节点图会自动把 nginx 放中间Web 实例排开这个布局基本不需要手动调。等我后面讲到常见问题你会发现用好这些基础语法已经能应对大多数架构图的绘制需求了。3.3 进阶子图、样式、HTML 标签与自动化生成基础链路搞定后我肯定会给图加上分组和样式。这一步是把图从“能看”推向“专业”的关键。分组用 subgraph 关键字。注意一个细节subgraph 的命名必须以cluster_开头Graphviz 才会正确渲染成带边框的容器块。如果名字随便起比如subgraph svc_group它不会画出容器效果只是单纯把节点分组布局。这个坑很多教程都没提我第一次用的时候也踩了。digraph architecture { rankdirLR; subgraph cluster_frontend { label前端层; stylerounded; nginx [labelNginx]; static [label静态资源]; } subgraph cluster_backend { label后端服务; stylerounded; web [labelWeb Server]; app [labelApplication]; } user [label用户, shapecircle]; user - nginx; nginx - static; nginx - web; app - db [label读写, styledashed]; }注意这里面的 label、style 都是常规操作还有一个细节app - db加了label读写和styledashed虚线和文字标注能解释这条边的关系语义避免看图的人瞎猜。接下来是颜色规范化。我在实践中总结出一个风格清晰的配置node [fontnameMicrosoft YaHei, fontsize12]; edge [color#666666, fontnameMicrosoft YaHei, fontsize10];这几行设置全局默认样式后面所有节点和边都会继承。节点默认字体、边默认颜色统一图就会比较干净。再给每组节点分别上色前端层一个色系后端层一个色系数据库单独一种颜色整个图的结构感立刻就出来了。至于自动化生成做法很简单。我比较常用的是把 DOT 文件提交到 Git 仓库然后在 CI 流程里加一个步骤dot -Tpng arch.dot -o arch.png也可以在本地用 VS Code 的 Graphviz 插件保存后自动预览。Node.js 项目也可以用 viz.js 在浏览器里渲染 DOT做可视化工具非常好用。有了这些方案图就和代码一样被管起来了。3.4 从纯文本到高保真D2 与 excalidraw 风格结合的补充方案除了 Graphviz 和 Mermaid最近两年我身边不少团队还在用 D2。D2 是一款比较新的声明式图表语言语法非常简洁写起来手感很好。它的布局算法和渲染风格比 Graphviz 现代曲线更顺滑而且默认配色就挺耐看。D2 的代码大概长这样user - nginx: request nginx - web: proxy web - db: query db - web: rows语法的简洁程度比 DOT 更极端学习成本可以忽略不计。D2 比较适合快速产出结构清晰的架构图但在非常复杂的图上自适应布局偶尔会失控。我的用法是系统全面架构图交给 Graphviz而需要频繁改、快速交流的局部图用 D2。还有一个方向就是手绘风格。excalidraw 这类工具提供的手绘风表达在传达“这是草图、想法还比较早期”的时候非常好用。excalidraw-libs 里也有人做了把 DOT 转手绘风格的方案。早期讨论方案的时候手绘风能有效降低读者对细节的执念把注意力集中在结构和关系上。这也是我最近在尝试的工作流规划期用手绘风方案确定后用 Graphviz 或 D2 绘制正式图。4. 常见问题与排查技巧实录4.1 布局总是不按预期怎么办代码驱动画图最常被吐槽的就是布局不可控。明明我想让 A 在左边B 在右边结果算法偏给整成一上一下。我自己的排查顺序是这样的。先检查是不是没有约束层级。Graphviz 的 dot 引擎默认按边的方向分层如果你希望 A 和 B 在同一层可以用ranksame显式约束{ ranksame; A; B; }这个语法非常常用。比如画负载均衡器和它后端的多个服务实例如果不加 rank 约束它们可能被排到不同层图就变得难以理解。加上 ranksame 后它们会整齐地排在同一行。如果问题是一个节点总被排到错误的位置有可能是图里存在大量跨边导致算法不知所措。这时候可以用constraintfalse告诉布局引擎这条边只表示关系不参与排序优化。我在大图中经常这么干能大幅减少边的“拉扯”。最后还有一个很多人不知道的技巧节点的声明顺序会影响布局倾向。虽然 dot 引擎有全局优化但同层级内部先声明的节点通常会被排在更靠前的位置。想微调左右排序可以调调代码里的节点顺序。这个方法不是绝对但在很多场景下实测有效。4.2 中文乱码与字体问题第一次用 Graphviz 画中文图的人几乎都会碰上乱码问题。默认情况下Graphviz 用的字体并不包含中文字符所以渲染出来全是方框。解决思路不复杂给全局设置一个支持中文的字体。Windows 上用微软雅黑macOS 上用苹方node [fontnameMicrosoft YaHei]; edge [fontnameMicrosoft YaHei];但注意字体不仅要在图定义里指定还要求你系统里确实装了对应字体。Linux 服务器跑渲染的时候最常见的坑就是服务器上没有微软雅黑。这一点在 CI 流程里尤其容易出现本地 Windows 跑得好好的服务器上一跑就乱码。解决方案是安装字体包或者改用 Linux 自带的中文字体比如 Noto Sans CJK SC然后在 DOT 里指定node [fontnameNoto Sans CJK SC];经验法则要在哪台机器渲染就用那台机器上确定存在的字体。写代码的人常常忽略字体依赖等 CI 一炸才意识到这个我在团队里已经见过太多次。4.3 导出图片模糊、边被切掉怎么解决好不容易画好图嵌入文档的时候发现图太模糊或者图的边缘被切掉了。这个问题的根源一般有两个。第一个是 DPI 太低。Graphviz 导出 PNG 的时候如果不指定 DPI默认是 96 的倍数放大看就糊了。给 CI 渲染脚本加上参数dot -Tpng -Gdpi300 arch.dot -o arch.png300 DPI 的图放到文档里完全够用。如果是嵌入网页的大图SVG 是更好的选择。Graphviz 支持直接导出 SVG它是矢量格式无论缩放多少倍都清晰。我用 SVG 的场景越来越多文档站里放 SVG读者放大查看细节也不会有任何模糊。边缘被切掉的问题通常是因为图里有节点坐标超出画布。这种情况在手动指定 position 后尤其常见。自动布局一般不会出这个问题但某些样式比如旋转、超长标签会撑破自动计算边界。解决方法是留边距在图片导出时加上dot -Gpad0.5 -Gsplinestrue -Tpng arch.dot -o arch.png另外我喜欢在 DOT 文件顶部加一个margin参数让画布四周留白graph [margin16];一行设置彻底解决节点贴着图片边缘的问题。背景干净图也显得大气。4.4 大图性能与可读性陷阱当我们画的图超过 50 个节点问题就变了布局引擎可能还能跑但渲染出来的图已经没人愿意去看了。这时候我一般会做两件事。第一件事是拆图。把一个大图拆成多个子图然后通过“超链接”把它们串起来。Graphviz 支持在节点上挂 URLweb [labelWeb Server, URLweb-detail.png];点击节点就能跳转到下一级详情图。文档场景下这种分层浏览的体验比一张巨无霸图好得多。第二件事是控制节点密度。如果再优化布局还是有人抱怨图乱那大概率不是布局问题而是信息量超载了。每张图要表达的核心线索控制在 1 到 2 条多余的边要么删掉要么拆到子图里去展示。记住一张拥挤的大图在文档里基本等于没有图因为没人能看懂。5. 让图表融进工作流5.1 文档即图表从源码到发布如果想把 diagram-design 的价值发挥到最大千万别停留在“本地画图、手动粘贴”的阶段。把图变成持续集成的产物才是真正意义上的工程化。我现在的工作流是这样。DOT 文件和 D2 文件按模块组织放进代码仓库统一放在docs/diagrams/目录。目录结构长这样docs/ ├── diagrams/ │ ├── arch/ │ │ ├── overall.dot │ │ └── deployment.dot │ ├── flow/ │ │ ├── login-flow.d2 │ │ └── order-flow.d2 │ └── Makefile └── README.mdMakefile 里定义渲染规则PNGS : $(patsubst %.dot,%.png,$(wildcard arch/*.dot)) PNGS $(patsubst %.d2,%.png,$(wildcard flow/*.d2)) all: $(PNGS) %.png: %.dot dot -Tpng -Gdpi300 $ -o $ %.png: %.d2 d2 $ $之后只要改动源码文件再执行make所有图就全部重新生成。再进阶一点把这个命令挂到 Git pre-commit hook 或者 CI 里每次推到远程仓库都自动渲染并进行图形差异比对。图上内容的变更、删减全部有档可查。这样下来团队里任何一个人收到的文档、看到的图永远是基于最新代码生成的而不会出现“这图是不是过期了”的疑问。5.2 团队协作时的图表规范最后聊聊团队合作。代码驱动的图比手动画的图更有团队协作的基础但前提是得有规矩。我给我们团队定的规则很简单第一所有图的源文件优先用文本格式禁止把最终图片当唯一交付物。第二图里面必须写清楚目标和范围比如在注释里用//声明这张图表达什么不表达什么。Graphviz 的注释是//或者/* */D2 也能写注释。别小看这几行注释几个月后回来维护你会庆幸当时写清楚了。第三改动图必须连带更新包含这张图的文档链接和相关说明。很多团队的图代码化了但文档链接还是指向老图结果图是新的上下文是旧的同样会造成误导。这些规范不复杂但价值非常大。图一旦能像代码一样被 review、被追溯文档的质量就完全不一样了。这也正是 diagram-design 的核心魅力它不只是一个画图技巧而是一种让技术表达变得更可靠、更可维护的工程思维方式。我在实际项目中最大的体会是画图这件事最忌讳的就是把工具当成目的。Graphviz、Mermaid、D2包括你后面可能接触到的其他工具它们都只是帮你把脑子里的结构清晰地表达出来的手段。真正值钱的是你对系统边界的判断对关系层级的梳理以及对读者阅读体验的在意。哪怕你只是把之前随手画在纸上的架构图用这篇博客里的方法重新写一遍你也会发现整理图的过程本身就是在整理你对系统的理解。