imetting/design/code-structure-standards.md

15 KiB
Raw Blame History

通用代码结构设计规范(强制执行)

本文档定义项目在长期演进中应遵守的结构边界、拆分原则与审计标准。

目标不是机械追求“小文件”“多目录”或“某种固定架构”,而是让任意语言、任意框架、任意部署形态下的代码都满足以下要求:

  • 入口清晰
  • 依赖方向稳定
  • 职责边界明确
  • 修改影响面可控
  • 新人可顺序读懂

本文档适用于:

  • 前端应用
  • 后端服务
  • 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. 执行基线

后续所有新增功能、重构与代码审计,均以本文档为默认判断依据:

  • 先判断职责边界是否正确
  • 再判断依赖方向是否健康
  • 再判断是否需要拆分或合并
  • 最后才考虑目录美观、文件长短和风格一致性

当“看起来更模块化”和“真实可读、可改、可验证”发生冲突时,优先后者。