15 KiB
15 KiB
通用代码结构设计规范(强制执行)
本文档定义项目在长期演进中应遵守的结构边界、拆分原则与审计标准。
目标不是机械追求“小文件”“多目录”或“某种固定架构”,而是让任意语言、任意框架、任意部署形态下的代码都满足以下要求:
- 入口清晰
- 依赖方向稳定
- 职责边界明确
- 修改影响面可控
- 新人可顺序读懂
本文档适用于:
- 前端应用
- 后端服务
- CLI / 脚本 / Worker
- SDK / Library
- 单仓或多仓项目
本文档自落地起作为后续开发与重构的默认结构基线。
1. 核心原则
1.1 先划清职责,再决定目录
- 先区分“入口层 / 业务编排层 / 领域规则层 / 基础设施层 / 共享基础层”,再决定是否拆目录、拆文件、拆包。
- 目录结构是职责设计的结果,不是先验答案。
- 同一个团队可以采用不同目录形态,但不能模糊职责边界。
1.2 领域内聚优先于机械拆分
- 第一判断标准是“是否仍然属于同一业务主题”,不是“文件还能不能再拆小”。
- 同一主题内的读取、写入、校验、少量派生逻辑,可以保留在同一模块中。
- 如果拆分只会制造更多跳转、隐藏真实依赖、降低顺序可读性,就不应继续拆。
1.3 装配层必须薄
- 启动入口、路由入口、页面入口、命令入口都只负责装配。
- 装配层可以做依赖注入、参数收集、状态接线、组件拼装、调用编排入口。
- 装配层不应承载复杂业务规则、数据库细节、文件系统细节、网络细节或长流程状态机。
1.4 副作用必须收口
- 数据库访问、文件读写、网络调用、缓存、定时器、浏览器存储、进程环境依赖等,都属于副作用。
- 副作用应集中在可识别的边界模块中,不应在页面、视图、路由、DTO、纯工具里四处散落。
- 任何需要 mock、替换、复用或测试隔离的外部依赖,都应有明确归属。
1.5 依赖方向必须单向
- 默认依赖方向应从外向内:入口层 -> 业务编排层 -> 领域规则层 -> 基础设施实现。
- 共享基础层可以被多个层使用,但不能反向依赖业务实现。
- 低层不能反向引用高层具体实现来“图省事”。
1.6 文件大小不是目标,跨职责才是风险
- 行数只作为预警信号,不作为强制拆分指标。
- 真正需要拆分的信号包括:
- 一个模块服务多个业务主题
- 一个模块同时承担输入解析、业务决策、数据访问和展示
- 一个改动常常需要在同一文件中切换多种关注点
- 一个模块需要为不同调用方维持多套语义
1.7 重构优先低风险搬运
- 结构重构优先做“职责收口、边界清理、命名校正、依赖下沉/上提”。
- 默认不要在同一轮改动里同时进行:
- 大规模结构调整
- 新功能开发
- 行为修复
- 如果确需并行,必须以最小范围控制风险,并显式验证关键路径。
1.8 命名必须体现主题
- 文件、目录、模块、类型、服务名都应直接表达责任。
- 禁止使用模糊命名掩盖职责,例如:
mischelpers2commonThingtemp_servicemanager_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 推荐的判断方式
当你不确定某段代码应该放哪里时,按顺序判断:
- 它是在接收输入、渲染输出、还是拼装启动吗
- 它是在表达一个完整业务流程吗
- 它是在表达不依赖外部协议的业务规则吗
- 它是在接数据库、文件、网络、缓存、浏览器或系统能力吗
- 它真的是跨域共享能力吗
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 命名规则
- 名称应表达业务主题或技术边界,不表达情绪和历史包袱。
- 优先使用“对象 + 语义”命名,而不是“抽象 + 序号”命名。
- 避免模糊后缀:
handler2newServicecommonUtilstempPage
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. 执行基线
后续所有新增功能、重构与代码审计,均以本文档为默认判断依据:
- 先判断职责边界是否正确
- 再判断依赖方向是否健康
- 再判断是否需要拆分或合并
- 最后才考虑目录美观、文件长短和风格一致性
当“看起来更模块化”和“真实可读、可改、可验证”发生冲突时,优先后者。