【斯坦福 AI 编程课 05】错误处理与恢复——Agent 的"自我修复"能力
CS146S: 现代软件开发学习笔记系列第五篇
上一篇我们聊了上下文管理——Agent 如何"记住"之前做的事。
但还有一个关键问题:Agent 犯错了怎么办?
如果 Agent 写的代码有 bug,它会一直错下去吗?还是能自己发现并修复?
这就是今天要聊的话题:错误处理与恢复。
为什么错误处理很重要?
Agent 也会犯错
很多人以为 AI Agent 是完美的,但实际上:
Agent:我来修复这个登录bug
(修改代码...)
测试:❌ 登录失败
Agent:哦,我改错了,让我重新修复
(又修改代码...)
测试:❌ 还是失败
Agent:让我再试试...
Agent 和人类一样,也会犯错。关键区别是:
- 人类:犯3次错就放弃了
- Agent:可以尝试100次,直到成功
但如果没有好的错误处理机制,Agent 就会:
- 重复同样的错误
- 浪费大量 token
- 甚至把代码改得更糟
真实案例
Claude Code 的错误处理:
# 第一次尝试
def calculate_average(numbers):
return sum(numbers) / len(numbers)
# 测试失败:空列表导致除零错误
# Claude Code 自动修复:
def calculate_average(numbers):
if not numbers:
return 0 # 添加边界检查
return sum(numbers) / len(numbers)
# 测试通过 ✅
Claude Code 会:
- 运行测试,发现错误
- 分析错误原因
- 修复代码
- 重新测试
- 直到通过
这就是"自我修复"能力。
错误处理的四个层次
第1层:语法错误(最容易)
特点:
- 代码无法运行
- 错误信息明确
- 容易定位
示例:
# 语法错误
def greet(name
print(f"Hello {name}") # 缺少右括号
# Agent 修复:
def greet(name):
print(f"Hello {name}")
处理策略:
- 使用 linter 自动检测
- 编译器/解释器报错
- Agent 直接修复
成功率:95%+
第2层:逻辑错误(较难)
特点:
- 代码能运行,但结果不对
- 错误信息不明确
- 需要测试发现
示例:
# 逻辑错误
def find_max(numbers):
max_num = 0 # 错误:应该初始化为负无穷
for num in numbers:
if num > max_num:
max_num = num
return max_num
# 测试失败:find_max([-1, -2, -3]) 返回 0
# Agent 修复:
def find_max(numbers):
if not numbers:
return None
max_num = float('-inf') # 修复:初始化为负无穷
for num in numbers:
if num > max_num:
max_num = num
return max_num
处理策略:
- 编写测试用例
- 运行测试,查看失败信息
- Agent 分析失败原因,修复代码
成功率:70-80%
第3层:集成错误(更难)
特点:
- 单元测试通过,但集成失败
- 涉及多个模块交互
- 难以复现
示例:
# 模块A
def get_user(user_id):
return db.query(f"SELECT * FROM users WHERE id = {user_id}")
# 模块B
def process_user(user_id):
user = get_user(user_id)
return user.name.upper()
# 集成错误:数据库连接失败时,get_user 返回 None
# process_user 调用 None.name 导致 AttributeError
# Agent 修复:
def process_user(user_id):
user = get_user(user_id)
if user is None:
return "Unknown User" # 添加错误处理
return user.name.upper()
处理策略:
- 集成测试
- Mock 外部依赖
- Agent 分析调用链,添加错误处理
成功率:50-60%
第4层:设计错误(最难)
特点:
- 代码运行正常,但设计有问题
- 性能差、可维护性差
- 需要重构
示例:
# 设计错误:N+1 查询问题
def get_users_with_posts(user_ids):
users = []
for user_id in user_ids:
user = db.query(f"SELECT * FROM users WHERE id = {user_id}")
posts = db.query(f"SELECT * FROM posts WHERE user_id = {user_id}")
user.posts = posts
users.append(user)
return users
# 100个用户 = 201次查询(1次查用户 + 100次查用户 + 100次查文章)
# Agent 重构:
def get_users_with_posts(user_ids):
# 一次查询获取所有用户
users = db.query(f"SELECT * FROM users WHERE id IN ({','.join(user_ids)})")
# 一次查询获取所有文章
posts = db.query(f"SELECT * FROM posts WHERE user_id IN ({','.join(user_ids)})")
# 在内存中关联
user_map = {u.id: u for u in users}
for post in posts:
if post.user_id in user_map:
user_map[post.user_id].posts.append(post)
return users
# 100个用户 = 2次查询
处理策略:
- 代码审查
- 性能分析
- Agent 重构代码
成功率:30-40%
Agent 的错误恢复策略
策略1:重试机制
适用场景:
- 网络请求失败
- 临时性错误
- 资源暂时不可用
示例:
import time
import random
def fetch_data_with_retry(url, max_retries=3):
for attempt in range(max_retries):
try:
response = requests.get(url, timeout=5)
return response.json()
except Exception as e:
if attempt < max_retries - 1:
wait_time = 2 ** attempt # 指数退避
time.sleep(wait_time)
else:
raise e
Agent 的重试逻辑:
第1次失败 → 等待1秒 → 重试
第2次失败 → 等待2秒 → 重试
第3次失败 → 抛出错误
策略2:回滚机制
适用场景:
- 代码改坏了
- 需要恢复到之前的状态
示例:
# Git 回滚
git checkout HEAD~1 -- broken_file.py
# 或使用 Agent 的自动回滚
if test_failed:
agent.revert_last_change()
agent.try_different_approach()
Agent 的回滚逻辑:
1. 修改代码
2. 运行测试
3. 如果失败 → 回滚代码
4. 尝试其他方案
策略3:分支探索
适用场景:
- 不确定哪个方案最好
- 需要尝试多个方向
示例:
Agent 的思考过程:
方案A:修改数据库查询
→ 测试:失败
方案B:添加缓存
→ 测试:成功,但性能提升不大
方案C:重构数据结构
→ 测试:成功,性能提升10倍 ✅
Agent 的分支探索:
approaches = [
"fix_query",
"add_cache",
"refactor_structure"
]
for approach in approaches:
agent.try_approach(approach)
if test_passes():
break
else:
agent.revert()
策略4:渐进式修复
适用场景:
- 错误很复杂
- 不能一次性修复
示例:
第1步:修复最明显的bug
第2步:添加错误处理
第3步:优化性能
第4步:重构代码
Agent 的渐进式修复:
# 初始版本
def process_data(data):
return data.split(',')
# 第1步:添加错误处理
def process_data(data):
if not data:
return []
return data.split(',')
# 第2步:处理异常情况
def process_data(data):
if not data:
return []
try:
return data.split(',')
except Exception as e:
log_error(e)
return []
# 第3步:添加类型检查
def process_data(data):
if not isinstance(data, str):
raise TypeError("Expected string")
if not data:
return []
try:
return data.split(',')
except Exception as e:
log_error(e)
return []
实战案例:Claude Code 的错误处理
案例1:修复类型错误
场景:
# 用户代码
def calculate_total(items):
total = 0
for item in items:
total += item.price * item.quantity
return total
错误:
TypeError: unsupported operand type(s) for +=: 'int' and 'str'
Claude Code 的修复过程:
1. 分析错误:
- price 或 quantity 是字符串
- 需要类型转换
2. 第一次修复:
total += float(item.price) * int(item.quantity)
3. 测试:
- ✅ 通过
4. 进一步优化:
def calculate_total(items):
total = 0.0 # 使用浮点数
for item in items:
try:
price = float(item.price)
quantity = int(item.quantity)
total += price * quantity
except (ValueError, AttributeError) as e:
log_error(f"Invalid item: {item}, error: {e}")
return total
5. 最终版本:
- ✅ 处理类型错误
- ✅ 添加异常处理
- ✅ 记录错误日志
案例2:修复性能问题
场景:
# 慢查询
def get_active_users():
all_users = User.query.all() # 查询所有用户
active_users = []
for user in all_users:
if user.last_login > datetime.now() - timedelta(days=30):
active_users.append(user)
return active_users
问题:
- 查询所有用户(可能10万+)
- 在内存中过滤
- 非常慢
Claude Code 的优化:
1. 分析问题:
- N+1 查询问题
- 应该在数据库层面过滤
2. 第一次优化:
def get_active_users():
threshold = datetime.now() - timedelta(days=30)
return User.query.filter(User.last_login > threshold).all()
3. 测试:
- 性能:10秒 → 0.1秒(提升100倍)
4. 进一步优化(添加索引):
# 在数据库迁移中添加:
CREATE INDEX idx_users_last_login ON users(last_login);
5. 最终版本:
- ✅ 查询速度提升100倍
- ✅ 添加数据库索引
- ✅ 代码更简洁
错误处理的最佳实践
1. 编写可测试的代码
❌ 错误示范:
def process_file(filename):
data = read_file(filename) # 硬编码文件读取
return parse_data(data)
✅ 正确示范:
def process_file(filename, file_reader=None):
reader = file_reader or default_file_reader
data = reader.read(filename)
return parse_data(data)
# 测试时可以注入 mock
def test_process_file():
mock_reader = MockReader()
result = process_file("test.txt", mock_reader)
assert result == expected
2. 使用断言和类型提示
❌ 错误示范:
def divide(a, b):
return a / b # 如果 b=0 会崩溃
✅ 正确示范:
def divide(a: float, b: float) -> float:
assert b != 0, "Divisor cannot be zero"
return a / b
3. 记录详细的错误信息
❌ 错误示范:
try:
process_data(data)
except Exception:
pass # 吞掉错误
✅ 正确示范:
try:
process_data(data)
except ValueError as e:
logger.error(f"Invalid data format: {data}, error: {e}")
raise
except Exception as e:
logger.error(f"Unexpected error processing data: {e}", exc_info=True)
raise
4. 提供降级方案
❌ 错误示范:
def get_weather(city):
response = requests.get(f"https://api.weather.com/{city}")
return response.json() # 如果API失败,整个功能崩溃
✅ 正确示范:
def get_weather(city):
try:
response = requests.get(f"https://api.weather.com/{city}", timeout=5)
return response.json()
except Exception as e:
logger.warning(f"Weather API failed: {e}")
return get_cached_weather(city) # 降级到缓存
总结:Agent 的"自我修复"能力
核心要素
-
检测错误:
- 自动运行测试
- 分析错误日志
- 监控性能指标
-
分析原因:
- 定位错误位置
- 理解错误类型
- 找到根本原因
-
修复代码:
- 尝试多种方案
- 渐进式修复
- 验证修复效果
-
防止复发:
- 添加测试用例
- 改进错误处理
- 优化代码结构
Agent vs 人类
| 能力 | 人类 | Agent |
|---|---|---|
| 检测错误 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 分析原因 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 修复代码 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 耐心程度 | ⭐⭐ | ⭐⭐⭐⭐⭐ |
| 创造性 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
结论:
- Agent 在检测错误和耐心方面超越人类
- 人类在分析原因和创造性方面更强
- 最佳组合:人类设计,Agent 执行,人类审核
未来展望
随着 Agent 能力的提升,错误处理会越来越好:
现在:
- Agent 能修复大部分语法和逻辑错误
- 集成错误和设计错误需要人类帮助
未来:
- Agent 能自动重构代码
- Agent 能预测潜在错误
- Agent 能从错误中学习
下一篇预告:我们将聊"规划与推理"——Agent 如何像人类一样思考和规划。
参考资料
本系列其他文章:
- 第01篇:从"会话"到"会做"——Coding Agent 解剖
- 第02篇:Coding Agent 解剖
- 第03篇:MCP 协议——AI Agent 的"万能接口"
- 第04篇:上下文管理——Agent 的"记忆宫殿"
Views: 5
