第 5 章:模块化开发与代码组织
用 SOLID 原则指导 AI — 构建可维护、可扩展的代码结构
🎯 本章学习目标
- 掌握分层架构中接口层、业务层、数据层的 AI 生成策略
- 能够用提示词让 AI 生成符合 SOLID 原则的代码
- 学会在 AI 编程中应用常用设计模式(策略、工厂、观察者)
- 建立"接口先行"的开发流程:先定义契约,再生成实现
- 掌握用提示词强制执行编码规范的技术
5.1 分层架构的 AI 生成策略
📡 接口层(API / Controller)
职责:接收请求、参数验证、调用业务层、返回响应
不应该做:包含业务逻辑、直接操作数据库
AI 生成策略:先给出完整的接口规格(路径、方法、参数、响应格式),让 AI 生成"薄"的控制层
⚙️ 业务层(Service / Domain)
职责:业务规则、工作流编排、事务管理
不应该做:关心 HTTP 细节、直接拼接 SQL
AI 生成策略:用业务规则描述(而非技术描述)来驱动 AI 生成
🗄️ 数据层(Repository / DAO)
职责:数据持久化、查询抽象、缓存管理
不应该做:包含业务判断、依赖上层模块
AI 生成策略:先定义 Repository 接口,再生成具体实现
5.2 SOLID 原则在 AI 生成代码中的应用
S — 单一职责(Single Responsibility)
提示词技巧:"每个类/函数只做一件事。如果它的描述需要用'和'连接两个不同的职责,就拆成两个。"
O — 开闭原则(Open/Closed)
提示词技巧:"使用策略模式或插件架构。新增功能时通过扩展新类而非修改现有代码。"
L — 里氏替换(Liskov Substitution)
提示词技巧:"子类必须能完全替代父类。在提示词中要求为接口生成验证测试,确保所有实现满足契约。"
I — 接口隔离(Interface Segregation)
提示词技巧:"不要创建一个大一统的接口。为每种使用场景定义精简的接口。AI 生成的接口方法不应超过 5 个。"
D — 依赖反转(Dependency Inversion)
提示词技巧:"高层模块和低层模块都依赖抽象。在提示词中要求使用 Protocol/ABC 定义抽象,具体实现通过依赖注入传入。"
5.3 设计模式提示词
策略模式(Strategy Pattern)
适用场景:NAS 系统中不同的文件存储后端(本地磁盘、网络存储、对象存储)
# AI 生成的策略模式框架
from abc import ABC, abstractmethod
class StorageBackend(ABC):
"""存储后端抽象接口"""
@abstractmethod
async def read(self, path: str, offset: int = 0, size: int = -1) -> bytes: ...
@abstractmethod
async def write(self, path: str, data: bytes) -> int: ...
@abstractmethod
async def delete(self, path: str) -> bool: ...
class LocalDiskBackend(StorageBackend):
"""本地磁盘实现"""
async def read(self, path, offset=0, size=-1):
# 使用 aiofiles 实现异步文件读取
...
class S3Backend(StorageBackend):
"""S3 兼容对象存储实现"""
async def read(self, path, offset=0, size=-1):
# 使用 aiobotocore 实现
...
class StorageManager:
"""存储管理器 — 依赖抽象,不依赖具体实现"""
def __init__(self, backend: StorageBackend):
self.backend = backend # 依赖注入
观察者模式(Observer Pattern)
适用场景:NAS 系统中的事件通知(磁盘故障、用户登录、配置变更)
class EventBus:
"""轻量级事件总线 — 发布/订阅模式"""
def __init__(self):
self._handlers: dict[str, list[callable]] = {}
def subscribe(self, event: str, handler: callable):
self._handlers.setdefault(event, []).append(handler)
async def publish(self, event: str, **data):
for handler in self._handlers.get(event, []):
await handler(**data)
# 使用示例
bus = EventBus()
bus.subscribe("disk.failure", send_alert_email)
bus.subscribe("disk.failure", log_to_syslog)
await bus.publish("disk.failure", device="/dev/sdb", reason="SMART error")
📋 本章提示词模板
模板 5.1:接口先行开发模板
请按接口先行的方式实现以下功能模块:
## 第一步:生成接口定义
请先定义抽象接口(使用 Python Protocol 或 ABC):
- 接口名:[名称]
- 方法列表:[方法名、参数、返回值、异常]
- 每个方法有完整的类型注解和 docstring
## 第二步:生成接口验证测试
为接口生成测试用例(不需要实现,使用 Mock):
- 验证接口契约的完整性
- 验证各种实现的互换性(里氏替换测试)
## 第三步:生成默认实现
基于接口定义,生成一个完整的默认实现:
- 遵循 [Repository / Service / Adapter] 模式
- 所有 IO 操作异步
- 完整的错误处理
模板 5.2:设计模式应用模板
我们需要实现 [功能描述]。请使用 [设计模式名称] 进行设计。
## 约束
- 语言:[Python 3.10+ / TypeScript / ...]
- 遵循 [设计模式] 的经典结构
- 所有组件接口化,支持替换实现
- 提供至少 2 个具体实现的示例
- 包含使用示例和集成测试
## 输出
1. 类图描述(Mermaid 格式)
2. 接口/抽象类定义
3. 至少 2 个具体实现
4. 工厂/注册机制(如何选择具体实现)
5. 使用示例
模板 5.3:代码规范强制执行模板
以下是你生成所有代码时必须遵守的规范。每一条都是强制性的。
## 命名规范
- 模块/文件:[kebab-case / snake_case]
- 类:[PascalCase]
- 函数/方法:[snake_case]
- 常量:[UPPER_SNAKE_CASE]
- 私有成员:以单下划线 _ 开头
## 代码结构
- 每个函数不超过 30 行(含 docstring)
- 每个类不超过 200 行
- 每个模块不超过 500 行
- 函数参数不超过 5 个(超过则用 dataclass 封装)
## 文档规范
- 每个公开函数使用 Google-style docstring
- 复杂逻辑处用行注释说明"为什么"(不是"做什么")
- 每个模块有模块级 docstring
## 错误处理
- 不使用裸 except:
- 异常信息必须包含足够的调试上下文
- 自定义异常类继承自项目基类 [BaseError]
## 代码质量
- 零类型警告(mypy --strict)
- 零 lint 警告(pylint / ruff)
- 测试覆盖所有公开接口
请在每次生成代码后,自检是否满足以上所有规范。
🛠️ 实战演练:NAS 存储管理模块的分层实现
从接口到实现的全流程
Step 1:定义 Repository 接口
from abc import ABC, abstractmethod
from typing import Optional
from dataclasses import dataclass
@dataclass
class DiskInfo:
device: str # /dev/sda
mount_point: str # /mnt/data
fs_type: str # ext4
total_gb: float
used_gb: float
uuid: str
model: str
class IDiskRepository(ABC):
"""磁盘信息数据访问接口"""
@abstractmethod
async def list_all(self) -> list[DiskInfo]: ...
@abstractmethod
async def get_by_device(self, device: str) -> Optional[DiskInfo]: ...
@abstractmethod
async def get_mounted(self) -> list[DiskInfo]: ...
class IMountRepository(ABC):
"""挂载操作数据访问接口"""
@abstractmethod
async def mount(self, device: str, mount_point: str, fs_type: str) -> bool: ...
@abstractmethod
async def unmount(self, mount_point: str, force: bool = False) -> bool: ...
@abstractmethod
async def get_fstab_entries(self) -> list[dict]: ...
Step 2:AI 生成业务层
class DiskService:
"""磁盘管理业务层 — 不依赖 FastAPI,不依赖具体数据源"""
def __init__(self, disk_repo: IDiskRepository, mount_repo: IMountRepository):
self._disks = disk_repo
self._mounts = mount_repo
async def get_dashboard_data(self) -> dict:
"""聚合仪表盘所需的磁盘概览数据"""
all_disks = await self._disks.list_all()
mounted = [d for d in all_disks if d.mount_point]
total = sum(d.total_gb for d in mounted)
used = sum(d.used_gb for d in mounted)
return {
"disk_count": len(all_disks),
"mounted_count": len(mounted),
"total_gb": round(total, 2),
"used_gb": round(used, 2),
"usage_percent": round((used / total * 100) if total else 0, 1),
"disks": [self._to_dict(d) for d in all_disks],
}
Step 3:AI 生成接口层(FastAPI 路由)
from fastapi import APIRouter, Depends, HTTPException
router = APIRouter(prefix="/api/disks", tags=["磁盘管理"])
@router.get("/")
async def list_disks(service: DiskService = Depends(get_disk_service)):
"""获取所有磁盘信息"""
return await service.get_dashboard_data()
@router.post("/mount")
async def mount_disk(
device: str, mount_point: str, fs_type: str = "auto",
service: DiskService = Depends(get_disk_service)
):
"""挂载磁盘"""
try:
success = await service.mount_disk(device, mount_point, fs_type)
if not success:
raise HTTPException(400, "挂载失败")
return {"status": "mounted", "device": device}
except ValueError as e:
raise HTTPException(400, str(e))
审查要点
- ✅ 依赖方向正确:API → Service → Repository,没有反向依赖
- ✅ Service 层没有导入 FastAPI 的任何类
- ✅ 使用依赖注入,方便单元测试
- ✅ 接口与实现分离,可以随时替换 Repository 实现
- ⚠️ 依赖注入容器(get_disk_service)需要额外管理
⚠️ 常见坑点
🕳️ 坑 1:AI 倾向于把逻辑塞进一个函数
AI 默认会生成"上帝函数"——一个函数做完所有事。在提示词中加约束:"每个函数只做一件事,如果描述中包含'和'字,拆成两个函数。"
🕳️ 坑 2:分层变跳层
AI 可能让 API 层直接调用 Repository,跳过 Service 层。在提示词中显式声明:"API 层只能调用 Service 层,不能直接调用 Repository。"
🕳️ 坑 3:设计模式过度使用
AI 可能为了"专业"而引入不必要的设计模式。简单场景下,一个函数就够。始终问自己:这个模式解决的是真实存在的复杂性,还是臆想的?
📝 本章小结
- 分层架构的核心是依赖方向:API → Service → Repository,绝不反向
- SOLID 原则不是教条,而是防止代码腐化的"免疫系统"
- 接口先行:先定义契约再实现,AI 是这个流程的加速器
- 常用设计模式(策略、观察者、工厂)用提示词可以精确控制 AI 生成的结构
- 规范提示词(模板 5.3)应该放在 CLAUDE.md 中,让 AI 每次生成都遵守
🤔 思考练习
- 选择一个你现有的模块,用接口先行的方式为它定义 Protocol 接口,然后让 AI 基于接口重新生成实现。对比旧代码的耦合度。
- 用策略模式重构一个 switch-case 或 if-elif 链(如支付方式选择、文件格式解析)。
- 撰写一份你的团队的编码规范提示词(参考模板 5.3),放入项目的 CLAUDE.md 中。