本节摘要:清晰的文件与模块结构让开发者按图索骥而非全局搜索。本节比较按功能划分与按类型划分两种组织策略(结论:功能优先、类型次之),给出扁平优先的目录原则、典型项目根目录布局与模块内代码顺序,并规范导入语句——绝对导入、三组分级、字母排序、禁止星号导入。
阅读完本节,你应当能够:
观察一个信号就能判断项目结构的好坏:开发者找一个功能时,是顺着目录走还是直接全局搜索。如果是后者,结构已经名存实亡。全局搜索不慢,但它有个致命副作用——你永远无法建立项目的整体心智地图。每次都是"找到哪算哪",代码库在脑子里始终是一堆碎片而不是一座有街区的城市。新人的感受最强烈:面对按类型划分的巨型目录(几十个互不相关的文件挤在同一个"控制器"目录里),完全无法判断"用户注销功能大概在哪"。
结构的价值正在于此:目录就是索引。看到功能名目录,不必打开任何文件就能定位;看到模块内部固定的代码排列,不必通读就能找到常量或入口。索引让阅读有起点、修改有边界——改一个功能时你清楚会触碰哪些文件,因为它们物理上就在一起。
按类型划分:所有模型放一堆、所有控制器放一堆、所有服务放一堆。优点是"同类文件好找";致命缺点是一个功能的代码被撕成三份——改"用户注销"要横跨三个目录,review diff 时上下文支离破碎,想删除或迁移一个功能更是全景式搜捕。
按功能划分:每个业务域一个目录(如认证、商品、通知),功能内部再按类型分。改"用户注销"只动认证目录;功能下线整个目录删除;新人从目录名就能建立业务全景。结论明确:优先按功能划分,功能内部再按类型划分——兼顾业务聚合与同类内聚。
目录嵌套越深,导航成本越高(心智路径长度等于目录深度)。原则是能扁平就不嵌套,除非逻辑上确有必要(比如一个功能确需子模块)。深达五层的目录树,通常意味着有人在用目录结构弥补设计上的犹豫不决。
模块内部的元素紧密相关(高内聚),模块之间的依赖尽量少而明确(低耦合)。操作层面的自检:如果一个"模块"里的文件互相几乎不引用、却都大量引用另一个模块,说明边界划错了,该重组。
项目根目录的标准件:项目说明文件、许可证、依赖清单、版本控制忽略配置、文档目录、测试目录、源代码目录。源代码内部按功能模块分目录,每个模块内部再有自己的分层(视图、数据、服务之类,按技术栈约定)。
模块文件内部的代码顺序同样要有章法,以一门常见脚本语言为例,约定俗成的排列是:模块文档字符串在最前,接着是导入语句(标准库、第三方、本项目三组,组间空行、组内按字母序),然后是模块级常量、类定义、函数定义,主执行入口(如果有)收尾。读者拿到任何一个陌生模块,都能按这个顺序秒速定位想看的东西。

导入区是模块的"依赖声明面",规范它有四条。绝对导入优先:相对导入在模块移动时容易断链、在深层嵌套时可读性差;绝对路径指哪到哪(项目内可用统一的路径别名减少冗长)。三组分级:标准库、第三方库、项目内部,组间空行分隔,一眼看出依赖的"重量"。组内字母排序:位置可预测,查找与合并冲突都减少。禁止星号导入:from 某模块 import 星号 会把不明来历的名字全部倒进当前命名空间——读者不知道某个名字是本文件定义的还是导入的,命名冲突风险也随之而来;要什么导什么。
对照示例(概念代码):
# 标准库 import os import sys # 第三方 import requests # 项目内部 from . import models from .utils import helpers
文件名是目录索引的最后一环。规范要点:全小写(避免在不同操作系统上大小写敏感不一致引发事故)、用下划线或连字符分词(跟语言社区约定)、名字反映内容而非作者或日期(user_service 好,zhang_new 坏)、组件文件可带类型后缀以便快速分类。原则与第 2.2 节命名通则一致:描述性、可搜索、无歧义。
⚠️ 常见坑:目录结构一次定稿、永不调整。项目演化中总会出现当初没料到的功能域,僵化的结构会逼着开发者把不相关的代码塞进错误的地方(因为"正确的目录"不存在)。结构需要随架构演进定期重组——重组时机选在功能边界明显错位时,且作为独立提交进行,不与功能改动混合。
💡 关键直觉:判断结构好坏的终极测试是"新人测试"——给一位新同事五分钟浏览目录,然后问他"用户注销功能的代码大概在哪"。答得上来,结构合格;答不上来,目录只是文件的仓库而不是代码的地图。
| 决策点 | 推荐做法 | 反面做法 | 判断依据 |
|---|---|---|---|
| 顶层划分 | 按业务功能 | 纯按技术类型 | 改动是否聚在一个目录 |
| 嵌套深度 | 两到三层封顶 | 五层以上 | 心智路径长度 |
| 模块内分层 | 接口 服务 数据 | 全部塞一个文件 | 单一职责 |
| 导入方式 | 绝对导入 显式导入 | 相对导入 星号导入 | 可追踪 可预测 |
| 测试位置 | 与模块镜像对应 | 全部堆一个平铺目录 | 定位测试成本 |
下一节进入模块内部的基本单元:函数与方法的设计规范。