拿我自己举例,这些年带过不少项目,也评审过无数技术方案,最让我头疼的往往不是代码,反而是图。架构图找不到边界,流程图箭头乱飞,时序图时间轴各画各的,换个人接手就得从头猜。后来我决定不再忍,把团队里的图表设计习惯做了一次彻头彻尾的重构,沉淀出一套叫"diagram-design"的图表设计体系。这篇文章就是这套体系从无到有的完整复盘,包括图形语义、配色规范、工具链选型,还有可以直接抄走的模板和避坑清单。
如果你和我一样,被"画图一时爽,维护火葬场"折磨过,或者你正在搭技术文档、做架构评审、写产品方案,那你应该能从中拿走不少能直接用的东西。这套思路不限定编程语言,也不绑定具体工具,团队和个人的场景都能落地。
1. 内容整体设计与思路拆解
1.1 那些年我们画过的"四不像"图
先聊聊我为什么非要折腾这件事。过去团队里的图大致分几种:有人用PPT的箭头硬拼架构图,有人直接把白板拍照丢进文档,有人用画图软件画完一张大图就导出成PNG,再也没法改。这些图有一个通病——它们只是"画"出来的,不是"设计"出来的。
画图这件事,看起来是把方框和箭头摆在一起,实际上是在表达系统结构、调用关系、数据流向、部署边界。没有统一的图形语义,就会出现同一个团队里,A用矩形表示服务,B用圆角矩形表示服务,C用云图标表示服务;A认为虚线是异步调用,B认为虚线是弱依赖。一张图单独看没什么,放到一起就是灾难。
还有配色。很多架构图简直是荧光笔开会,红橙黄绿青蓝紫全上,看半天找不到重点。等到维护期才发现,模板不统一、图标风格混杂、导出格式五花八门,谁都不敢动,一改就乱。
1.2 diagram-design 想解决的问题
我做的这套 diagram-design,本质上不是某一个软件,而是一整套"图表设计规范 + 工具链 + 模板库"。它要解决三件事:
第一,统一图形语言。团队里所有人画出来的图,形状含义一致、箭头方向一致、颜色语义一致,任何一张图拿出来不用问原作者就能读懂。
第二,让图变成可维护的文档。图和代码一样进Git,能diff、能review、能回滚。改图不是重画,而是改描述文件。
第三,降低画图成本。沉淀模板和组件库,新人照着模板套,十分钟就能画出一张符合规范的图,不需要从空白画布开始纠结。
这套体系并不是要限制创造力,反而是把"语义表达"和"视觉美观"解耦。简单说,你想表达什么由规范保证,想画得好看留给你自己发挥。
1.3 适合谁来参考
不管你是后端开发、前端开发、运维、架构师,还是经常画流程图的产品经理和项目经理,这套规范都适用。如果你正在建设技术文档体系,那更值得往下看,图和文字一样,应当被当成一等公民来管理。
2. 核心图形语言:让每张图都遵循同一套"语法"
2.1 图形形状的语义约定
我优先做的第一件事,是定义"图形词汇表"。说白了,就是让团队里每一个形状都有唯一确定的含义,就像编程语言里的关键字,不能一个词多个意思。
我最终定下的基础约定是这样的:
- 矩形:内部服务、模块、组件,是架构图的主角;
- 圆角矩形:执行动作、流程步骤、行为节点;
- 菱形:判断、决策、分支;
- 圆柱体:数据库、缓存、对象存储等持久化组件;
- 人形图标:外部用户或角色;
- 云形图标:外部系统、第三方服务、不可控环境。
这里面最容易犯的错,是"云图标滥用"。很多同学一画外部系统就放一朵云,把自家在公有云上的服务器也画成一朵云。我在规范里写死了一个原则:云容器只允许用来表达"你不可控的边界外部",自己部署在云上的服务一律画成矩形,顶多用一个大的虚线区域框起来标注"云环境"。
因为你画架构图的核心目的是让别人看清"系统的可控边界",如果连可控不可控都不区分,这张图在架构评审时就没有意义了。
2.2 颜色花哨不如语义明确
颜色是重灾区。我见过一张图用了十几种颜色,最后根本不知道哪块是重点。色彩在技术图里只有一个作用:辅助语义分类,而不是装饰。
我定了一套非常克制的调色板,总共四类背景色,外加一类警示色:
| 语义 | 色值 | 用途 |
|---|---|---|
| 核心服务蓝 | #2563EB(文字用 #FFFFFF) | 自研的内部核心服务 |
| 稳定模块绿 | #16A34A(文字用 #FFFFFF) | 已上线、成熟稳定的模块 |
| 中间件/异步橙 | #EA580C(文字用 #FFFFFF) | 消息队列、异步任务、第三方依赖 |
| 存储紫 | #7C3AED(文字用 #FFFFFF) | 数据库、各类存储 |
| 风险红 | #DC2626 | 废弃节点、风险点、尚不存在的规划点 |
你可能注意到了,我把红色留给了"风险/废弃",而不是"紧急/重要"。原因是技术图里的红色如果被随便用来标重点,那真正出现风险时红色就失效了。团队成员如果想强调某个"重点模块",正确做法是加粗边框或放大节点,而不是换颜色。
2.3 线条与箭头的隐形语言
线条比形状更容易被忽略,但信息量极大。我规定了几种基础线型,要求团队所有图里只能出现这四种:
- 实线箭头:同步调用 / 强依赖;
- 虚线箭头:异步调用 / 弱依赖 / 事件通知;
- 粗实线箭头:核心业务主链路,数据流主路径;
- 灰色实线(无箭头):边界框,用于圈定一组组件。
很多人画图的时候喜欢在每个连线上加"请求/响应"双边箭头,我建议大部分场景不要这么做。除非你在画非常底层的协议时序,否则架构图只需要标注"谁发起的调用、调用方向是什么"。双向箭头看着严谨,实际上会让图变乱,而且大部分时候"响应"都是跟随"请求"的,不需要单独表达。
还有一条铁律:同一种关系,在同一张图里只能有一种画法。你不能一会儿用虚线表示异步,一会儿用虚线表示历史迁移路径。如果图里确实需要表达另一种关系,找一种新的线型出来,但不能复用已有含义的线型。
3. 工具链选型:我只留这四类
3.1 代码生成类:D2 与 PlantUML
在选择工具这件事上,我经历了好几轮折腾。最开始大家用在线白板画,后来改成专业绘图软件,再后来发现不管是哪家,只要图一多、协作者一多就失控。最终我把主力工具换成了"代码生成图"。
代码化画图的好处,说破天也就一条:图是文本,文本就能进Git,能diff,能review。你的架构图从一个"导出的文件"变成一份"跟着代码走的文档",这个变化是革命性的。
我最常用的代码画图工具是 D2,它语法简单、自动布局默认效果不错,而且对中文支持比某些老牌工具好。举个例子,下面这段代码就是一张极简的订单系统架构图:
vars: { d2-config: { layout-engine: dagre theme-id: 200 } } client: 用户端 { shape: person } api: API Gateway { shape: rectangle style.fill: "#2563EB" style.font-color: "#FFFFFF" } order: 订单服务 { shape: rectangle style.fill: "#2563EB" style.font-color: "#FFFFFF" } db: 订单数据库 { shape: cylinder style.fill: "#7C3AED" style.font-color: "#FFFFFF" } mq: 消息队列 { shape: queue style.fill: "#EA580C" style.font-color: "#FFFFFF" } client -> api: 提交订单 api -> order: 创建订单 order -> db: 读写订单数据 order -> mq: 发送订单事件这段代码画出来,就是一张线条清晰、颜色符合规范、节点语义明确的图。改一个节点名字,重新执行一次编译命令就行,完全不破坏整体布局。
PlantUML 我也保留着,它更适合画时序图和状态图,语法成熟,不需要额外装太多依赖。王炸场景是快速生成时序图:
@startuml actor 用户 participant "前端" as FE participant "鉴权服务" as Auth database "用户库" as DB 用户 -> FE: 输入账号密码 FE -> Auth: 登录请求 Auth -> DB: 查询用户信息 DB --> Auth: 返回用户数据 alt 密码匹配 Auth -> FE: 登录成功 FE -> 用户: 跳转首页 else 密码错误 Auth -> FE: 返回错误码 FE -> 用户: 提示密码错误 end @enduml这段代码生成的时序图,内部消息顺序、分支并发、参与者关系都一目了然。重点在于它完全是文本,团队review代码的时候,顺带就把图给review了。
3.2 手绘/白板类:Excalidraw
代码画图虽然好,但有一个场景永远替代不了:讨论中的草图。评审会上大家思路还很发散,图一小时一变,这时候你要是用D2或者PlantUML去改,光想语法就分心了。
Excalidraw 是手绘风格的画板,支持多人协作,画出来的图自带"我们正在讨论"的松弛感。我之所以把它纳入规范,是因为它特别适合当"思考的容器",画错了随手擦掉重来,没有任何心理负担。
但我给团队定了一条规矩:Excalidraw 只用来画"过程图",不允许直接贴进正式技术文档。正式文档里的图,要么是代码生成的定稿图,要么是经过走查后重新绘制的干净矢量图。手绘图的随意感放在设计评审阶段是优点,放在对外文档里就是事故。
3.3 综合绘图类:draw.io
也不是所有图都适合写代码。有些图特别强调"手工排版的美感",比如容量规划图、网络拓扑图、IDC机房部署图,用代码生成反而要花大量时间调整坐标。这类图我推荐 draw.io,就是我们现在常说的 diagrams.net。
它免费、离线可用、支持存成XML格式,XML同样可以放进Git里做版本管理。而且它和很多文档平台有集成,导出SVG、PNG都很方便。我一般只在两种情况下用 draw.io:一是画网络拓扑和机房部署这类自身就带"物理位置语义"的图,二是给非技术协作方画汇报材料。
需要提醒的是,draw.io 默认模板风格比较随意,如果用,请自己定义一套带团队规范颜色的自定义形状库,别直接用默认配色。不然十个人画出来十种风格,后患无穷。
3.4 图表设计资产:SVG图标集与资源库
工具选完,还有一层隐形的东西要定:图标和资源。
技术图里经常要画人、服务器、数据库、消息队列、缓存、负载均衡、手机端、PC端,这些如果没有统一图标源,每个人从网上随便搜图,风格就乱了。
我的做法是选一套线性图标库作为统一来源,要求所有图的图标必须是同一套风格,线宽一致、圆角一致、透视一致。如果团队没有预算,用开源的SVG图标集就够了。重要的是在团队文档里写清楚"只用这一套,别的不准用"。
图标这东西有个特点:一旦混搭,视觉逼格瞬间全无。反过来,只要图标统一,哪怕排版一般,整体也不会太丑。
4. 实操:从零搭一份"diagram-design"图表模板
4.1 搭建目录与版本管理
工具链定好之后,我开始搭仓库。目录结构非常关键,它决定了团队图表的组织方式。我从 diagram-design 里沉淀出来的推荐结构是这样的:
docs/ diagrams/ 00-template/ architecture.d2 sequence.puml board.excalidraw.json 01-system/ order-system/ architecture.d2 sequence-login.puml 02-feature/ 2025-04-payment/ flow.puml network.drawio.xml 99-archive/ old-design.d2几点说明:
- 00-template 放的是团队最新定稿的空白模板,任何人开新图都必须从这里复制;
- 01-system 按系统维度组织,一个系统的多张图放一个目录;
- 02-feature 按项目迭代组织,一个特性做完,图纸留在仓库里;
- 99-archive 放废弃图,不删除,留底。
这种组织方式让"找图"变成一件非常容易的事。你想了解订单系统的整体架构,去 01-system/order-system 下看 architecture.d2 即可;想了解某个特性的时序,去 02-feature 找对应目录。和代码结构一一对应,新同学最容易上手。
4.2 用 D2 落地一张系统架构图
接下来我拿一个更完整的例子,带你走一遍完整实操。假设我要画一个电商后台的商品服务架构图,我不会从空白画布开始,而是复制 00-template/architecture.d2。
一份带规范和注释的 D2 模板长这样:
vars: { d2-config: { layout-engine: dagre theme-id: 200 sketch: false } } # 外部用户 app: 商家后台 { shape: web style.fill: "#F8FAFC" style.stroke: "#64748B" } # 网关 / 接入层 gw: 接入网关 { shape: rectangle style.fill: "#2563EB" style.font-color: "#FFFFFF" } # 应用服务层 product: 商品服务 { style.fill: "#2563EB" style.font-color: "#FFFFFF" } stock: 库存服务 { style.fill: "#2563EB" style.font-color: "#FFFFFF" } # 数据层 product_db: 商品库 { shape: cylinder style.fill: "#7C3AED" style.font-color: "#FFFFFF" } stock_db: 库存库 { shape: cylinder style.fill: "#7C3AED" style.font-color: "#FFFFFF" } # 异步中间件 mq: 商品变更消息 { shape: queue style.fill: "#EA580C" style.font-color: "#FFFFFF" } # 依赖关系 app -> gw: API 请求 gw -> product: 商品查询/管理 gw -> stock: 库存查询 product -> product_db: 读写 stock -> stock_db: 读写 product -> mq: 发布变更事件 stock -> mq: 发布库存事件这里我故意用了固定色板和统一形状。把它提交到Git之后,每次修改都留痕,比任何人用画图软件导出一次PNG然后发群里的流程可靠得多。
有一个细节要说明:我在模板顶部配置了layout-engine: dagre,这是D2的自动布局引擎。它能让依赖关系更规整,减少交叉线。但自动布局适合"导出成图"的场景,如果你希望某些节点固定位置,D2也支持手动指定坐标,这在后文排查部分我再展开。
4.3 用 PlantUML 落地一张时序图
时序图在技术评审里出场率特别高。登录、下单、支付退款,所有涉及多个系统协作的流程,一张清晰时序图胜过大段文字。
PlantUML 的画法,我建议团队统一使用简洁风格,别加太多花哨的颜色。一个规范的登录时序图模板:
@startuml skinparam sequenceMessageAlign center skinparam maxMessageSize 200 actor 用户 participant "前端" as FE participant "认证中心" as AUTH database "用户库" as DB participant "会话服务" as SESSION 用户 -> FE: 输入账号密码 FE -> AUTH: POST /login AUTH -> DB: 查询用户 DB --> AUTH: 返回用户信息 alt 校验通过 AUTH -> SESSION: 创建会话 SESSION --> AUTH: token AUTH --> FE: 登录成功 + token FE -> 用户: 进入首页 else 校验失败 AUTH --> FE: 401 错误 FE -> 用户: 显示错误提示 end @enduml画时序图最忌讳的是把所有消息都塞进一张图。经验值是"一图一事",一个时序图只讲清一个业务场景。如果你发现需要画超过8个参与者,赶紧拆图,否则评审时没人能看清消息顺序。
另外,alt/else 这种分支块在评审中要重点检查,因为它最容易隐藏bug。有人漏画异常分支,有人把全部异常都画成else,这些都会误导后面对代码的信任。
4.4 导出前的视觉走查清单
图代码写完了,不代表就可以直接贴文档。我在diagram-design里专门维护了一份导出前走查清单,每次提交图片前照着过一遍:
- 标题是否清晰?每张正式文档里的图都应该有图号和标题;
- 字体是否统一?中文字体统一用系统标准的无衬线体,不允许出现宋体之类的衬线字体;
- 颜色是否都在语义色板内?如果图里有色板之外的颜色,要么是有意的风险标识,要么就是违规;
- 是否有孤立节点?没有任何连线关系的节点,要么删除,要么补上边界框说明它存在的意义;
- 边界是否有明确标注?多人协作的图,哪个区域是谁的职责要一目了然;
- 文字是否溢出节点?D2和PlantUML一般不会,但draw.io手工排版很容易出现文字被截断。
这份清单我打印贴在工位上,也写进团队的README。每次导出图片前五分钟扫一遍,能避免很多低级返工。
5. 常见问题与排查技巧实录
5.1 中文乱码和字体问题
代码生成图最常见的坑是中文乱码。D2在国内用户环境下一般问题不大,但PlantUML需要本地安装字体支持,否则生成出来的图片里中文全变成方框。
我的排查顺序是:先看本地系统字体是否缺少中文字体,再看PlantUML的渲染命令里是否显式指定了字体。PlantUML里可以通过 skinparam 指定:
skinparam defaultFontName "Microsoft YaHei"或者用:
skinparam defaultFontName "PingFang SC"Mac和Windows各选对应的中文字体。D2则可以在全局样式中指定字体族,例如:
vars: { d2-config: { font-family: "PingFang SC, Microsoft YaHei" } }这个步骤不做,你的图即使布局再好看,导出也是白费。
5.2 自动布局的"失控"现象
代码生成工具的自动布局在节点少于10个时效果很好,节点一旦超过20个,就会出现连线交叉、节点重叠、布局诡异的现象。
我的建议优先级从高到低是:先拆分图,一张图画一个子系统或一个业务场景;拆完之后还不行,再手动干预局部坐标。比如D2里可以给某个节点指定坐标:
product: 商品服务 { style.fill: "#2563EB" style.font-color: "#FFFFFF" top: 400 left: 300 }但手动指定坐标属于"高成本维护"方案,一旦节点位置变了,后续所有相对定位都要重新调整,所以能拆图解决的绝对不要去拖拽。
在实际项目里,一张图超过20个节点说明你的系统职责边界划分可能有问题。这本身就是一个值得反思的信号。
5.3 协作冲突:图被多人改乱
当图进入Git之后,协作冲突还是会存在,尤其是多人同时修改同一个D2文件。这本身不是坏事,Git会帮你看到冲突。
真正让图变乱的往往是"规范没人执行"。有些同学画图的时候不复制模板,从旧图上复制粘贴一堆过期节点,或者临时起意加了一个语义之外的颜色。这个问题的根治办法是把检查做成自动化的,在CI流程里加一个脚本,校验D2文件是否包含禁止的颜色值、是否包含未定义的形状。这个脚本不需要多复杂,核心就是字符串扫描加规则判断。
如果团队还没有CI,最低成本的做法是在MR描述里加一条"是否使用diagram-design模板",把人工提醒做成习惯。
5.4 导出图片模糊不清
另一个高频问题是"图明明很清晰,导出到文档里就糊了"。原因是导出成了低分辨率PNG。
我的统一要求是:一切正式文档优先用SVG,SVG是矢量格式,任何缩放都不会糊;如果平台不支持SVG,必须导出2倍图甚至3倍图PNG。例如D2导出PNG时可以用--scale 3参数:
d2 --scale 3 input.d2 output.png这个参数会把输出图片的分辨率提升三倍。PlantUML导出PNG时也可以通过参数调整分辨率,但更省事的方案是直接导SVG再转换。
这条建议看着简单,能帮你省掉无数"图片看不清"的沟通成本。
5.5 历史图没人维护怎么办
我相信很多团队的真实情况不是"没有规范",而是"历史包袱太重"。之前用在线白板画的图,或者已经导出成PNG贴进WIKI的图,根本没法批量迁移。
我的处理策略是分三步走:
- 存量图全部归档进 99-archive 目录,不删除,但明确标记"仅历史参考";
- 新建文档一律强制使用新模板;
- 每个月挑一到两张最高频引用的存量图,重绘成新格式,逐步消化。
不用想着一次性把几十张图全部重画,不现实,也没必要。先把新增的图管起来,历史图按需重绘,半年之后整个文档库就会干净很多。
6. 最后分享一点我的真实体会
在我实际推行 diagram-design 的过程中,最大的阻力不是什么技术问题,而是"意识"。很多人觉得画图是小事,随手就来,没必要搞一堆规矩。但我见过太多次因为一张过期的架构图,让新人和外部协作方把系统理解错了,导致线上事故的案例。
图表是技术债务的一部分,而且是被严重低估的一部分。图和代码一样,只有被持续维护、被review、被纳入版本管理,才能成为可靠的团队资产。我的建议是:先把形状语义、配色、工具链这几项定下来,其他细节可以边跑边补,不要一开始追求完美,动起来比什么都重要。
最后再分享一个小技巧。我在团队里每周五下午都会花十分钟,随机挑一张本周新增的图做"视觉走查",不看内容只看规范。这种做法不会占用大量时间,但能让所有人持续意识到"图也是要维护的"。坚持几个月以后,团队里再也没出现过那种配色素乱、虚线实线乱用的架构图。将来如果这套体系跑顺了,我会考虑把它扩展成一份公共组件库,配合代码仓库自动发布,让画图这件事真正变成开箱即用。