1. 从“diagram-design”这个标题说起它到底在解决什么问题第一次看到“diagram-design”这个标题很多人会下意识觉得它是个画图工具或者某个设计软件的插件。但如果你真正做过系统设计、写过技术方案、画过架构图就会明白这个标题背后藏着的是一类非常具体的痛点图怎么画才既准确又好看既能让工程师看懂又能让非技术角色快速理解。我自己在带项目的过程中最头疼的事情之一就是“画图”。早期用通用绘图工具画出来的东西自己看着还行但一放到评审会上问题就全暴露了——箭头方向混乱、模块边界不清、颜色用得毫无章法、图例缺失导致别人根本不知道每个方块代表什么。更麻烦的是每次系统改版图就得重画一遍因为之前的图完全没有“可维护性”可言。“diagram-design”这个主题本质上要解决的就是三个层面的问题第一图的语义准确性也就是你画的图能不能真实反映系统结构第二图的视觉可读性也就是别人能不能在十秒内抓住重点第三图的可维护性也就是当系统演进时你的图能不能低成本地跟着更新。这篇文章适合几类人看如果你是需要画架构图、流程图、时序图的技术人员这里面的经验能帮你少走弯路如果你是技术文档写作者或者产品经理需要跟工程师对齐方案那图的设计逻辑对你同样重要如果你只是偶尔需要画个示意图来辅助表达那至少能让你避开最常见的几个坑。我接下来会从“为什么大多数人的图都画错了”开始逐步拆到工具选型、语义分层、视觉规范、维护策略最后给出一套可以直接抄作业的实操流程。所有内容都基于我在实际项目中反复踩坑、反复调整后沉淀下来的做法不是教科书式的理论。2. 为什么你画的图别人总是看不懂三个根深蒂固的认知误区2.1 把“画出来”当成“画清楚”很多人画图的心态是“我把该有的东西都放上去了看不懂是你的问题”。这种心态直接导致图变成了信息垃圾场——所有模块、所有连接、所有注释全堆在一张图上没有任何层次和优先级。我见过一张典型的“大杂烩架构图”二十多个方块挤在一起箭头交叉得像蜘蛛网颜色用了七八种但没有任何图例说明。画图的人自己知道每个元素代表什么但换一个人来看第一反应就是“这图跟我有什么关系”。正确的做法是先做减法再做加法。在动手画之前先问自己这张图的核心信息是什么如果只能保留三个元素我会保留哪三个把这三个元素先画出来确保它们之间的关系清晰可见然后再考虑是否需要补充次要信息。一张图只讲一件事这是铁律。2.2 混淆“技术视角”和“业务视角”技术人员画图时习惯从实现细节出发比如会画出具体的服务名称、数据库表名、消息队列的topic。但看图的如果是业务方或者产品经理他们关心的是“用户请求怎么流转”“数据在哪里被处理”“哪个环节可能出问题”。我踩过的一个典型坑是给业务方讲一个订单系统的改造方案我画了一张包含十几个微服务、三个数据库、两个消息中间件的详细架构图。讲了十分钟业务方问了一句“所以用户下单之后到底发生了什么”我当时就意识到我的图是给工程师看的不是给业务方看的。后来我养成了一个习惯同一套系统至少准备两张图。一张是“业务流转图”用业务语言描述用户操作和数据流向方块里写的是“订单创建”“库存扣减”“支付确认”这类业务动作另一张是“技术实现图”用技术语言描述服务拆分和中间件依赖。两张图分开使用不要混在一起。2.3 忽视“图的可维护性”这是最容易被忽略但代价最大的问题。很多人画图是一次性的——画完导出成图片往文档里一贴就完事了。等到系统改版需要更新图的时候发现原始文件找不到了或者原始文件里的图已经跟实际系统差了十万八千里只能重画。我在一个持续迭代的项目里吃过这个亏。第一版架构图是用某个在线工具画的画完之后导出PNG放在文档里。三个月后系统增加了两个新服务我需要更新图结果发现那个在线工具的免费版已经限制了我的项目数量之前的图打不开了。最后只能对着旧图重新画一遍浪费了大半天时间。从那以后我坚持一个原则图的源文件必须用纯文本格式保存。什么叫纯文本格式就是像Mermaid、PlantUML、Graphviz这类用代码描述图形的工具。这样做的好处是图的内容可以跟代码一起放在版本控制里每次修改都有记录而且不依赖任何特定平台的账号或付费计划。3. 工具选型为什么我最终放弃了拖拽式绘图3.1 拖拽式工具的隐性成本刚入行的时候我也用过各种拖拽式绘图工具。鼠标一拖一放方块和箭头就出来了看起来很方便。但用久了就会发现几个问题对齐全靠手动稍微复杂一点的图方块之间的对齐、箭头的走向、间距的统一全都要手动调整。一张图调半小时是常事。版本管理困难大多数拖拽式工具的文件格式是私有的二进制格式没法用Git做diff改了什么完全看不出来。协作效率低多人同时编辑一张图要么冲突要么需要轮流操作没法像代码一样做合并。复用性差画好的图很难拆成组件复用到其他图里每次都要重新画。这些问题的本质是拖拽式工具把“画图”变成了“手工劳动”而不是“工程实践”。手工劳动的问题是不可重复、不可验证、不可自动化。3.2 代码化绘图工具的核心优势代码化绘图工具也叫“绘图即代码”的思路完全不同。你用一段结构化文本描述图的内容工具负责渲染成图。这种方式带来的好处是根本性的对比维度拖拽式工具代码化工具版本管理二进制文件无法diff纯文本Git友好修改成本手动调整容易破坏布局改代码自动重新布局复用性复制粘贴容易出错引入组件参数化复用协作方式轮流编辑或冲突分支合并代码评审输出一致性依赖个人审美统一模板风格一致我现在的做法是所有需要长期维护的图一律用代码化工具画。只有那种一次性的、临时讨论用的草图才会用白板或拖拽工具快速画一下。3.3 主流代码化绘图工具的实际体验对比我实际用过并且推荐的工具主要有三个Mermaid、PlantUML和Graphviz。它们各有侧重适合不同的场景。Mermaid的优势是上手快、语法简单而且很多Markdown编辑器原生支持。画流程图、时序图、状态图非常方便。缺点是布局控制能力弱复杂图的自动布局有时候不太理想。PlantUML的功能更强大支持的图类型更多包括架构图、部署图、甘特图等而且可以通过skinparam做比较细致的样式控制。缺点是语法相对复杂学习曲线比Mermaid陡一些。Graphviz是最底层的用DOT语言描述节点和边布局算法非常强大。适合画那种节点关系复杂、需要自动布局的图。缺点是需要对布局参数有比较深的理解否则出来的图可能很丑。我的选择策略是日常流程图和时序图用Mermaid复杂架构图和部署图用PlantUML节点关系特别复杂的网络图用Graphviz。三者不冲突可以按场景切换。提示不要试图用一个工具解决所有问题。我见过有人非要用Mermaid画复杂的部署架构图结果布局怎么调都不对最后浪费了大量时间。工具选型的第一原则是“匹配场景”而不是“统一工具”。4. 语义分层一张好图的骨架是怎么搭出来的4.1 先定义“层”再定义“块”画图最容易犯的错误是直接开始画方块。正确的顺序应该是先确定这张图要分几层每层代表什么含义然后再往每层里填内容。以最常见的系统架构图为例我通常会把图分成四层用户层代表系统的使用者或外部触发源比如浏览器、移动端、第三方系统。接入层负责请求的接收和初步处理比如网关、负载均衡、CDN。服务层核心业务逻辑所在按业务域拆分成多个服务。数据层数据存储和缓存比如数据库、缓存、对象存储。分层的好处是看图的人可以快速定位自己关心的部分。业务方看用户层和服务层运维看接入层和数据层开发看服务层内部的拆分。每一层内部的方块用同一种颜色或同一种形状视觉上自然形成分组。4.2 箭头的语义必须明确箭头是图里最容易产生歧义的元素。一个箭头到底代表“数据流向”“调用关系”“依赖关系”还是“控制流”如果不加区分看图的人就会困惑。我的做法是用不同的线型和箭头样式区分不同的关系。比如实线箭头表示同步调用请求-响应虚线箭头表示异步消息发送后不等待空心箭头表示数据流向只传数据不触发逻辑带圆点的线表示依赖关系编译期依赖非运行时调用然后在图的角落放一个图例说明每种线型代表什么。这个图例不需要很大但必须有。我见过太多图因为缺少图例导致读者要靠猜来理解箭头含义。4.3 方块的粒度控制方块里写什么直接决定了图的信息密度。写得太粗图没有信息量写得太细图变成代码清单。我的经验法则是方块里写“职责”不写“实现”。比如一个服务方块里面写“订单管理”而不是“OrderService.java”写“支付处理”而不是“调用第三方支付接口的适配器”。职责是稳定的实现是易变的。图应该反映稳定的结构而不是易变的细节。如果某个方块的内部逻辑确实需要展开那就单独画一张更细的图而不是把所有细节塞进一张图里。这就是所谓的“分层展开”总览图只画到服务级别需要深入某个服务时再画一张该服务内部的组件图。4.4 一个实际的语义分层示例假设我们要画一个“用户下单”的流程。按照语义分层的思路可以这样组织第一层是触发源用户在移动端点击“提交订单”。第二层是接入网关请求经过API网关做鉴权和限流。第三层是业务服务订单服务创建订单库存服务扣减库存支付服务发起支付。第四层是数据存储订单数据写入订单库库存变更写入库存库支付记录写入支付库。每一层用不同的背景色区分层与层之间的箭头标注清楚传递了什么数据。这样一张图业务方能看到“下单要经过哪些环节”开发能看到“服务之间的调用关系”运维能看到“涉及哪些存储组件”。一张图三种视角各取所需。5. 视觉规范让图从“能看”变成“好看”的实操细节5.1 颜色使用的三条铁律颜色是图里最容易被滥用的元素。我见过太多图用了十几种颜色结果反而让人眼花缭乱。我的建议是遵循三条铁律第一同一层用同一种颜色。比如所有服务层方块都用浅蓝色所有数据层方块都用浅绿色。这样看图的人可以通过颜色快速识别层次。第二强调色只用于强调。整张图的主色调控制在两到三种以内然后用一种对比强烈的颜色比如橙色或红色来标注需要特别注意的部分比如“新增模块”“风险点”“待改造部分”。第三颜色要有语义。不要为了好看而用颜色。每种颜色都应该对应一个明确的含义并且这个含义要在图例里说明。如果一种颜色找不到明确的语义那就不要用。5.2 字体和字号的实际选择字体问题看起来很小但实际影响很大。我的经验是中文字体用黑体类如思源黑体、微软雅黑不要用宋体。宋体在屏幕上小字号时笔画太细看不清。字号至少12px如果是投影演示至少16px。我见过太多图因为字号太小投到屏幕上后排根本看不清。方块内文字左对齐或居中对齐要统一。不要有的方块左对齐有的居中看起来会很乱。避免在方块内使用多种字号。方块标题可以加粗但字号保持一致。5.3 间距和对齐的自动化处理手工调整间距是效率杀手。代码化工具的最大优势就是自动布局但自动布局也需要一些参数调优。以PlantUML为例可以通过skinparam nodesep和skinparam ranksep来控制节点间距和层级间距。默认值有时候太紧凑适当调大可以让图更透气。但也不要调得太大否则图会显得松散。对齐方面代码化工具通常会自动处理。但如果你发现某些节点没有对齐可以检查是不是因为节点之间的连接关系导致了布局算法的特殊处理。有时候调整一下连接的顺序布局结果就会好很多。5.4 图例和标注的放置策略图例不是可有可无的装饰而是图的重要组成部分。我的做法是图例放在图的右下角或左下角不要放在顶部因为顶部通常是标题区域。图例内容要精简只列出图中实际用到的符号和颜色不要列一堆没用的。关键标注用引线指向具体元素不要用大段文字在图的空白处解释。引线标注更直观读者不需要来回找对应关系。注意如果你的图需要大量文字解释才能看懂那说明图本身没有画清楚。图应该能独立传达核心信息文字只用于补充细节。6. 可维护性设计系统改版时图怎么跟着变6.1 把图当成代码来管理这是最核心的理念转变图不是文档的附件而是代码的另一种表达形式。既然代码要进版本控制图也应该进版本控制。我的做法是在项目仓库里建一个docs/diagrams目录所有图的源文件.mmd、.puml、.dot都放在这里。每次系统架构变更时修改对应的图源文件提交时跟代码变更放在同一个commit里。这样图的变更历史跟代码的变更历史是完全对齐的。更进一步可以在CI流程里加一步自动把图源文件渲染成PNG或SVG然后发布到文档站点。这样每次代码合并后文档里的图自动更新不需要手动操作。6.2 用变量和模板减少重复代码化工具的另一个优势是支持变量和模板。比如你有多个环境开发、测试、生产它们的架构基本一致只是某些组件的地址或数量不同。这时候可以用变量来参数化一份图源文件生成多张环境相关的图。PlantUML支持!define和!include可以把公共部分抽成独立的文件然后在主文件里引入。Mermaid虽然变量支持弱一些但可以通过模板引擎如Handlebars在渲染前做替换。我实际用过的一个做法是把服务列表定义成一个JSON文件然后用脚本读取JSON生成Mermaid代码。这样新增服务时只需要改JSON图自动更新。对于服务数量多、变更频繁的系统这种方式能省下大量维护成本。6.3 变更时的检查清单每次系统变更后我建议按以下清单检查图是否需要更新是否有新增或删除的服务/模块是否有调用关系的变化新增调用、删除调用、调用方式从同步变异步是否有数据存储的变化新增数据库、表结构重大调整是否有部署架构的变化新增节点、机房迁移图例是否需要更新新增了线型或颜色这个清单看起来简单但实际执行时很容易漏。我自己的做法是把清单写成一个Markdown模板每次变更时复制一份逐项打勾。养成习惯后图的准确率会大幅提升。7. 一套可以直接抄作业的实操流程7.1 从需求到草图先想清楚再动手拿到一个画图需求时不要急着打开工具。先花五分钟想清楚三个问题第一这张图给谁看是给开发看、给业务方看、还是给运维看不同的受众决定了图的详细程度和术语选择。第二这张图要回答什么问题是“系统有哪些模块”还是“请求怎么流转”还是“数据存在哪里”一张图只回答一个问题。第三这张图需要多详细是总览级别的还是某个模块的详细设计总览图控制在10-15个元素以内详细图可以到30个元素左右再多就应该拆图了。想清楚这三个问题后用纸笔或白板快速画一个草图。草图不需要好看只需要把元素和关系理清楚。草图确认无误后再开始用工具画正式图。7.2 用Mermaid快速出第一版对于大多数流程图和时序图我推荐先用Mermaid出第一版。原因是语法简单改起来快而且可以直接嵌在Markdown里预览。一个典型的Mermaid流程图代码大概长这样flowchart TD A[用户提交订单] -- B[API网关] B -- C{鉴权通过?} C --|是| D[订单服务] C --|否| E[返回401] D -- F[库存服务] D -- G[支付服务] F -- H[(订单库)] G -- I[(支付库)]这段代码渲染出来的图结构清晰箭头方向明确。如果发现布局不理想调整一下节点顺序或连接关系重新渲染即可不需要手动拖拽。7.3 用PlantUML做精细化调整当图的结构比较复杂或者需要更精细的样式控制时我会切换到PlantUML。PlantUML支持更多的图类型和更细的样式参数。比如要画一个带分层的架构图可以用package或rectangle来分组用skinparam来控制颜色和间距startuml skinparam rectangle { BackgroundColor LightBlue BorderColor DarkBlue } skinparam nodesep 30 skinparam ranksep 40 rectangle 用户层 { [移动端] as mobile [Web端] as web } rectangle 服务层 { [订单服务] as order [库存服务] as inventory } mobile -- order web -- order order -- inventory enduml这段代码渲染出来的图层次分明间距适中。如果需要调整颜色或间距改一下skinparam参数即可不需要重新画。7.4 渲染输出和嵌入文档图源文件写好后需要渲染成图片格式嵌入文档。我通常用两种方式方式一本地渲染。安装对应的CLI工具如plantuml、mmdc在本地把源文件渲染成PNG或SVG。SVG格式更好因为可以无损缩放而且文件体积小。方式二CI自动渲染。在CI流程里加一步每次代码合并后自动渲染所有图源文件然后发布到文档站点。这种方式适合团队协作确保文档里的图始终是最新的。嵌入文档时我建议用SVG格式并且在图下方加一行小字说明“图源文件位置docs/diagrams/xxx.puml”。这样读者如果发现图有问题可以直接找到源文件提修改建议。7.5 一个完整的实操案例假设我们要为一个“文件上传服务”画架构图。按照上面的流程第一步明确受众和问题。受众是开发团队要回答的问题是“文件上传的完整链路是什么”。第二步画草图。在纸上画出客户端 - 上传网关 - 文件服务 - 对象存储以及文件服务 - 元数据库的写入路径。第三步用Mermaid出第一版。把草图翻译成Mermaid代码渲染后检查布局和箭头方向。第四步用PlantUML精细化。如果Mermaid的布局不理想或者需要加颜色区分层次切换到PlantUML重新画。第五步渲染并嵌入文档。把最终图渲染成SVG嵌入到设计文档中并在图下方标注源文件位置。第六步提交到版本控制。把源文件和渲染后的SVG一起提交到仓库确保后续变更可追溯。这套流程看起来步骤多但实际操作起来一张中等复杂度的图大概15-20分钟就能完成。相比拖拽式工具反复调整对齐和间距效率反而更高。8. 几个我踩过的坑和对应的解决方案8.1 图太复杂导致渲染失败有一次我画一张包含50多个节点的部署图用Mermaid渲染时直接卡死了。后来发现是节点太多布局算法计算量太大。解决方案拆图。把一张大图拆成多张小图每张图聚焦一个子系统。如果确实需要总览可以画一张高层级的简图然后用链接指向各个子系统的详细图。8.2 中文字体渲染异常用某些工具渲染时中文字体可能显示为方块或乱码。这是因为工具默认字体不支持中文。解决方案在配置里指定中文字体。比如PlantUML可以通过skinparam defaultFontName Microsoft YaHei来指定。Mermaid可以在CSS里设置font-family。8.3 箭头交叉过多影响可读性当节点之间的连接关系复杂时箭头交叉是难免的。但过多的交叉会让图变得难以阅读。解决方案调整节点顺序。代码化工具的布局算法会根据节点的声明顺序来优化布局。把关联紧密的节点放在一起声明可以减少交叉。另外可以尝试不同的布局方向从上到下、从左到右选择交叉最少的那种。8.4 图例和实际图不一致有时候改了图的内容但忘了更新图例导致图例里说明的符号在实际图中找不到。解决方案把图例也当成代码的一部分。如果工具支持用变量或宏来定义图例确保图例和实际使用的符号是同一来源。如果不支持至少在每次修改图后对照检查一遍图例。9. 关于“diagram-design”这件事我最后想分享的几个心得画图这件事说到底是一种沟通能力。图只是媒介核心是你能不能把复杂的事情说清楚。我见过很多人花大量时间在工具和样式上但图的内容本身逻辑混乱这就本末倒置了。我的建议是先把逻辑理清楚再考虑怎么画。逻辑清晰的话哪怕用最简单的工具画出来的图别人也能看懂。逻辑混乱的话再漂亮的图也只是装饰。另外不要追求“一张图解决所有问题”。图是有边界的一张图能说清楚一件事就已经很好了。需要说清楚多件事就画多张图。图与图之间用链接或引用关联起来形成一个图的网络而不是把所有信息塞进一张图里。最后保持图的更新。一张过时的图比没有图更糟糕因为它会误导人。把图纳入日常的变更流程跟代码一样对待这样才能保证图的长期价值。我在实际项目里坚持用代码化工具画图已经三年多了最大的感受是前期学习成本确实存在但一旦形成习惯效率提升是巨大的。尤其是当系统频繁变更时改几行代码就能更新一张图这种体验是拖拽式工具给不了的。如果你还在用拖拽工具画需要长期维护的图我强烈建议你花一个下午的时间试试Mermaid或PlantUML大概率你会回不去。