
刚把最近一套 diagram-design 的实践心得整理完忍不住想聊几句。很多人一听到画图就觉得是“找个工具拖一拖、连几根线”的事可真到架构评审、方案汇报的时候一张没经过设计的图能把所有人拖进歧义里。我自己踩过这个坑才发现 diagram-design 不是顺手涂鸦它是一套关于信息编排、视觉层级和语义表达的完整方法值得我们把每个细节都抠清楚。这篇文章我准备写给所有需要在文档、评审、技术方案里放图的人后端工程师、前端工程师、架构师、产品经理甚至做数据分析和知识管理的朋友。内容的核心就一件事——怎么让你画的图又快又清楚既能表达复杂关系又不会让读者产生错误理解。我会从“图为什么会乱”讲起把工具选型、信息层级、色彩布局、代码化自动化这些环节逐一展开最后分享一些实际排查中积累的经验都是踩过的坑换来的。1. 先搞清楚 diagram-design 到底在解决什么问题1.1 一张没设计的图是怎么毁掉一场评审的我印象很深的一次方案评审同事贴了一张系统资源图节点大概有二十多个箭头颜色有五六种线有实线有虚线有人还在上面标了蓝色批注。结果评审会的前十分钟大家一直在争论“这条虚线到底表示定时任务还是异步消息”还有人把蓝色批注误当成缺失的模块。最后架构师只能打开代码仓库现查逻辑。一场本该讨论性能指标的评审变成了看图猜谜。这个场景并不少见。之所以会这样是因为画图的人默认“线就是关系”但没有定义是什么关系默认“颜色是装饰”但没有赋予颜色固定语义默认“图能表达一切”却没有考虑读者的阅读路径。Diagram-design 的第一层价值就是逼着你在画图之前先做取舍哪些信息必须保留哪些信息可以删掉哪些关系适合用空间位置表意、哪些必须靠箭头和标签来讲清楚。把这些想明白图才开始具备“交流能力”。1.2 diagram-design 的本质是信息编排不是画图有一句话流传很广一图胜千言。但它成立的前提是这张图是经过编排的。我更喜欢把 diagram-design 理解成“把一段复杂逻辑翻译成二维空间语言”的过程。这个翻译过程里你需要决定节点如何摆放、层级如何划分、连线如何路由、文字如何标注甚至要决定整张图的阅读起点在哪、视觉终点落在何处。举个最简单的例子同样是画三个服务之间的调用关系A 服务在下、B 服务在中间、C 服务在上的布局和把三个服务排成一行的布局传递的信息重心完全不同。前者暗示了上下层依赖后者更像是并列流程。如果再加一个人坚持把所有接口名都写在箭头上另一个人只写“HTTP”这两种图表达的信息密度也不一样。所以 diagram-design 本质是内容策划工作工具只是最后落笔的载体。1.3 不同图种的“设计重心”差别很大不是所有图都叫架构图diagram 这个英文词是一个大类。在实际工作里我至少会区分四种常见图它们的设计重心完全不同。架构图重点关注模块边界、层次关系和数据流向。这类图最怕信息爆炸一个框里塞二十个服务名还不如直接给链接流程图重点在于分支条件和循环路径它的设计核心是“读者能不能顺着主干一路走到底”时序图则强调消息先后顺序和对象之间的交互时间轴清晰比什么都重要数据知识类图更关注关系类型、基数约束和层级归类。很多人在画时序图时用架构图的设计习惯结果整张图没有时间轴感交互顺序全靠猜。理解图种差异是 diagram-design 落地的第一步。2. 图表工具怎么选从自由画布到代码描述的三条路线2.1 自由画布类工具上手最快但风格约束是短板自由画布工具的代表有 draw.io现在叫 diagrams.net、Excalidraw、FigJam、ProcessOn 之类的。它们最大的优点是“所见即所得”拖拽节点、连线条小白五分钟就能画出一张像样的图。但这类工具也有一个明显问题自由意味着不统一。同一个团队里有人喜欢圆角矩形有人用直角线条颜色能凑成一道彩虹。我见过有团队用 draw.io 画架构图最后每张图的字体大小都不一样导出图片的缩放比例也不统一放在文档里像拼贴画。所以如果用自由画布工具一定要配套一个团队模板把节点样式、配色、字体、箭头类型固定下来否则图和图之间根本没有一致性。适合自由画布工具的典型场景是快速原型讨论、白板头脑风暴、对布局精细度要求不高的内部草图。2.2 代码描述类工具用文本画图让图保持可追溯代码描述类工具我日常用得最多主要有 PlantUML、Mermaid 这类。它们的核心思路是拿文本定义节点和关系再通过渲染引擎生成图片。这么做的好处很实在图可以做版本管理、可以参与代码评审、可以自动生成也可以嵌进 Markdown 文档里。代价是布局可控性有限。你没法像在画布工具里那样随手把一个节点拖到某个位置。所有位置都是自动布局引擎决定的。这在图很复杂的时候会变成灾难比如二十个节点互相连线自动布局可能会把线绕成一个蜘蛛网。我的建议是代码类工具适合结构相对规则、逻辑清晰的图比如流程图、状态图、类图、简单架构图。它不适合需要精细控制大量节点绝对位置的复杂系统图硬要用也不是不行只是布局调整的时间成本会高得离谱。2.3 可编程绘图方案适合把图当作产品的一部分第三种路线是相对小众但非常有力量的一类比如用 D2、Graphviz、DiagramsPython这类可编程方案。它们和代码描述类工具的区别在于你不仅可以描述节点和关系还能通过编程逻辑动态生成图。举个例子如果你有一个服务依赖清单希望每周自动产出一张最新版架构图用 D2 或者 Python 脚本接上清单数据就能做到“图跟着数据走”。我推荐把这一类划入“工程化绘图”路线。它的设计目标不是让某一张图多漂亮而是让图成为可维护的工程资产。使用场景很明确系统复杂度高、图需要频繁更新、对一致性严格要求。它不适合一次性的临时草图因为初始化成本和学习成本比前两类高。2.4 工具选型建议别迷信单一方案我自己的实践里从来没有“一个工具打天下”的说法。我的建议是组合使用临时沟通用 Excalidraw正式文档优先用代码类工具复杂架构图落在 D2 或 Graphviz 上需要精细排版时再用 draw.io 手工调整导出。下表是我常用的选型逻辑可以给大家参考。工具类型代表工具适合场景不太适合的场景上手成本自由画布draw.io / Excalidraw / FigJam快速草图、头脑风暴、自由排版需要版本管理、团队风格统一的正式文档低代码描述Mermaid / PlantUML文档内嵌图、流程简单清晰、自动化渲染复杂拓扑、对布局有强要求的图中可编程绘图D2 / Graphviz / Diagrams复杂架构、动态数据驱动、CI 集成临时的简单草图中高一个小提醒切换工具是有代价的。项目越往后历史图的存量越多迁移成本越高。所以建议大家尽量在项目初期就定好主工具把样式模板一起定下来而不是画到一半频繁换工具最后所有图都长着不同的“脸”。3. 真正让图“好看又好懂”的核心层级、网格与视觉动线3.1 信息层级用视觉权重告诉读者先看哪里这是我画图时最看重的一点。一张图的信息层级决定了读者扫一眼之后是抓住了核心结构还是迷失在边角细节里。信息层级的控制手段不多但非常有效大小、粗细、颜色和留白。大节点天然比小节点更吸引注意力深色和粗线框比浅色细线框更重饱和度高的颜色会比灰色更跳。如果一张图里所有节点同等大小、同等粗细、同一颜色那它就没有层级只有一堆并列方块。实际操作中我会把核心服务放大一号、加深描边把辅助节点缩小三分之一、降低饱和度虚线框和浅色背景一般用来表达逻辑分组或外部边界让次要信息退到视觉后方。3.2 网格与对齐让图有隐藏在背后的秩序感对齐这件事技术人有时候会忽略但设计师永远不会。为什么有些图看起来“很稳”有些图看起来“不太舒服”大部分时候都是对齐的问题。节点没有对齐、连线没有垂直水平、模块间距忽大忽小都会让读者产生难以言说的不信任感。我画图时会脑子中放一张看不见的网格核心模块与核心模块之间保持相同间距同层节点尽量放在同一水平线上跨层连线尽量走正交线段而不是斜线。如果你用的是 draw.io打开对齐辅助线再画效果会好很多如果是代码类工具D2 的自动布局通常默认采用分层布局本来就是对齐的。还有一个小技巧相邻组之间保持一致的留白比如所有模块之间的横向间距统一为 40px纵向间距统一为 60px。这种秩序感不需要用户刻意察觉但一定会影响阅读体验。3.3 阅读动线让读者按照你规划的路径走读图和读文章一样有一个视线移动的顺序。传统网页是从左到右、从上到下系统架构图则多为自上而下的数据流这也是大多数人默认的“主链路”。所以画一张用户从发起请求到收到响应的时序图我会把时间方向固定为从上到下画一张系统分层架构图我会把用户入口放最上方数据存储放最下方中间按逻辑层展开。阅读动线设计的核心是主链路上的元素必须处于视觉中心支线细节往两侧或下方放。这不是拍脑门的规矩它符合人的自然视觉习惯。如果一张图的主链路东一个节点西一个节点读者的视线会在图里往返跳跃信息读取效率会断崖式下降。我见过最好的做法是把主链路设计成一个近似直线或“Z”字型的路径让读者能一口气读完主干再回头研究分支。3.4 颜色与文本规范有限色板能救整张图颜色的作用不是“好看”而是区分语义。最典型的做法是定义一套固定色板核心业务模块用一种色系外部依赖用另一种色系异常或告警路径用红色稳定已上线路径用绿色。整张图里的颜色数量控制在 4 到 6 种以内超出这个数量色块就会变成干扰。除了颜色文本同样需要做控制。节点名称尽量不超过两行能用一个词说清楚就不要用一句话箭头上的标签优先用名词或动词短语比如“HTTP”“异步”“创建订单”而不是一整行描述性文字。字号上主标题最大分组标签次之节点内文字再次之连线上标签最小。我自己的默认设定是主标题 16px分组标签 13px节点文字 12px连线标签 10px这样整张图的文字秩序是清晰的。3.5 实操示例画一张注册模块的局部架构图用一个例子把前面几个原则串起来。假设要画“用户注册”的局部架构图主链路是客户端提交表单 - 网关转发 - 用户服务校验并入库 - 消息队列发送欢迎通知。画之前我先确定阅读动线从上到下。客户端在最顶部网关在第二层用户服务在第三层数据库和消息队列在第四层搜索服务和邮件服务兜底在底部。主链路上的节点我都用相同大小的矩形边框颜色统一为主色数据库用圆柱体区分形状消息队列用柱状图外部依赖比如邮件服务用浅灰背景并加上“外部”角标。页面左边放一个输入参数说明的分组框右边放异常处理的跳转分支它们都不是主线所以用浅色虚线框包起来弱化存在感。这样即使读者不看任何文字解释也能理出“发起注册 - 校验入库 - 异步通知”的主脉络后续有问题再从两侧分组框去找细节。这就是一套完整的层级、网格、动线、颜色设计下来形成的效果。4. 把图表变成工程资产diagram as code 的自动化实战4.1 图为什么要进入版本控制很多团队的项目文档里图片是一张 PNG或者是 draw.io 的源文件但源文件不会随代码评审走最后图片和代码就越来越不一致。把图纳入版本控制最大的价值不是“好看”而是让图和代码一样具有历史、可评审、可回溯。用代码描述图之后一次改动可能只是某个节点名称变了Pull Request 里能清楚看到变化的是哪几行如果直接截图更新 PNG评审者只能看到新旧两张图肉眼比对差异。另一个好处是图可以跟着文档仓库走服务重构后更新依赖描述文件重新渲染一遍所有引用位置都能同步更新。图不再是“做完就丢的一次性产物”而是一份持续演进的技术资料。4.2 一个可复用的“文档内嵌图”方案我目前用得比较顺手的方案是Markdown 文档 D2 代码块 构建时渲染。D2 相比其他同类工具有几个吸引我的点语法直观类似声明式、布局引擎稳定、支持集群和标签并且渲染出的 SVG 可以设置主题代码风格上更接近现代 DSL 而不是上世纪语法。具体工作流是在 docs/diagrams 目录下放 .d2 源文件在 Markdown 中引用它对应的 SVG 图片然后在本地或 CI 里用一条命令把 .d2 渲染成 SVG。每次改图我只需要编辑源文件、提交、重新渲染文档里的图就自动更新了。这套组合不挑文档系统MkDocs、VitePress、GitBook 都能用。如果你更熟悉 PlantUML也可以套用同样的思路只是语法习惯不同。4.3 D2 实战一段简单的微服务架构图下面用 D2 写一个非常简单的微服务链路大家感受一下这套“代码画图”的方式。用户: 用户端 网关: API 网关 订单: 订单服务 库存: 库存服务 支付: 支付服务 用户 - 网关: HTTPS 网关 - 订单: 创建订单 订单 - 库存: 扣库存 订单 - 支付: 发起支付这段代码定义了四个节点和四条关系线用箭头上的文本来标注协议或行为。D2 会自动计算布局输出一张自上而下的依赖图。如果想让订单服务和库存、支付之间形成更清晰的分组可以引入 cluster订单: 订单服务 { 创建订单 查询订单 } 库内: 依赖服务 { 库存 支付 } 订单.创建订单 - 库内.库存: RPC 订单.创建订单 - 库内.支付: RPC这种写法把“订单服务内部的子模块”和“它依赖的外部服务”明确区分开渲染出来以后读者能一眼看出哪些模块属于同一个服务边界。D2 里的 cluster 非常像真实架构中的“服务边界”或“命名空间”这个表达能力和叙事价值是普通拖拽画布比较难做到的。4.4 让图在文档系统里自动更新的集成细节有了源文件和图片输出只剩最后一步自动化。我在 MkDocs 项目里通常加一个构建步骤先执行 d2 渲染所有 .d2 文件到 images 目录再执行 mkdocs build。在 CI 流水线上也会挂一个对应的 job确保每次代码合并前图都被重新生成而不是有人忘了更新 PNG。这里有几个容易踩的细节第一不同操作系统字体不一致会导致 SVG 排版差异建议 CI 里固定使用同一种字体基础镜像第二生成的文件名尽量用语义化命名比如 architecture-order-detail.svg不要叫 final_v3.svg第三如果文档站点需要深色主题可以考虑让 D2 同时输出一套浅色和深色主题的 SVG用 CSS 的 prefers-color-scheme 切换而不是强迫所有页面统一用亮色或暗色风格。这些看起来是小事但实际维护体验差距很大。5. 常见问题与排查技巧实录5.1 布局乱跳为什么同一个源文件换台机器图就变了代码类图工具最令人头疼的问题之一就是同一个 .d2 文件在本机渲染和 CI 上渲染出来的图不完全一样。多数原因不是代码写错而是字体缺字导致节点宽度变化或者是自动布局引擎版本差异。排查思路是固定渲染环境用同一版本的工具、同一套字体配置、同一个基础镜像。如果图确实有大范围差异再去检查定义里是否有依赖外部动态数据的变量。我的习惯是在项目根目录放一个 .tool-versions 或 requirements.txt把 D2 或 Graphviz 的版本锁住。这看起来是工程化手段但往往能解决百分之八十的“玄学布局问题”。5.2 中文乱码与字体缺失永远不要忽略字体配置用代码类工具画图中文乱码是一个高频问题。多数情况下是因为当前环境没有安装支持中文的字体渲染出来的 SVG 里中文变成方框或者完全不显示。解决方案很简单但也很容易被忽略在 Dockerfile 或 CI 构建环境里明确安装中文字体比如 Noto Sans CJK SC。如果是本地渲染把字体文件放到项目中通过 D2 或 Graphviz 的配置文件指定字体名就能确保不同机器上效果一致。另外提一句中英文混排时建议把默认字体设置为包含中文子集的英文字体比如 Inter 加上 Noto Sans CJK SC 的 fallback这样优先显示英文字形中文也有兜底。别小看字体这一步很多图“突然变丑”都是字体回退导致的。5.3 图太大渲染卡顿复杂度需要主动拆分画到一定规模工具再快也会开始卡顿。我见过有人在一个画布里画两百多个节点最后打开要等好几秒导出的 SVG 十几兆放在文档里浏览器都扛不住。这种问题靠优化渲染引擎解决不了只能靠图的结构去治把一张巨图拆成一张总览图加若干张局部详图总览图只展示服务边界和数据主干局部详图负责细节。拆图的另一个好处是每张图都有自己的叙事焦点。总览图讲清楚系统有哪些服务、边界在哪、数据从哪里进从哪里出局部图讲清楚某个服务内部流程。这样文档的层次也变清晰了读者可以按需查阅而不是对着大图来回拖动寻找目标。我的经验是单张图节点数尽量控制在 30 到 50 个以内如果超过这个量级说明架构本身需要分层表达。5.4 多人协作时风格漂移建立团队图表规范画图的风格问题跟写代码的风格问题一样如果不定义规范每个人都会按自己的习惯来。团队里一旦出现“五颜六色的箭头”“节点一会儿圆角一会儿直角”图的可信度就会下降。我通常会建议团队维护一份简短的图表规范内容不需要厚几页就够节点形状的语义、主色和辅助色的具体色值、箭头线型含义、字体字号、导出图片的尺寸和格式。规范最好直接写进文档仓库里并给出标准模板文件。新成员入职后让他们基于模板画图而不是从空白画布开始能省掉大量后期统一成本。最后再分享一个我个人很受用的自检习惯图保存之前退后一步问自己——如果一个从没见过这套系统的人只花三十秒看这张图能不能说出主链路是什么如果说不出来我会重新调整层级和视觉动线而不是急着加更多文字解释。先让图自己能说话再去补充细节这就是我在 diagram-design 这件事上最大的心得。