imetting/design/code-structure-standards.md

500 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 通用代码结构设计规范(强制执行)
本文档定义项目在长期演进中应遵守的结构边界、拆分原则与审计标准。
目标不是机械追求“小文件”“多目录”或“某种固定架构”,而是让任意语言、任意框架、任意部署形态下的代码都满足以下要求:
- 入口清晰
- 依赖方向稳定
- 职责边界明确
- 修改影响面可控
- 新人可顺序读懂
本文档适用于:
- 前端应用
- 后端服务
- CLI / 脚本 / Worker
- SDK / Library
- 单仓或多仓项目
本文档自落地起作为后续开发与重构的默认结构基线。
---
## 1. 核心原则
### 1.1 先划清职责,再决定目录
- 先区分“入口层 / 业务编排层 / 领域规则层 / 基础设施层 / 共享基础层”,再决定是否拆目录、拆文件、拆包。
- 目录结构是职责设计的结果,不是先验答案。
- 同一个团队可以采用不同目录形态,但不能模糊职责边界。
### 1.2 领域内聚优先于机械拆分
- 第一判断标准是“是否仍然属于同一业务主题”,不是“文件还能不能再拆小”。
- 同一主题内的读取、写入、校验、少量派生逻辑,可以保留在同一模块中。
- 如果拆分只会制造更多跳转、隐藏真实依赖、降低顺序可读性,就不应继续拆。
### 1.3 装配层必须薄
- 启动入口、路由入口、页面入口、命令入口都只负责装配。
- 装配层可以做依赖注入、参数收集、状态接线、组件拼装、调用编排入口。
- 装配层不应承载复杂业务规则、数据库细节、文件系统细节、网络细节或长流程状态机。
### 1.4 副作用必须收口
- 数据库访问、文件读写、网络调用、缓存、定时器、浏览器存储、进程环境依赖等,都属于副作用。
- 副作用应集中在可识别的边界模块中不应在页面、视图、路由、DTO、纯工具里四处散落。
- 任何需要 mock、替换、复用或测试隔离的外部依赖都应有明确归属。
### 1.5 依赖方向必须单向
- 默认依赖方向应从外向内:入口层 -> 业务编排层 -> 领域规则层 -> 基础设施实现。
- 共享基础层可以被多个层使用,但不能反向依赖业务实现。
- 低层不能反向引用高层具体实现来“图省事”。
### 1.6 文件大小不是目标,跨职责才是风险
- 行数只作为预警信号,不作为强制拆分指标。
- 真正需要拆分的信号包括:
- 一个模块服务多个业务主题
- 一个模块同时承担输入解析、业务决策、数据访问和展示
- 一个改动常常需要在同一文件中切换多种关注点
- 一个模块需要为不同调用方维持多套语义
### 1.7 重构优先低风险搬运
- 结构重构优先做“职责收口、边界清理、命名校正、依赖下沉/上提”。
- 默认不要在同一轮改动里同时进行:
- 大规模结构调整
- 新功能开发
- 行为修复
- 如果确需并行,必须以最小范围控制风险,并显式验证关键路径。
### 1.8 命名必须体现主题
- 文件、目录、模块、类型、服务名都应直接表达责任。
- 禁止使用模糊命名掩盖职责,例如:
- `misc`
- `helpers2`
- `commonThing`
- `temp_service`
- `manager_new`
---
## 2. 通用分层模型
以下是跨语言可复用的职责模型。项目不要求逐字使用这些目录名,但必须能映射到这些边界。
### 2.1 入口层 / 接口层
典型形态:
- 前端的 `App`、路由入口、页面入口
- 后端的 router / controller / handler
- CLI 的 command / main
- Worker 的 job handler / consumer entry
职责:
- 接收输入
- 做基础参数解析与协议适配
- 调用业务编排层
- 返回结果或渲染输出
禁止:
- 写复杂业务规则
- 直接拼装 SQL / ORM 流程
- 直接进行大段文件系统读写
- 直接进行复杂网络编排
- 在入口层内维护长生命周期状态机
### 2.2 业务编排层 / 用例层
典型形态:
- service
- use case
- action
- controller hook
- page model
- workflow
职责:
- 表达一个明确业务流程
- 协调多个依赖
- 承载事务边界、步骤顺序、状态推进
- 组织权限判断、前置校验、错误分支
要求:
- 一个模块只负责一个业务域或一个稳定子流程
- 可以依赖基础设施接口,但不应把具体协议细节暴露给上层
- 可以包含少量私有 helper但 helper 仅服务当前主题
### 2.3 领域规则层
典型形态:
- domain service
- policy
- rule
- validator
- entity behavior
- pure business helpers
职责:
- 承载稳定的业务规则和领域语义
- 保持尽量纯净、可测试、与外部协议解耦
- 统一业务概念、状态转换、派生计算
要求:
- 不直接访问数据库、网络、文件、浏览器环境
- 不依赖 UI、HTTP、CLI、消息队列等入口协议
### 2.4 基础设施层 / 数据访问层
典型形态:
- repository
- gateway
- API client
- storage adapter
- cache adapter
- filesystem adapter
- persistence implementation
职责:
- 封装外部系统细节
- 处理数据库、缓存、HTTP、对象存储、消息队列、浏览器存储、操作系统能力
- 提供可复用的边界接口
要求:
- 只解决“怎么接外部系统”,不承担业务决策
- 协议转换、序列化、连接管理、重试策略等应在此层收口
### 2.5 共享基础层
典型形态:
- constants
- shared types
- date / string / number helpers
- 通用 UI 基础组件
- 通用错误定义
要求:
- 必须是真正跨域、稳定、低语义耦合的内容
- 不允许把业务逻辑伪装成“common / shared / utils”
- 一旦某模块开始依赖特定业务名词,它就不再是共享基础层
---
## 3. 目录与模块组织规范
### 3.1 允许的组织方式
项目可以采用以下任一方式:
- 按领域优先组织:`<domain>/<entry|application|infra|shared>`
- 按层优先组织:`entry/ application/ domain/ infra/`
- 混合组织:顶层按领域,领域内再分层
- Monorepo 组织:`apps/ packages/ services/ workers/`
允许多种组织方式并存,但必须满足:
- 同一仓库内的同类代码遵循一致的判断逻辑
- 每个模块都能被映射到明确职责层
- 依赖方向清晰、稳定、可审计
### 3.2 推荐的判断方式
当你不确定某段代码应该放哪里时,按顺序判断:
1. 它是在接收输入、渲染输出、还是拼装启动吗
2. 它是在表达一个完整业务流程吗
3. 它是在表达不依赖外部协议的业务规则吗
4. 它是在接数据库、文件、网络、缓存、浏览器或系统能力吗
5. 它真的是跨域共享能力吗
### 3.3 一个模块只能有一个主语义
- 一个模块可以有多个函数,但只能服务一个主职责。
- 如果一个文件既是“页面”又是“API 聚合器”又是“缓存控制器”又是“视图组件”,就已经越界。
- 如果一个 router 文件开始长时间停留在 SQL、文件读写、事务与状态轮询细节上也已经越界。
### 3.4 兼容层与过渡层
- 允许存在短期兼容层、导出层、适配层。
- 兼容层必须被明确标记为过渡用途。
- 禁止长期把新逻辑继续堆回兼容层。
---
## 4. 前端通用规范
本节适用于 Web、桌面端、移动端和前端壳应用不绑定 React / Vue / Svelte 等具体框架。
### 4.1 页面/路由入口必须薄
- 页面文件默认负责页面装配、布局组织、边界兜底。
- 页面可持有少量与页面展示强绑定的状态。
- 当页面开始同时承担以下两项及以上时,应拆出页面级编排模块:
- 多个接口请求
- 轮询或定时器
- 上传/下载流程
- 多个 Drawer / Modal / Sheet 子流程
- 复杂权限判断
- 大量数据清洗与派生
### 4.2 视图组件默认无副作用
- 纯视图组件只接收整理好的 props。
- 纯视图组件默认不直接请求接口、不直接碰浏览器存储、不直接起轮询。
- 如果一个组件必须自带数据流程,它应被明确视为“功能组件”或“场景组件”,而不是伪装成通用组件。
### 4.3 页面级业务流程应集中编排
- 页面相关的请求、轮询、草稿保存、上传进度、权限行为、状态联动,应尽量集中在页面编排层。
- 页面编排层可以是 hook、store、controller、presenter 或 view-model不限定技术名词。
- 不要求为了形式而一律抽 hook只有在页面入口已经承担过多流程时才拆。
### 4.4 前端基础设施应收口
- API client
- 本地存储
- Session / token 持久化
- 浏览器标题、副作用事件、定时器策略
- 配置读取
以上能力应收口在可识别模块中,不应被页面随机复制。
### 4.5 复用原则
- 提炼稳定复用模式,不提炼偶然重复。
- 三处以上重复,优先评估抽取。
- 如果抽取后的接口比原地代码更难理解,不应抽取。
- 不允许制造“只有一个页面使用、但包装层很多”的伪复用。
---
## 5. 后端通用规范
本节适用于 HTTP 服务、RPC 服务、任务处理器和后台作业,不绑定 FastAPI / Spring / NestJS / Gin 等框架。
### 5.1 启动入口必须只做装配
- `main`
- app factory
- bootstrap
- container
这些入口只负责:
- 创建应用实例
- 注册路由/处理器
- 初始化中间件
- 装配依赖
- 生命周期绑定
不应承担:
- 业务规则
- SQL 或 ORM 编排
- 文件系统细节
- 长流程任务控制
### 5.2 Router / Controller / Handler 只做协议转换
允许:
- 接收请求参数
- 基础校验
- 调用用例层
- 将领域错误映射为接口错误
不允许:
- 在 handler 内直接堆大量 SQL
- 一边处理权限,一边处理事务,一边操作文件,一边组装响应模型
- 在 handler 内实现长流程状态机
### 5.3 Service / Use Case 以业务域组织
- 一个 service 文件只负责一个业务域或一个稳定子主题。
- 同域内的查询、写入、校验、少量派生逻辑可以在一起。
- 如果一个 service 同时承担多个主题,应优先拆主题而不是拆技术动作。
### 5.4 数据访问与外部适配要收口
- 数据库查询
- ORM 组装
- 缓存细节
- 第三方 HTTP 调用
- 文件上传下载
- 对象存储
- 队列/任务系统
这些细节应尽量沉到 repository / gateway / adapter / infra 中。
### 5.5 Schema / DTO / Contract 必须纯净
- DTO 只用于定义契约,不应携带数据库、文件系统、网络调用或业务副作用。
- 契约字段演进必须可追踪。
- 避免让数据库模型、接口模型、领域模型长期混成一种结构。
---
## 6. CLI / 脚本 / Worker 规范
- 命令入口只负责解析参数、准备依赖、调用用例。
- 脚本如果会长期保留,必须从“一次性脚本”升级为可读的结构化模块。
- Worker handler 只负责接收消息、提取 payload、调用业务流程、回写状态。
- 重试、幂等、死信、超时策略等运行时策略应有单独归属,不应散落在业务逻辑内部。
---
## 7. 拆分与合并准则
### 7.1 何时应拆分
满足任一项即可考虑拆分:
- 同一模块出现多个业务主题
- 同一模块同时依赖多种外部系统
- 同一模块同时承担输入解析、业务编排、持久化和展示
- 多人修改时经常产生冲突
- 阅读一个改动需要频繁跨越无关上下文
- 相同规则被复制到多个入口
### 7.2 何时不应拆分
- 仍是单一主题
- 代码虽长但顺序可读
- 继续拆只会制造纯转发层
- 继续拆会让调用链更深、定位更慢
- 抽出后接口语义比原代码更含糊
### 7.3 合并也是一种优化
- 如果多个模块只是在相互转发、没有独立语义,应考虑合并。
- 如果拆分之后需要同时打开 4 到 6 个文件才能理解一个简单流程,通常已经过度拆分。
---
## 8. 命名与依赖规则
### 8.1 命名规则
- 名称应表达业务主题或技术边界,不表达情绪和历史包袱。
- 优先使用“对象 + 语义”命名,而不是“抽象 + 序号”命名。
- 避免模糊后缀:
- `handler2`
- `newService`
- `commonUtils`
- `tempPage`
### 8.2 依赖规则
- 上层可以依赖下层抽象,不应依赖下层杂乱细节。
- 共享层不能反向依赖业务层。
- 领域模块之间若需协作,应通过明确用例、接口或边界对象完成,而不是互相穿透内部实现。
---
## 9. 测试与验证要求
### 9.1 结构改动后的默认验证
- 前端结构改动后,至少执行构建或类型校验。
- 后端结构改动后,至少执行语法校验、启动校验或最小测试集。
- 如果改动涉及契约、权限、状态流转、持久化边界,应追加针对性验证。
### 9.2 测试优先级
- 先保关键业务路径
- 再保跨层边界
- 再保复杂状态流转
- 最后补充纯工具覆盖
### 9.3 文档同步要求
以下情况必须同步设计文档或架构说明:
- 新增一层明确职责边界
- 新增一个稳定领域模块模板
- 改变入口层、用例层、基础设施层的责任划分
- 引入新的运行时或新的跨项目复用规范
---
## 10. 评审与审计清单
做结构评审时,默认检查以下问题:
### 10.1 入口层
- 入口是否足够薄
- 是否混入业务规则
- 是否混入外部系统细节
### 10.2 业务编排层
- 是否以业务主题组织
- 是否承担了过多无关流程
- 是否存在纯转发服务
### 10.3 领域规则层
- 是否仍保持纯净
- 是否被框架、HTTP、数据库协议污染
### 10.4 基础设施层
- 副作用是否收口
- 是否把业务规则偷偷塞回 adapter / utils / core
### 10.5 共享层
- 是否真的跨域复用
- 是否把业务逻辑伪装成 common / shared / utils
### 10.6 演进风险
- 新增功能是否沿着既有边界落位
- 兼容层是否在持续变厚
- 是否出现单文件多职责继续膨胀
---
## 11. 禁止事项
- 禁止为了图省事把新逻辑堆回入口层
- 禁止为了“文件更短”制造无语义的纯包装层
- 禁止在 `utils` / `common` / `shared` 中隐藏领域逻辑
- 禁止让页面、路由、handler 直接承载大量持久化或文件系统细节
- 禁止把 schema / DTO / config 当成业务逻辑容器
- 禁止一次改动里同时重写结构、协议、UI 和业务行为,且没有明确验证策略
---
## 12. 执行基线
后续所有新增功能、重构与代码审计,均以本文档为默认判断依据:
- 先判断职责边界是否正确
- 再判断依赖方向是否健康
- 再判断是否需要拆分或合并
- 最后才考虑目录美观、文件长短和风格一致性
当“看起来更模块化”和“真实可读、可改、可验证”发生冲突时,优先后者。