软件工程领域已经花了几十年时间研究如何管理复杂度。
从 MVC 到 DDD,从单体架构到微服务,从分层设计到六边形架构,各种框架、设计模式和工程实践层出不穷。它们解决的问题大同小异:如何组织代码,如何降低耦合,如何让系统在规模不断扩张的情况下仍然保持可维护性。
然而有一个问题,却很少被单独拿出来讨论。
假设把一个二十万行代码的项目交给一个经验丰富的工程师,让他修复一个线上问题。你觉得最大的成本是什么?
通常人们会认为是理解业务逻辑、理解系统架构,或者理解历史设计决策。但实际情况往往并非如此。在很多项目中,最大的成本其实是找到代码。
这里的“找到代码”并不是指找到某个文件的位置。现代 IDE 已经足够强大,全局搜索、调用链分析、类型跳转都非常成熟。真正困难的是找到系统运行时的入口,找到某段逻辑是在什么条件下被触发的,以及它是如何进入当前执行链路的。
例如,订单支付成功后为什么会发放积分?这个逻辑可能存在于支付回调中,也可能存在于 MQ 消费者中,可能由 Spring Event 触发,也可能是定时补偿任务完成的。即使你知道积分相关代码位于哪个模块,也不意味着你知道它是在什么时候、由什么入口触发执行的。
对于一个陌生系统而言,代码的存储结构和代码的运行结构往往是两套不同的体系。目录结构、包结构、模块划分描述的是代码如何组织,而真正决定系统行为的,是 HTTP 请求、消息队列、定时任务、事件监听器、RPC 调用、WebSocket 消息以及各种启动任务构成的运行时入口。
有趣的是,这个问题在 AI Agent 时代变得更加明显。
前段时间,我使用 Qwen3-Coder-Next 对一个老项目进行优化。
需求其实非常简单。
两个优化点,没有新增功能,没有架构调整,甚至不涉及数据库变更。按照正常开发经验,这种级别的修改最终落地的代码可能不到一百行。
然而整个任务执行完成后,消耗了400万+ Token。
最开始我以为是模型推理效率的问题。后来复盘整个过程才发现,问题根本不在推理,而在寻找。
模型用了大量时间搜索关键词、打开文件、分析调用关系、理解执行链路,然后不断排除无关模块。
订单相关代码读了一遍,支付相关代码读了一遍,消息消费者读了一遍,定时任务读了一遍,事件监听器读了一遍。
最后发现真正需要修改的位置只有两个方法。
换句话说,400万Token 里,99%都不是花在“改代码”上,而是花在“找到代码”上。
这件事恰恰也是人类开发者每天都在做的事情。
只不过 Agent 消耗的是 Token,而人类消耗的是时间。
软件工程里有很多被广泛讨论的成本。
开发成本、维护成本、测试成本、部署成本、沟通成本。
但很少有人单独讨论导航成本(Navigation Cost)。
所谓导航成本,是指开发者或 Agent 为了找到正确代码位置而付出的成本。
它不创造业务价值,不产生功能,不修复 Bug,但几乎存在于所有开发活动中。
修复问题之前,需要先找到问题。
实现需求之前,需要先找到入口。
优化性能之前,需要先找到链路。
很多团队统计开发效率时,会关注编码时间、测试时间、发布时间,却很少统计:为了找到需要修改的位置,究竟浪费了多少时间。
而随着系统规模扩大,以及 Agent 开始参与开发流程,这部分成本正在变得越来越昂贵。
最近看到一个有意思的开源项目:codegraph。
它会扫描整个仓库,分析目录结构、模块依赖、入口文件以及调用关系,然后生成一份代码地图。
很多人把它看成 Agent 的辅助工具。
但我更关注另一件事。
Codebase Map 的出现,本身就在证明一件事情:导航已经成为一个独立问题。
如果导航不是问题,为什么会有人专门开发一套工具来生成代码地图?
如果项目天然具备可导航性,为什么 Agent 需要先扫描几万行代码才能开始工作?
如果代码入口很容易定位,为什么每个接手项目的人都要花大量时间寻找系统从哪里开始运行?
Codebase Map 其实是在给 Agent 补导航。
它解决的问题不是代码理解,而是代码定位。
但它的工作方式是后置的。
Agent 进入项目。
扫描项目。
理解项目。
生成地图。
然后开始工作。
整个过程本质上是在重复发现已经存在的信息。
而这些信息其实从项目创建的第一天就已经存在。
HTTP 入口不会突然改变。
MQ 消费者不会凭空出现。
定时任务、RPC 服务、事件监听器也不是运行时才生成的。
既然所有后来者都需要这份地图,那么地图本身为什么不成为项目资产?
如果一个问题每个接手项目的人都会遇到,每个 Agent 也都会遇到,那么它本质上就不是个人问题,而是项目问题。
一个新人接手项目,第一件事是找入口;一个 Agent 接手项目,第一件事还是找入口。十个新人接手项目,会重复找十次;十个 Agent 执行任务,也会重复找十次。
而这些信息实际上并没有变化。
HTTP 请求入口在哪里,MQ 消费入口在哪里,哪些定时任务会执行,哪些 Spring Event 会被触发,哪些 RPC 服务对外暴露。
这些信息从项目创建开始就已经存在。
既然如此,为什么要让每一个后来者重新发现一遍?
软件工程里有一个很经典的原则:不要重复劳动。
但对于系统入口的发现过程,整个行业似乎一直在重复劳动。
新人重复寻找。
老员工重复解释。
Agent 重复搜索。
每一次接手项目,都要重新建立一遍运行时认知。
如果只讨论理念,这件事听起来有些抽象。
举个例子。
假设线上反馈:用户支付成功了,但积分没有到账。
对于接手项目的人来说,第一个问题并不是如何修复,而是积分逻辑从哪里进入系统。
是支付回调直接发放?
是 MQ 异步消费?
是 Spring Event?
还是定时补偿任务?
很多时候,真正消耗时间的不是修复逻辑,而是定位逻辑。
如果项目里存在这样一份入口索引:
# 系统入口索引
## HTTP接口
| 功能 | 路径 | 方法 | 实现类 | 方法名 |
| ---- | ----------------- | ---- | --------------- | -------- |
| 创建订单 | /api/order/create | POST | OrderController | create() |
| 取消订单 | /api/order/cancel | POST | OrderController | cancel() |
## MQ消费者
| 功能 | Topic | 实现类 | 方法名 |
| ------- | ---------------------- | ---------------------- | -------------------- |
| 订单创建后处理 | order.queue | OrderEventProcessor | handleOrderCreated() |
| 支付结果回调 | payment.callback.queue | PaymentCallbackHandler | onPaymentResult() |
## 定时任务
| 功能 | Cron | 实现类 | 方法名 |
| ------ | ------------ | --------------------- | ---------------- |
| 清理超时订单 | 0 2 * * * | CleanExpiredOrdersJob | execute() |
| 生成日报 | 0 0 10 * * ? | DailyReportJob | generateReport() |
## Spring事件
| 功能 | Event | 实现类 | 方法名 |
| --------- | -------------- | ------------------ | ------------- |
| 支付完成后发放积分 | OrderPaidEvent | PointEventListener | onOrderPaid() |
## RPC服务
| 功能 | 服务 | 实现类 | 方法名 |
| ------ | -------------- | ------------------ | ------------- |
| 查询用户信息 | UserRpcService | UserRpcServiceImpl | getUserInfo() |
## WebSocket消息
| 功能 | 消息类型 | 实现类 | 方法名 |
| ------ | ------------ | -------------------- | ----------- |
| 实时聊天消息 | chat.message | ChatWebSocketHandler | onMessage() |
## Startup初始化任务
| 功能 | 类型 | 实现类 | 方法名 |
| ---- | ----------------- | ----------------- | ----- |
| 缓存预热 | CommandLineRunner | CacheWarmupRunner | run() |
那么接手者面对“支付成功但积分没到账”这个问题时,根本不需要全局搜索。
他首先会查看支付回调和订单支付事件对应的入口实现,然后直接进入对应代码。
整个过程从“探索系统”变成了“验证逻辑”。
这里最重要的一点是,这份文档并不解释业务,不解释架构,不解释实现,甚至不记录参数。
它只负责回答一个问题:
系统有哪些地方会触发代码执行?
剩下的事情交给源码。
因为接手项目的人本来就应该具备阅读当前技术栈代码的能力。
这也是我开始思考 Runtime Entry Outline(REO) 的原因。
它并不试图替代架构文档,也不试图解释业务逻辑,甚至不关心代码实现。
它只记录一件事情:
系统有哪些运行时入口。
很多人看到这里,第一反应可能是:这不就是文档吗?
其实不完全是。
传统文档描述的是系统如何设计,而 Runtime Entry Outline(REO) 描述的是系统如何运行。
前者关注结构,后者关注入口。
前者回答的是“系统长什么样”。
后者回答的是“系统从哪里开始执行”。
从 Agent 的角度看,这种信息甚至比架构图更有价值。
因为 Agent 最昂贵的成本之一并不是理解代码,而是寻找代码。
如果入口已经被明确列出,Agent 可以直接定位到相关文件,而不是从整个仓库开始搜索。
对于人类开发者来说也是一样。
很多线上问题的修复时间只有几分钟。
真正耗费时间的,是找到需要修改的位置。
过去的软件工程关注的是代码资产。
源代码是资产,数据库设计是资产,架构设计是资产。
但随着系统规模越来越大,以及 AI Agent 开始参与开发流程,还有一种资产开始变得越来越重要:系统认知。
哪些地方会触发代码执行,哪些入口承担核心业务,哪些链路真正影响系统行为。
这些信息长期存在于老员工的大脑里,却很少以结构化的形式沉淀下来。
于是每一个新人都要重新探索一遍,每一个 Agent 也要重新搜索一遍。
如果一个知识会被反复获取,如果一个过程会被反复执行,那么它就不应该依赖经验传承,而应该成为项目本身的一部分。
如果每个新人、每个 Agent 都要重新构建同一份地图,那么这份地图本身就应该成为项目资产。
或许未来项目仓库里,除了 README 之外,还会存在另一份默认文件:
系统入口索引。
它不负责解释系统。
它只负责告诉后来者:
代码从哪里开始运行。
发表回复