# 第九章 模块化开发

> Arietids Learn · 个人课程复习笔记
> 在线阅读：https://learn.arietids.net/courses/programming/chapter-09/

## 关键考点总览
1. 模块化程序设计的优缺点
2. 模块划分的原则与方法
3. 头文件的作用与设计（#ifndef/#define/#endif 防止头文件重复包含，避免同一编译单元内重复定义）
4. 库的概念与设计（接口 .h + 实现 .cpp）
5. 枚举类型在模块间共享数据类型的应用
6. 静态全局变量作为模块内部状态
7. 石头剪刀布游戏的模块划分示例
8. 随机数库的设计与实现
9. 龟兔赛跑用随机数模拟概率的方法

---

## 详细知识点

### 9.1 模块化程序设计

#### 定义/语法
- **模块化开发**：把一个整体任务拆分成若干个子任务，每个子任务当成独立程序编写，作为软件的一个模块；不同模块之间只通过接口相连，不需要了解其他模块内部的运作情况。
  [来源：第9章 p5，模块化开发的诞生]

#### 规则/规范
- **模块化优点**：
  1. 方便单个模块功能调试、升级
  2. 多人协作互不干扰
  3. 方便模块间组合，代码更简洁
  4. 模块可以多次重用，减少工作量
  [来源：第9章 p6，为什么用模块化软件架构]

- **模块化弊端**：
  1. 可能引入额外开销：抽象层增多、间接调用增加；若模块边界跨进程/网络（微服务、RPC），还会叠加序列化与通信开销（同进程内函数调用开销通常很小）。
  2. 依赖风险：被依赖模块“下架/取消发布、接口变更、版本不兼容”等都会导致构建或运行失败（left-pad 属于依赖包被取消发布引发的大范围构建失败案例）。
  [来源：第9章 p7，模块化开发的弊端]

#### 易错点与辨析
- ⚠️ **left-pad 事件教训**（2016年）：在依赖树极深的生态里，一个极小的依赖包（left-pad）被作者从 npm 取消发布（unpublish）就能造成大量项目构建/安装失败；该事件凸显了依赖治理（版本、镜像、锁文件等）的重要性。
  [来源：第9章 p7，Notes]
- **生态改进措施**：
  - 不可变发布（以 npm 为例）：注册表数据不可变，package@version 一旦发布内容不能被覆盖修改，且同名同版本永不可再次发布（即使后来 unpublish）；但 unpublish 在满足官方条件时允许，否则更推荐 deprecate 而不是直接下架。
  - 命名空间支持：引入 `@scope/package` 格式
  - 锁定依赖版本：使用 `package-lock.json` 或 `yarn.lock` 固定依赖树
  [来源：第9章 p13，left-pad 事件的影响]

---

### 9.2 模块划分

#### 规则/规范
- **模块划分原则**（没有唯一“标准答案”，但应遵循高内聚、低耦合、单一职责、信息隐藏/接口稳定等工程原则）：
  1. 先把总需求分解成子任务
  2. 将子任务抽象为模块（通常由接口 .h + 实现 .cpp 组成；也可能由多个源文件/类共同实现）
  3. 模块内部将完成某一具体功能的指令序列封装到函数中
  4. 安排不同模块之间的连接关系，集成联调
  [来源：第9章 p23，模块划分]

- **划分依据**：功能强关联的指令序列放在一个模块中，联系不紧密、相互之间没有太多数据传递的指令序列放到不同模块中。
  [来源：第9章 p23，Notes]

#### 代码模式
- **石头剪刀布游戏模块划分示例**：
  - 主模块 `main.cpp`：`main()`
  - 获取选择模块 `select.cpp`：`selection_by_player()` 和 `selection_by_machine()`
  - 比较模块 `compare.cpp`：`compare()`
  - 输出模块 `print.cpp`：`report()`, `prn_game_status()` 和 `prn_help()`
  [来源：第9章 p25，模块划分]

---

### 9.3 头文件设计

#### 定义/语法
- **头文件作用**：模块和外界的接口，用来存放：
  1. 公用函数声明
  2. 常量和数据类型定义
  [来源：第9章 p26，头文件]

- **#include 头文件本质是预处理阶段的“文本包含/展开”**：它把头文件内容原样插入当前编译单元（translation unit）；函数/变量的定义在各 .cpp 分别编译后，由链接器在链接阶段解析，与 .cpp 文件名是否同名无关。
  [来源：第9章 p26，头文件]

#### 规则/规范
- **防止头文件重复包含**的预处理结构：
  ```cpp
  #ifndef 标识符
  #define 标识符
  // 头文件内容...
  #endif
  ```
  [来源：第9章 p36，头文件的设计]

#### 代码模式
- **枚举类型定义头文件示例**：
  ```cpp
  // 文件：p_r_s.h
  #ifndef p_r_s_h
  #define p_r_s_h
  enum p_r_s {paper, rock, scissor, game, help, quit};
  enum outcome {win, lose, tie, error};
  #endif
  ```
  [来源：第9章 p36，重写 p_r_s.h]

- **函数声明头文件示例**：
  ```cpp
  // 文件：select.h
  #ifndef select_h
  #define select_h
  #include "p_r_s.h"
  p_r_s selection_by_player();
  p_r_s selection_by_machine();
  #endif
  ```
  [来源：第9章 p37，其他模块的头文件]

#### 易错点与辨析
- ⚠️ **同一编译单元内多次 include 会造成类型重复定义**：若某个 .cpp 通过不同路径（直接/间接）重复 `#include "p_r_s.h"`，而 `p_r_s.h` 又缺少防重复包含保护，就会触发枚举/结构体等重复定义的编译错误。
  [来源：第9章 p35，多次include造成类型重复定义]

- ⚠️ **解决方法**：使用 `#ifndef`/`#define`/`#endif` 结构，`ifndef` 检查该定义是否已出现，若已出现则跳过，否则执行 `define` 部分。
  [来源：第9章 p36，Notes]

---

### 9.4 模块内部状态

#### 定义/语法
- **模块内部状态**：对外部模块不可见、仅在当前 .cpp（编译单元）内共享的状态（通常由本模块内多个函数共同读写）。可用 `static`（内部链接）或匿名命名空间实现。
  [来源：第9章 p32，print模块的设计与实现]

#### 代码模式
- **静态全局变量示例**：
  ```cpp
  // 文件：print.cpp
  // 模块的内部状态
  static int win_cnt = 0, lose_cnt = 0, tie_cnt = 0;
  ```
  [来源：第9章 p33，print模块的设计与实现]

---

### 9.5 库的设计与实现

#### 定义/语法
- **库**：一些模块/函数的集合体。
  - 特点：内部互动不多，功能有所关联
  - 如标准库 `iostream` 包含输入输出功能，`cmath` 包含数学运算函数
  [来源：第9章 p39，设计自己的库]

- **库的组成**：
  1. **库的接口**（头文件 .h）：库的用户必须了解的内容，包括函数原型、符号常量和自定义类型
  2. **具体实现**（源文件 .cpp）：函数的具体实现代码
  [来源：第9章 p40，库的设计]

#### 规则/规范
- **（常见约定）库的头文件与实现文件常使用相同“基名”**：如 `random.h` + `random.cpp`，便于维护与定位；但这只是命名惯例，不是语法/编译器强制要求，一个库也可能包含多个 .cpp。
  [来源：第9章 p41，库的实现]

- **库的设计要考虑通用性**：库函数的功能是从一类相似的应用中抽象概括而来，要考虑尽可能广泛的适用范围。
  [来源：第9章 p39，设计自己的库]

- **实现文件格式要求**：
  1. 注释：简单介绍库的功能
  2. `#include` 此 cpp 文件所需的头文件，便于编译器检查函数定义和原型声明的一致性
  3. 每个函数的实现代码（前面必须有注释）
  [来源：第9章 p41，库的实现]

#### 代码模式
- **随机数库接口设计**：
  ```cpp
  // 文件：random.h
  #ifndef random_h
  #define random_h
  
  // 函数：RandomInit
  // 用法：RandomInit()
  // 作用：此函数初始化随机数种子
  void RandomInit();
  
  // 函数：RandomInteger
  // 用法：n = RandomInteger(low, high)
  // 作用：此函数返回一个low到high之间的随机数，包括low和high
  int RandomInteger(int low, int high);
  
  #endif
  ```
  [来源：第9章 p43，库接口的设计]

- **随机数库实现**：
  ```cpp
  // 文件：random.cpp
  #include <cstdlib>
  #include <ctime>
  #include "random.h"
  
  void RandomInit()
  {
      srand(time(NULL));
  }
  
  int RandomInteger(int low, int high)
  {
      return (low + (high - low + 1) * rand() / (RAND_MAX + 1));
  }
  ```
  [来源：第9章 p44，函数实现]

---

### 9.6 随机数模拟概率

#### 执行逻辑
- **用随机数模拟概率的方法**：生成 0-9 之间的随机数，根据概率分配区间：
  - 50% 概率 → 随机数 0-4（5个数）
  - 20% 概率 → 随机数 5-6（2个数）
  - 30% 概率 → 随机数 7-9（3个数）
  [来源：第9章 p50，如何用随机数模拟概率]

#### 代码模式
- **龟兔赛跑中乌龟移动函数**：
  ```cpp
  int move_tortoise()
  {
      int probability = RandomInteger(0, 9);
      if (probability < 5) return 3;      // 快走 50%
      else if (probability < 7) return -6; // 后滑 20%
      else return 1;                       // 慢走 30%
  }
  ```
  [来源：第9章 p51，Move模块]

- **龟兔赛跑中兔子移动函数**：
  ```cpp
  int move_rabbit()
  {
      int probability = RandomInteger(0, 9);
      if (probability < 2) return 0;       // 睡觉 20%
      else if (probability < 4) return -9; // 大后滑 20%
      else if (probability < 5) return 14; // 快走 10%
      else if (probability < 8) return 3;  // 小步跳 30%
      else return -2;                      // 慢后滑 20%
  }
  ```
  [来源：第9章 p51，Move模块]

---

### 9.7 例题结论

#### 例题结论
- **【石头剪刀布游戏设计要点】**
  - 用枚举类型 `p_r_s` 表示出拳选项：`paper, rock, scissor, game, help, quit`
  - 用枚举类型 `outcome` 表示结果：`win, lose, tie, error`
  - 模块间共享类型定义放在公共头文件中
  - 使用 `rand() % 3` 生成 0-2 的随机数模拟计算机出拳
  - 累计战绩用模块内部静态变量保存
  [来源：第9章 p27-34，石头剪刀布完整示例]

- **【龟兔赛跑程序结构】**
  - 主循环：`while (rabbit < RACE_END && tortoise < RACE_END)`
  - 每轮更新位置：`tortoise += move_tortoise(); rabbit += move_rabbit();`
  - 使用 `RandomInit()` 初始化随机数种子
  - 胜负判断：按“是否到达终点（>= RACE_END）”判定；若同一回合双方都到达则平局，否则先到达（或到达者）获胜（不要仅用 `rabbit > tortoise` 直接比较位置）。
  [来源：第9章 p48-53，龟兔赛跑示例]

---

### 9.8 团队管理（扩展知识）

#### 规则/规范
- **两张披萨原则**（Jeff Bezos / Amazon）：团队应小到“两张披萨能喂饱”，常见表述为理想少于 10 人（不同资料会给出不同范围，但核心是减少沟通链路与官僚成本）。
  [来源：第9章 p14，模块开发团队管理]

- **小团队优势**（经验规律/定性结论）：
  - 人数更少通常意味着沟通链路更短、决策更快、责任更清晰；
  - 但也可能带来资源不足、跨团队对齐成本上升、重复建设等问题；
  - 如需引用定量数据，请补充可追溯出处（报告/论文/链接）以便核验。
  [来源：第9章 p21，管理效果（请以课程材料原文为准）]

---

## 总结要点（来自 PPT 总结页）

1. **学会利用模块化程序设计技术来解决一个大问题**
2. **学会在模块中保存内部状态**（使用 static 全局变量）
3. **学会从程序中抽取出库以及如何设计和使用库**
   [来源：第9章 p54，总结]
