backend-bugfix
npx skills add https://github.com/penkzhou/swiss-army-knife-plugin --skill backend-bugfix
Agent 安装分布
Skill 文档
Backend Bugfix Workflow Skill
æ¬ skill æä¾å端æµè¯ bugfix ç宿´å·¥ä½æµç¥è¯ï¼å æ¬é误åç±»ä½ç³»ã置信度è¯åç³»ç»å TDD æä½³å®è·µã
é误åç±»ä½ç³»
å端æµè¯å¤±è´¥ä¸»è¦å为以ä¸ç±»åï¼æé¢çæåºï¼ï¼
1. æ°æ®åºé误ï¼30%ï¼
çç¶ï¼æ°æ®åºè¿æ¥å¤±è´¥ãæ¥è¯¢é误ãäºå¡é®é¢
è¯å«ç¹å¾ï¼
IntegrityErrorãOperationalErrorsqlalchemy.exc.*å¼å¸¸UNIQUE constraint failed- äºå¡æªæäº¤ææªåæ»
è§£å³çç¥ï¼æ£ç¡®å¤çäºå¡è¾¹ç
# Before - äºå¡æªæ£ç¡®å¤ç
def create_user(db: Session, user: UserCreate):
db_user = User(**user.dict())
db.add(db_user)
db.commit() # å¤±è´¥æ¶æ åæ»
return db_user
# After - ä½¿ç¨ try/except ç¡®ä¿äºå¡å®å
¨
def create_user(db: Session, user: UserCreate):
try:
db_user = User(**user.dict())
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
except IntegrityError:
db.rollback()
raise HTTPException(status_code=409, detail="User already exists")
2. éªè¯é误ï¼25%ï¼
çç¶ï¼è¾å ¥éªè¯å¤±è´¥ãSchema ä¸å¹é
è¯å«ç¹å¾ï¼
ValidationErrorpydantic.error_wrappers422 Unprocessable Entityfield requiredé误
è§£å³çç¥ï¼å®å Pydantic Schema
# Before - 缺å°éªè¯
class UserCreate(BaseModel):
email: str # æ²¡ææ ¼å¼éªè¯
# After - ä½¿ç¨ Pydantic éªè¯å¨
class UserCreate(BaseModel):
email: EmailStr
@field_validator('email')
@classmethod
def email_must_be_valid(cls, v):
if not v or '@' not in v:
raise ValueError('Invalid email format')
return v.lower()
3. API é误ï¼20%ï¼
çç¶ï¼ç«¯ç¹è¿åéè¯¯ç¶æç ãè·¯ç±ä¸å¹é
è¯å«ç¹å¾ï¼
HTTPException404 Not Foundã405 Method Not Allowed- ååºæ ¼å¼ä¸ç¬¦å颿
è§£å³çç¥ï¼æ£æ¥è·¯ç±å®ä¹åè¯·æ±æ¹æ³
# ç¡®ä¿ç«¯ç¹å®ä¹æ£ç¡®
@router.get("/users/{user_id}", response_model=UserResponse)
async def get_user(user_id: int, db: Session = Depends(get_db)):
user = db.query(User).filter(User.id == user_id).first()
if not user:
raise HTTPException(status_code=404, detail="User not found")
return user
4. 认è¯é误ï¼10%ï¼
çç¶ï¼è®¤è¯å¤±è´¥ãæéä¸è¶³
è¯å«ç¹å¾ï¼
401 Unauthorized403 Forbidden- Token ç¸å ³é误
credentialséªè¯å¤±è´¥
è§£å³çç¥ï¼æ£æ¥è®¤è¯æµç¨å Token å¤ç
# ç¡®ä¿ Token éªè¯æ£ç¡®
async def get_current_user(token: str = Depends(oauth2_scheme)):
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
user_id: str = payload.get("sub")
if user_id is None:
raise credentials_exception
except JWTError:
raise credentials_exception
return user_id
5. 弿¥é误ï¼8%ï¼
çç¶ï¼å¼æ¥æä½è¶ æ¶ãå¹¶åé®é¢
è¯å«ç¹å¾ï¼
TimeoutErrorCancelledErrorasyncioç¸å ³å¼å¸¸- 缺å°
awaitå ³é®å
è§£å³çç¥ï¼æ£ç¡®ä½¿ç¨ async/await
# Before - å¿è®° await
async def get_data():
result = fetch_from_external_api() # ç¼ºå° await
return result
# After - æ£ç¡®çå¾
弿¥æä½
async def get_data():
result = await fetch_from_external_api()
return result
6. é ç½®é误ï¼5%ï¼
çç¶ï¼é ç½®å 载失败ãç¯å¢åé缺失
è¯å«ç¹å¾ï¼
KeyErrorenvironmentç¸å ³é误settingså 载失败
è§£å³çç¥ï¼ä½¿ç¨ Pydantic Settings 管çé ç½®
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
database_url: str
secret_key: str
class Config:
env_file = ".env"
settings = Settings()
置信度è¯åç³»ç»
è¯åæ åï¼0-100ï¼
| åæ° | çº§å« | è¡ä¸º |
|---|---|---|
| 80+ | é« | èªå¨æ§è¡ |
| 60-79 | ä¸ | æ è®°éªè¯åç»§ç» |
| 40-59 | ä½ | æå询é®ç¨æ· |
| <40 | ä¸ç¡®å® | 忢æ¶éä¿¡æ¯ |
置信度计ç®
置信度 = è¯æ®è´¨é(40%) + 模å¼å¹é
(30%) + ä¸ä¸æå®æ´æ§(20%) + å¯å¤ç°æ§(10%)
è¯æ®è´¨éï¼
- é«ï¼æå®æ´å æ ãè¡å·ãå¯ç¨³å®å¤ç°
- ä¸ï¼æé误信æ¯ä½ç¼ºä¸ä¸æ
- ä½ï¼ä» ææ¨¡ç³æè¿°
模å¼å¹é ï¼
- é«ï¼å®å ¨å¹é å·²ç¥é误模å¼
- ä¸ï¼é¨åå¹é
- ä½ï¼æªç¥é误类å
ä¸ä¸æå®æ´æ§ï¼
- é«ï¼æµè¯ä»£ç + æºä»£ç + é ç½® + æ°æ®åº Schema
- ä¸ï¼åªææµè¯ææºä»£ç
- ä½ï¼åªæé误信æ¯
å¯å¤ç°æ§ï¼
- é«ï¼æ¯æ¬¡è¿è¡é½å¤ç°
- ä¸ï¼å¶åï¼å¯è½ä¸æ°æ®æå¹¶åç¸å ³ï¼
- ä½ï¼ç¯å¢ç¸å ³
TDD æµç¨
RED Phaseï¼å失败æµè¯ï¼
import pytest
from fastapi.testclient import TestClient
def test_create_user_duplicate_email(client: TestClient, db_session):
"""æµè¯éå¤é®ç®±åºè¿å 409"""
# 1. 设置åç½®æ¡ä»¶
client.post("/api/users", json={"email": "test@example.com", "name": "User 1"})
# 2. æ§è¡è¢«æµæä½
response = client.post("/api/users", json={"email": "test@example.com", "name": "User 2"})
# 3. æè¨ææç»æ
assert response.status_code == 409
assert "already exists" in response.json()["detail"]
GREEN Phaseï¼æå°å®ç°ï¼
# åªå让æµè¯éè¿çæå°ä»£ç
# ä¸è¦ä¼åï¼ä¸è¦æ·»å é¢å¤åè½
def create_user(db: Session, user: UserCreate):
existing = db.query(User).filter(User.email == user.email).first()
if existing:
raise HTTPException(status_code=409, detail="User already exists")
# ... åå»ºç¨æ·é»è¾
REFACTOR Phaseï¼éæï¼
# æ¹å代ç ç»æ
# ä¿ææµè¯éè¿
# æ¶é¤éå¤
# æåå
Œ
±é»è¾å°æå¡å±
è´¨éé¨ç¦
| æ£æ¥é¡¹ | æ å |
|---|---|
| æµè¯éè¿ç | 100% |
| 代ç è¦çç | >= 90% |
| æ°ä»£ç è¦çç | 100% |
| Lint (flake8) | æ é误 |
| TypeCheck (mypy) | æ é误 |
pytest å¸¸ç¨æ¨¡å¼
Fixtures
@pytest.fixture
def db_session():
"""å建æµè¯æ°æ®åºä¼è¯"""
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
yield session
session.close()
@pytest.fixture
def client(db_session):
"""å建æµè¯å®¢æ·ç«¯"""
def override_get_db():
yield db_session
app.dependency_overrides[get_db] = override_get_db
return TestClient(app)
弿¥æµè¯
import pytest
@pytest.mark.asyncio
async def test_async_operation():
result = await some_async_function()
assert result is not None
åæ°åæµè¯
@pytest.mark.parametrize("status_code,detail", [
(400, "Invalid input"),
(404, "Not found"),
(409, "Already exists"),
])
def test_error_responses(client, status_code, detail):
# æµè¯å¤ç§éè¯¯åºæ¯
pass
常ç¨å½ä»¤
# è¿è¡å端æµè¯
make test TARGET=backend
# è¿è¡ç¹å®æµè¯
make test TARGET=backend FILTER=test_create_user
# æä½¿ç¨ pytest ç´æ¥è¿è¡
pytest tests/ -k "test_create_user" -v
# è¦ççæ£æ¥
pytest --cov=app --cov-report=term-missing --cov-fail-under=90
# Lint æ£æ¥
flake8 app/ tests/
# ç±»åæ£æ¥
mypy app/
# 宿´ QA
make qa
ç¸å ³ææ¡£
ææ¡£è·¯å¾ç±é
ç½®æå®ï¼best_practices_dirï¼ï¼ä½¿ç¨ä»¥ä¸å
³é®è¯æç´¢ï¼
- æµè¯æä½³å®è·µï¼å ³é®è¯ “testing”, “pytest”, “backend”
- æ°æ®åºæä½ï¼å ³é®è¯ “database”, “sqlalchemy”, “transaction”
- API 设计ï¼å ³é®è¯ “api”, “endpoint”, “fastapi”
- é®é¢è¯æï¼å ³é®è¯ “troubleshooting”, “debugging”