公司动态
开源工具将Pull Request变成动画架构图,让结构变化一目了然
接手一个新仓库或者评审一个改动范围比较大的 PR 时最消耗精力的往往不是读代码本身而是先要在脑子里拼出“这次改动到底动了架构的哪一块”。文件多了之后人的短期记忆根本装不下完整的调用链和依赖关系评审就很容易退化成“局部正确、整体失控”。这个开源项目解决的就是这件事——把每一次 Pull Request 都变成一张动画架构图让“变更前”和“变更后”的结构差异直接可视化出来。先说判断它不是要替代人工 code review而是想解决评审前最重的那部分认知负担这个改动会影响哪些模块、哪些服务、哪些依赖以及架构是否正在悄悄腐化。全文会围绕四个问题展开这类工具解决的真实痛点是什么把 PR 变成动画架构图背后需要哪些技术环节如何低成本接入现有仓库和 CI 流程以及实际落地时会踩到哪些坑。无论你是后端负责人、前端小组 leader还是刚接触开源项目的新人这篇文章都会给你一个可执行的接入思路。先澄清一个容易混淆的点本文说的 PR 是 Git 领域的 Pull Request合并请求不是视频剪辑软件 Premiere Pro。如果你搜索相关关键词时看到大量“PR 下载”“PR 安装包”的内容多半是被同名软件干扰了。本文介绍的是代码评审场景下的开源工具。1. 这篇文章真正要解决的问题先看一个最常见的场景团队里有人提交了一个改动 800 行的 PR涉及 3 个服务、改了十几个接口。传统评审方式是什么打开文件列表逐个文件看 diff然后在脑子里把这次改动影响到的模块拼成一张图。遇到调用链比较深的代码还要在 IDE 里来回跳转或者去问“这个服务是被谁调用的”。这个过程至少有四个痛点。第一上下文切换成本极高。读代码本身就是连续注意力事件但 diff 是按文件组织的大脑要在多个模块之间反复加载和卸载上下文一次评审下来真正理解业务逻辑的时间反而不多。第二静态架构图一定会过期。很多仓库里都有一份 architecture.md画着系统架构图但通常没人维护因为架构图更新是额外负担。等到新人入职照着旧图理解系统很容易踩坑。第三架构变更没有“变更记录”。架构评审如果只发生在架构评审会议上那就太晚了。真正应该被评审的是每一次 PR 对系统结构的实际影响但这些影响往往藏在成百上千行 diff 里。第四新人上手成本高。一个新人要理解“为什么 order 服务依赖 user 服务”“为什么新增一个 payment 服务中间经过了什么”只能靠问人和翻代码缺乏一张随时间演进的动态结构图。所以这个项目切入的点非常准把架构图从“人工维护的静态文档”变成“每次 PR 自动生成的动态产物”。它做的是在 diff 进入 main 分支之前就把结构变化呈现出来。这不是为了替代评审人而是把评审的起点抬高——你不需要先从零理解系统而是先看到差异再决定要不要深入细节。方式维护成本实时性适合场景人工读代码脑补高每次评审都要重来小改动静态架构图文档低容易过期团队入门PR 驱动动画架构图中每次变更自动更新中大型项目2. 核心概念PR、架构图与动画化在继续之前先把三个关键词讲清楚。Pull Request合并请求是 Git 工作流里发起代码变更的标准方式。一次 PR 通常包含一个 feature 分支相对主分支的全部提交。对于自动化工具有一个天然好处它天然提供了“变更前”和“变更后”两个状态这正是生成差异对比所需的输入。很多工具把目光放在“评审完是否通过”上而这类工具把目光放在“这次变更的结构本质是什么”上角度完全不同。架构图这个词在不同场景下含义差别很大这里需要明确层次。一种粒度和服务级别画出网关、订单、用户、支付几个服务之间的依赖关系适合全局视角另一种是模块级别关注某个服务内部的 package、module 和 import 关系适合评审具体改动还有一种是类级别基于 AST 分析对象之间的关联信息量最大但噪音也最大。实际工具通常允许配置 nodeGranularity决定在哪个层级生成节点和边。“动画化”最容易被误解成花哨包装。实际上它的核心价值不是好看而是用时间轴替代空间对比。传统 diff 工具展示两个 SVG 并排摆放静态图片需要读者自己对齐做 mental diff。动画架构图则是先渲染变更前的结构再平滑过渡到变更后的结构新增的节点和边用一种颜色高亮删除的用另一种颜色淡出。这个过程直观呈现了一次 PR 对架构的“作用力”比看两份静态图高效得多。另外要注意这个项目标了 open-source开源这一点对很多团队至关重要。自托管意味着代码不会出内网安全审计更容易通过同时也意味着你可以自己改渲染逻辑甚至把生成的架构数据接入自己的架构治理平台。对于一个内部项目来说这种可定制性往往比功能清单更值钱。3. 从 PR 到动画架构图原理拆解虽然这个项目对外表现是“一个命令生成一张图”但背后的处理链路并不简单。按我的理解它至少包含四个环节。第一个环节是 diff 提取。为了准确对比工具需要拿到 PR 的 base 分支和 head 分支两套代码快照。常见做法是用git fetch拉取目标分支再用 worktree 把两个版本同时 check out 到本地避免反复切换目录。如果你在 CI 里跑还要注意 GitHub Actions 默认只拉取浅克隆必须设置fetch-depth: 0否则拿不到完整的 base 历史。第二个环节是结构建模。工具会扫描指定路径下的源码通过词法分析和语法分析提取出模块、类、函数、接口、依赖关系。对于不同语言这一步的实现难度差别很大。像 TypeScript 可以借助 TypeScript Compiler API 拿到完整的类型信息Java 可以用语法树加 classpath 分析依赖Python 则相对复杂一些因为 import 可能藏在条件语句里。开源项目一般会选择先支持某一种主语言再逐步扩展。第三个环节是差异对比。把 base 和 head 两个版本分别建模后工具会进行图匹配哪些节点是原有的哪些是新增的哪些被删除了哪些边的方向发生了变化。真正有挑战的是“改名如何识别”——一个服务从OrderService重命名为OrderCore在 AST 上是删了一个节点加了一个节点但语义上是同一个东西。好的工具会结合相似度算法做匹配避免把一次重命名渲染成一场大地震。第四个环节是动画渲染。结构模型转成可视化图形后动画可以输出为 SVG支持 CSS transition 和 SMIL、GIF、WebP甚至 HTML 页面。渲染层要考虑节点布局算法通常用层次布局或力导向布局布局不稳定会导致动画看起来像在乱跳体验很差。很多项目会固定一个稳定的坐标映射让节点在变更前后尽量保持原位只在结构真正变化的地方做移动和增删。理解了这个链路你就能预判它适合什么场景、不适合什么场景。适合的是以服务、模块、核心类为粒度的架构评审不适合的是想拿它做精确到行的代码 diff那是 git diff 和 IDE 的职责。4. 环境准备与前置条件在接入之前先确认基础环境。下面这些要求是这类工具的通用前置条件具体版本请以项目 README 为准。Git 2.x用于拉取分支、创建 worktree、计算 diff。低版本的 Git 对 worktree 支持不完整。Node.js 16 及以上或 Python 3.9 及以上取决于项目主要用哪套运行时。建议看 README 里写明的安装方式。一个 GitHub 仓库或兼容 Git 的托管平台用来实际验证 PR 场景。GitHub Actions 接入最顺滑。GitHub Token 或 CI 平台的凭据如果要在 PR 上自动评论需要pull-requests: write权限。如果你不打算马上接 CI只想本地试一下那么只需要 Git、运行时和一个真实的仓库副本。建议先用一个结构清晰的中小型开源仓库测试而不是一上来就扫公司那个几百万行代码的 monorepo。安装通常只需要一条命令。下面用pr-arch-diagram作为示例命令名实际项目叫什么以 README 为准。# 以 npm 全局安装为例 npm install -g pr-arch-diagram # 检查是否安装成功 pr-arch-diagram --version # 如果项目使用 Python 发行 # pip install pr-arch-diagram如果安装时遇到网络问题先检查 npm registry 配置如果是内网环境建议使用公司 npm 镜像不要使用任何不合规的代理方案。安装后先跑一次--help确认支持的子命令列表再继续下一步。5. 核心流程从本地验证到 CI 自动接入这类工具最合理的用法是“CI 自动触发 PR 评论展示 产物可下载”。本地方便调试CI 才能形成团队规范。先看本地怎么跑通。在 PR 分支上执行生成命令把 base 和 head 分别指向目标分支和当前分支。以常见的 GitHub 仓库为例# 先切到 PR 分支并拉取最新主分支 git checkout feature/checkout git fetch origin main # 生成当前改动对应的架构对比图 pr-arch-diagram diff \ --base origin/main \ --head HEAD \ --format svg \ --output ./arch-diff.svg # macOS 直接打开查看 open arch-diff.svg # Linux 可以用浏览器打开 xdg-open arch-diff.svg通过这一步你可以先验证工具是否兼容你的项目结构输出的图形是否足够清晰。如果本地生成都失败不要急着配 CI先在本地把问题解决。本地验证通过后再把它接进 GitHub Actions。这里的核心思路是在pull_request事件触发时用 worktree 的方式拿到 base 版本和 head 版本生成 diff 图然后上传产物并在 PR 下留言。下面是一个完整的 workflow 示例你可以对照自己的项目修改路径和命令名。# 文件路径.github/workflows/pr-architecture-diagram.yml name: pr-architecture-diagram on: pull_request: types: [opened, synchronize, reopened] permissions: contents: read pull-requests: write jobs: diagram: runs-on: ubuntu-latest steps: - name: Checkout PR head uses: actions/checkoutv4 with: fetch-depth: 0 - name: Prepare base version run: | git fetch origin main:refs/remotes/origin/main git worktree add /tmp/repo-base origin/main - name: Install diagram tool run: npm install -g pr-arch-diagram - name: Generate architecture diff run: | pr-arch-diagram diff \ --base /tmp/repo-base \ --head $PWD \ --format svg \ --output ./arch-diff.svg - name: Upload artifact uses: actions/upload-artifactv4 with: name: arch-diff path: ./arch-diff.svg - name: Post comment uses: actions/github-scriptv7 with: script: | const fs require(fs); const svg fs.readFileSync(arch-diff.svg, utf8); const uri encodeURIComponent(svg); const comment ## 架构变更图\n\n\n; await github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: comment });这段 workflow 里最值得留意的有三点。一是fetch-depth: 0没有它你拿不到 base 分支完整历史。二是 worktree 方式它让你在不切分支的情况下同时保留两套代码副本适合需要同时扫描 base 和 head 的场景。三是权限配置pull-requests: write是发评论所必需的最小权限其他地方尽量用read。数据 URI 方式的 SVG 评论在 GitHub 上未必都能正常渲染如果发现不显示可以退化为在评论里贴 artifact 链接。6. 完整示例与配置解析为了让配置更加可控大多数工具会支持一个配置文件让你指定扫描范围和渲染参数。下面是一份演示用的配置结构字段名很可能跟实际项目不一致务必以 README 中的 schema 为准。# 文件路径.archdiagram.yml project: language: typescript entryPoints: - src/index.ts arch: scanPaths: - src ignore: - **/*.test.ts - **/*.spec.ts nodeGranularity: module # service | module | class animation: format: svg highlightAdded: #22c55e highlightRemoved: #ef4444 durationMs: 1500 ci: postComment: true artifactName: architecture-diagram逐项解释一下配置的含义。project.language告诉解析器使用哪个语言的语法分析器不同语言依赖的解析库差别很大entryPoints是可选字段用于让解析器知道从哪些入口开始分析调用链不填就全量扫描。arch.scanPaths控制扫描范围这一步很关键如果仓库里既有业务代码又有脚本和测试建议用 ignore 排除测试文件否则生成的图会非常杂乱。nodeGranularity直接决定图的抽象级别服务级适合全局评审模块级适合日常 PR类级噪音大适合单模块深度分析。animation里的颜色和时长是渲染层的调参项highlightAdded和highlightRemoved尤其重要因为架构图的核心是让变化一眼可见。为了让“从结构数据到图形”这一步更直观我写了一个极简的 Node.js 示例模拟渲染层的工作给定服务和依赖边的列表输出 base 和 head 两张 SVG 图。真实生产级工具会用完整的语法树和依赖图驱动但渲染逻辑的基本思路是一样的。// 文件路径scripts/build-pr-diagram.js const fs require(fs); function buildGraph(services, edges) { const gap 170; const startX 100; const startY 120; let svg ; svg edges.map(([from, to]) { const x1 startX from * gap; const x2 startX to * gap; return line x1${x1} y1${startY} x2${x2} y2${startY} stroke#94a3b8 stroke-width2 marker-endurl(#arrow)/; }).join(\n ); svg services.map((name, i) { const cx startX i * gap; return ( g\n rect x${cx - 55} y${startY - 25} width110 height50 rx8 fill#3b82f6/\n text x${cx} y${startY 5} text-anchormiddle fill#fff font-size14${name}/text\n /g ); }).join(\n ); return svg; } const svgTemplate (title, content) ?xml version1.0 encodingUTF-8? svg xmlnshttp://www.w3.org/2000/svg width800 height200 text x20 y30 font-size16 font-weightbold${title}/text defs marker idarrow viewBox0 0 10 10 refX8 refY5 markerWidth6 markerHeight6 orientauto-start-reverse path dM 0 0 L 10 5 L 0 10 z fill#94a3b8/ /marker /defs ${content} /svg; const beforeServices [gateway, order, user]; const beforeEdges [[0, 1], [0, 2]]; const afterServices [gateway, order, user, payment]; const afterEdges [[0, 1], [0, 2], [1, 3]]; fs.writeFileSync(before.svg, svgTemplate(before: main, buildGraph(beforeServices, beforeEdges))); fs.writeFileSync(after.svg, svgTemplate(after: feat/checkout, buildGraph(afterServices, afterEdges))); console.log(OK: before.svg / after.svg generated);运行方式很简单node scripts/build-pr-diagram.js这个示例虽然简单但有两点值得说明。第一它演示了渲染层应该保持纯粹的“数据结构到图形”映射不掺杂业务逻辑这样后续换布局算法或换输出格式都很容易。第二它把 base 和 head 分别输出成独立 SVG你可以在浏览器里自测效果体会一下“两张静态图”和“一个动画过渡”之间的体验差距。真正的开源项目通常会把这两帧合成为动画常见做法是在 SVG 里用animate标签或 CSS transition 实现节点增删和边移动。7. 运行结果与效果验证运行结束后你需要确认三件事。第一命令是否成功。如果看到OK: before.svg / after.svg generated这类输出说明流程已经走到渲染环节。如果没有输出文件先检查--output路径是否有写权限以及扫描路径是否真的存在源码。第二生成的 SVG 是否包含有效结构。用浏览器打开后应该能看到服务/模块作为矩形节点依赖作为箭头边新增节点使用高亮颜色删除节点淡出。如果图上只有一个孤零零的节点说明语法分析没有识别出依赖关系需要检查project.language是否匹配实际语言或者scanPaths是否覆盖到了真正包含依赖的目录。第三动画是否流畅。真正的动画产物会是arch-diff.svg或其他格式包含 before 和 after 两帧之间的过渡。验证时重点看两点节点有没有在变化前后产生不合理的跳跃颜色是否能让你在 3 秒内说出“这次 PR 增加什么、删除了什么”。如果看不出变化要么是改动本身没有触及架构结构要么是差异对比逻辑没有被触达。如果 CI 运行失败排查顺序建议是先看日志里是否有权限报错403再看是否是编译或解析步骤超时尤其大仓库最后才看渲染层的报错。不要直接跳到渲染层大部分 CI 失败发生在环境准备和数据获取阶段。8. 常见问题与排查思路下面整理了几个实际接入时容易遇到的问题按出现频率排序问题现象可能原因排查方式解决方案CI 中报 403 权限不足Actions 的 pull-requests 写权限未开启检查 workflow 的 permissions 段增加pull-requests: write权限大仓库扫描超时代码量大或依赖解析耗时过长查看 CI 日志定位超时步骤缩小scanPaths提高nodeGranularity为服务级生成结果没有新增节点改动只发生在业务逻辑内部未改变结构对比 base 与 head 的模型输出属于正常情况日志会提示 no structural changes动画不播放浏览器不支持或 SVG 嵌入了外部 JS查看浏览器控制台报错导出 GIF/WebP或改用静态图片模式中文文本乱码SVG 中文字体缺失检查系统字体和 SVG 字体设置在 SVG 中指定中文字体或嵌入字体文件monorepo 识别错误多包仓库扫描边界设置不当查看配置中的scanPaths及日志按子包分别配置入口和扫描路径重命名被识别成删除新增图匹配算法没有做相似度匹配查看工具版本和算法说明升级版本或在配置中开启 rename detection关于最后一个问题值得多说一句。重命名检测是架构 diff 工具里的硬骨头处理不好会产生大量误报让评审者以为架构发生了巨变实际只是改了个名字。这往往也是这类开源项目迭代最快的地方接入前最好查一下项目的 release notes 或 issue看看对重命名检测的支持程度。9. 最佳实践与工程建议如果你决定在团队里引入这类工具以下几条建议能让它真正发挥作用。第一控制生成粒度不要一上来就扫全仓库。建议从一开始就明确“这张图是给架构评审看的不是给代码 Review 看的”所以默认粒度设置在模块级或服务级类级别只用于单独的分析任务。粒度太细图会变成毛线团反而没人看。第二PR 是生成触发条件不是唯一使用场景。除了每次 PR 自动生成还可以在里程碑或者版本发布前手动执行一次生成把这段时间的整体架构变化沉淀成一份可归档的图表。这样既能满足日常评审又能服务月度架构复盘。第三把工具输出和架构治理规则结合。不要只满足于“生成一张图”可以进一步约定如果新增依赖但没有更新配置或者出现环依赖CI 可以给出告警。当然这类规则需要谨慎设计避免过度约束导致团队抵触最好先跑通生成再逐步加规则。第四安全与授权遵循最小权限原则。CI 中的 Token 只授予必要权限不要使用具备写仓库权限的超级 Token生成产物如果包含内部模块名和依赖关系建议存放在私有 artifact 或内网存储不要默认公开。第五重视稳定性优先于丰富功能。接入初期优先保证“每次 PR 都能稳定生成、图能看懂、评论不刷屏”。如果每个 PR 都评论一张大图评审者很快会麻木。可以考虑仅在结构发生变化时评论没有结构变化就静默跳过。10. 总结与后续学习方向这个开源项目真正解决的不是“画图自动化”而是把架构理解这件事从人的脑内建模中抽离出来交给每一次 PR 自动完成。它让架构评审从“回顾性的、滞后的、依赖个人经验的”变成“变更发生时、自动的、可追溯的”。对中大型项目来说这类工具的引入价值远大于那点 CI 成本。看完这篇文章你下一步可以这样实践挑选一个结构清晰的中小型仓库本地用演示命令跑通生成再把它接到一个草稿 PR 上验证 CI 流程确认稳定后再逐步纳入团队规范。配置和字段以实际项目 README 为准重点先理解“base 与 head 两套代码快照对比”这个核心链路很多问题都能顺着这个链路定位到原因。如果想要继续深入可以关注这几条线不同语言下的 AST 解析差异、依赖图的重命名检测算法、SVG 动画与布局稳定性优化以及架构治理规则的工程化落地。把这几个方向吃透你会发现这类工具能做的事远比“把 PR 变成动画图”这几个字看上去更多。