Chapter 05

AI 代码审查与重构实践

建立系统化的 AI 辅助代码审查流程,让 AI 成为你最有效的代码评审员

AI 代码审查的工作流设计

为什么 AI 代码审查有价值?

传统代码审查面临几个挑战:审查者的时间有限,往往只能做粗略检查;审查者可能有盲点,特别是对自己不熟悉的技术栈;审查耗时长,成为 PR 合并的瓶颈。AI 代码审查可以:在几秒内完成全面的静态分析、发现人类容易忽视的安全漏洞和性能问题、始终如一地执行代码规范检查、帮助新人理解代码库的最佳实践。

分层审查策略

代码变更(PR/diff) ↓ ┌─────────────────────────────────────┐ │ 层1:自动化审查(AI + 静态分析工具) │ │ - 语法/类型错误(编译器) │ │ - 代码风格(lint 工具) │ │ - 安全漏洞(Semgrep/Snyk) │ │ - AI 快速审查(Copilot PR Review) │ └─────────────────────────────────────┘ ↓(通过后) ┌─────────────────────────────────────┐ │ 层2:AI 深度审查(Claude/GPT-4) │ │ - 逻辑正确性 │ │ - 性能问题 │ │ - 错误处理完整性 │ │ - 架构一致性 │ └─────────────────────────────────────┘ ↓(通过后) ┌─────────────────────────────────────┐ │ 层3:人工审查(精力集中于核心逻辑) │ │ - 业务逻辑正确性 │ │ - 架构决策合理性 │ │ - 团队约定遵守情况 │ └─────────────────────────────────────┘

审查 Prompt 模板库

安全审查 Prompt

你是一位安全工程师,请对以下代码进行安全审查。

重点检查:
1. **注入漏洞**:SQL 注入、命令注入、LDAP 注入、XPath 注入
2. **认证/授权问题**:权限检查缺失、不安全的会话管理
3. **敏感数据暴露**:日志中打印密码/Token、明文存储敏感信息
4. **安全配置**:硬编码密钥、弱加密算法(MD5/SHA1 作密码哈希)
5. **输入验证**:未验证的用户输入、路径遍历漏洞
6. **依赖安全**:使用已知漏洞的依赖版本

对每个发现的问题,请提供:
- 严重程度(Critical/High/Medium/Low)
- 具体代码位置(行号)
- 漏洞类型(OWASP Top 10 分类)
- 修复建议(包含修复后的代码示例)

代码:
```
{CODE_HERE}
```

性能审查 Prompt

你是一位性能优化专家,请分析以下代码的性能问题。

重点检查:
1. **数据库查询**:N+1 查询问题、缺少索引使用、SELECT * 滥用
2. **算法复杂度**:嵌套循环、重复计算、低效的数据结构选择
3. **内存使用**:大对象不必要的复制、内存泄漏风险
4. **网络请求**:串行请求是否可以并行化、缺少缓存
5. **同步阻塞**:在 async 上下文中使用了同步 I/O

对每个性能问题:
- 估计影响级别(高/中/低)
- 解释问题原因
- 提供优化方案(包含代码示例)
- 如果可能,估计优化后的性能提升

代码:
```
{CODE_HERE}
```

可维护性审查 Prompt

请以高级工程师的视角审查以下代码的可维护性。

评估维度:
1. **可读性**:命名是否清晰、注释是否必要且准确
2. **单一职责**:函数/类是否承担了过多职责
3. **重复代码**:是否存在明显的 DRY 违反
4. **测试友好性**:代码是否易于单元测试(低耦合)
5. **错误处理**:异常处理是否完整、错误信息是否有助于调试
6. **扩展性**:对未来需求变化的适应能力

评分(1-10)并说明理由,然后列出最重要的 3-5 条改进建议。

代码:
```
{CODE_HERE}
```

理解遗留代码

用 AI 读懂祖传代码

遗留代码(Legacy Code)往往缺乏文档、命名混乱、逻辑复杂。AI 可以帮助你快速建立对遗留代码的认知:

# 逐层解析策略

# 第一步:整体理解
"阅读这段代码,用 5 句话解释它做了什么事情,
不需要解释每一行,只需要高层次的功能描述"

# 第二步:关键逻辑
"这段代码中最核心的业务逻辑是什么?
用流程图(文本形式)展示主要的执行路径"

# 第三步:潜在风险
"这段代码有哪些可能的边界情况和潜在 Bug?
如果要修改这段代码,需要注意什么?"

# 第四步:现代化建议
"如何用现代最佳实践重写这段代码?
请保持接口不变,只改实现"
# 遗留代码示例(真实世界中常见的"祖传代码"风格)
def proc_dt(d, t, f=0):
    import datetime
    r = []
    for i in d:
        x = i[0]
        y = i[1] if len(i) > 1 else None
        if x and y:
            z = datetime.datetime.strptime(x + ' ' + y, t)
            if f:
                z = z + datetime.timedelta(hours=f)
            r.append(z)
    return r

# AI 解析后的清晰版本
def parse_datetime_pairs(
    date_time_pairs: list[tuple[str, str]],
    format_str: str,
    timezone_offset_hours: float = 0
) -> list[datetime.datetime]:
    """
    将日期字符串和时间字符串对解析为 datetime 对象列表。

    Args:
        date_time_pairs: [(日期字符串, 时间字符串), ...] 格式的列表
        format_str: datetime 解析格式(如 '%Y-%m-%d %H:%M:%S')
        timezone_offset_hours: 时区偏移量(小时),默认 0

    Returns:
        解析成功的 datetime 对象列表(跳过不完整的条目)
    """
    result = []
    for date_str, time_str in date_time_pairs:
        if not date_str or not time_str:
            continue
        dt = datetime.datetime.strptime(f"{date_str} {time_str}", format_str)
        if timezone_offset_hours:
            dt += datetime.timedelta(hours=timezone_offset_hours)
        result.append(dt)
    return result

AI 辅助重构

常见重构场景与 Prompt

提取函数(Extract Function)

"以下代码过长,请将其拆分为多个小函数,
每个函数不超过 20 行,遵循单一职责原则。
保持所有功能不变,只是拆分结构。"

消除重复(DRY)

"以下代码中有明显的重复逻辑,请:
1. 识别出所有重复的模式
2. 提取为通用函数/基类
3. 重写调用方使用新的通用函数
保持对外接口不变。"

设计模式应用

"以下代码中有大量的 if/elif 条件分支,
请用策略模式(Strategy Pattern)重构,
消除条件分支,提高可扩展性。
新增支付方式时无需修改现有代码。"

重构安全网:先有测试

重构前必须有测试覆盖,否则无法保证重构不破坏功能。AI 可以帮助你在重构前快速生成测试:

# 步骤1:用 AI 生成当前代码的"特征测试"(Characterization Tests)
# 这类测试描述代码的当前行为(即使行为可能是错的)

"""
我要重构以下函数,请为其生成特征测试(Characterization Tests)。
特征测试的目的是捕捉当前行为,包括边界情况和潜在的怪异行为。
使用 pytest,并添加清晰的注释说明每个测试的意图。
"""

# AI 生成的特征测试示例
class TestProcDt:
    def test_normal_case(self):
        """正常的日期时间对解析"""
        result = proc_dt(
            [("2024-01-15", "10:30:00")],
            "%Y-%m-%d %H:%M:%S"
        )
        assert len(result) == 1
        assert result[0].year == 2024

    def test_missing_time_is_skipped(self):
        """缺少时间字符串的条目被跳过"""
        result = proc_dt(
            [("2024-01-15", None)],
            "%Y-%m-%d %H:%M:%S"
        )
        assert result == []

    def test_timezone_offset_applied(self):
        """时区偏移正确应用"""
        result = proc_dt(
            [("2024-01-15", "10:00:00")],
            "%Y-%m-%d %H:%M:%S",
            f=8  # UTC+8
        )
        assert result[0].hour == 18

何时接受/拒绝 AI 建议

决策框架

不是所有 AI 建议都值得采纳。以下是一个实用的决策框架:

直接接受
修复明显的语法错误、格式化代码、添加类型注释、生成模板性代码(CRUD 接口、数据类等)。这类建议风险低、价值明确。
审核后接受
重构建议、性能优化、错误处理改进。需要理解 AI 的修改逻辑,确认不改变行为,运行测试验证。
谨慎对待
安全相关修改(加密算法、认证逻辑)、数据库 Schema 变更、公共 API 接口变更。需要有领域专家审核,不能只依赖 AI。
拒绝
AI 不理解业务上下文的"优化"、会改变可见行为的重构(即使看似等价)、"过度工程化"的设计模式应用。
永远不要盲目接受 AI 的安全代码

AI 生成的加密、认证、权限检查代码往往看起来合理,但可能存在微妙的漏洞。这类代码必须由了解安全的工程师人工审查,不能只靠"AI 说这样没问题"。

结对编程模式

AI 作为 Pair Programmer

将 AI 融入结对编程流程,可以显著提升效率。以下是几种有效的结对模式:

模式1

驾驶员-导航员(Driver-Navigator)

你作为"驾驶员"手写代码,AI 作为"导航员"提供实时建议和审查。每完成一个功能块,让 AI 审查刚写的代码。

模式2

橡皮鸭调试(Rubber Duck Debugging)

遇到 Bug 时,向 AI 详细描述你的理解和假设。描述的过程本身往往帮助你发现问题;AI 的提问也常常揭示盲点。

模式3

TDD 搭档

你写测试,AI 写实现;或者 AI 生成测试用例,你验证测试的合理性后再实现。这种模式能保证测试覆盖率。

AI 代码审查的原理与局限

AI 擅长审查什么?

了解 AI 代码审查的能力边界,才能正确使用它,而不是过度依赖:

模式匹配型问题(AI 擅长)
SQL 注入、XSS、未处理异常、N+1 查询、硬编码密码——这些都是有固定代码模式的问题,在训练数据中见过大量案例,AI 能高准确率识别。等同于"有规则的静态分析",但比 ESLint/Bandit 更灵活,能理解上下文。
代码风格和可读性(AI 擅长)
命名是否有意义、函数是否过长、注释是否准确、是否有重复代码——AI 对这类主观性问题有很好的判断力,且能给出具体的改进建议(而不只是"请提高可读性")。
业务逻辑正确性(AI 不擅长)
"这段折扣计算逻辑是否符合业务规则"——AI 不了解你的业务规则,无法判断业务逻辑是否正确,只能判断代码是否"看起来合理"。这类审查只有了解业务的工程师能做。
架构级问题(AI 不擅长)
"这个设计决策是否适合我们的团队规模和技术债务情况"——架构判断需要了解整个系统的历史和约束,AI 看不到这些背景。它可能建议引入微服务,但你的团队只有2人,维护成本远超收益。

重构的风险管理

AI 辅助重构最大的风险不是"代码写错了",而是"测试覆盖不足导致问题发现太晚"。以下是重构风险从低到高的排序:

重构风险等级(从低到高):

低风险(AI 可以自由发挥):
  ✓ 重命名局部变量(作用域清晰)
  ✓ 提取重复的字符串为常量
  ✓ 添加文档注释
  ✓ 调整代码格式

中等风险(需要测试覆盖):
  ⚠ 提取函数(可能改变副作用范围)
  ⚠ 修改函数参数顺序
  ⚠ 将同步代码改为异步
  ⚠ 合并相似函数

高风险(需人工充分审查):
  ✗ 修改数据库模型(迁移风险)
  ✗ 重构核心算法
  ✗ 修改公共 API 接口(破坏性变更)
  ✗ 改变认证/权限逻辑

验证 AI 代码建议的黄金标准

"代码能跑通"不等于"代码是正确的"。验证 AI 建议需要系统性的检查:

# 示例:AI 生成了这段"优化"的代码
def get_user_permissions(user_id: int) -> set[str]:
    # AI 将原来的循环"优化"为集合推导式
    return {
        perm.name
        for role in db.query(Role).filter(Role.user_id == user_id).all()
        for perm in role.permissions  # 注意:这里隐含了 N+1 查询!
    }

# 验证步骤1:语义等价性检查
# 原始代码是否做了同样的事?是否有遗漏的条件判断?

# 验证步骤2:边界条件测试
def test_get_user_permissions():
    # 正常用户有多个角色
    assert get_user_permissions(1) == {"read", "write"}
    # 用户没有任何角色(空集合,不是 None)
    assert get_user_permissions(999) == set()
    # 用户 ID 为负数(应该返回空集合而不是报错)
    assert get_user_permissions(-1) == set()

# 验证步骤3:性能验证(这里 AI 实际上引入了 N+1 查询)
# 应该用 SELECT ... JOIN 一次性查询,而不是逐条查 permissions
# 正确写法:
from sqlalchemy.orm import joinedload

def get_user_permissions_optimized(user_id: int) -> set[str]:
    return {
        perm.name
        for role in db.query(Role).options(joinedload(Role.permissions))# 预加载权限,避免 N+1
            .filter(Role.user_id == user_id).all()
        for perm in role.permissions
    }
AI 重构的常见隐形陷阱
第5章小结