欢迎关注公众号:【冬瓜白】

一、问题背景

1.1 业务场景:TMF架构重构的代码审查

最近在参与对核心系统的架构重构中,需要将一个庞大的业务模块重构为符合TMF(Transaction Management Framework)四层架构的模块化设计。

原始代码规模(全链路)

层级 代码量 说明
入口Processor类 2,388行 前置校验、后置处理、流程编排
核心Service类 962行 核心业务逻辑、事务处理、数据持久化
直接依赖服务 ~15,000行 55+个依赖服务(费用计算、库存、保险、订单等)
间接调用链路 ~30,000行 二级、三级依赖的业务逻辑
全链路总计 ~50,000行 需要理解和审查的代码范围

重构后结构

层级 文件数 说明
Activity层 2 流程编排、事务边界管理
Ability层 3 业务能力抽象
Extension层 11 业务身份路由(到家/到店/寄修)
Step层 48 原子业务步骤
Model层 1 上下文数据模型
总计 65个文件 需要逐一审查的新代码

复杂度数据

  • 校验逻辑:21+种,涉及参数校验、故障校验、权益校验、保险校验等
  • 业务类型:3种,各有差异化逻辑
  • 服务类型:11种,各有特殊处理
  • 特殊业务场景:5种(涉及不同商品和履约方式的差异化处理)
  • 依赖服务:55+个(@Autowired 40+,@DubboConsumer 12+,@Resource 5+)
  • 方法映射:40+个方法需要逐一验证语义一致性

这次重构的核心挑战不在于代码编写,而在于如何确保重构后的代码与原代码在语义上完全一致——这需要对65个文件进行逐一审查,验证每个方法的迁移是否正确。

1.2 LLM上下文窗口的物理限制

用具体数据来量化这个问题:

审查任务规模分析

核心代码量:
├── 原始Processor + Service:~3,350行
├── 重构后65个文件:~5,200行(平均80行/文件)
└── 小计:~8,550行

审查所需参照代码:
├── 直接依赖服务代码:~15,000行(需要理解调用关系)
├── TMF架构规范文档:~500行
└── 小计:~15,500行

全链路理解所需:
├── 间接依赖链路:~30,000行
└── 业务规则文档:~2,000行

总计:~56,000行代码需要在审查过程中被理解和参照

实际审查挑战

即使只聚焦于核心的65个新文件和原始代码的对比,也面临巨大挑战:

  • 单次对比:每个Step需要与原方法逐行对比,平均每个对比需要~200行上下文
  • 累积上下文:审查到第30个文件时,前面的审查结论需要被记住
  • 跨文件依赖:Step之间存在执行顺序依赖,需要全局视角

主流LLM上下文窗口(截至2025年):

模型 上下文窗口 约等于代码行数
GPT-4 Turbo/GPT-4o 128K tokens 30,000-50,000行
Claude Sonnet 3.5 200K tokens 50,000-80,000行
Claude Sonnet 4.5 200K-1M tokens 50,000-250,000行
本地模型(Llama等) 8K-32K tokens 2,000-8,000行

注:Claude Sonnet 4.5 标准版为200K,Enterprise版可达500K,API可使用1M(beta)。代码行数换算基于Java代码平均每行约50-80字符、每token约4字符估算,实际因代码风格而异。

表面上看,Claude的200K上下文似乎足够。但实际情况远比这复杂:

  1. 有效上下文衰减:有研究表明,当上下文超过4K tokens时,LLM对中间位置信息的检索准确率可下降20%以上("Lost in the Middle"现象)
  2. 多轮对话累积:每轮对话都会消耗上下文,10轮对话后可用空间急剧减少
  3. 审查深度要求:代码审查需要逐行对比,不是简单的"看一眼"

实际测试结果

在我的实践中,当尝试一次性审查超过15个文件时,LLM开始出现:

  • 遗漏关键逻辑差异
  • 直接报错:Context limit exceeded unexpectedly. Please start a new session to continue.

1.3 问题本质:有状态任务 vs 无状态模型

这个问题的本质是一个阻抗不匹配

┌─────────────────────────────────────────────────────────────┐
│                    代码审查任务特性                           │
├─────────────────────────────────────────────────────────────┤
│  • 有状态:需要记住已审查内容、发现的问题、待验证项            │
│  • 长周期:可能跨越多个Session,甚至多天                      │
│  • 累积性:后续审查依赖前序审查的结论                         │
│  • 全局视角:需要从整体架构角度评估每个文件                    │
└─────────────────────────────────────────────────────────────┘
                              ↓ 阻抗不匹配
┌─────────────────────────────────────────────────────────────┐
│                    LLM模型特性                               │
├─────────────────────────────────────────────────────────────┤
│  • 跨请求无状态:单次请求内有上下文,但请求间不保留记忆        │
│  • 短周期:单次对话有上下文限制                              │
│  • 瞬时性:Session结束后所有上下文丢失                       │
│  • 局部视角:只能看到当前上下文中的内容                       │
└─────────────────────────────────────────────────────────────┘

传统的解决思路是"把所有内容塞进上下文",但这在大规模任务中注定失败。我们需要一种新的思维方式。


二、行业解决方案分析

在深入我的解决方案之前,让我们先看看业界是如何应对这个问题的。

2.1 RAG(检索增强生成)

原理:将文档切片存入向量数据库,查询时检索相关片段注入上下文。

┌──────────┐    ┌──────────┐    ┌──────────┐
│ 代码文件  │ → │ 向量化    │ → │ 向量数据库 │
└──────────┘    └──────────┘    └──────────┘
                                     ↓
┌──────────┐    ┌──────────┐    ┌──────────┐
│ 用户查询  │ → │ 相似检索  │ → │ 相关片段  │ → LLM
└──────────┘    └──────────┘    └──────────┘

适用场景:知识问答、文档搜索、基于相似性的代码片段查找

代码审查局限性

  • 代码审查通常需要完整的方法上下文和调用链,而RAG基于语义相似性检索,返回的往往是离散的代码片段,难以保证审查所需的结构完整性
  • 跨文件依赖关系的处理能力有限:虽然可通过精心设计的切片策略(如包含调用关系)部分缓解,但对深度嵌套或复杂的调用链,仍容易丢失关键上下文
  • 更适合“点查询”(如:这个函数是做什么的?),而非“面审查”(如:这10个文件重构后逻辑是否完全一致?)

2.2 Agent记忆系统

原理:为Agent配置外部记忆存储,在对话间持久化关键信息。

# 伪代码示例
class AgentMemory:
    def __init__(self):
        self.short_term = []  # 当前对话
        self.long_term = VectorDB()  # 持久化记忆
        self.working = {}  # 工作记忆
    
    def remember(self, key, value):
        self.long_term.store(key, value)
    
    def recall(self, query):
        return self.long_term.search(query)

适用场景:通用Agent任务、客服对话

代码审查局限性

  • 记忆检索依赖语义匹配的近似性,而代码审查需要精确的结构化状态(如:已审查文件列表、发现的特定问题编号)
  • 缺乏任务进度追踪的天然机制:记忆是点状的,难以自动维护“已完成/待完成”的全局视图
  • 大规模、长周期的审查任务中,记忆的检索准确率可能随着信息量增加而下降,导致关键信息遗漏

2.3 任务分解(Task Decomposition)

原理:将大任务分解为小任务,逐个执行。

大任务:审查65个文件
    ├── 子任务1:审查Activity层(3个文件)
    ├── 子任务2:审查Ability层(3个文件)
    ├── 子任务3:审查Extension层(11个文件)
    └── 子任务4:审查Step层(48个文件)

适用场景:可并行的独立任务、流水线式处理

代码审查局限性

  • 子任务间存在依赖关系时(如:Step层的审查依赖Ability层的设计结论),简单的线性分解可能导致信息断层
  • 缺乏全局状态同步机制:每个子任务完成后,其结论和上下文通常不被自动传递给后续任务
  • 任务中断与恢复较脆弱:一旦流程被打断,重新接续需要人工重新梳理上下文和进度

2.4 方案对比总结

方案 状态持久化 进度追踪 跨Session 结构化输出 代码审查适用性
RAG ⭐⭐
Agent记忆 ⭐⭐
任务分解 ⭐⭐⭐
状态外部化 ⭐⭐⭐⭐⭐

这些方案各有优势,但都无法完美解决大规模代码审查的需求。我需要一个专门设计的解决方案。


三、实践方案:状态外部化模式

3.1 核心洞察

经过多次失败的尝试,我得出一个关键结论:

不要试图让LLM"记住"状态,而是让LLM"读写"状态。

这个思维转变带来了全新的设计思路:

传统思路:LLM内部状态 → 上下文窗口限制 → 任务失败
新思路:  外部状态文件 → LLM读取/更新 → 无限扩展

3.2 架构设计

┌─────────────────────────────────────────────────────────────────┐
│                        状态外部化架构                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   ┌─────────────┐         ┌─────────────────────────────────┐  │
│   │   Session 1  │ ──────→│                                 │  │
│   └─────────────┘         │                                 │  │
│                           │    状态文件(Markdown)           │  │
│   ┌─────────────┐         │                                 │  │
│   │   Session 2  │ ──────→│    • 进度追踪文档                │  │
│   └─────────────┘         │    • 批次审查报告                │  │
│                           │    • 问题发现记录                │  │
│   ┌─────────────┐         │    • 跨Session继续指南           │  │
│   │   Session N  │ ──────→│                                 │  │
│   └─────────────┘         └─────────────────────────────────┘  │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

3.3 状态文件设计

状态文件是整个方案的核心,我设计了三层结构:

第一层:进度追踪文档
# XXX代码审查进度文档

## 当前审查位置
- 当前批次: Batch-5
- 当前文件: XxxValidateStep.java
- 上次审查时间: 2025-12-27
- 下次继续位置: Batch-5 第6个文件

## 审查批次规划
| 批次 | 内容 | 文件数 | 状态 |
|------|------|--------|------|
| Batch-1 | Activity层 | 3 | 🟢 已完成 |
| Batch-2 | Ability层 | 6 | 🟢 已完成 |
| Batch-3 | Extension层 | 8 | 🟢 已完成 |
| Batch-4 | Step层(1) | 10 | 🟢 已完成 |
| Batch-5 | Step层(2) | 10 | 🟡 进行中 |
| ... | ... | ... | ... |
第二层:批次审查报告
# Batch-X 审查报告

## 审查文件清单
| 文件 | 架构符合性 | 语义一致性 | 问题 |
|------|------------|------------|------|
| XxxStep.java | ✅ | ✅ | 无 |
| YyyStep.java | ⚠️ | ✅ | ARCH-001 |

## 发现的问题
### ARCH-001: 分层边界违规
- 文件: YyyStep.java
- 问题: 直接使用了Mapper,违反分层原则
- 建议: 封装到Service层
第三层:跨Session继续指南
## 跨Session继续指南

### 新Session提示词模板
请继续执行XXX代码审查任务。
进度文档位置:docs/analysis/XXX-审查进度.md
请先阅读该文档了解当前进度,然后继续审查。

### 现有Session继续
直接说:"继续审查下一个批次"

3.4 实际Prompt模板

为了确保方案的可复现性,这里分享实际使用的Prompt模板:

初始化Prompt

你是一个代码审查专家,熟悉TMF四层架构。请执行以下任务:
1. 阅读进度文档:docs/analysis/XXX-审查进度.md
2. 理解当前审查状态和已完成的批次
3. 继续执行下一个待审查批次
4. 审查完成后更新进度文档和批次报告

审查重点:
- 架构符合性:是否符合TMF分层规范
- 语义一致性:与原代码逻辑是否完全一致
- 依赖完整性:所需的Service/Mapper是否正确注入

批次审查Prompt

请审查以下文件,对比原代码验证语义一致性:

【原代码】
文件:XxxProcessor.java
方法:processXxx() 第120-180行

【新代码】
文件:XxxStep.java
完整内容:[粘贴代码]

【审查要求】
1. 逐行对比业务逻辑是否一致
2. 检查异常处理是否完整迁移
3. 验证依赖服务调用是否正确
4. 输出审查结论和发现的问题

跨Session恢复Prompt

我需要继续之前的代码审查任务。
进度文档位置:docs/analysis/XXX-审查进度.md

请:
1. 读取进度文档,告诉我当前状态
2. 列出下一个待审查的批次内容
3. 等待我确认后开始审查

3.5 并发控制与版本管理

在多人协作或长周期任务中,状态文件的一致性需要额外关注:

单人场景

  • 依赖文件系统的原子写入即可
  • 建议每次更新后立即保存

多人协作场景

推荐方案:Git版本控制
├── 每次审查前:git pull 获取最新状态
├── 审查完成后:git commit + push 提交更新
└── 冲突处理:状态文件设计为追加式,减少冲突概率

状态文件设计原则:
• 进度文档:只更新"当前位置"字段,冲突概率低
• 批次报告:每批次独立文件,天然无冲突
• 问题记录:追加式写入,合并时取并集

断点恢复机制

异常中断 → 读取最近检查点 → 验证状态完整性 → 从断点继续
                ↓
        若状态不完整 → 回退到上一个完整检查点

3.6 工作流程

┌─────────────────────────────────────────────────────────────────┐
│                        审查工作流程                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  1. 初始化阶段                                                   │
│     ├── 分析任务规模                                            │
│     ├── 规划审查批次                                            │
│     └── 创建进度追踪文档                                         │
│                                                                 │
│  2. 批次执行阶段(循环)                                         │
│     ├── 读取进度文档,定位当前位置                               │
│     ├── 加载当前批次文件                                         │
│     ├── 执行审查(架构+语义)                                    │
│     ├── 生成批次报告                                            │
│     └── 更新进度文档                                            │
│                                                                 │
│  3. 收尾阶段                                                    │
│     ├── 汇总所有批次报告                                         │
│     ├── 生成最终审查报告                                         │
│     └── 输出行动项清单                                          │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

3.7 实战效果

使用这套方案,我成功完成了65个文件的代码审查:

指标 数值
总文件数 65
审查批次 9
跨Session次数 3
架构符合性 98%(2个轻微问题)
语义一致性 100%

关键发现示例

  1. 事务边界验证:发现原afterProcess()中的10个操作需要在事务内执行,验证新代码已正确迁移到processCommit()
  2. 分层违规检测:发现2个Step直接使用了Mapper,违反了TMF分层原则
  3. 方法映射完整性:验证了40+个方法从原Processor到新Step的完整映射

四、方法论提炼:四种设计模式

从这次实践中,我提炼出四种可复用的设计模式:

4.1 模式一:状态外部化模式(State Externalization)

问题:LLM无状态,无法跨Session保持任务状态

解决方案:将任务状态持久化到外部文件,LLM通过读写文件维护状态

┌─────────────┐     读取      ┌─────────────┐
│    LLM      │ ←──────────── │  状态文件    │
│             │ ──────────────→│             │
└─────────────┘     更新      └─────────────┘

适用场景:任何需要跨Session持续的任务

4.2 模式二:批次处理模式(Batch Processing)

问题:大规模任务超出单次处理能力

解决方案:将任务分解为可管理的批次,每批次独立完成并记录

大任务 → [批次1] → [批次2] → ... → [批次N] → 汇总
           ↓         ↓              ↓
        [报告1]   [报告2]  ...   [报告N]

关键设计

  • 批次大小:8-12个文件/批次(经验值)
  • 批次边界:按逻辑分组(如:同一层级、同一功能)
  • 批次依赖:明确批次间的依赖关系

4.3 模式三:检查点模式(Checkpoint)

问题:长任务中断后无法恢复

解决方案:在关键节点保存检查点,支持从任意检查点恢复

## 检查点记录
| 检查点 | 时间 | 状态 | 可恢复 |
|--------|------|------|--------|
| CP-1 | 10:00 | Batch-1完成 | ✅ |
| CP-2 | 11:30 | Batch-3完成 | ✅ |
| CP-3 | 14:00 | Batch-5进行中 | ✅ |

恢复策略

新Session启动 → 读取检查点 → 定位最近完成点 → 继续执行

4.4 模式四:协议驱动模式(Protocol-Driven)

问题:不同Session间的LLM行为不一致

解决方案:定义明确的交互协议,确保任何Session都能正确继续任务

## 跨Session协议

### 启动协议
1. 读取进度文档
2. 解析当前状态
3. 加载相关上下文
4. 继续执行

### 执行协议
1. 执行当前批次
2. 生成批次报告
3. 更新进度文档
4. 输出继续指南

### 终止协议
1. 汇总所有报告
2. 生成最终报告
3. 标记任务完成

五、深度思考:从"对话"到"协议"的范式转变

5.1 传统LLM交互的局限

传统的LLM交互是对话式的:

用户: 帮我审查这段代码
LLM: 好的,我发现以下问题...
用户: 继续审查下一个文件
LLM: 好的...(但可能已经忘记之前的上下文)

这种模式的问题:

  • 隐式状态:状态隐藏在对话历史中
  • 脆弱性:Session中断即状态丢失
  • 不可预测:LLM行为依赖上下文理解

5.2 协议驱动的新范式

我提出的方案是协议驱动的:

用户: 执行审查协议,进度文档位置:xxx
LLM: [读取进度] → [执行批次] → [更新状态] → [输出报告]
用户: 继续
LLM: [读取进度] → [执行下一批次] → [更新状态] → [输出报告]

这种模式的优势:

  • 显式状态:状态明确存储在外部文件
  • 可恢复性:任何时候都能从检查点恢复
  • 可预测性:LLM行为由协议定义,而非上下文推断

5.3 本质洞察

LLM在单次请求内通过注意力机制维护上下文,但跨请求是无状态的——这是设计状态外部化方案的关键认知。

一旦接受这个事实,我们就能设计出更健壮的人机协作模式:

┌─────────────────────────────────────────────────────────────────┐
│                     人机协作新范式                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   人类角色:                                                     │
│   • 定义任务目标                                                 │
│   • 设计状态结构                                                 │
│   • 制定执行协议                                                 │
│   • 监督执行过程                                                 │
│                                                                 │
│   LLM角色:                                                      │
│   • 读取状态                                                     │
│   • 执行协议定义的操作                                           │
│   • 更新状态                                                     │
│   • 输出结构化结果                                               │
│                                                                 │
│   状态文件角色:                                                 │
│   • 持久化任务状态                                               │
│   • 连接不同Session                                              │
│   • 提供审计追踪                                                 │
│   • 支持任务恢复                                                 │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

5.4 更广泛的应用

这种模式不仅适用于代码审查,还可以应用于:

场景 状态文件内容 批次划分
大规模文档翻译 翻译进度、术语表、质量检查点 按章节/段落
数据迁移验证 迁移进度、校验结果、异常记录 按表/批次
系统重构 重构进度、依赖分析、测试状态 按模块/层级
安全审计 审计进度、漏洞清单、修复状态 按组件/风险等级

六、总结

6.1 核心要点

  1. 认清本质:LLM跨请求是无状态的,不要期望它在不同Session间"记住"任何东西
  2. 状态外部化:将任务状态持久化到外部文件,LLM通过读写文件维护状态
  3. 批次处理:将大任务分解为可管理的批次,每批次独立完成
  4. 协议驱动:定义明确的交互协议,确保跨Session的一致性
  5. 检查点机制:在关键节点保存状态,支持任务恢复

6.2 实践建议

对于需要使用LLM处理大规模任务的团队,我的建议是:

  1. 前期投入:花时间设计好状态文件结构和执行协议
  2. 批次规划:根据任务特点合理划分批次(建议8-12个文件/批次)
  3. 文档驱动:让Markdown文档成为人机协作的"共享内存"
  4. 持续改进:根据实践反馈优化协议和状态结构

6.3 展望

随着LLM能力的提升,上下文窗口会越来越大,但状态外部化的思想仍然有价值:

  • 它提供了可审计性:所有状态变更都有记录
  • 它支持协作:多人可以基于同一状态文件协作
  • 它增强了可靠性:即使LLM出错,状态文件也能帮助恢复

6.4 AI时代的核心竞争力:思路比执行更重要

最后,我想分享一个更深层的思考:

在AI时代,你不需要知道每个设计模式"怎么执行",只需要知道"有这些思路可用"。

本文提炼的四种设计模式——状态外部化、批次处理、检查点、协议驱动——你不需要记住每个模式的实现细节。因为:

  1. 执行可以交给AI:当你面对类似问题时,只需告诉AI"我想用状态外部化模式来解决跨Session的状态管理问题",AI就能帮你实现具体细节。

  2. 思路才是稀缺资源:AI擅长执行,但不擅长"定义问题"和"选择方案"。知道"这类问题可以用批次处理模式解决",这个认知本身就是价值。

  3. 审查比编写更重要:在本文的实践中,我设计了状态文件结构和审查协议,AI执行了65个文件的具体审查。我的价值在于"设计"和"审查AI的输出",而非亲自执行每一步。

这正是本文"从对话到协议"范式转变的延伸:

传统开发者:学习模式 → 记住细节 → 亲自实现
AI时代开发者:了解模式 → 设计协议 → 指导AI执行 → 审查结果

架构师的核心竞争力正在从"会写代码"转向"会设计协议"。

希望这篇文章的价值不在于表达"状态文件的Markdown格式怎么写",而应该是"原来跨Session的大规模任务可以通过状态外部化来解决"。有了这个认知,下次遇到类似问题时,就知道该往哪个方向思考——具体实现,交给AI就好。

思考:与LLM协作的最佳方式,不是把它当作一个"聪明的助手",而是把它当作一个"强大的执行引擎"——你负责设计协议和管理状态,它负责高效执行。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐