在很长一段时间里我对“画图”这件事的态度都是能拖绝不敲。鼠标点点拽拽拉几条带箭头的线图就出来了看着还挺规整。直到某个项目迭代到第三个月架构图改了十几版每次评审前都要花半天手动挪框、调线、确认箭头没指错方向我才下决心把所有图表全部迁移到代码化的 diagram-design 工作流。所谓 diagram-design简单说就是不再交给别人一张静态的 PNG而是交出一套描述性的源文件。图表不再是“画出来的”而是“写出来的”。改图不再是打开画板找框而是改一行文本重新生成。这个转变听起来只是工具换了实际上把图表的定位从一次性的交付物变成了一种和代码一样可评审、可版本控制、可持续维护的资产。这篇文章我不打算讲空泛的方法论就从我真实切换这套工作流的经历出发说说我为什么换、踩过哪些坑、怎么用代码把一张大图从零到一设计出来以及这套方式对我团队协作方式产生的连锁反应。如果你也在为架构图、数据流图、部署图这些持续过期的文档头疼这篇内容应该能给你一条能直接“抄作业”的路。1. 先说说我为什么把拖拽画图换成手写代码过去的画图流程估计很多人都熟画布拉大从左侧图形库里拖矩形、圆角框、圆柱体然后一条线一条线地连。刚开始还好图简单框少线直。等到系统慢慢变复杂问题就全冒出来了。最让我崩溃的是第三个月那次大改版。当时要调整整个调用关系把原来服务A直连服务B改成经过一个消息队列。听起来只是改两条线的事但实际改动牵涉了十几个节点所有和这两个服务相关的连线都得检查。我花了两个小时拆线、接线、重新对齐标签。改完之后发给前端同事他回了一句“这里面订单服务和支付服务的箭头好像反了。”我打开图一看确实是反的。问题是这已经不是我第一次改错了。这种错误很难靠眼睛发现——框一多线一交叉人眼对方向的判断力会急剧下降。第二次让我下决心的场景是项目评审。评审用的架构图是三个月前画的里面已经有三四个服务改名了还有一个新加的模块完全没画上去。我没时间改只能一边讲一边口头解释“这张图有点旧了大家忍一下”。作为开发者我接受不了代码里有过期注释却默默接受了文档里有过期架构图这本身就很不合理。接触 diagram-design 之后我把思维重新梳理了一遍图表不应该是一张“图”而应该是一个“程序”的输出结果。我把节点定义、分组关系、连线条目全部写在一个文本文件里然后运行一条命令生成 PNG、SVG 或者其他格式。图表的所有信息都在文本里不会出现“图里画的和真实系统不一致”这种低级问题——因为源文件就是参照代码事实写出来的清清楚楚。这里有个额外收益代码化生成的图表天然是可以用diff对比的。改动之前是什么样改动之后是什么样加了哪些节点删了哪些线一目了然。拖拽画图做不到这一点你没法让 Git 帮你 review 一张图片的变更。2. 动手前先想清楚diagram-design 要解决的核心问题在开始抄工具、写代码之前我很建议先定义清楚一个问题你画图是为了给别人看还是为了让自己想清楚系统这两种目的对图表的要求完全不一样。如果是“为了让自己想清楚”那图的重点在于信息层级不需要漂亮甚至可以很丑只要逻辑关系正确。如果是“为了给别人看”那就要考虑图的可读性颜色不要超过三种、连线尽量少交叉、重要节点放在视觉中心。diagram-design 这套工作流最有意思的地方在于它能把这两种需求统一起来——源文件负责表达逻辑输出阶段负责控制视觉表现。我把需要解决的问题拆成三个维度第一个维度是一致性。架构图最大的问题是容易过期。代码化的思路是让图表描述和代码事实同源比如从部署清单、配置文件中读取节点的定义生成图表时自动带入。这样代码改了图也跟着改源文件描述的永远是最新的状态。第二个维度是高效协作。多人同时改一张图的时候拖拽式工具几乎没法做差异合并。文本文件则没有这个问题每个人改自己负责的那一段提交分支最后合并和改代码的流程完全一样。这意味着图表评审可以像代码评审一样进行。第三个维度是输出稳定性。手动画图的时候今天心情好框就对齐一点赶时间的时候线就随意拉。代码化之后只要源文件一致输出的图就完全一样格式、字体、清晰度都能固定下来不会出现同一个架构图在不同文档里长得完全不像的情况。如果想得更细工具选型上也有讲究。我把市面上的方案从输入方式上分成了四类方案类型输入方式布局能力维护成本适合场景纯拖拽工具鼠标操作强但取决于人高改一次挪半天一次性提案、快速脑暴文本描述型Markdown 风格文本中自动布局低改动即改文日常文档、快速草图编程代码型用代码定义节点和边高可精细控制中需要一点编程基础大型系统架构图、需要承接数据生成的场景专用 DSL 型领域专用语法中高中低语法有学习成本特定类型的图如时序图、状态图我自己主力使用的是编程代码型原因很简单最后一个项目是个微服务系统服务数量接近三十个节点之间存在大量的数据依赖和调用依赖。这种规模的图拖拽工具基本是一打开就劝退文本描述型工具在控制复杂布局的时候又显得很吃力。编程代码型给了我足够的自由度——我可以用函数把同一类节点批量生成也可以把一组相关服务包成一个集群想怎么组合就怎么组合。3. 一条路走通从需求描述到一张可交付架构图理论讲完落点实操。我用一个虚构的“订单中心”系统举例带你走完整条路径。这个系统不算复杂但足以展示 diagram-design 的核心思路信息架构先行、节点分组控制、连线表达语义、样式提升可读性。3.1 先写信息架构别急着开画很多人画图第一件事是打开工具开始拖最后图特别容易乱。正确的顺序是先用文字把图画出来。我一般先写一个纯文本清单客户端模块 服务层 API网关 订单服务 库存服务 数据层 主数据库 缓存集群这一步的目的是把图画里到底有哪些东西定下来。我见过太多人图拉到一半才发现漏了一个服务然后又补框又改线整体布局全乱。先列清单至少保证内容完整。3.2 用代码定义节点与分组信息架构定下来之后就可以用代码表达层次关系了。下面是一个简化的演示代码展示节点和集群的写法from diagrams import Diagram, Cluster, Edge from diagrams.generic.compute import Server from diagrams.generic.database import SQL from diagrams.generic.network import Internet from diagrams.generic.analytics import Analytics with Diagram(订单中心架构, directionLR, showFalse, outformatpng): user Internet(客户端) with Cluster(服务层): api Server(API网关) order Server(订单服务) stock Server(库存服务) with Cluster(数据层): main_db SQL(主数据库) cache SQL(缓存集群) user Edge(labelHTTPS请求) api order order Edge(label读写) main_db order stock order cache这段代码的思路是Diagram是画布Cluster是带边框的分组节点用对象表示表示方向的连线。我选择编程代码型而不是文本描述型原因从这段代码里就能看出来——当系统复杂到一定程度“给节点加属性”“批量生成节点”“根据外部数据动态生成图”这些需求只有代码能扛得住。如果你只是偶尔画个简单流程图文本描述型可能更加轻便但一旦开始做大图代码的扩展性优势就体现出来了。代码里的directionLR表示从左到右布局。这个参数值得多花点心思很多图看起来乱根本不是结构乱而是布局方向选错了。调用型链路适合LR从左到右分层架构适合TB从上到下数据流转图适合RL从右到左。选错方向再好的结构也会让人看起来费劲。3.3 连线的语义比数量更重要一张图质量高低很大程度取决于连线。我踩过一个典型坑把所有关系都用同一种箭头表示最后整个图变成一团乱麻——调用、数据读写、消息通知全长得一样没人分得清。在 diagram-design 里建连的时候就该想清楚每条线的语义。上面示例代码只写了三种关系用户到网关是请求订单服务到主数据库是读写订单服务到缓存是加速访问。如果换成完整系统我一般会把连线分成四类同步调用实线箭头标注接口名异步消息虚线箭头标注消息主题数据读写实线箭头标注“读写”或 CRUD配置下发点划线标注配置项名称区分这四类关系的价值在于评审的时候大家能直接从箭头样式上判断这是个什么样的依赖而不是从文字标签里一个字一个字抠。人的视觉系统对形状比对文字敏感得多这是天然优势。3.4 样式与标注让人三秒钟抓住重点代码生成的图默认是朴素风格的但并不意味着不能做视觉设计。我会用两个手段提升可读性。第一个是颜色分组。可以用节点属性给不同语义的模块上不同的色对外暴露的网关用一套色核心业务服务用另一套色基础设施用第三套色。注意颜色数量控制在三到四种超过这个数图就会变成花坛而不是架构图。第二个是图例。如果整个图用了多种线型、多种颜色一定要放一个图例区块告诉读者绿色虚线代表什么、蓝色实线代表什么。很多程序员画图不重视图例但看图的人十有八九不是画图的人他们需要解释成本越低的图越好。做完这一步运行的输出就已经是一张结构清晰、语义明确、可以交付的图了。整个过程里我基本没碰一点鼠标全是在编辑器中完成。4. 代码化图表真正值钱的地方可持续维护一开始我以为换到 diagram-design 只是让画图更快后来我发现这个想法太保守了。它最大的价值根本不是“生成速度快”而是让图表变成了一种可持续维护的资产。4.1 把图表源文件纳入版本管理我现在的做法是所有架构图的源文件都放在代码仓库的docs/目录下和项目代码共用一个仓库。改动代码改了接口顺手把架构图源文件也改了提交的时候一起进 PR。评审的人看到代码 diff 旁边还有图表 diff会顺手检查一下逻辑是否一致。没有这一点图很快又回到“三个月后过期”的老路。只把图当成输出物每次画完就把工具关掉这个图的生命周期就到此为止了。但放到版本管理里图就和代码产生了同样的“被维护”约束。我的经验是一张图的真实寿命取决于它的源文件是否跟着代码一起提交。4.2 建立节点命名规范代码写多了都会强调命名规范图表源文件里的节点命名同样重要而且更容易被忽略。我团队里现在的约定是节点 ID 用驼峰命名必须和代码里对应的服务名一致节点显示名用中文或英文统一不能在同一个图里混用集群命名前缀统一比如cluster_开头连线的label必须写清楚协议或操作类型禁止只写“调用了”这种模糊描述这套规范的价值在一个月后才会体现出来。架构图每隔一段时间就要更新如果当时的命名没有规律改图的人往往是画图的人自己还得先做一遍考古才知道那个节点对应现在哪个服务。4.3 用模块化拆分大图系统一旦大到几十个节点一张图无论如何都会显得拥挤。我的做法是拆图不是拆成多张彼此无关的图而是拆成一张总览图加若干张细节图。总览图只画最核心的服务和最重要的依赖控制在十个节点以内。每个核心服务可以单独有一张细节图展示它内部的子模块、依赖的下游服务、涉及的数据表。两种图的源文件放在同一个目录下共享同一套节点定义。这样既不会信息过载又不会丢失细节。这和写代码很像一个函数最好控制在几十行一个类最好只干一件事那么一张图也应该只有一个主题、一组焦点。模块化拆分不仅有利于展示更有利于维护——改动只涉及局部文件不会动到全局结构。4.4 建立图表与文档的联动关系到现在我写技术方案、接口文档时已经不会再去截图了。文档里直接引用图表生成产物的文件路径写成内嵌链接或者相对路径引用。构建脚本跑一次所有文档里引用的图表同步更新不会再出现“文档说的是 A 版本图却是 B 版本”的尴尬。这个做法的前提是图表源文件必须放在和文档同一个代码仓库里。如果图躺在某个工具的云空间里文档在另一个仓库里这种联动关系就建立不起来一切又会回到手动复制粘贴的零散状态。5. 几个被反复踩的坑布局、字体、导出、协作没有任何工具是完美的diagram-design 也有它自己的脾气。接下来这几个坑都是我在实际项目中真实遇到过、并且已经找到稳定解法的。5.1 连线的交叉问题布局引擎的“天真”代码化图表最大的痛点之一就是自动布局有时候会画出一条横穿全图的连线特别难看。原因是布局引擎处理的是抽象图结构不知道你视觉上想强调哪条路径。我的解决方案有三个从简单到复杂排下来调整节点在代码里的声明顺序有时候只是换两行顺序连线路径就顺了显式控制连接路径给重要的连线指定一个绕行方向让它不要穿过核心区域如果图实在复杂到没法自动排出好效果就手动拆成更小的图这里我想强调一个心态自动布局不是用来追求完美的它的目标只是省掉手工排版维护成本的一半。剩下的一半由你通过结构调整来补齐。5.2 中文字体的渲染问题代码生成图最容易被忽视的问题之一是中文字体在导出时变成方框或者乱码。有些工具默认字体不含中文输出的图片里所有中文全成了豆腐块。我现在的做法是在配置里显式声明一个系统中文字体路径并且统一设置字族。这一点看起来不起眼但团队里至少有两个人栽在这上面过第一次导出时标题全是乱码折腾半天才反应过来是字体问题。另外还有一个小细节清晰度。如果文档要求不高PNG 够用。但如果你要把图放进打印版的方案或者投到大屏上SVG 几乎是必须的。SVG 是矢量图无损放大而且文本是真正的文本选中后还能复制。我在工程文档里现在默认都导出 SVG。5.3 集群嵌套太深层级混乱Cluster 是可以嵌套的一开始我觉得这是个很好的功能于是用得很奔放集群套集群一层又一层。结果导出来的图边框套边框视觉上极其混乱。后来我把嵌套层级限制在两层以内——最外层是系统边界里面一层是模块边界再往下的分组不是靠边框而是靠命名前缀来区分。这个限制从源头上避免了那种“盒子套盒子、套到看不出主体”的问题。5.4 与文档平台嵌入时的尺寸适配讲真这个坑非常琐碎但每次遇到都非常浪费生命。同样的图在宽屏显示器上看起来比例合适放到文档模板里就会被裁切或者拉伸变形。现在我在生成图之前会先确认目标文档的正文宽度然后显式设置输出画布的宽度参数避免整张图生成之后才发现比例不匹配。如果你有多处使用同样一张图的需求我会建议生成一份宽版用于演示一份标准版用于嵌入文档虽然浪费一点存储但省下来的适配时间非常可观。6. diagram-design 对团队协作方式的连锁反应聊完工具细节说说它对我的真实协作方式带来的改变。这个过程是逐步发生的。最开始只是我个人改图效率变高了后来发现 code review 的时候我可以直接在评审意见里引用架构图的源文件行号——“第 37 行这个连线订单服务和支付服务之间不应该走同步调用改成异步更合适。”这种精确的表达在拖拽画图时代是不可能做到的你只能说“订单服务和支付服务那根线感觉有点问题”。再后来因为图和代码同源新同学入职看文档的时候可以直接照着架构图的源文件去追代码里的对应服务。他们反馈说这比看一张静态 PNG 有用得多因为源文件里能看出画图的人当时的思考过程比如为什么这么分组、为什么这条线标了异步。还有一个很实际的点对外展示。以前要做项目汇报我得提前一小时把架构图改好、截图、贴进 PPT。现在只需要在构建脚本里更新一下图表的输出文件汇报前正常跑一遍就能拿到最新版本。图里的每个参数、每个依赖都是和当前代码事实一致的。所以如果你问我 diagram-design 到底带来了什么我不会只说“画图更快了”。我真正感受到的是图表从一个脆弱的一次性产出物变成了一个健康的、和项目同步演化的基础设施。它让团队里每个人看的都是同一张真实反映现状的图也让每个想了解系统的人多了一条从文档代码化通往代码世界的路径。最后分享一个小技巧是我踩过多次坑之后才摸索出来的每次大版本迭代结束时留出十分钟专门对着架构图源文件做一次“代码事实核对”——把图上出现的每一个节点去代码仓库里确认它是否真实存在把每一条连线对应的实际调用或数据流确认一遍。十分钟的自查省掉的是后面几小时的图文档返工。这套工作流真正稳定的状态靠的不是工具本身而是这张图和代码之间那种持续对齐的习惯。