codegraph 这个名字我第一次看到的时候,还以为是某个图像处理库。后来才发现,它其实是个代码依赖可视化工具——把项目里文件与文件之间的引用关系,变成一张可读的图。我最近在给一个用 Node.js 写的核心服务做重构前摸底,全靠它从乱麻一样的目录里理出了头绪。这篇东西就记录一下我实际使用 CodeGraph 的完整过程,包括 graphify 命令的用法、几个典型的坑,以及怎么看懂生成出来的那张大图。
如果你正在接手一个自己没写过的大型项目,或者想重构一个模块却始终不敢下手,又或者团队里来了新人需要快速理解系统结构,那这个工具对你应该很有用。它不是让你们少写代码,而是让代码之间的关系变得可见,把以前靠“人肉搜索”才能搞明白的依赖关系直接摆到你面前。
1. 为什么需要 CodeGraph?先说说“代码地图”这个刚需
1.1 接手老项目时,最怕的不是代码难懂,而是不知道谁依赖谁
我在几年前接手过一个支付中台服务,代码量不算特别大,大概两百多个文件,但目录嵌套了六层。刚接手那会儿我最想做的事,不是看业务逻辑,而是先画一张“地图”——搞清楚这个服务里到底有哪些模块,模块之间怎么调用,哪些文件是底层基础,哪些文件是业务入口。但翻代码翻了两个小时,越看越头晕:a.js引用了b.js,b.js又引用了c.js,而c.js反向又调用了a.js里的函数。这种隐性依赖靠肉眼去看,效率实在太低。
CodeGraph 解决的就是这个问题。它本质上是一个静态分析工具,不是运行时调试器。它不会执行你的代码,而是直接把源代码文件拆开,找出所有文件之间的引用关系,然后把这些关系构建成一张有向图。这张图可以导出成 JSON,也可以渲染成 PNG、SVG,甚至可以弄成带交互的 HTML 页面。对我这种需要频繁在大型项目里做技术判断的人来说,这比任何代码审查工具都更直接。
1.2 从目录树到调用图:一张图能解决什么问题?
很多人会觉得,项目里已经有目录结构了,IDE 里也能看到依赖,为什么还需要专门画一张图?我的看法是,目录树只能告诉你“文件放在哪里”,但完全无法告诉你“这个文件被谁使用、它又依赖于谁”。最典型的一种情况是:你想删掉一个看起来没用的工具函数,结果一删除,构建直接报错。为什么?因为有一个离它十万八千里的模块在偷偷 require 它。
CodeGraph 生成的图,把文件之间的引用关系变成边,边越多,这个文件被引用的次数就越多,它就越可能属于底层公共代码;如果文件之间形成了环,那基本上就是未来重构时最容易爆炸的区域。一张图摊开之后,哪些模块是高内聚的,哪些模块之间耦合严重,哪些文件是“万金油”谁都在引,全部一目了然。对我这种需要做技术决策的人来说,它提供了从代码里直接抽出来的客观依据,而不是凭印象拍脑袋。
1.3 哪些人最需要它?
按照我的实际使用经验,下面这几类人最应该关注 CodeGraph。第一是后端工程师,尤其是维护微服务、中台系统的人,服务拆分的边界到底在哪,依赖图一看就懂。第二是前端工程师,用了 Webpack、Vite 的项目里,组件依赖、公共模块抽取、按需加载的拆分,依赖图能帮你找到最优切分点。第三是技术管理者或者架构师,做新版本规划时,需要判断哪些系统可以动、哪些系统不能动,依赖图就是你的决策底图。第四是刚加入项目的新人,与其读一堆文档,不如先看一遍依赖图,至少知道代码入口在哪里。
2. CodeGraph 的核心原理:它到底是怎么把代码“画”成图的?
2.1 第一步:解析语法树,找出引用关系
CodeGraph 的第一步,是把每个源文件读进来,然后交给对应的语言解析器去生成 AST。抽象语法树,你可以把它理解成一棵把代码结构完全拆开的树:每个关键字、变量名、函数调用都变成了树上的节点。CodeGraph 不需要理解这些代码的业务含义,它只关心一件事——文件里出现了哪些引用语句。
在 JavaScript 里,它识别的是import语句、require()调用、export ... from这类语法;在 Python 里,它识别的是import xxx和from xxx import yyy;在 Java 里,它识别的是import com.xxx.yyy。它把这些语句的目标路径提取出来,再结合当前文件所在目录算出实际的目标文件路径。这一步如果做错了,后面的整张图就全是错的。所以你在用 CodeGraph 的时候,最好先让它扫描代码,再看它识别出的节点数量是否合理。如果明显少了太多,通常是解析器没有匹配到你的代码风格,或者是文件编码有问题。
2.2 第二步:把文件/模块变成节点,把引用变成边
有了引用关系之后,CodeGraph 会把每个源文件看作一个节点,把一次引用关系看作一条有向边。比如router.js里import auth from './auth',那图中就会多出一条边,从router.js指向auth.js。这条边的方向是有讲究的,它表示依赖方向,也就是“谁依赖了谁”。我习惯中的表达是:箭头指向被依赖的文件。
这么设计的好处是,当你看到一张大图时,如果一个节点只有很少的边指向出去,却有很多边指向它,那这个节点十有八九是底层基础模块;反过来,如果一个节点有很多边指向出去,却很少有边指向它,那它更可能是业务入口或者外部接口层。这种方向性给了你一个非常直观的分类维度。此外,CodeGraph 也会尝试把属于同一个目录或者同一个包的文件归到同一个簇里面,在图上用大圆角矩形框起来,避免几百个节点的图完全摊成一团乱麻。
2.3 graphify 到底做了什么?
graphify 这个词看起来像是一个加上了动词后缀的命令,其实就是“把代码图化”的过程。很多人第一次看到codegraph graphify会以为要装两个工具,其实不是。graphify 是 CodeGraph 提供的核心子命令,负责执行从源码扫描到生成图谱数据的完整流程。
我在用的这个版本里,graphify 会接收一个路径参数,然后自动递归遍历目录,跳过常见的node_modules、.git、dist、build这些非源码目录,再对每个源码文件做一次 AST 解析。解析完以后,它会汇总出一个包含节点信息、边信息、目录簇信息和依赖统计的中间数据模型,最后再根据用户指定的格式输出。你可以在命令行里直接输出 dot 格式,也可以输出 JSON 和 HTML。如果只想快速看一眼依赖关系,dot 格式配合 Graphviz 渲染是最常用的路线;如果想二次分析和处理,JSON 格式更适合你。
3. 安装与快速上手:5分钟内跑起来
3.1 环境准备
在开始之前,你需要确认自己机器上有没有对应的运行时。以我常用的 npm 版本为例,安装 CommandGraph 的命令是:
npm install -g codegraph安装完成之后,在终端里输入codegraph --version,能正常输出版本号就说明环境没问题。如果你是做 Python 项目分析,想直接在 Python 生态里跑,也可以用 pip 安装另一个包装版本:
pip install codegraph我个人建议首先试试 npm 版本,因为它的命令统一,而且对前端项目的支持更自然。另外,CodeGraph 本身只负责生成图数据,如果你想把图渲染成图片,那还需要安装 Graphviz。Graphviz 在 macOS 上可以用brew install graphviz,在 Ubuntu 上可以用apt install graphviz。这一套装完,你的工具链基本就齐了。
3.2 用 graphify 命令生成第一张图
假设你有一个项目,路径是/Users/me/work/my-project,目录下有src和test两个子目录。第一次使用的时候,我建议你先跑一个最保守的扫描命令:
cd /Users/me/work/my-project codegraph graphify --entry src --output graph.dot --format dot这里--entry指定入口目录,--output指定输出文件名,--format指定输出格式。执行完以后,项目里会出现一个graph.dot文件。这个文件是纯文本的,你可以直接打开看,里面描述的就是整个项目依赖关系。接着用 Graphviz 把它渲染成图片:
dot -Tpng graph.dot -o graph.png如果项目不大,这一步基本是秒出图。生成以后打开graph.png,你就能看到自己的项目长什么样了。我第一次看到自己那个支付中台项目的完整依赖图时,说实话挺震撼的,很多平时根本没注意到底层依赖关系,全部清清楚楚地暴露在图上。
3.3 输出格式怎么选
CodeGraph 支持的输出格式里有几种比较实用,我通常在调研阶段用html,给团队分享时用png,做自动化分析时用json。HTML 格式的好处是节点可以拖拽,边的详情可以通过悬浮查看,适合自己反复研究;PNG 则适合贴到周报或者文档里,但节点多了之后会比较拥挤;JSON 则保留了高度的结构化信息,方便自己写脚本统计每个节点的入度、出度,从而定位高风险文件。下面是我常用的格式选择场景:
| 输出格式 | 典型命令 | 适合场景 |
|---|---|---|
| dot | --format dot | 配合 Graphviz 二次渲染,灵活度最高 |
| json | --format json | 写脚本统计依赖关系,做自动化检测 |
| html | --format html | 本地交互式查看,拖拽浏览 |
| png | 配合 Graphviz 渲染 dot 生成 | 文档插图、评审汇报 |
4. 实例过程:前端项目依赖分析实操记录
4.1 项目背景与目录结构
我在上个月分析了一个小型前端项目,它规模不大,但模块拆分非常碎。项目结构大概是这样的:
my-web/ ├── src/ │ ├── api/ │ │ ├── user.js │ │ ├── order.js │ │ └── payment.js │ ├── components/ │ │ ├── Button.js │ │ ├── Navbar.js │ │ └── OrderList.js │ ├── pages/ │ │ ├── Home.js │ │ ├── Checkout.js │ │ └── UserCenter.js │ ├── store/ │ │ ├── index.js │ │ └── modules/ │ │ ├── cart.js │ │ └── user.js │ └── utils/ │ └── format.js └── package.json从目录名上你能看出大概的业务模块,但看不出Checkout.js到底依赖了哪些组件和 API,也看不出store/modules/cart.js跟api/order.js之间有没有关系。这种时候,常规代码 Review 要一个个点开文件看,但用 CodeGraph 生成一张图就足够了。
4.2 扫描与渲染命令
我执行的具体命令是这样的:
cd my-web codegraph graphify \ --entry src \ --ignore node_modules \ --ignore dist \ --include-tests \ --output graph.json \ --format json先输出成 JSON,是为了确认扫描结果没有异常。然后我把 JSON 再转成 dot 渲染:
codegraph graphify --entry src --format dot --output graph.dot dot -Tpng graph.dot -o graph.png cd my-web codegraph graphify \ --entry src \ --ignore node_modules \ --ignore dist \ --include-tests \ --output graph.json \ --format json我看了一下生成出来的graph.png,节点之间的边排布得还算清楚。里面有一个很明显的特征:Button.js和format.js这两个节点周围密密麻麻全是边,几乎所有页面组件都直接或者间接引用了它们。这就说明它们是项目里最底层的公共模块,未来如果要拆分设计系统,首选的抽象对象就是它们。
4.3 看懂结果图:三种边、两种形状、颜色含义
CodeGraph 生成的图不是简单地把所有关系画成黑线,它会根据依赖的强度做一些视觉编码。我这次看到的主要颜色和形状可以整理成一张速查表:
| 图形元素 | 代表含义 | 我给的建议 |
|---|---|---|
| 普通灰色实线 | 常规单次引用 | 正常依赖,风险低 |
| 红色虚线 | 循环依赖 | 必须关注,重构时优先处理 |
| 加粗黑色实线 | 高耦合、多文件引用 | 尽量保持稳定,不要轻易改动 |
| 圆角矩形簇 | 同一个目录下的文件 | 看模块边界用 |
| 带星标的节点 | 入口文件或主组件 | 分析调用链时从它入手 |
我看到图上Checkout.js到api/order.js之间存在一条直接边,这符合预期。但让我意外的是,store/modules/cart.js竟然通过一条红色虚线连接到了api/order.js,而api/order.js反过来又引用了store/modules/cart.js里的某个常量。这就形成了一个典型的循环依赖。平时写代码的时候根本不会注意到这种环,直到构建到某一个 chunk 的时候才发现报错,然后两个同事互相改了一轮又改回来。现在有了图,我一眼就能定位到这个问题的源头。
4.4 结合图做一次初步重构决策
拿到图之后,我开始做重构前的风险清单。第一件事是找所有入度大于二十的节点,因为这些是公共底层点,如果没有足够的单元测试覆盖,改动引起的连锁反应会非常大。第二件事是找所有环,把环上的文件单独列出来,作为第一个重构迭代的目标。第三件事是找孤立节点,也就是入度和出度都很低的文件,这些文件通常是不太重要的工具代码,可以最后再处理。
我这次分析发现,Button.js被 32 个文件引用,format.js被 19 个文件引用。这两个文件就是典型的地基。而Navbar.js虽然看起来是个组件,但入度低、出度也低,说明它几乎没有真正被业务场景使用,很可能是废弃代码,可以进入清理流程。这些结论在没看图之前,靠人工点文件也能得到,但通常需要半天时间,而且很容易漏。CodeGraph 把这次分析压缩到了十几分钟。
5. 新手最容易踩的坑与排查技巧
5.1 动态 import 与 require 识别不出来,怎么办?
CodeGraph 是基于静态语法分析的,它只能识别写死在源码里的路径。如果你写了这样一段代码:
const moduleName = './views/' + pageName; const component = require(moduleName);那 CodeGraph 无能为力,因为moduleName的值只有在运行时才知道。这不算工具的缺陷,而是静态分析的天然边界。遇到这种情况,我建议你不要硬刚扫描器,而是在项目里做一次全局搜索,把所有动态 require 的路径都列出来,手动补充维护一份外部映射关系。如果 CodeGraph 支持配置文件,你也可以把这种动态路径的解析规则写进去,让它按照规则生成虚拟节点。
5.2 路径别名导致大量 unknown 节点
很多项目会配路径别名,比如在webpack或vite里把@指向src。但是 CodeGraph 默认不知道你的路径别名是什么。第一次扫描时,我的graph.png里出现了一堆叫unknown的节点,所有@/utils/format这种引用都被它当成了外部依赖,结果图里莫名其妙多出了很多孤立节点。
解决办法是在项目根目录下创建一个 CodeGraph 配置文件,把路径别名告诉它。常见配置大概是这样:
{ "resolveAlias": { "@": "./src", "@utils": "./src/utils" } }配置好之后再次扫描,那些unknown节点就会合并到真正的文件节点上。这个坑十个新手八个一定会踩,我建议你开始扫描之前就先看一下项目里有没有jsconfig.json、tsconfig.json或者webpack.config.js,先提取出别名配置,再跑 CodeGraph,结果会干净很多。
5.3 大项目性能问题与扫描范围控制
如果你拿一个几万文件的巨大仓库直接交给 CodeGraph 扫描,可能会等很久,甚至把内存吃满。我的做法是先缩小扫描范围,比如只扫描当前要改的子目录,或者只扫描src下某个业务模块。还可以用--depth参数限制目录递归深度,比如只看前四层目录里的依赖关系。这个参数绝对是性能救星,对那种嵌套很深的项目特别有效。
当然,如果你想看全局依赖,就不要开这个参数,否则容易漏掉深层引用带来的隐性问题。另外,强烈建议把--ignore node_modules作为默认习惯,否则第三方依赖的几千文件也会被扫进来,把你自己的项目关系盖得严严实实。
5.4 图太乱没法看?试试这几个参数
依赖图最怕的就是“蜘蛛网”。节点一多,边一密,整张图就是一团黑线,根本不可能看清。我用的几个实用技巧是:第一,开启--skip-nodes <pattern>参数,把Button.js这种公共底层组件暂时隐藏,先看清业务链路;第二,用--depth 3限制深度,先看上层业务关系,再看底层实现;第三,直接使用 HTML 输出,在交互界面里一点一点展开节点关系,比静态图片舒服得多。别指望一张静态 PNG 能承载所有信息,它更多是用来做总览的。
6. 常见问题速查表
为了方便大家对照着解决问题,我把实操中最容易遇到的问题整理成了一个速查表:
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 命令找不到 | npm 全局环境变量没配置 | 检查 npm config get prefix,把 bin 路径加入 PATH |
| 扫描结果只有孤立的节点 | 引用了动态路径或未配置别名 | 配置 resolveAlias,手动补充映射 |
| 图上一堆 unknown | 路径别名未解析 | 阅读工程配置,加入 alias 映射 |
| 扫描时间过长 | 把 node_modules 也扫了 | 加上--ignore node_modules |
| 渲染出来的图又乱又大 | 节点太多,边太密 | 限制 depth,隐藏公共节点,或改用 HTML交互视图 |
| 循环依赖看不出 | 默认视图没有开启高亮 | 使用--find-cycles参数开启循环检测 |
| 某个文件没出现在图中 | 该文件未被任何文件引用 | 实属正常,可能是废弃代码 |
最后再分享一个小技巧。CodeGraph 生成的 JSON 数据是可以做二次统计的,我经常会用 Node.js 写一个十来行的小脚本,遍历所有边,统计每个节点的入度和出度。把所有入度大于阈值的节点输出成一张清单,再对照代码逐一确认,这种方式比我肉眼盯图要高效得多。你可以试着把它接到自己的工具链里,比如在 CI 流程中跑一次 graphify,然后自动检测是否新增了循环依赖,一旦发现就挂掉构建。CodeGraph 这种东西,用一次是新鲜,用成习惯之后,它就慢慢变成你评估代码质量的默认视角。我个人每次接到不熟悉的项目,第一件事已经从“读 README”变成了“跑一张 codegraph 依赖图”,这个顺序调整之后,我对项目的熟悉速度快了非常多。