大前端团队如何落地 AI Friendly,如何避免AI写出"屎山"代码

2 阅读8分钟

ִ 这是个很值得深想的问题。AI 辅助开发的"屎山"问题本质上是一个治理问题,不是 AI 本身的问题——AI 是放大器,好的治理被放大变得更好,混乱的治理被放大变成更快速的熵增。

下面从几个层次系统性地拆解:


一、根基层:建立 AI 可读的「项目宪法」

这是最重要、最被忽视的一层。AI 每次进入项目都是"新来的",如果没有一份清晰的项目约定文件,它会根据它对"一般最佳实践"的理解来生成代码,而不是你们团队的最佳实践。

CLAUDE.md / AGENTS.md(项目级 AI 说明书)

# 项目架构约定
- 状态管理:只用 Zustand,不引入 Redux/Jotai
- 样式方案:Tailwind utility-first,禁止写内联 style
- 数据获取:统一用 React Query,不直接用 useEffect 发请求
- 组件类型:UI组件在 /components/ui,业务组件在 /components/features
- 禁止事项:不允许使用 any,不允许直接操作 DOM

# 当前技术债务(AI 不要动这些文件)
- /legacy/payment 模块正在重构中,不要修改

这个文件的价值是:所有 AI 工具(Claude、Cursor、Copilot)都会优先读取它,让 AI 的每次输出都在你的约定范围内。

关键治理动作:

  • 把架构决策记录(ADR, Architecture Decision Record)写成 Markdown 放进仓库,让 AI 能检索到"为什么这样设计"
  • .cursor/rules.claude/ 目录下按功能域拆分规则文件,避免一个巨型文件

二、类型层:TypeScript 是 AI Friendly 的地基

类型系统是 AI 和人类开发者共同依赖的「合约」。类型越严格,AI 产出的代码越准确,越不容易引入运行时错误。

具体要做的事:

  • 开启严格模式,不妥协"strict": true + "noUncheckedIndexedAccess": true。宽松的类型配置会让 AI 生成"能跑但不安全"的代码。
  • 所有 API 响应类型化:用 Zod / valibot 在运行时校验 + 推导类型,而不是手写 interface 然后靠 as 强转。AI 理解 Zod schema 的语义能力远强于理解注释。
// AI 不友好:靠注释约定
const user = response.data as User // hope it's correct

// AI 友好:运行时 + 编译时双重保障
const user = UserSchema.parse(response.data)
  • 用 branded types 表达业务约束:让类型携带业务语义,AI 就不会混淆 UserIdOrderId
type UserId = string & { readonly __brand: 'UserId' }
type OrderId = string & { readonly __brand: 'OrderId' }
  • 函数签名要表达意图:返回类型显式标注,参数用 options object 而非位置参数(AI 不会搞错参数顺序)。

三、架构层:让 AI 能「安全地局部推理」

屎山代码最大的特征是:牵一发而动全身。AI 改一个地方,不知道影响了哪里。好的架构应该让每个模块可以被独立理解和修改。

边界清晰的模块化(Feature-Sliced Design 或类似思想)

src/
  features/
    user-auth/
      api.ts          # 只有这里能调用 /auth/* 接口
      store.ts        # 只有这里管理 auth 状态
      components/     # 只有 user-auth 用的组件
      types.ts        # 只有这里的类型
      index.ts        # 对外暴露的公共 API
  shared/
    ui/               # 纯展示组件,无业务逻辑
    lib/              # 通用工具函数

这种结构的 AI Friendly 价值:AI 在处理某个 feature 时,只需要加载这个 feature 目录下的文件就能完整理解上下文,不需要全局扫描。

禁止跨层依赖(用 ESLint 规则强制执行)

// eslint-plugin-boundaries 或 eslint-plugin-import 配置
"boundaries/no-unknown-imports": "error"
// features 不能互相引用,只能通过 shared 通信

副作用隔离:数据获取、DOM 操作、全局状态修改都要集中在明确的地方,纯函数写业务逻辑。AI 对纯函数的理解和改写准确率远高于充满副作用的代码。


四、契约层:用 Schema 驱动前后端协作

这一层是防止 AI 生成"前后端对不上"代码的关键。

OpenAPI / tRPC / GraphQL 作为单一真相来源

后端定义 OpenAPI spec
    ↓
openapi-typescript 自动生成前端类型
    ↓
AI 生成业务代码时,类型已经正确
    ↓
即使 AI 不知道后端细节,也不会写出类型错误的调用

具体工具链:

  • openapi-typescript:从 OpenAPI 生成 TypeScript 类型,接口变更时自动同步
  • orval / hey-api:进一步生成 React Query hooks,连数据获取层都不需要手写
  • msw (Mock Service Worker) :基于同一份 schema 生成 mock,AI 写测试时有准确的 mock 数据

这样做之后,AI 就算不知道接口文档,也能从类型推断出正确的调用方式。


五、组件层:建立可被 AI 正确使用的 Design System

这是大前端团队最有杠杆效应的投入点。

组件要"自文档化"

interface ButtonProps {
  /**
   * 按钮的视觉层级
   * - primary: 页面主操作,每个视图最多一个
   * - secondary: 次要操作
   * - ghost: 不需要强调的操作,如取消
   */
  variant: 'primary' | 'secondary' | 'ghost'
  
  /**
   * 加载状态:显示 spinner 并禁用点击
   * 异步操作期间必须设为 true,防止重复提交
   */
  loading?: boolean
}

丰富的 JSDoc 注释让 AI 在使用组件时不只是知道有这个 prop,还知道什么场景用什么值。

Storybook + storybook-addon-docs

每个组件有完整的使用示例,AI 在生成业务页面时可以参考正确的用法,而不是凭空猜测组件 API。更重要的是,AI 被 Cursor 等工具索引时会优先读 stories 文件。

component-index.ts 明确暴露边界

// ui/index.ts —— AI 只应该从这里引入组件
export { Button } from './button'
export { Modal } from './modal'
// 没有 export 的内部实现文件,AI 不应该直接引用

六、测试层:让 AI 的每次修改都有安全网

没有测试,AI 改代码就像在黑暗中走路。有了测试,AI 不仅能改代码,还能自我验证。

测试策略(按 AI Friendly 优先级排序)

  1. 类型测试(零运行成本,最高性价比):用 tsdexpect-type 确保泛型和条件类型正确
  2. 单元测试:纯函数、工具函数、业务逻辑——AI 改这些时能立刻验证
  3. 组件测试(Testing Library) :测用户行为,不测实现细节(实现细节会随 AI 重构变化)
  4. 契约测试(Pact) :验证前后端接口约定不被破坏

关键原则:测试要测行为,不测实现。测"用户点击提交按钮后,表单被清空",而不是"某个 setState 被调用"。这样 AI 重构内部实现时,测试不会因为内部细节变化而大面积失败。

在 CI 中加入 AI 感知的门禁

# .github/workflows/ai-guard.yml
- name: Type Coverage Check
  run: npx type-coverage --at-least 95  # 类型覆盖率不低于 95%

- name: Circular Dependency Check  
  run: npx madge --circular src/  # AI 引入循环依赖立刻报错

- name: Bundle Size Guard
  run: npx size-limit  # AI 引入大依赖立刻发现

七、防腐层:阻止 AI 引入「熵增」的 CI 规则

这是防止屎山代码最重要的工程机制——把约定变成机器强制执行的规则,AI 生成的代码也必须通过。

ESLint 规则集(团队共建,持续维护)

// 防止 AI 引入错误模式的规则
rules: {
  'no-restricted-imports': ['error', {
    patterns: ['lodash', '!lodash-es'],  // 只允许 tree-shakable 版本
  }],
  '@typescript-eslint/no-explicit-any': 'error',
  '@typescript-eslint/no-non-null-assertion': 'warn',
  'import/no-cycle': 'error',  // 禁止循环依赖
  'react-hooks/exhaustive-deps': 'error',  // AI 经常漏写依赖
}

Prettier + EditorConfig:统一格式化,AI 生成的代码进入仓库前自动格式化,不产生格式噪音。

Husky + lint-staged:在 commit 时自动跑类型检查和 lint,AI 生成的代码在进入仓库前就被校验。


八、文档层:让团队知识对 AI 可索引

决策日志(ADR)

# ADR-007: 选择 Zustand 而非 Redux Toolkit

## 背景
2024年Q3评估状态管理方案...

## 决策
选择 Zustand,原因:
1. 样板代码少 70%,AI 生成代码更简洁
2. 不需要 Provider 包裹,减少组件树复杂度

## 后果
- 禁止在新代码中引入 Redux(见 eslint no-restricted-imports)
- 现有 Redux 代码在 /legacy 目录,计划 2025Q1 迁移完毕

这类文件放在仓库里,AI 在修改相关代码时能检索到,不会"好心"引入你们已经决定废弃的方案。

/docs/conventions/ 目录,按主题拆分:

  • api-calling.md:如何调用接口
  • error-handling.md:错误处理规范
  • performance.md:性能约定(哪些操作必须做懒加载)

总结:建设优先级路线图

第一阶段(治理基础)
├── CLAUDE.md / AGENTS.md —— 给 AI 立规矩
├── TypeScript strict mode —— 类型地基
└── ESLint + Husky 门禁 —— 机器强制执行约定

第二阶段(结构清晰)
├── Feature-Sliced 或类似模块化架构
├── OpenAPI → 自动生成类型和 hooks
└── Design System 组件自文档化

第三阶段(测试安全网)
├── 行为测试(而非实现测试)
├── 契约测试
└── CI 质量门禁(类型覆盖率、包大小、循环依赖)

第四阶段(知识沉淀)
├── ADR 决策日志
├── 分域约定文档
└── Storybook 组件使用示例

核心思想只有一句话:把团队的集体智慧编码进机器可执行的规则和 AI 可读的文档里,而不是依赖"大家都知道"的口头约定。 AI 时代,口头约定会被 AI 无视;机器约束和文档约定会被 AI 遵守。