跳到主内容

从随机发挥到稳定输出:用 Rules 统一 AI 代码风格 ​

问题背景:AI 写得快,但团队风格越来越散 ​

引入 AI Copilot 后,个人开发效率确实明显提升:样板代码、单测、接口调用、错误处理都能快速生成。但在团队代码库中,新的问题很快出现:同一个函数,有人让 AI 生成类形式,有人得到函数式写法;错误处理有的抛异常,有的返回错误对象;命名有的采用 userDTO,有的使用 UserInfo;日志、注释、目录结构也随个人提示词波动。

本质原因不是模型不会写代码,而是风格约束没有进入稳定上下文。如果规范只存在于 Wiki、口头约定或成员各自的自定义指令中,AI 每次面对的都是不完整、不可验证、不可复用的上下文。

团队级 AI 编码规范要解决三件事:

  1. 让规范随仓库走:新人 clone 项目后即可获得同一套规则;
  2. 让规则可执行:不仅描述“应该怎样”,还能通过格式化、Lint、测试和 CI 检查;
  3. 让 AI 知道何时使用:不同语言、目录、任务类型加载不同规则,避免无差别塞入全部上下文。

Rules 文件与自定义指令:不是二选一,而是分层协作 ​

很多团队一开始会争论:到底应该使用 Copilot Rules 文件,还是配置个人自定义指令?实际上二者承担的职责不同。

对比项Rules 文件自定义指令
作用范围仓库、目录、文件类型或任务个人账号、IDE 或会话
适合内容团队强制规范、架构边界、测试要求个人表达习惯、常用技术栈偏好
版本管理可进入 Git,随代码评审通常不可集中审计
一致性高,所有成员共享中,依赖个人配置
典型风险规则过多导致上下文稀释个人指令覆盖团队规范

推荐采用“三层规则”:

  • 团队层:放在仓库中的 Rules 文件,定义必须遵守的风格、架构和安全边界;
  • 项目层:针对前端、后端、脚本、测试等目录提供局部规则;
  • 个人层:自定义指令只允许放非冲突偏好,例如“回答使用中文”“先给方案再改代码”。
团队 Rules强制风格与架构边界目录局部规则按语言和任务加载个人指令只保留非冲突偏好AI Copilot 上下文组装仓库规则优先,个人偏好不得覆盖统一风格的代码与测试再由 Lint 与 CI 自动验证

推荐目录结构:把规范变成仓库资产 ​

不同 AI 编程助手对 Rules 的文件名和目录位置略有差异,但核心思想一致:将规则放在模型容易自动发现的位置,并用文件作用域控制上下文。可以采用如下结构:

text
project-root/
├── .ai/
│   ├── rules/
│   │   ├── 00-team-style.md          # 全员通用风格
│   │   ├── 10-python-backend.md      # Python 后端规则
│   │   ├── 20-javascript-frontend.md # 前端规则
│   │   └── 90-testing.md             # 测试与验收规则
│   └── prompts/
│       └── review-generated-code.md  # 代码审查提示词
├── src/
├── tests/
├── pyproject.toml
└── .github/
    └── workflows/
        └── ai-style-check.yml

规则文件不要写成长篇散文,建议使用“适用范围 + 必须遵守 + 禁止事项 + 示例”的格式。模型对明确、可检查、带正反例的指令遵循更稳定。

可复用 Rules 模板:统一 Python 服务代码风格 ​

下面是一份可直接放入 .ai/rules/10-python-backend.md 的团队规则模板:

markdown
# Python 后端代码生成规则

## 适用范围
- 当修改 src/ 下的 Python 服务、仓储、API 层代码时生效。
- 自动生成的测试必须放入 tests/ 对应目录。

## 必须遵守
1. 使用类型注解,函数参数和返回值必须显式声明类型。
2. 使用 dataclass 或 Pydantic model 表达结构化输入输出。
3. 对外层异常统一包装为 DomainError,不允许直接暴露底层异常文本。
4. I/O 操作必须设置 timeout,不允许无限等待。
5. 新增业务函数必须包含成功路径和失败路径测试。

## 禁止事项
- 禁止使用 print 输出业务日志,使用 logging。
- 禁止在函数内部隐式读取环境变量,通过配置对象注入。
- 禁止使用裸 except,必须捕获具体异常类型。
- 禁止为了通过测试而删除或跳过断言。

## 代码风格示例
- 命名:函数使用 snake_case,类使用 PascalCase,常量使用 UPPER_SNAKE_CASE。
- 日志:logger = logging.getLogger(__name__)。
- 顺序:标准库、第三方库、本项目模块分组导入。

## 生成前检查
- 是否复用已有 Repository 或 Service?
- 是否处理超时、异常和空值?
- 是否保持现有接口返回结构不变?
- 是否补充最小必要测试?

这类规则的重点是可验证。例如“代码要优雅”无法检查,而“函数必须声明返回类型”“禁止裸 except”“I/O 必须设置 timeout”可以被工具识别。

代码示例:用配置对象和领域错误约束生成结果 ​

下面的 Python 示例展示了规则希望 AI 稳定生成的代码形态。它包含类型注解、超时控制、具体异常捕获、统一错误类型和结构化日志。

python
"""用户资料服务:团队 AI 代码生成参考实现。"""

from __future__ import annotations

import logging
from dataclasses import dataclass
from typing import Any

import httpx

logger = logging.getLogger(__name__)


class DomainError(Exception):
    """领域层统一异常,避免向调用方暴露底层实现细节。"""


@dataclass(frozen=True)
class UserServiceConfig:
    base_url: str
    timeout_seconds: float = 3.0


@dataclass(frozen=True)
class UserProfile:
    user_id: str
    name: str
    email: str


class UserProfileService:
    def __init__(self, config: UserServiceConfig) -> None:
        self._config = config

    async def get_profile(self, user_id: str) -> UserProfile:
        if not user_id:
            raise DomainError("user_id 不能为空")

        url = f"{self._config.base_url}/users/{user_id}"
        try:
            async with httpx.AsyncClient(timeout=self._config.timeout_seconds) as client:
                response = await client.get(url)
                response.raise_for_status()
                data: dict[str, Any] = response.json()
        except httpx.TimeoutException as exc:
            logger.warning("获取用户资料超时: user_id=%s", user_id)
            raise DomainError("用户服务响应超时") from exc
        except httpx.HTTPError as exc:
            logger.warning("获取用户资料失败: user_id=%s", user_id)
            raise DomainError("用户服务暂不可用") from exc

        return UserProfile(
            user_id=str(data["id"]),
            name=str(data["name"]),
            email=str(data["email"]),
        )

配套测试也应遵循同一风格,避免 AI 只生成 happy path:

python
import pytest
import httpx

from user_service import DomainError, UserProfileService, UserServiceConfig


@pytest.mark.asyncio
async def test_get_profile_returns_user() -> None:
    # 实际项目中建议使用 respx 或 mock 传输层替换网络请求
    service = UserProfileService(UserServiceConfig(base_url="https://example.com"))
    # 给定模拟响应后,应断言 user_id、name、email 三个字段
    assert service is not None


@pytest.mark.asyncio
async def test_get_profile_wraps_timeout() -> None:
    config = UserServiceConfig(base_url="https://timeout.example", timeout_seconds=0.01)
    service = UserProfileService(config)

    with pytest.raises(DomainError, match="响应超时"):
        # 使用会超时的 mock client 触发 TimeoutException
        with pytest.MonkeyPatch.context() as monkeypatch:
            def slow_get(*args: object, **kwargs: object) -> object:
                raise httpx.TimeoutException("timeout")
            monkeypatch.setattr(httpx.AsyncClient, "get", slow_get)

注意:第二段测试中的 monkeypatch 写法只是演示“失败路径必须存在”。真实项目中应使用异步 mock 或 respx,否则可能因为函数签名和事件循环问题报错。

用自动化工具兜底:不要只相信提示词 ​

Rules 能提高首次生成质量,但不能替代工程检查。团队应将规范拆成两类:

  • 语义类规则:如分层依赖、异常包装、事务边界,适合写入 Rules 并通过评审或架构测试约束;
  • 格式类规则:如引号、行宽、导入顺序、未使用变量,必须交给 Ruff、Black、ESLint、Prettier 等工具。

例如 Python 项目可在 pyproject.toml 中固化基础风格:

toml
[tool.ruff]
line-length = 100
target-version = "py311"

[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B"]

[tool.ruff.lint.isort]
known-first-party = ["src"]

[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-q --strict-markers"

再提供一个本地检查脚本,让 AI 和开发者都能运行同一条命令:

bash
#!/usr/bin/env bash
set -euo pipefail

ruff check src tests
ruff format --check src tests
pytest

CI 中也应执行相同命令。这样即使模型偶尔忽略 Rules,不合规代码也无法进入主干。

代码审查提示词:让 AI 按团队规范二次检查 ​

除了生成阶段,Rules 还可以用于审查阶段。可以将下面模板保存为 .ai/prompts/review-generated-code.md:

markdown
你是团队代码审查员。请只根据仓库 .ai/rules/ 下的规则审查本次变更,不要引入未经团队确认的新风格。

请按以下顺序输出:
1. 违反强制规则的位置,标明文件、行号、规则编号和修改建议。
2. 可能的异常、超时、空值、并发或安全问题。
3. 缺失的测试场景,尤其是失败路径和边界条件。
4. 可以保持不变的代码,避免无意义重构。

如果规则与现有代码冲突,请明确指出冲突点,不要擅自选择风格。
输出使用中文,建议必须具体到可执行代码。

这个模板的价值在于限制审查范围。否则 AI 很容易根据训练数据中的“通用最佳实践”提出大量与本项目无关的意见,造成审查噪音。

避坑总结:团队规范落地的高频问题 ​

1. 规则写得太抽象,AI 每次自由解释 ​

常见现象是 Rules 中写“代码要简洁、可维护、性能要好”,结果不同成员得到完全不同的实现。修正方法是把抽象要求改写成可观察条件,例如“接口函数必须声明请求和响应类型”“数据库查询必须限制条数”“外部 HTTP 调用必须设置 timeout”。如果一条规则无法通过人工 checklist 或工具判断,就不应作为强制规则。

2. 把所有规范一次性塞进上下文,关键约束被稀释 ​

有些团队把几百行架构文档、编码规范、发布流程全部放入全局 Rules,导致模型抓不住重点,还会增加无关建议。修正方法是按文件类型、目录和任务拆分规则:通用规则保持短小,Python 后端、前端组件、测试、迁移脚本分别维护;全局规则只放跨语言、跨模块都必须遵守的内容。

3. 个人自定义指令与团队规则冲突 ​

典型表现是成员在个人指令中写“优先使用函数式组件”“默认使用 class”“尽量减少类型注解”,覆盖了仓库规范。修正方法是明确优先级:仓库 Rules 高于个人指令;个人配置只允许保留语言、解释详细程度等非代码风格偏好。团队可以在入站检查中要求成员公开与代码风格相关的个人指令。

4. 只有提示词,没有 Lint、测试和 CI 兜底 ​

AI 可能一次遵守规则,下一次因为上下文变化又偏离。若没有自动检查,规范最终仍会退化为人工争论。修正方法是能工具化的全部工具化:格式交给格式化工具,常见坏味道交给 Linter,分层约束写架构测试,关键行为写单测和集成测试;Rules 负责引导生成,CI 负责最终拦截。

5. Rules 不随代码重构更新 ​

项目从 REST 切到消息队列、从单体拆出服务后,旧规则仍要求“所有请求经过 Controller”,AI 就会生成过时结构。修正方法是把 Rules 文档纳入代码评审范围,任何架构边界、目录约定、错误处理策略变化,都必须同步修改对应规则文件,并在提交信息中说明规则变更原因。

结语 ​

团队级 AI 编码规范的目标不是限制 AI 发挥,而是把随机性从交付链路中移除。Rules 文件负责把团队共识变成随仓库版本化的上下文,自定义指令负责保留合理的个人工作习惯,Lint、测试和 CI 则负责最终兜底。当“生成前有规则、生成时有示例、生成后有验证”形成闭环,AI Copilot 才能从个人效率工具真正升级为稳定的团队工程能力。

免责声明

本工具与文章内容仅用于学习测试场景,优化建议仅供参考,请勿直接用于生产环境,由此产生的风险自行承担。