Files
xiaoxia-saas/docs/CODING-STANDARD.md
T
xiaoxia 3862996045
CI/CD Pipeline / Check if frontend-only change (push) Waiting to run
CI/CD Pipeline / Validate - Code Quality (push) Waiting to run
CI/CD Pipeline / Validate - Type Check (mypy) (push) Waiting to run
CI/CD Pipeline / Validate - Migration (alembic) (push) Waiting to run
CI/CD Pipeline / Unit Tests (push) Blocked by required conditions
CI/CD Pipeline / Integration Tests (push) Blocked by required conditions
CI/CD Pipeline / Frontend Lint (push) Waiting to run
CI/CD Pipeline / Frontend Unit Tests (push) Blocked by required conditions
CI/CD Pipeline / PR Build API Image (push) Waiting to run
CI/CD Pipeline / PR Build Web Image (push) Waiting to run
CI/CD Pipeline / PR Build Worker Image (push) Waiting to run
CI/CD Pipeline / Build Staging API Image (push) Waiting to run
CI/CD Pipeline / Build Staging Web Image (push) Waiting to run
CI/CD Pipeline / Build Staging Worker Image (push) Waiting to run
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Blocked by required conditions
CI/CD Pipeline / Staging E2E Tests (push) Blocked by required conditions
CI/CD Pipeline / Staging API Integration Tests (push) Blocked by required conditions
CI/CD Pipeline / Build Production API Image (push) Waiting to run
CI/CD Pipeline / Build Production Web Image (push) Waiting to run
CI/CD Pipeline / Build Production Worker Image (push) Waiting to run
CI/CD Pipeline / Deploy Production (push) Blocked by required conditions
CI/CD Pipeline / Production Browser E2E (push) Blocked by required conditions
CI/CD Pipeline / ACR Image Cleanup (push) Blocked by required conditions
refactor(#783): 统一API文件命名风格为kebab-case (#788)
2026-07-23 22:34:56 +08:00

11 KiB
Executable File
Raw Blame History

编码规范

本文档定义新 SaaS 项目的 Python 编码规范。


1. 基础规范

遵循 PEP 8,以下是重点和补充。

1.1 命名规范

模块/包:小写 + 下划线

# ✅ 正确
from packages.domain import entities
from packages.adapters.in_memory import project_repository

# ❌ 错误
from packages.Domain import Entities
from packages.adapters.InMemory import ProjectRepository

PascalCase

# ✅ 正确
class Project:
    pass

class InMemoryProjectRepository:
    pass

# ❌ 错误
class project:
    pass

class in_memory_project_repository:
    pass

函数/变量:小写 + 下划线

# ✅ 正确
def create_project(workspace_id: str, name: str) -> Project:
    pass

user_count = 10

# ❌ 错误
def CreateProject(workspace_id: str, name: str) -> Project:
    pass

UserCount = 10

常量:大写 + 下划线

# ✅ 正确
MAX_PROJECT_NAME_LENGTH = 100
DEFAULT_PAGE_SIZE = 20

# ❌ 错误
maxProjectNameLength = 100
default_page_size = 20

私有属性/方法:前缀 _

class Project:
    def __init__(self):
        self._internal_state = {}
    
    def _validate(self):
        pass

2. Type Hints

强制使用 type hints,提升代码可读性和 IDE 支持。

# ✅ 正确
def create_project(workspace_id: str, name: str, description: str = "") -> Project:
    pass

def list_projects(workspace_id: str) -> list[Project]:
    pass

def get_project(project_id: str) -> Project | None:
    pass

# ❌ 错误
def create_project(workspace_id, name, description=""):
    pass

复杂类型

from typing import Protocol, Any

# Dict/List
def update_metadata(metadata: dict[str, Any]) -> None:
    pass

# Optional (Python 3.10+ 用 | None)
def get_user(user_id: str) -> User | None:
    pass

# Protocol
class Repository(Protocol):
    def get(self, id: str) -> Entity | None:
        pass

3. Dataclass

优先使用 dataclass 定义实体和值对象。

from dataclasses import dataclass, field
from datetime import datetime, timezone

# ✅ 正确
@dataclass(slots=True)
class Project:
    id: str
    workspace_id: str
    name: str
    description: str = ""
    created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))

# ❌ 错误(不用 dataclass
class Project:
    def __init__(self, id: str, workspace_id: str, name: str, description: str = ""):
        self.id = id
        self.workspace_id = workspace_id
        self.name = name
        self.description = description

为什么用 slots=True

  • 节省内存
  • 防止意外添加属性
  • 提升性能

4. 注释与文档

4.1 模块/类/函数注释

使用中文注释

def create_project(workspace_id: str, name: str, description: str = "") -> Project:
    """
    创建项目。
    
    Args:
        workspace_id: 工作空间 ID
        name: 项目名称(不能为空)
        description: 项目描述(可选)
    
    Returns:
        创建的项目实体
    
    Raises:
        ValueError: 项目名称为空时
    """
    pass

4.2 复杂逻辑注释

# 正确做法:复杂逻辑加注释
def calculate_priority(job: IngestJob) -> int:
    # 优先级规则:
    # 1. FAILED 状态最高(需要重试)
    # 2. PENDING 状态次高(等待处理)
    # 3. PROCESSING 状态最低(正在处理)
    if job.status == IngestJobStatus.FAILED:
        return 100
    elif job.status == IngestJobStatus.PENDING:
        return 50
    else:
        return 10

5. 异常处理

5.1 使用具体异常

# ✅ 正确
def get_project(project_id: str) -> Project:
    if not project_id:
        raise ValueError("项目 ID 不能为空")
    
    project = repository.get(project_id)
    if project is None:
        raise KeyError(f"项目 {project_id} 不存在")
    
    return project

# ❌ 错误
def get_project(project_id: str) -> Project:
    if not project_id:
        raise Exception("错误")  # 太宽泛

5.2 自定义异常

class ProjectNotFoundError(Exception):
    """项目不存在异常。"""
    pass

class ProjectNameTooLongError(ValueError):
    """项目名称过长异常。"""
    pass

6. Clean Architecture 约束

6.1 依赖方向

Apps (api/worker/web)
    ↓
Application (use cases)
    ↓
Ports (interfaces) ← Adapters (implementations)
    ↓
Domain (entities/rules)

Domain 层

  • 不能依赖任何外层
  • 不能依赖 SQLAlchemy、FastAPI、Celery
  • 只能依赖 Python 标准库
# ✅ 正确(Domain 层)
from dataclasses import dataclass
from datetime import datetime
from uuid import uuid4

@dataclass(slots=True)
class Project:
    id: str
    name: str

# ❌ 错误(Domain 层)
from sqlalchemy import Column, String  # ❌ 不能依赖 SQLAlchemy
from fastapi import HTTPException  # ❌ 不能依赖 FastAPI

@dataclass(slots=True)
class Project:
    id: str
    name: str

Application 层

  • 可以依赖 Domain + Ports
  • 不能依赖 Adapters

Adapters 层

  • 可以依赖 Domain + Ports
  • 可以使用外部库(SQLAlchemy、Redis 等)

7. 测试

7.1 测试文件命名

tests/
├── integration/
│   ├── test_projects.py
│   ├── test_ingest_pipeline.py
│   └── test_classification_pipeline.py
└── unit/
    ├── test_project_entity.py
    └── test_asset_validation.py

7.2 测试函数命名

# ✅ 正确
def test_create_project_with_valid_name():
    pass

def test_create_project_with_empty_name_should_fail():
    pass

def test_list_projects_by_workspace():
    pass

# ❌ 错误
def test1():
    pass

def test_project():
    pass

7.3 测试结构(AAA 模式)

def test_create_project():
    # Arrange(准备)
    workspace_id = "ws-1"
    name = "测试项目"
    repository = InMemoryProjectRepository()
    use_case = CreateProjectUseCase(repository)
    
    # Act(执行)
    project = use_case.execute(
        CreateProjectCommand(workspace_id=workspace_id, name=name)
    )
    
    # Assert(断言)
    assert project.name == name
    assert project.workspace_id == workspace_id

8. 代码格式化

8.1 行长度

  • 最大 120 字符
  • 优先 88 字符(Black 默认)

8.2 导入顺序

# 1. 标准库
import os
from datetime import datetime
from typing import Protocol

# 2. 第三方库
from fastapi import FastAPI
from sqlalchemy import Column

# 3. 本地模块
from packages.domain import Project
from packages.application import CreateProjectUseCase

8.3 空行

# 类之间:2 行
class User:
    pass


class Workspace:
    pass


# 函数之间:1 行
def create_user():
    pass

def list_users():
    pass

9. 安全规范

9.1 禁止硬编码敏感信息

# ❌ 错误
DATABASE_URL = "postgresql://admin:password123@localhost/db"
API_KEY = "sk-1234567890abcdef"

# ✅ 正确
import os
DATABASE_URL = os.getenv("DATABASE_URL")
API_KEY = os.getenv("API_KEY")

9.2 输入验证

# ✅ 正确
def create_project(name: str) -> Project:
    clean_name = name.strip()
    if not clean_name:
        raise ValueError("项目名称不能为空")
    if len(clean_name) > 100:
        raise ValueError("项目名称不能超过 100 字符")
    
    return Project(id=uuid4().hex, name=clean_name)

10. 性能规范

10.1 避免 N+1 查询

# ❌ 错误
projects = repository.list_by_workspace("ws-1")
for project in projects:
    assets = asset_repository.list_by_project(project.id)  # N+1

# ✅ 正确
projects = repository.list_by_workspace("ws-1")
project_ids = [p.id for p in projects]
assets = asset_repository.list_by_projects(project_ids)  # 一次查询

10.2 使用生成器

# ✅ 正确(大数据集)
def list_all_assets() -> Generator[Asset, None, None]:
    for asset in repository.stream():
        yield asset

# ❌ 错误(加载全部到内存)
def list_all_assets() -> list[Asset]:
    return repository.list_all()  # 可能 OOM

8. 前端规范(React + TypeScript

8.1 文件命名规范

类型 风格 示例 说明
目录名 kebab-case editing-planner/, asset-selector/ 全小写,多单词用短横线连接
组件文件 PascalCase AssetSelector.tsx, MediaPanel.tsx 与组件导出名一致
页面组件 PascalCase EditingPlanner.tsx, MyTemplates.tsx 放在 kebab-case 目录中
API 文件 kebab-case template-editor.ts, voice-clone.ts 与 RESTful 资源路径风格一致
Hooks camelCase (use前缀) useAuth.ts, useCloneProgress.ts React 官方惯例
Store kebab-case auth-store.ts, ui-store.ts
工具函数/helpers kebab-case format-duration.ts, date-utils.ts
类型定义 kebab-case types.ts, subtitle-types.ts 目录内类型定义可用 types.ts
常量 UPPER_SNAKE_CASE MAX_UPLOAD_SIZE, API_BASE_URL
测试文件 与被测文件同名 + .test auth.test.ts, AssetSelector.test.tsx 放在 test/ 目录下,保持相同相对路径

8.2 组件命名

  • 组件名使用 PascalCase,与文件名一致
  • 默认导出组件名与文件名相同
  • 高阶组件/包装器用 with 前缀:withAuth(Component)
  • 渲染属性组件用 Render 后缀:UserRender
// ✅ 正确
// 文件: AssetSelector.tsx
const AssetSelector: React.FC<AssetSelectorProps> = ({ assets }) => { ... };
export default AssetSelector;

// ❌ 错误
// 文件: asset-selector.tsx
const assetSelector = () => { ... };

8.3 变量与函数命名

  • 变量/函数camelCase
  • 布尔变量:用 is/has/should/can 前缀
  • 事件处理函数:用 handle 前缀 + 事件名
  • 事件 handler prop:用 on 前缀
// ✅ 正确
const isLoading = true;
const hasError = false;
const handleSubmit = () => { ... };

<Button onClick={onClick} />

8.4 导入路径

  • 使用 @/ 别名引用 src/ 下的文件
  • 同一目录内用相对路径 ./
  • 导入顺序:React → 第三方库 → @/内部模块 → 相对路径 → 样式
import React, { useState } from 'react';
import { Button, Modal } from 'antd';
import { useAuth } from '@/hooks/useAuth';
import { Asset } from '@/api/asset-selector';
import { MediaPanel } from './MediaPanel';
import './AssetSelector.css';

8.5 CSS/样式命名

  • CSS Modules / CSS 类名:kebab-case
  • styled-componentsPascalCase(与组件一致)
  • Tailwind 工具类遵循官方惯例
/* ✅ 正确 */
.asset-selector { ... }
.asset-item { ... }
.asset-item--active { ... }

最后更新: 2026-07-23
版本: v1.1