开发

软件开发相关知识

Spec驱动开发(SDD)实战:用OpenSpec和Cursor Rules告别AI编程混乱

为什么"氛围编程"不够用?

项目越大,AI编程的问题越明显: - 每次请求孤立,AI不知道系统整体设计 - 两次生成的代码风格、模式完全不同 - "改了这里,坏了那里"的副作用频繁

SDD的核心:先写规范,再让AI生成实现


一、OpenSpec YAML定义

# specs/auth.spec.yaml
name: UserAuthentication
version: "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,积累经验再推广。