AI写了90%的代码,谁来守住底线——31万行重构的约束实践

2026-06-04 29 预计阅读时间:1 分钟
来源:tech.meituan.com AI 摘要 原文链接

免责声明:本文为 AI 摘要整理,建议结合原文阅读。摘要可能省略上下文、版本差异或边界条件,不作为官方说明。

预计阅读时间:15 分钟

当仓库里超过九成代码由 AI 生成,决定系统走向的不再是"谁写得更快",而是"谁能约束 AI 的输出"。没有统一规范,AI 只会成倍放大混乱——命名不一致、架构漂移、重复抽象层层叠加,技术债不是线性增长,而是指数膨胀。

我们用 31 万行代码的重构实践验证了一个思路:把 Agent 评测的方法论搬到 AI Coding 管理上,通过技术债盘点、Rule 建设、重构 SOP 和 Pre-PR 机制,让重构从"高成本专项工程"变成"随迭代持续推进的日常动作"。

技术债不是模糊感觉,而是可量化的清单

AI Coding 的技术债有一个特征:表面看起来运行正常,内部结构却在持续腐烂。因为 AI 倾向于在局部上下文中补全代码,不会主动回溯全局一致性。一个函数名 getUserById 和另一个 fetch_user_by_id 并存,AI 不会觉得有问题,但人类维护者会。

第一步不是动手重构,而是把债盘点清楚。我们按三个维度做梳理:

维度 具体项 量化方式
命名一致性 同一概念的不同命名变体 grep 统计变体数量
架构合规 跨层调用、循环依赖 依赖图分析工具输出
重复抽象 功能重叠的函数/类 相似度检测或人工标注

盘点结果不是一份文档,而是一份可追踪的清单,每条债有编号、影响范围和优先级。这和 Agent 评测中的"评测项清单"逻辑一致——先定义测什么,再定义怎么测。

Rule 不是写给人看的规范,而是喂给 AI 的约束

传统代码规范写在 Confluence 里,开发者偶尔翻翻,AI 根本不看。AI Coding 的 Rule 必须以机器可读、上下文可注入的形式存在。

我们建设的 Rule 分三层:

  • 全局 Rule:项目级架构约束、命名约定、禁止模式,写入 .cursorrules 或类似配置文件,AI 每次生成代码时自动加载。
  • 模块 Rule:特定目录下的局部约定,比如 src/api/ 下必须用 RESTful 命名、统一错误码格式,放在对应目录的 .mdc 文件中。
  • 任务 Rule:单次重构任务的精确约束,比如"本次只改命名,不改逻辑",在 prompt 中显式声明。

下面是一个实际可用的全局 Rule 文件示例:

# .cursorrules — 项目级 AI Coding 约束
# 此文件会被 AI 编码工具在每次生成时自动注入上下文

project:
  name: "order-service"
  language: "python"
  framework: "fastapi"

architecture:
  layers:
    - name: "api"
      path: "src/api/"
      rule: "只做参数校验和路由,不包含业务逻辑"
    - name: "service"
      path: "src/service/"
      rule: "业务逻辑层,禁止直接操作数据库"
    - name: "repo"
      path: "src/repo/"
      rule: "数据访问层,禁止引用 service 层"
  dependency_direction: "api  service  repo,禁止反向依赖"

naming:
  function: "snake_case,动词开头,如 get_order_by_id"
  class: "PascalCase,名词,如 OrderService"
  variable: "snake_case,禁止驼峰混用"
  constant: "UPPER_SNAKE_CASE"
  # 同一概念的统一命名映射,AI 必须遵守
  concept_map:
    user_id: ["user_id", "userId", "uid"]  # 禁止使用后两个变体
    order_id: ["order_id", "orderId", "oid"]  # 禁止使用后两个变体

prohibited_patterns:
  - "禁止在 api 层写 SQL 查询"
  - "禁止用 print 替代 logger"
  - "禁止硬编码配置值,必须从 settings 读取"
  - "禁止 catch 所有异常后静默忽略"
  - "禁止在循环内做数据库查询(N+1 问题)"

error_handling:
  framework: "fastapi.HTTPException"
  format:
    status_code: "HTTP 状态码"
    detail: "面向调用方的错误描述"
    internal_code: "内部错误码,格式 ERR_{MODULE}_{NUMBER}"

关键设计点:concept_mapprohibited_patterns 是最有效的两个字段。前者解决 AI 命名漂移问题,后者划定硬性红线。Rule 不是越长越好,而是越精确越好——AI 对"禁止"比"建议"响应更稳定。

重构 SOP:把大手术拆成可评测的小步骤

31 万行代码不可能一次性重构。我们的做法是把重构拆成可评测的原子步骤,每个步骤有明确的输入、输出和验证条件——这和 Agent 评测中"每个评测项独立可判"的思路完全一致。

一个典型的重构 SOP 流程:

SOP: 统一命名重构
├── Step 1: 扫描变体
│   输入: concept_map + 项目源码
│   输出: 变体清单 (文件路径 + 行号 + 当前命名)
│   验证: 变体数量 > 0 才继续
│
├── Step 2: 执行替换
│   输入: 变体清单 + 目标命名
│   输出: 替换后的文件 diff
│   验证: diff 中只有命名变更,无逻辑变更
│
├── Step 3: 回归测试
│   输入: 替换后的代码
│   输出: 测试报告
│   验证: 全部通过,无新增失败
│
├── Step 4: Rule 更新
│   输入: 本次统一的概念
│   输出: 更新后的 concept_map
│   验证: concept_map 包含新概念条目

每个 Step 的验证条件就是评测判据。AI 执行 Step 2 时,prompt 里要写清楚约束:

# refactor_prompt.py — 生成重构 prompt 的辅助脚本
# 用法: python refactor_prompt.py --concept user_id --target user_id

import argparse
import json

def build_prompt(concept: str, target_name: str, variants_file: str) -> str:
    """根据 concept_map 和扫描结果,生成精确的重构 prompt"""

    with open(variants_file) as f:
        variants = json.load(f)

    # 只取当前概念的变体
    items = [v for v in variants if v["concept"] == concept]

    prompt = f"""你正在执行一个命名统一重构任务,严格约束如下:

1. 只修改命名,不修改任何逻辑、注释或格式
2. 将以下变体统一为 `{target_name}`:
{json.dumps(items, indent=2, ensure_ascii=False)}
3. 修改后运行: pytest tests/ -x --tb=short
4. 如果测试失败,回滚所有变更并报告失败原因
5. 输出变更文件列表和每个文件的 diff 摘要

禁止行为:
- 禁止重构之外的任何改动
- 禁止删除或新增代码行
- 禁止修改 import 顺序
"""
    return prompt

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--concept", required=True)
    parser.add_argument("--target", required=True)
    parser.add_argument("--variants-file", default="variants.json")
    args = parser.parse_args()

    prompt = build_prompt(args.concept, args.target, args.variants_file)
    print(prompt)

运行方式:

# Step 1: 扫描命名变体(可以用 grep + AI 辅助分析)
grep -rn "userId\|uid" src/ --include="*.py" | python scan_variants.py > variants.json

# Step 2: 生成重构 prompt
python refactor_prompt.py --concept user_id --target user_id --variants-file variants.json

# Step 3: 将 prompt 交给 AI 执行,人工审核 diff
git diff --stat  # 只看变更范围
git diff         # 逐行确认只有命名变更

# Step 4: 运行回归测试
pytest tests/ -x --tb=short

SOP 的核心价值:每个步骤的输出可评测,失败可定位,回滚可执行。AI 不是自由发挥,而是在受限轨道上运行。

Pre-PR:AI 代码进入主分支前的最后一道闸门

AI 生成的代码最容易出问题的不是逻辑错误(测试能拦住),而是结构性违规——跨层调用、硬编码、命名不一致。这些在功能测试中不会暴露,但在长期维护中会持续腐烂。

Pre-PR 机制就是在代码提交到主分支之前,用自动化 + 人工双重评测拦截结构性问题:

# .github/workflows/pre-pr-check.yml
# AI 生成代码的 Pre-PR 结构性检查

name: Pre-PR Structural Check

on:
  pull_request:
    types: [opened, synchronize]

jobs:
  architecture-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      # 检查跨层依赖:api 层禁止直接 import repo 层
      - name: Cross-layer dependency check
        run: |
          violations=0
          # api 层不应直接引用 repo 层
          for f in src/api/*.py; do
            if grep -n "from src.repo" "$f"; then
              violations=$((violations + 1))
              echo "::error file=$f::API layer directly imports repo layer"
            fi
          done
          if [ $violations -gt 0 ]; then
            echo "Found $violations cross-layer violations"
            exit 1
          fi

      # 检查命名变体:禁止 concept_map 中标记的变体出现
      - name: Naming convention check
        run: |
          violations=0
          # 禁止 userId 和 uid 变体
          for variant in userId uid; do
            matches=$(grep -rn "$variant" src/ --include="*.py" | wc -l)
            if [ $matches -gt 0 ]; then
              violations=$((violations + matches))
              echo "::warning::Found $matches occurrences of prohibited variant '$variant'"
            fi
          done
          # 禁止 oid 和 orderId 变体
          for variant in orderId oid; do
            matches=$(grep -rn "$variant" src/ --include="*.py" | wc -l)
            if [ $matches -gt 0 ]; then
              violations=$((violations + matches))
              echo "::warning::Found $matches occurrences of prohibited variant '$variant'"
            fi
          done
          if [ $violations -gt 0 ]; then
            echo "Found $violations naming violations"
            exit 1
          fi

      # 检查硬编码配置值
      - name: Hardcoded config check
        run: |
          # 搜索常见的硬编码模式
          hardcoded=$(grep -rn "localhost:5432\|localhost:6379\|hardcoded_password" src/ --include="*.py" | wc -l)
          if [ $hardcoded -gt 0 ]; then
            echo "::error::Found $hardcoded hardcoded config values"
            exit 1
          fi

      # 检查 N+1 查询模式(循环内数据库调用)
      - name: N+1 query pattern check
        run: |
          # 简化检测:for/while 循环体内出现 session.query 或 db.query
          violations=0
          for f in src/api/*.py src/service/*.py; do
            if python -c "
import ast, sys
tree = ast.parse(open('$f').read())
for node in ast.walk(tree):
    if isinstance(node, (ast.For, ast.While)):
                    for child in ast.walk(node):
                        if isinstance(child, ast.Call):
                            func = child.func
                            if isinstance(func, ast.Attribute) and func.attr in ('query', 'execute', 'fetchone', 'fetchall'):
                                print(f'$f:line {child.lineno}')
                                sys.exit(1)
"; then
              violations=$((violations + 1))
            fi
          done
          if [ $violations -gt 0 ]; then
            exit 1
          fi

      # 运行功能测试
      - name: Run tests
        run: pytest tests/ -x --tb=short

Pre-PR 的设计原则:功能测试拦逻辑错误,结构性检查拦架构腐烂。两者缺一不可。AI Coding 场景下,结构性检查的权重应该高于传统项目,因为 AI 的"局部合理、全局混乱"倾向比人类更强。

从专项到日常:重构的可持续节奏

31 万行重构最关键的认知转变是:重构不能是里程碑式的专项工程,必须是迭代中的日常动作

实现这个转变需要三个条件:

  1. 债可追踪——每条技术债有编号和状态,不是模糊的"代码质量差"。
  2. 步可原子——每次重构只做一件事,有明确的评测判据,失败可回滚。
  3. 门可自动——Pre-PR 自动拦截结构性违规,不需要人工逐行审查。

我们最终的工作节奏:

每个迭代周期(1-2周):
  ├── 开发阶段:AI 生成代码 + Rule 约束 + 任务级 prompt
  ├── 提交阶段:Pre-PR 自动检查 → 人工审核结构性变更
  ├── 合入阶段:通过后合入主分支
  └── 盘点阶段:从债清单中选 2-3 条,作为下个迭代的重构任务

每个迭代消化 2-3 条债,31 万行代码的债清单大概 60-80 条,20-30 个迭代就能清完。不需要停下来做"重构月",也不需要全员投入。AI 写新代码,人类管旧债,Rule 拦新增债——三条线并行推进。

采用建议与风险边界

先盘点,再写 Rule,最后建 SOP——顺序不能反。没有债清单,Rule 写不出精确的 concept_mapprohibited_patterns;没有 Rule,SOP 的约束条件无从定义。

Rule 的维护成本容易被低估。项目演进时,concept_map 和架构分层会变化,Rule 必须同步更新。我们每两周 review 一次 Rule 文件,删除过时约束,新增当前痛点。过时的 Rule 比 没有 Rule 更危险——AI 会遵守已经不合理的约束。

Pre-PR 检查项要从实际痛点出发,不要提前铺满。一开始只加最痛的两三条(比如命名变体和跨层依赖),跑稳了再逐步增加。检查项太多,开发者会绕过或忽略所有警告。

AI Coding 的管理本质是评测问题——定义什么是好的输出,定义如何判断输出是否达标,定义不达标时如何回退。Agent 评测的思路——清单化、可判性、可追溯——恰好是管理 AI Coding 最需要的骨架。31 万行代码验证了这一点:约束 AI 的能力,才是决定系统走向的能力


相关推荐