为什么"氛围编程"不够用?
项目越大,AI编程的问题越明显:- 每次请求孤立,AI不知道系统整体设计- 两次生成的代码风格、模式完全不同- "改了这里,坏了那里"的副作用频繁
SDD的核心:先写规范,再让AI生成实现。
一、OpenSpec YAML定义
# specs/auth.spec.yamlname: UserAuthenticationversion: "1.0"constraints: - "密码必须使用bcrypt(cost=12),禁止明文或MD5" - "JWT Token:Access 15分钟,Refresh 7天" - "登录失败5次后锁定30分钟" - "禁止在日志中输出password、token等敏感信息"endpoints: - id: login path: "/api/v1/auth/login" method: POST request: body: email: {type: string, format: email, required: true} password: {type: string, minLength: 8, required: true} response: success: status: 200 body: access_token: {type: string} expires_in: {type: integer, unit: seconds} errors: - status: 401 code: "INVALID_CREDENTIALS" - status: 429 code: "ACCOUNT_LOCKED" test_cases: - name: "正常登录" given: {email: "test@example.com", password: "ValidPass123"} expect: {status: 200, has_fields: ["access_token"]} - name: "错误密码" given: {email: "test@example.com", password: "WrongPass"} expect: {status: 401, body.code: "INVALID_CREDENTIALS"} - name: "账号锁定" setup: "登录失败5次" expect: {status: 429}# 基于Spec生成代码openspec generate --spec specs/auth.spec.yaml --framework fastapi --output src/auth/# 自动生成API+测试文件二、Cursor Rules项目级上下文
<!-- .cursor/rules/auth.mdc -->---globs: src/auth/**alwaysApply: true---## 强制约束- 密码:passlib.hash.bcrypt.hash(password, rounds=12)- JWT:python-jose,算法固定HS256- 认证失败返回通用错误,不透露是用户名还是密码错## 当前实现- Access Token: 15分钟 | Refresh Token: 7天- 账号锁定:5次失败→Redis存储30分钟锁定## 目录规范- models.py:只放Pydantic模型- service.py:业务逻辑+依赖注入- router.py:只处理HTTP层## 禁止操作- 禁止在日志输出password/token/session_id- 禁止直接导入config.py中的密钥(用DI)三、SDD与TDD结合(自动生成测试)
# OpenSpec自动生成的测试套件class TestUserAuthentication: async def test_login_success(self, client, create_user): user = await create_user(email="test@example.com", password="ValidPass123") resp = await client.post("/api/v1/auth/login", json={"email":"test@example.com","password":"ValidPass123"}) assert resp.status_code == 200 assert "access_token" in resp.json() async def test_login_wrong_password(self, client, create_user): await create_user(email="test@example.com", password="ValidPass123") resp = await client.post("/api/v1/auth/login", json={"email":"test@example.com","password":"WrongPassword"}) assert resp.status_code == 401 assert resp.json()["code"] == "INVALID_CREDENTIALS" # 验证不透露具体失败原因 assert "密码" not in resp.json()["message"]四、SDD的实际收益
实测数据(认证模块重构项目):- AI生成代码格式一致性:73% → 97%- 因AI改A坏B导致的回归bug:减少60%- 新人理解系统设计的时间:减少40%
建议从最复杂、最容易出错的核心模块开始引入SDD,积累经验再推广。