@chengzhao-dev/dsh-cpp-workflow 0.6.13 → 0.6.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/README.en.md +2 -2
  2. package/README.md +2 -2
  3. package/package.json +1 -1
  4. package/skills/building-cpp/SKILL.md +18 -16
  5. package/skills/building-cpp/references/build-script.md +4 -4
  6. package/skills/building-cpp/references/static-analysis.md +1 -1
  7. package/skills/building-cpp/steps/02-tooling.md +2 -2
  8. package/skills/building-cpp/steps/03-check.md +4 -3
  9. package/skills/configuring-cmake/SKILL.md +17 -17
  10. package/skills/configuring-cmake/steps/03-check.md +2 -2
  11. package/skills/developing-cpp/SKILL.md +13 -13
  12. package/skills/developing-cpp/examples/comments-declaration.md +22 -27
  13. package/skills/developing-cpp/examples/comments-implementation.md +1 -1
  14. package/skills/developing-cpp/examples/exceptions.md +2 -2
  15. package/skills/developing-cpp/examples/naming.md +1 -8
  16. package/skills/developing-cpp/examples/structure.md +12 -12
  17. package/skills/developing-cpp/references/change-model.md +55 -0
  18. package/skills/developing-cpp/references/comments.md +38 -21
  19. package/skills/developing-cpp/references/declaration-order.md +1 -1
  20. package/skills/developing-cpp/references/exceptions.md +4 -4
  21. package/skills/developing-cpp/references/glossary.md +1 -1
  22. package/skills/developing-cpp/references/google-cpp-style.md +4 -21
  23. package/skills/developing-cpp/references/language.md +2 -2
  24. package/skills/developing-cpp/references/layout.md +4 -13
  25. package/skills/developing-cpp/references/naming.md +2 -2
  26. package/skills/developing-cpp/references/performance.md +2 -1
  27. package/skills/developing-cpp/references/priority.md +3 -1
  28. package/skills/developing-cpp/references/project-design.md +2 -2
  29. package/skills/developing-cpp/scripts/checking-comments.mjs +5 -0
  30. package/skills/developing-cpp/steps/01-behavior.md +10 -10
  31. package/skills/developing-cpp/steps/02-boundary.md +17 -19
  32. package/skills/developing-cpp/steps/03-declaration.md +20 -13
  33. package/skills/developing-cpp/steps/04-implementation.md +16 -16
@@ -0,0 +1,55 @@
1
+ # 变更模型
2
+
3
+ 三个技能共用这一份模型。操作决定从哪一步进、这次改动的范围多大;域决定改动落在哪一层。操作不是域,域也不是步骤。
4
+
5
+ ## 三种操作
6
+
7
+ | 操作 | 判据 | 可以 | 不可以 |
8
+ | --- | --- | --- | --- |
9
+ | 写作(起稿) | 缺可观察行为、契约或事实 | 新增类型、函数、文件、target、环境轴;补后置条件与成员事实 | 发明项目没有声明的行为、命令、标准或目录 |
10
+ | 改写 | 可观察行为与契约不变,组织、顺序或位置不对 | 移动、删除、换序、重写块段;保留事实与论证线 | 新增事实、数字、命令、结论或行为;改变否定、条件、范围、时间、归属 |
11
+ | 润色 | 只改措辞、命名或注释 | 删复述、换词、拆句、统一术语 | 改结构、改事实、加句子或加信息;把可能或通常改成确定 |
12
+
13
+ 范围随任务定:单点修正只改判为不合格的一处;整段重构先定删留,再改块段,最后逐句。只读审查不改文件,按操作判缺什么,只报告;不调构建、渲染或回执。
14
+
15
+ ## 四个域
16
+
17
+ | 域 | 回答的问题 | 可以改 | 不能改 |
18
+ | --- | --- | --- | --- |
19
+ | 写作 | 这次要保证什么新事实或新行为? | 行为、契约、后置条件、成员事实 | 已有事实、否定、条件、数字、引文 |
20
+ | 布局 | 它放在哪一层、哪个文件、什么顺序? | 文件位置、模块边界、声明顺序、注释位置 | 行为、措辞、命名 |
21
+ | 排版 | 它长什么样? | 缩进、行宽、空行、注释符号、标签格式 | 标识符、字符串、命令、语义 |
22
+ | 润色 | 这句话是否准确、必要、可读? | 命名、用词、句式、复述、术语统一 | 结构、事实、段落顺序、信息量 |
23
+
24
+ 四域按「写作 → 布局 → 排版 → 润色」推进。前一层没落定,不进入下一层;后一层发现前一层没做完,退回那一层,不在当前层补。`priority.md` 的五级次序是冲突时的判据:行为与契约 > 结构 > 清晰 > 一致 > 形式。
25
+
26
+ ## 跨技能交接
27
+
28
+ 源码 → `configuring-cmake` → `building-cpp`。一次改动同时碰到三层时,先定源码行为与边界,再写 target,最后配工具并跑检查;前一段结论落下再开下一段。只改注释时,声明注释 → 实现注释 → CMake 契约注释 → 构建诊断,逐层按四域走。
29
+
30
+ ## 写作边界
31
+
32
+ - 不新增原文没有的事实、数字、命令、性能结论、引文和出处;不把「可能」「通常」「在本书里」改成确定说法;保留否定、条件、范围、时间和归属。
33
+ - 不写否定式能力边界:不列「不检查…」「不校验…」「不负责…」「不判断…」清单;前置条件写成「调用方保证…」「仅接受…」或 `@pre`。状态结果「不做修改」「保持不变」是本次调用的结果,不是能力边界。
34
+ - 同一概念只用一个首选词;术语、命令、标识符、路径、URL 与围栏内容不改。
35
+ - 注释写声明里看不出的事实;实现注释写为什么、边界与失败后果,不复述代码。
36
+
37
+ ## 依据
38
+
39
+ - IPEd《Types of editing》把编辑分为 substantive editing、copyediting 与 proofreading;本模型把「写作、改写、润色」对应到「新增、重组、措辞」三档,排版独立成形式层。
40
+ - Google C++ Style Guide、LLVM Coding Standards 与 ISO C++ Core Guidelines 的注释条目都要求写意图、用法与陷阱,不把代码翻译成自然语言。
41
+ - 这些资料只解释模型,不替代项目技能、`environment.json` 或用户明确要求。
42
+
43
+ ## 何时打开
44
+
45
+ 判不准操作或域、跨技能交接、或四域冲突时打开。只做单点润色且入口明确时不必打开;步骤文件已点名的条文仍按步骤打开。
46
+
47
+ ## 通用自检
48
+
49
+ 1. 操作判对了吗:这次是新增行为、重组结构,还是只改措辞?范围是单点还是整段?
50
+ 2. 域判对了吗:这次只动一层,还是混了两层?混了时是否按写作 → 布局 → 排版 → 润色拆开?
51
+ 3. 事实有没有变:有没有新增数字、命令、结论、引文,或把「可能」「通常」改成确定?
52
+ 4. 边界写正了吗:有没有「不检查…」清单?前置条件是否写成「调用方保证…」「仅接受…」或 `@pre`?
53
+ 5. 原样保留了吗:术语、命令、标识符、路径、URL 与围栏内容是否未被改写?
54
+ 6. 次序守住了吗:前一层是否已落定并经检查,后一层才开始?冲突是否按 `priority.md` 判?
55
+
@@ -2,11 +2,14 @@
2
2
 
3
3
  注释按写作、布局、排版、润色四节组织;文件头、类型、函数与实现注释是注释所在的位置,配置文件的注释另算。位置越靠外,越讲概念与契约;越靠里,越讲实现理由。同一件事只写一次。
4
4
 
5
+ 四域不是四个步骤:操作决定从哪一步进,域决定这次改哪一层;两者的边界与次序见 `change-model.md`。本节只写注释里的落点。
6
+
5
7
  按四类改动查:写作决定写什么,布局决定放哪一层哪一行,排版决定符号、宽度与标签,润色删复述、统一句式。四步都可能删句子,但后置条件和成员约定只有写作能定,润色不得以「精简」为由删掉。
6
8
 
7
9
  ## 写作:只写声明里看不出的事实
8
10
 
9
- - **第一句先写结果、限制或检查项**:类型用名词短语写它是什么;普通函数写正常返回什么、失败时返回什么或抛什么;布尔查询与 `operator==` 写「检查…是否…」。第一句不写「返回 true/false」;`@return` 仍要写明成立时返回 true、不成立时返回 false。条件、参数与实现理由放第二句以后。
11
+ - **第一句先写结果、保证或检查项**:类型用名词短语写它是什么;普通函数写正常返回什么、失败时返回什么或抛什么;布尔查询与 `operator==` 写「检查…是否…」。第一句不写「返回 true/false」;`@return` 仍要写明成立时返回 true、不成立时返回 false。条件、参数与实现理由放第二句以后。
12
+ - **不写否定式能力边界**:注释不列「不检查…」「不校验…」「不负责…」「不判断…」这类清单;只写本函数保证什么、调用方必须满足什么。前置条件写成「调用方保证…」「仅接受…」或 `@pre`,布尔查询只写检查什么。状态结果可以说「不做修改」「保持不变」,因为它描述的是这次调用的结果,不是能力范围。`scripts/checking-comments.mjs` 会报 `negative-scope`。
10
13
  - 公共注释回答「这是什么」和「调用方必须遵守什么」;实现注释回答「为什么这样写」和「边界在哪里」。设计取舍、方案对比与演进历史写到书稿、README 或提交说明。
11
14
  - 用中文散文写完整句子,句号结尾;标识符、标准库类型、`@param` 这类标签保持原文。
12
15
  - 写之前三问:删掉这句,读者会误解设计或做错事吗?这句只是复述签名或代码吗?这个限制能不能由类型、`const` 或 `[[nodiscard]]` 表达?第一问为否就删,第二问为是就删,第三问为是就改代码。
@@ -15,9 +18,9 @@
15
18
 
16
19
  ### 类型:名词短语与不变量
17
20
 
18
- 对象是类、结构体、枚举和类型别名。第一句是名词短语:类写「这个类型是什么」,纯数据结构和枚举写「这个取值表示什么」。
21
+ 对象是类、结构体、枚举和类型别名。第一句是名词短语:类写「这个类型是什么」,可用全角冒号补初始状态;纯数据结构和枚举写「这个取值表示什么」。
19
22
 
20
- 只有类有不变量,且不重复领域尺寸时,才加第二段。第二段直接写不变量,不加「限制:」这类标签,也不写「这一层不负责什么」。纯数据结构和枚举不写第二句。
23
+ 只有类有不变量,且不重复领域尺寸时,才加第二段。第二段直接写不变量与生命周期:初始状态、状态迁移、重置条件;生命周期写在类注释,不拆到成员注释。不加「限制:」这类标签,也不写「这一层不负责什么」。纯数据结构和枚举不写第二句。
21
24
 
22
25
  ```cpp
23
26
  /// 井字棋棋盘。
@@ -31,7 +34,7 @@ class Board {
31
34
  - 不写类型系统已经表达的性质(`值语义`、`所有查询都是 const`)。
32
35
  - 不写尚未发生的用法、方案对比与取舍论证、广告句。
33
36
  - 纯数据结构不写「只保存…,不拥有…,不含…」这类否定式边界;这些由成员类型和所有权表达。
34
- - 领域尺寸不写进类型注释:格子数由 `cells_` 的声明表达,棋盘边长由 `kBoardRows`、`kBoardCols` 表达。
37
+ - 领域尺寸不写进类型注释:格子数由 `cells_` 的声明表达,棋盘边长由 `kRowCount`、`kColumnCount` 表达。
35
38
  - 枚举与只承载数据的结构体:第一句是名词短语,写这个取值表示什么。状态枚举上一行写「格子的状态。」,结果枚举写「表示…结果。」;行尾 `///<` 的两种句式见「枚举项与 `struct` 短字段:两种句式」。
36
39
 
37
40
  ### 构造函数与析构函数:先写标签,再写职责
@@ -47,18 +50,32 @@ Board() = default;
47
50
 
48
51
  ### 数据成员:上一行 `///`,先写名词再写约定
49
52
 
50
- 类数据成员的主注释写在声明上一行,统一用 `///`,私有成员也一样。第一句先写这个成员是什么,再在同一句里补类型和名字看不出的约定:轴向、单位、哨兵值、所有权、外部约定、初值含义或改写时机与次序。有补充时写成一句 `/// 名词:约定。`,冒号用全角 `:`,句号结束;修饰必须依附于被修饰的名词,不能单独占一行。行尾不写 `//` 或 `///<`;`///<` 只留给枚举项和 `struct` 的短字段。名字和类型已经说清时不写。
53
+ 类数据成员的主注释写在声明上一行,统一用 `///`,私有成员也一样。第一句先写这个成员是什么,再在同一句里补类型和名字看不出、只属于这个成员的约定:轴向、单位、哨兵值、所有权、外部约定。初值含义、状态迁移与重置规则若属于类级不变量,写在类注释,成员注释不重复;成员注释保持一句,名字和类型已经说清时可以不写。
54
+
55
+ 有补充时写成一句 `/// 名词:约定。`,冒号用全角 `:`,句号结束;修饰必须依附于被修饰的名词,不能单独占一行。行尾不写 `//` 或 `///<`;`///<` 只留给枚举项和 `struct` 的短字段。
51
56
 
52
57
  ```cpp
53
58
  /// 棋盘格子数组:外层下标是行,内层下标是列。
54
- std::array<std::array<CellType, kBoardCols>, kBoardRows> cells_{};
59
+ std::array<std::array<CellState, kColumnCount>, kRowCount> cells_{};
55
60
  /// 当前玩家:开局由 X 先手。
56
61
  Player current_player_ = Player::kCross;
57
62
  /// 对局结果:落子被拒时保持原值。
58
63
  GameResult result_ = GameResult::kInProgress;
59
64
  ```
60
65
 
61
- `board_` 这类名字已经说明存放对象的成员不写「对局棋盘」;`cells_` 不写「存储…」这种把声明读一遍的句子,但上一行要先说它是棋盘格子数组,再补轴向、哨兵和改写次序。`clang-format` 不会把行尾注释搬到上一行,位置由本规则和检查器保证。
66
+ 类的生命周期写在类注释,成员注释只留这个成员是什么:
67
+
68
+ ```cpp
69
+ /// 格子:初始状态为 `kEmpty`。
70
+ ///
71
+ /// `Place` 至多一次将空白格子置为 `kX` 或 `kO`;`Clear` 将其重置为 `kEmpty`。
72
+ class Cell {
73
+ /// 格子当前的状态。
74
+ CellState state_{CellState::kEmpty};
75
+ };
76
+ ```
77
+
78
+ `board_` 这类名字已经说明存放对象的成员不写「对局棋盘」;`cells_` 不写「存储…」这种把声明读一遍的句子,但上一行要先说它是棋盘格子数组,再补轴向与哨兵。`clang-format` 不会把行尾注释搬到上一行,位置由本规则和检查器保证。
62
79
 
63
80
  ### 枚举项与 `struct` 短字段:两种句式
64
81
 
@@ -80,9 +97,9 @@ GameResult result_ = GameResult::kInProgress;
80
97
  /// 检查坐标能否落子,不修改棋盘。
81
98
  ///
82
99
  /// @param position 目标行列坐标。
83
- /// @param cell_type 要放置的棋盘格子类型,必须是 kX 或 kO。
84
- /// @return 可以落子返回 kSuccess;越界、cell_type 为 kEmpty 或目标已有棋子时返回对应拒绝值。
85
- [[nodiscard]] PlaceCellResult CheckPlaceable(CellPosition position, CellType cell_type) const;
100
+ /// @param state 要放置的棋盘格子类型,必须是 kX 或 kO。
101
+ /// @return 可以落子返回 kPlaced;越界、state 为 kEmpty 或目标已有棋子时返回对应拒绝值。
102
+ [[nodiscard]] Cell::PlaceResult CheckPlaceable(Position position, CellState state) const;
86
103
  ```
87
104
 
88
105
  - 布尔查询与 `operator==`:第一句写「检查…是否…」;`@return` 写成「成立时返回 true;不成立时返回 false」,不写「否则」省略后半句。`Is`、`Has` 开头的名字与 `bool` 返回类型已经把成立条件说清时可省略 `@return`,不为了形式补一句同义反复。
@@ -122,11 +139,11 @@ GameResult result_ = GameResult::kInProgress;
122
139
  | 层 | 行数 | 第一句 | 写什么 |
123
140
  | --- | --- | --- | --- |
124
141
  | 文件头 | 3–7(实现文件 2) | `@file` 文件名 + `@brief` 一句话 | 本文件由哪几部分组成 |
125
- | 类型注释 | 2–14 | 名词短语:这个类型或取值是什么 | 只有类的不变量 |
142
+ | 类型注释 | 2–14 | 名词短语:这个类型或取值是什么 | 只有类的不变量与生命周期 |
126
143
  | 特殊成员 | 1–3 | 标签(默认/拷贝/移动构造函数、析构函数) | 后置条件或职责 |
127
144
  | 函数注释 | 3–10(按标签数放宽) | 正常返回什么、失败时返回或抛什么、检查什么 | 每个参数、返回值与失败方式 |
128
145
  | 无标签函数 | 2 | 两段:后置条件/职责,再补边界 | 仅限构造、析构、`void` 无参数 |
129
- | 数据成员 | 上一行 1 句(`/// 名词:约定`) | 名字和类型看不出的范围、初值含义、轴向或哨兵 | 什么时候变 |
146
+ | 数据成员 | 上一行 1 句(`/// 名词:约定`) | 这个成员是什么;只属于它的轴向、哨兵或所有权 | 只写成员自身,不重复类注释的初值与状态迁移 |
130
147
  | 实现注释 | 每个空行阶段 1 句 | 边界、失败后果或为什么停在这里 | `//`,只给重要函数 |
131
148
 
132
149
  ## 布局:放在哪一层、哪一行
@@ -142,13 +159,13 @@ GameResult result_ = GameResult::kInProgress;
142
159
 
143
160
  ### 文件头
144
161
 
145
- 每个手写源文件开头写一段 doxygen 文件头:第一行写 `@file <文件名>`,第二行写 `@brief` 一句话说明这个文件定义或实现什么,用一行空注释隔开后,只写本文件自己的组成部分或边界。`@brief` 在头文件以「定义」起句,在实现文件以「实现」收句。空行后以「包含」起句,逐项写「中文概念 `类型名`」,顺序与声明一致;不写「文件包含」这类标签,也不写行为清单。`@file` 的参数必须与文件名逐字相同,检查器会核对。`@brief` 始终写出;`@author`、`@date`、`@copyright`、`@version` 的开关出厂打开,只有填了非空值才写出,空值整条省略;作者、年份和变更史按团队约定填写,不填就不出现在源码里。删掉把读者指去另一个头文件、另一个目录或另一层的句子,也删掉「这一层不负责什么」。不写作者、版权年份、变更日志和外部资料路径。
162
+ 每个手写源文件开头写一段 doxygen 文件头:第一行写 `@file <文件名>`,第二行写 `@brief` 一句话说明这个文件定义或实现什么,用一行空注释隔开后,只写本文件自己的组成部分或边界。类型定义型头文件写「…相关类型定义。」,空行后以「封装了」起句,逐项写「中文概念 `类型名`」,顺序与声明一致;其他头文件写本文件定义什么,实现文件以「实现」收句。不写「文件包含」这类标签,也不写行为清单。`@file` 的参数必须与文件名逐字相同,检查器会核对。`@brief` 始终写出;`@author`、`@date`、`@copyright`、`@version` 的开关出厂打开,只有填了非空值才写出,空值整条省略;作者、年份和变更史按团队约定填写,不填就不出现在源码里。删掉把读者指去另一个头文件、另一个目录或另一层的句子,也删掉「这一层不负责什么」。不写作者、版权年份、变更日志和外部资料路径。
146
163
 
147
164
  ```cpp
148
165
  /// @file cell.h
149
- /// @brief 定义格子状态与格子。
166
+ /// @brief 格子相关类型定义。
150
167
  ///
151
- /// 包含格子状态 `CellState` 与格子 `Cell`。
168
+ /// 封装了格子的状态枚举 `CellState` 以及格子实体 `Cell`。
152
169
  ```
153
170
 
154
171
  选择块注释时,同一段写成:
@@ -156,9 +173,9 @@ GameResult result_ = GameResult::kInProgress;
156
173
  ```cpp
157
174
  /**
158
175
  * @file cell.h
159
- * @brief 定义格子状态与格子。
176
+ * @brief 格子相关类型定义。
160
177
  *
161
- * 包含格子状态 `CellState` 与格子 `Cell`。
178
+ * 封装了格子的状态枚举 `CellState` 以及格子实体 `Cell`。
162
179
  */
163
180
  ```
164
181
 
@@ -169,7 +186,7 @@ GameResult result_ = GameResult::kInProgress;
169
186
  ## 排版:符号、宽度与标签
170
187
 
171
188
  - 注释符号在插件设置里选择:`///` 是默认写法,`/** ... */` 是另一种 Doxygen 写法;文件头与声明注释统一用同一种,函数体内仍只用 `//`。块注释每行以 ` * ` 对齐,收尾单独一行 ` */`。
172
- - 行宽跟项目 `.clang-format` 的 `ColumnLimit` 走(Google 基线是 80 列,中文按两列计)。
189
+ - 行宽跟项目 `.clang-format` 的 `ColumnLimit` 走;项目没写时按 Google 默认 80 列,中文按两列计。
173
190
  - 行尾注释与代码之间空两格;`///<` 只接在枚举项和 `struct` 短字段声明行尾。
174
191
  - `@param`、`@return`、`@throws` 标签保持英文;`@param` 后先写参数名,再写一句。
175
192
  - `TODO:` 后先写定位(阶段名或 issue 号),再写一个短横线,再写动作;不写 `TODO(某人)`,不写没有动作的 TODO。
@@ -185,8 +202,8 @@ GameResult result_ = GameResult::kInProgress;
185
202
  - 禁止在类型注释里写领域尺寸(3×3、九个格子、边长);尺寸由成员声明和常量表达。
186
203
  - 禁止把成员功能清单按声明顺序抄一遍;函数契约写在各自函数上。
187
204
  - 同一事实只写一处:声明注释、实现注释、书稿和 README 不各讲一遍。
188
- - 删掉注释后读者不会做错事、或注释只重复签名时,删;但写作节要求保留的后置条件、轴向、哨兵和改写时机不在此列。
189
- - 只改注释时仍走第 3、4 步,然后回读:文件头 → 类型(名词短语 + 只有类的不变量)→ 特殊成员(标签 + 后置条件/职责)→ 函数(结果/失败方式/检查项 + 参数)→ 数据成员(只读上一行 `///`:名词 + 全角冒号 + 约定;行尾不应有注释)→ 实现(每个空行阶段的边界或理由)→ 术语与新符号是否真实存在 → 运行 `checking-comments.mjs` 兜底。
205
+ - 删掉注释后读者不会做错事、或注释只重复签名时,删;但写作节要求保留的后置条件、类级生命周期、轴向和哨兵不在此列。
206
+ - 只改注释时仍走第 3、4 步,然后回读:文件头 → 类型(名词短语 + 只有类的不变量与生命周期)→ 特殊成员(标签 + 后置条件/职责)→ 函数(结果/失败方式/检查项 + 参数)→ 数据成员(只读上一行 `///`:名词 + 全角冒号 + 约定;行尾不应有注释)→ 实现(每个空行阶段的边界或理由)→ 术语与新符号是否真实存在 → 运行 `checking-comments.mjs` 兜底。
190
207
  - 注释写给维护者,正文写给读者;设计理由、方案比较与算法取舍写在正文或实现注释里,公开接口只留契约。
191
208
  - 禁止用注释代替类型、`const` 或 `[[nodiscard]]`。
192
209
 
@@ -199,5 +216,5 @@ GameResult result_ = GameResult::kInProgress;
199
216
  - 说已设置状态、已有状态,不说「已放置」「有棋子」。
200
217
  - 说更新,不说「写入」「放置」「落子」。
201
218
  - 说未变更、已变更,不说「失败」「成功」。
202
- - 说「不进行覆盖」「不做修改」「保持不变」;「不进行覆盖」只出现在 `.cpp`。
219
+ - 说「不进行覆盖」「不做修改」「保持不变」;「不进行覆盖」只出现在 `.cpp`。这些是本次调用的结果,不是能力边界,不要把它们扩成「不检查…」清单。
203
220
  - 「设置」只管有没有状态,「更新」只管这次动作,「已有状态」只管拒绝覆盖,三者不互换。
@@ -113,6 +113,6 @@ class TaskTable {
113
113
  ### 函数体内的空行
114
114
 
115
115
  - 以 `return` / `throw` 结束的 `if` 守卫,与下一组语句之间空 1 行。
116
- - 同一组语句不拆:三个校验之后的赋值与 `return kSuccess` 之间不空行。
116
+ - 同一组语句不拆:三个校验之后的赋值与 `return kPlaced` 之间不空行。
117
117
  - 只有次序依赖的一对 `if`(`GetWinner`、`JudgeAfterPlace`)之间空 1 行。
118
118
  - 最多 1 行,不连续空两行。
@@ -15,7 +15,7 @@ E.1 要求尽早决定错误处理策略:先问调用方还能不能继续、
15
15
 
16
16
  策略一旦定下,声明、实现、测试和入口都用同一套;不要在同一层里一半返回错误码、一半抛异常。
17
17
 
18
- 判断是否算「混用」,看函数合同而不是看返回类型:查询必须产出一个值、失败时产不出(如 `GetCellType` 越界),就抛;写入操作把「这次不写」当作正常结果、调用方本来就要分支(如 `PlaceCell` 返回 `PlaceCellResult::kRejected`,`CheckPlaceable` 返回具体原因),就用返回值。动作可能因为多个前置条件失败、而调用方需要区分原因时,返回 `enum class` 而不是 `bool` 加输出参数。两者并存时,公共头必须把「拒绝」与「抛」的边界各写一行;没有这行注释才算混用。
18
+ 判断是否算「混用」,看函数合同而不是看返回类型:查询必须产出一个值、失败时产不出(如 `GetState` 越界),就抛;写入操作把「这次不写」当作正常结果、调用方本来就要分支(如 `Place` 返回 `Cell::PlaceResult::kOccupied`,`CheckPlaceable` 返回具体原因),就用返回值。动作可能因为多个前置条件失败、而调用方需要区分原因时,返回 `enum class` 而不是 `bool` 加输出参数。两者并存时,公共头必须把「拒绝」与「抛」的边界各写一行;没有这行注释才算混用。
19
19
 
20
20
  ## 什么时候抛
21
21
 
@@ -58,7 +58,7 @@ E.31:`catch` 子句从派生到基类、从具体到通用排序,`catch (...
58
58
 
59
59
  ## 不抛的边界
60
60
 
61
- E.12:在不可能或不可接受抛异常的地方用 `noexcept`。声明前先读实现:函数体里出现 `at()` 或其他可能抛出的调用,就去掉 `noexcept`;不要再包一层只转发下标的私有函数来维持 `noexcept`,直接写 `cells_.at(row).at(col)` 并去掉 `noexcept`。正例是只比较两个整数的 `IsInRange`;反例是用 `at()` 读取并会抛 `std::out_of_range` 的 `GetCellType`。E.13:直接拥有资源的函数不要抛,资源释放应交给 RAII。E.16:析构、`deallocate`、`swap` 以及异常类型的拷贝/移动构造不得失败;析构和 `swap` 应当是 `noexcept`。
61
+ E.12:在不可能或不可接受抛异常的地方用 `noexcept`。声明前先读实现:函数体里出现 `at()` 或其他可能抛出的调用,就去掉 `noexcept`;不要再包一层只转发下标的私有函数来维持 `noexcept`,直接写 `cells_.at(row).at(col)` 并去掉 `noexcept`。正例是只比较两个整数的 `IsInRange`;反例是用 `at()` 读取并会抛 `std::out_of_range` 的 `GetState`。E.13:直接拥有资源的函数不要抛,资源释放应交给 RAII。E.16:析构、`deallocate`、`swap` 以及异常类型的拷贝/移动构造不得失败;析构和 `swap` 应当是 `noexcept`。
62
62
 
63
63
  E.30:不要写异常规范(`throw(...)` 之类);C++11 之后用 `noexcept` 表达「不抛」。需要无异常构建时,由项目单独决定,不要把这套异常指导改写成两面都对的模糊话。
64
64
 
@@ -91,8 +91,8 @@ int main() {
91
91
 
92
92
  ```cpp
93
93
  throw std::out_of_range{std::format(
94
- "Board::GetCellType: board position is out of range (row={}, col={}); board is {}x{}",
95
- position.row, position.col, kBoardRows, kBoardCols)};
94
+ "Board::GetState: board position is out of range (row={}, col={}); board is {}x{}",
95
+ position.row, position.col, kRowCount, kColumnCount)};
96
96
  throw std::logic_error{"Ai::Choose: game is already over"};
97
97
  throw std::logic_error{"EmptyCellsOrThrow: no empty cell left to place"};
98
98
  ```
@@ -1,6 +1,6 @@
1
1
  # 术语
2
2
 
3
- 代码标识符用英文;注释和说明用中文时,同一个概念只用一种译法。下表是本项目的默认译法,项目技能可以增补,但不要改已有词的含义。
3
+ 代码标识符用英文;注释和说明用中文时,同一个概念只用一种译法。下表是本项目的默认译法,项目技能可以增补,但不要改已有词的含义。标识符怎么拼(大小写、前缀、语义角色)见 `references/naming.md`;本表只管术语译法与近义词辨析。
4
4
 
5
5
  | 英文 | 中文 | 说明 |
6
6
  | --- | --- | --- |
@@ -24,26 +24,9 @@
24
24
 
25
25
  ## 命名
26
26
 
27
- | 种类 | 规则 | 例子 |
28
- | --- | --- | --- |
29
- | 类型、类、结构、枚举、别名、模板参数 | PascalCase | `TaskTable`, `OrderResult`, `TaskState` |
30
- | 函数、成员函数 | PascalCase | `Contains`, `GetCurrentOrder` |
31
- | 访问器、修改器 | Google 允许 PascalCase 或 snake_case | `Count()` / `count()`、`SetCount()` / `set_count()`;同一 API 只选一种,不要混用 |
32
- | 变量、参数、局部变量 | snake_case | `task_count`, `current_order` |
33
- | 类数据成员 | snake_case + 尾随 `_` | `tasks_`, `orders_` |
34
- | 结构体数据成员 | snake_case,无尾随下划线 | `name`, `priority` |
35
- | 常量、枚举子 | `k` + PascalCase | `kTaskCount`, `Order::kDraft` |
36
- | 命名空间 | snake_case | `taskflow`, `warehouse` |
37
- | 宏 | 尽量不用;必须用时全大写并带项目前缀 | `CPP_WORKFLOW_ASSERT` |
38
-
39
- - 一个 API 不混用两套命名。对外函数用 PascalCase,成员变量用 snake_case,不要出现 `get_task` 与 `GetTask` 并存。
40
- - 查询函数遵循同一 API 选定的访问器约定:Google 允许 `Count()` 或 `count()`,也允许 `GetCurrentOrder()`;同一 API 内不要混用两套。
41
- - 谓词用 `Is` / `Has` / `Can`,成对规则可用 `Completes` 这类限定式;唯一前缀集与 `Contains` 例外见 `references/naming.md`。返回 `bool` 但语义不是判断的函数,换成领域动词。
42
- - 尝试用 `Try`:`TryReserve()` 返回结果类型;确定性动作直接用领域动词:`CompleteTask()`、`Reset()`。
43
- - 映射函数的名字要含来源与目标角色:`OrderToReceipt()`、`TaskTableToString()`;禁止裸 `To()` / `Of()`。
44
- - 计算函数用动词开头:`ScoreOrder()`、`CollectPendingTasks()`。
45
- - `current_` 前缀只在有多个候选、且「当前」确实可能混淆时使用;只有一个当前对象时不要加。
46
- - 避免 `Manager`、`Helper`、`Util` 这类不说明职责的后缀;名字要能回答「它负责什么」。
27
+ 大小写、前缀、成员尾随 `_`、常量与枚举子的 `k` 前缀、命名空间与文件名的完整规则表,以及访问器、谓词、`Try`、映射函数、`current_` 前缀的语义角色,见 `references/naming.md`。本文只保留两处 Google 原文允许选择、而本项目需要统一的点:
28
+
29
+ - 访问器、修改器 Google 允许 PascalCase 或 snake_case;本项目统一 PascalCase,同一 API 不混用两套。
47
30
  - 测试名用 PascalCase 描述行为,例如 `RejectsOutOfRangeTask`;测试文件用 snake_case。
48
31
 
49
32
  ## 类型与声明
@@ -92,7 +75,7 @@ Google 原文不使用 C++ 异常;本工作流只在开发阶段的源码指
92
75
  - 组合消息以 `std::format` 为主:`std::format("任务 {} 进入状态 {}", task, state)`。
93
76
  - `std::print` 只在 C++23 且标准库支持时使用,通常放在程序边界;库代码仍用 `std::format` 组合消息。C++20 及更早把格式化结果写到 `std::cout` / `std::cerr` / `std::ostringstream`。
94
77
  - 简单字面量输出可以直接用 `std::cout << "..."`;需要拼接变量、对齐、数值格式时用 `std::format`。
95
- - 列宽上限 80,每级缩进 2 个空格,代码不用 tab;这些都来自 Google 预设,不要在 `.clang-format` 里覆盖。
78
+ - 列宽跟工程根 `.clang-format` 的 `ColumnLimit` 走;项目已声明 100 时按 100,模板资产不写 `ColumnLimit`。每级缩进 2 个空格,代码不用 tab;缩进与对齐由 `.clang-format` 统一,不在条文里另定一套。
96
79
  - 浮点输出固定精度,整数不依赖隐式格式;`std::format` 的格式串写全。
97
80
 
98
81
  ## 注释
@@ -61,13 +61,13 @@ auto total = 0.0; // auto 只能用 =,不能用 {}
61
61
 
62
62
  ## 函数、参数与返回值
63
63
 
64
- - 函数名用 PascalCase,动词开头;查询用 `Is` / `Has` / `Can` / `Get` / `Find`,转换用 `SourceToTarget`。
64
+ - 命名规则以 `references/naming.md` 为准:函数 PascalCase、查询 `Is` / `Has` / `Can` / `Get` / `Find`、转换 `SourceToTarget`。
65
65
  - 输入参数默认按值传小对象、按 `const&` 传大对象;需要修改调用方对象时用引用参数,并在名字或注释里标 `[out]`。
66
66
  - 优先返回值而不是输出参数;需要返回多个值时用 `struct` 聚合,不用多个 `bool` 输出参数。
67
67
  - 返回容器或大对象时按值返回,依赖 RVO / 移动;不要返回局部对象的引用或指针。
68
68
  - 单参数构造函数加 `explicit`,除非确实需要隐式转换;转换运算符也加 `explicit`。
69
69
  - 不在虚函数上写默认参数;默认参数在静态类型处绑定,派生类改默认值会造成意外。
70
- - 函数只做一件事;超过一屏时先找可命名的子步骤,不要用注释给长函数分段。
70
+ - 函数只做一件事;超过一屏时先找可命名的子步骤,分阶段与注释写法见 `steps/04-implementation.md`。
71
71
 
72
72
  ## 类、结构体与特殊成员
73
73
 
@@ -1,6 +1,6 @@
1
1
  # 源码布局
2
2
 
3
- 布局服务于边界:库是纯的,程序是薄的,测试从公共接口进入。这里只定目录和文件职责;CMake target 在 `building-cpp` 的定构建。
3
+ 布局服务于边界:库是纯的,程序是薄的,测试从公共接口进入。这里只定目录和文件职责;CMake target 由 `configuring-cmake` 的 `steps/01-targets.md` 定。
4
4
 
5
5
  ## 组件
6
6
 
@@ -49,20 +49,11 @@ app/
49
49
 
50
50
  ## 文件扩展名与归属
51
51
 
52
- - `.h`:公共或私有头文件;公共头放 `include/`,私有头放 `src/`。
53
- - `.hpp`:与 `.h` 同一套槽位;一个工程只选一种头扩展名,不因扩展名换结构。
54
- - `.cpp`:实现文件;新工程与示例一律使用。已有工程沿用它原本的实现后缀,不批量改名。
55
- - `.cc`:已有工程常见的实现后缀;本工作流的示例不选它,但审阅这类工程时按 `.cpp` 同一套规则处理。
56
- - `.inc`:稀有文本包含;本工作流不新写。
57
- - 头文件、实现文件的槽位与守卫名以 `references/file-template.md` 为准;本文只定位置,不重复守卫模板。
52
+ 扩展名、槽位与守卫名以 `references/file-template.md` 为准;本文只定位置:公共头放 `include/`,私有头放 `src/`,实现文件放 `src/`,测试放 `tests/`。
58
53
 
59
54
  ## 头文件契约
60
55
 
61
- - 公共头自包含:单独编译一个只包含它的 `.cpp` 必须通过。
62
- - 头文件不定义非 inline 的普通全局变量;共享常量用 `inline constexpr`。
63
- - 头文件不写 `using namespace`;不污染全局命名空间。
64
- - 模板和 `constexpr` 的实现可以留在头文件;普通函数体放 `.cpp`。
65
- - 头文件不包含 `<iostream>`;需要流时在 `.cpp` 或程序层包含。
56
+ 自包含、共享常量、`using namespace`、模板实现与 `<iostream>` 的规则见 `references/google-cpp-style.md`「头文件」和 `references/file-template.md`。本文只强调一条边界:公共头只暴露稳定契约,测试只包含公共头。
66
57
 
67
58
  ## 测试
68
59
 
@@ -77,6 +68,6 @@ app/
77
68
 
78
69
  1. 先定边界(第 2 步),列出公共头。
79
70
  2. 移动文件,不改行为;测试保持绿色。
80
- 3. 再改 CMake target(`building-cpp` 的定构建),最后跑 `building-cpp` 的通过检查。
71
+ 3. 再改 CMake target(`configuring-cmake` 的 `steps/01-targets.md`),最后跑 `building-cpp` 的 `steps/03-check.md`。
81
72
 
82
73
  一次只做一种迁移;不要把「拆库 + 改名 + 改行为」混在一起。
@@ -29,7 +29,7 @@
29
29
  | 判断 | `Is` / `Has` / `Can` | `IsEmpty()`、`HasStock()`、`HasLine()` | 返回 `bool`,不抛异常 |
30
30
  | 查找 | `Find` | `FindTask()` | 找不到是正常结果,返回 `optional` 或迭代器,不抛 |
31
31
  | 计数 | `Count` | `CountPendingTasks()` | 返回数量,不修改状态 |
32
- | 尝试 | `Try` / 领域动词 | `TryReserve()`、`PlaceCell()` | 可能因多个前置条件失败但不抛异常,返回 `enum class` 结果;调用方按名区分失败原因 |
32
+ | 尝试 | `Try` / 领域动词 | `TryReserve()`、`Place()` | 可能因多个前置条件失败但不抛异常,返回 `enum class` 结果;调用方按名区分失败原因 |
33
33
  | 确定性动作 | 领域动词 | `CompleteTask()`, `Reset()` | 前置条件满足时一定成功;无法完成任务时抛 |
34
34
  | 计算 | 动词开头 | `ScoreOrder()` | 从输入算出结果,不修改外部状态 |
35
35
  | 映射 | `<来源>To<目标>` | `OrderToReceipt()` | 名字必须含两端角色 |
@@ -50,7 +50,7 @@
50
50
  - 前缀集就是这四类:`Is` / `Has` / `Can`,外加与成对规则一一对应的 `Completes`、`Would` 这类限定式;`Contains` 只保留给容器成员(`std::vector::empty` 一类已有标准名),不拿它当一般谓词。
51
51
  - `Get` 只用于取值,`Set` 只用于设值;不要用 `CheckLine()` 当布尔 getter。
52
52
  - `Check` / `Validate` 留给会报告错误、产生诊断或可能失败的操作;它们不返回纯 `bool` 查询。
53
- - 动作可能因为多个前置条件失败、而调用方需要区分原因时,返回 `enum class`(`PlaceCellResult`、`WinPreviewResult`),不要用 `bool` 加输出参数,也不要拆成一串 `IsXxx` 判断再重复检查。
53
+ - 动作可能因为多个前置条件失败、而调用方需要区分原因时,返回 `enum class`(`Cell::PlaceResult`、`WinPreviewResult`),不要用 `bool` 加输出参数,也不要拆成一串 `IsXxx` 判断再重复检查。
54
54
  - 假设性查询在名字里写明条件:`PreviewWin(position, cell)` 读作「这一手会成线吗」,返回 `WinPreviewResult` 区分「不能落子」与「能落但不赢」;只有成功与不报错的纯判断才用 `bool`。同一 API 只选一种,改名时同步声明、定义、调用点、测试和注释。
55
55
  - 私有谓词同样用肯定式;不要用 `flag`、`status` 或 `check` 这类不说明判据的名字。
56
56
 
@@ -2,6 +2,8 @@
2
2
 
3
3
  先证明慢在哪里,再改代码。没有测量的“优化”通常只是把复杂度搬了个位置。
4
4
 
5
+ 本文件只管性能判断与测量;语言规则见 `references/language.md`,容器与算法选择见 `references/standard-library.md`,静态检查开关见 `building-cpp` 的 `references/static-analysis.md`。
6
+
5
7
  ## 测量顺序
6
8
 
7
9
  1. 写清可观察的性能目标:延迟、吞吐、内存上限,或“输入规模翻倍时耗时如何增长”。
@@ -69,7 +71,6 @@ ASan/UBSan 有运行期开销,只在测试和排查时开;发布构建仍要
69
71
  - 不要用全局可变状态换速度;正确性和可测试性先于常数因子。
70
72
  - 不要为未测量的热点写复杂的手工内存管理。
71
73
  - 不要用 `reinterpret_cast` 绕过类型系统;需要二进制布局时用 `std::bit_cast` 并确认可平凡复制。
72
- - 不要关闭整类 clang-tidy 检查来让构建通过;修代码或局部 `NOLINT` 并写理由。
73
74
  - 不要把 `std::endl` 当换行符;它每次都会 flush。
74
75
 
75
76
  ## 检查清单
@@ -2,6 +2,8 @@
2
2
 
3
3
  源码规则不是同一层的检查表。两条规则冲突时,先保住更高层,再谈低层的「更短」「更统一」「更整齐」。
4
4
 
5
+ 本表管写作内部的取舍顺序;三种操作与四个域的先后见 `references/change-model.md`。
6
+
5
7
  | 级别 | 保住什么 | 典型判断 | 冲突时让位给谁 |
6
8
  | --- | --- | --- | --- |
7
9
  | 1 行为与契约 | 可观察行为、不变量、所有权、异常保证不走样 | 这个后置条件删掉后,调用方还知道构造出来的对象是什么状态吗? | 不让位。为短、为顺、为统一删掉契约,都是失败 |
@@ -13,7 +15,7 @@
13
15
  ## 常见冲突
14
16
 
15
17
  - **注释更短 vs 契约完整**:删掉构造后置条件、参数范围或失败方式换来的短,不算润色;先补回契约,再压句子。
16
- - **位置统一 vs 成员可读**:数据成员的主注释都在声明上一行,但上一行必须先说成员是什么,再补轴向、哨兵或改写时机;不能为了「每行只写一个修饰」把名词丢掉。
18
+ - **位置统一 vs 成员可读**:数据成员的主注释都在声明上一行,但上一行必须先说成员是什么,再补只属于该成员的轴向、哨兵或所有权;类级初值与状态迁移放在类注释,不能为了「每行只写一个修饰」把名词丢掉。
17
19
  - **格式统一 vs 语义准确**:同族函数用同一种注释句式,但不能把不同的失败条件写成同一句话;先准确,再统一能统一的部分。
18
20
  - **语言风格 vs 消息内容**:异常消息先说来源、条件、实际值和限度;用哪种自然语言由项目约定,排版规则不替内容做决定。
19
21
  - **句子更短 vs 成立条件**:删掉「在什么情况下」换来的短,不算润色。状态结论必须带条件:「终局后空格仍在」要写成「一方已三连而棋盘未满时,空格仍然留着;平局时棋盘已满,没有空格」。这条同时约束代码注释与读者正文。
@@ -21,7 +21,7 @@
21
21
  ### 取值的边界
22
22
 
23
23
  - 封闭取值一次列全。格子的取值是空、X、O;不要把 X 和 O 做成不含空的棋子,再用空值表示空格。
24
- - 位置是行和列,不是格子的另一种取值。胜负、轮到谁、已下过的序列属于棋盘或对局,不要塞进位置类型。
24
+ - 位置是行和列,不是格子的另一种取值。胜负、轮到谁、已下过的序列属于棋盘或对局,不要塞进位置类型。坐标类型单独一个头文件;持有尺寸的类型包含它,不把坐标塞回尺寸类型里。
25
25
  - 有不变量的格子用公开构造的类,默认是空,X 或 O 至多写入一次。私有构造函数只留给必须经过工厂才能建立的对象,不用来表达三种取值,也不用来藏起行和列。
26
26
  - 一次写入能区分的失败都放进结果枚举。传入空取值是结果项,不抛异常。
27
27
  - 只由某一个类型产生的结果枚举嵌在该类型里:格子的写入结果和对局的写入结果取值不同,不合成命名空间里的一个 `PlaceResult`。
@@ -72,7 +72,7 @@
72
72
  - 平台变化(Windows/WSL/macOS 路径、动态库查找)放构建脚本或适配层,不进核心。
73
73
  - 测试需要替换的外部依赖(时钟、随机源、输入输出)通过接口注入,不在核心内部直接调用。
74
74
 
75
- 公共头文件只暴露稳定契约;实现细节放 `.cpp` 或私有头。一个组件一个 `CMakeLists.txt`,组件的 `include/` 是对外接口,`src/` 是实现。
75
+ 公共头文件只暴露稳定契约;实现细节放 `.cpp` 或私有头。目录形态、扩展名与头文件槽位以 `references/layout.md` 和 `references/file-template.md` 为准。
76
76
 
77
77
  ## 什么时候抽共享库
78
78
 
@@ -37,6 +37,7 @@ const RATIONALE = /而不是|而非|代价.{0,4}忽略|只留一份|与其|比.{
37
37
  const FORWARD_REF = /(?:留在|归|交给|由)\s*`?[A-Z][A-Za-z0-9_]*`?/
38
38
  const TYPE_SYSTEM = /值语义|自由拷贝/
39
39
  const NEGATED_BOUNDARY = /只保存|不拥有|不含/
40
+ const NEGATIVE_SCOPE = /不(?:检查|校验|验证|负责|判断)/
40
41
  const DOMAIN_SIZE = /\d+\s*[×xX]\s*\d+|九格|九个格子/
41
42
  const PREDICATE_DECL = /^(?:\[\[nodiscard\]\]\s*)?(?:static\s+)?bool\s+(?:Is|Has|Can|Completes)[A-Za-z0-9_]*\s*\(|operator==/
42
43
  const DECL_KEYWORDS = /^(class|struct|union|enum|namespace|using|typedef|template|#|if|else|for|while|switch|return|case|default)\b/
@@ -356,6 +357,9 @@ export function checkText(text, options = {}) {
356
357
  if (isType && NEGATED_BOUNDARY.test(block)) {
357
358
  add(start + 1, 'negated-boundary', '类型注释用「只保存/不拥有/不含」写否定式边界:改成名词短语,边界交给成员类型表达')
358
359
  }
360
+ if (NEGATIVE_SCOPE.test(block)) {
361
+ add(start + 1, 'negative-scope', '注释用「不检查/不校验/不负责/不判断」写能力边界:改成正向限定,前置条件写「调用方保证…」或 @pre')
362
+ }
359
363
  if (isType && DOMAIN_SIZE.test(block)) {
360
364
  add(start + 1, 'domain-size', '类型注释在写领域尺寸:格子数由 cells_ 之类成员声明表达,删掉这句')
361
365
  }
@@ -398,6 +402,7 @@ export function checkText(text, options = {}) {
398
402
  if (isType && FORWARD_REF.test(block)) add(doc.start + 1, 'forward-ref', '类型注释在交代别的类型负责什么:只写本类型的契约,删掉这句,不要挪到文件头')
399
403
  if (isType && TYPE_SYSTEM.test(block)) add(doc.start + 1, 'type-system', '类型注释在复述类型系统已表达的性质:可拷贝与 const 查询由声明给出,删掉这句')
400
404
  if (isType && NEGATED_BOUNDARY.test(block)) add(doc.start + 1, 'negated-boundary', '类型注释用「只保存/不拥有/不含」写否定式边界:改成名词短语,边界交给成员类型表达')
405
+ if (NEGATIVE_SCOPE.test(block)) add(doc.start + 1, 'negative-scope', '注释用「不检查/不校验/不负责/不判断」写能力边界:改成正向限定,前置条件写「调用方保证…」或 @pre')
401
406
  if (isType && DOMAIN_SIZE.test(block)) add(doc.start + 1, 'domain-size', '类型注释在写领域尺寸:格子数由 cells_ 之类成员声明表达,删掉这句')
402
407
  const firstDocLine = doc.contents[0] ?? ''
403
408
  if (!isType && PREDICATE_DECL.test(declaration) && !firstDocLine.startsWith('检查')) {
@@ -2,31 +2,31 @@
2
2
 
3
3
  ## 产出
4
4
 
5
- 一句可验收的行为描述,加上这一处的输入、输出和失败方式。行为句只写观察得到的结果,不写类名、容器或算法。
5
+ 一句可验收的行为描述,加上这一处的输入、输出和失败方式。行为句只写观察得到的结果;类名、容器和算法留给第 3 步。
6
6
 
7
7
  ```text
8
8
  给定 <输入>,程序输出 <结果>;当 <非法输入> 时,输出 <错误> 并以 <方式> 结束。
9
9
  ```
10
10
 
11
- 有项目阶段表时,先找到这一阶段的行;行为句必须落在这一行范围内。没有阶段表时,只写用户在这次请求里点名的行为,不编造阶段。
11
+ 有项目阶段表时,先找到这一阶段的行,行为句落在这一行范围内;没有阶段表时,只写用户在这次请求里点名的行为。
12
12
 
13
13
  ## 通用情况
14
14
 
15
- - 先写「用户能看到什么」:终端输出、文件内容、退出码、库函数返回值。内部状态不算可观察行为。
16
- - 一次只定一个可验收的切片。切片太大就拆成两个行为句,分别走完四步。
17
- - 失败方式也要写:非法参数、空输入、越界、文件不存在时,是返回错误值、打印提示,还是抛异常。
18
- - 行为句不绑定实现。「用 `std::vector` 保存」是声明或实现的决定,不是行为。
19
- - 需要新增输出时,组合消息用 `std::format`;`std::print` 只在 C++23 且是程序边界(`apps/`、`main`)时可用,库代码不直接打印。
15
+ - 可观察的是终端输出、文件内容、退出码和库函数的返回值;内部状态不算可观察行为。
16
+ - 一次只定一个可验收的切片;切片太大就拆成两句行为,各走一遍四步。
17
+ - 失败方式与成功路径同写:非法参数、空输入、越界、文件不存在时,是返回错误值、打印提示,还是抛异常。
18
+ - 新增输出用 `std::format` 组合消息;`std::print` 只在 C++23 且位于程序边界(`apps/`、`main`)时使用,设施选型见 `references/standard-library.md`。
19
+ - 操作与范围按 `references/change-model.md` 判:本步是写作的入口;改写只在行为被打破时回到这里。
20
20
 
21
21
  ## 情况
22
22
 
23
- **新功能**:先写最小可观察切片,例如「输入名字,输出一句问候」。不要一次把交互、存档、排行榜都写进第 1 步。
23
+ **新功能**:先写最小可观察切片,例如「输入名字,输出一句问候」。交互、存档、排行榜不一次写进第 1 步。
24
24
 
25
25
  **领域规则**:先把动词和名词写成领域值——任务、状态、动作、结果。取值封闭时用 `enum class`;规则有歧义时列出至少两个解释并问用户,不静默选一个。领域值怎么选见 `references/project-design.md`。
26
26
 
27
- **修缺陷**:行为句写成「现在观察到 X,期望观察到 Y」,并保留复现输入。修完只验证 X → Y,不顺手改无关行为。
27
+ **修缺陷**:行为句写成「现在观察到 X,期望观察到 Y」,并保留复现输入。修完只验证 X → Y。
28
28
 
29
- **重构**:行为句写成「输入输出不变」,并列出必须保持的边界(退出码、错误文本、文件格式)。重构不得改变可观察行为。
29
+ **重构**:行为句写成「输入输出不变」,并列出必须保持的边界(退出码、错误文本、文件格式)。
30
30
 
31
31
  **没有阶段表**:只写用户点名的行为;用户没说失败方式时,按现有程序惯例补一句,并在完成时说明这是补充假设。
32
32
 
@@ -2,38 +2,36 @@
2
2
 
3
3
  ## 产出
4
4
 
5
- 一张边界表:每个组件放什么、哪些头文件是公共接口、库 target 依赖什么、程序 target 依赖什么。还要写清终端输入输出放在哪里。
5
+ 一张边界表:每个组件的职责、哪些头文件是公共接口、库依赖什么、程序依赖什么,以及终端输入输出落在哪一层。目录形态与文件归属以 `references/layout.md` 为准,本步只决定取舍。
6
6
 
7
7
  ```text
8
- <component>/include/<name>.h 公共接口
9
- <component>/src/<name>.cpp 实现
10
- tests/ 测试
11
- app 只做参数、输入输出和装配
8
+ <component> 职责:<一句话> 公共头:<name>.h 依赖:<标准库 / 其他组件>
9
+ app 职责:参数、输入输出、装配 依赖:<component>
12
10
  ```
13
11
 
14
12
  ## 通用情况
15
13
 
16
- - 库 target 不依赖终端、GUI、网络脚本或全局单例;它只接收值、返回值、在无法完成任务时抛异常。
17
- - 程序 target 链接库,负责读参数、读标准输入、打印结果、把退出码翻译成进程退出码。
18
- - 公共头文件直接放在组件的 `include/` 下,包含时写裸文件名,例如 `#include "task_list.h"`;由构建把该目录加入 include 搜索路径,不要写从仓库根起的长路径或 `../` 相对路径。
19
- - 依赖方向从程序指向库。库不反向包含程序的 `cli.h`,也不引用 `main.cpp`。
20
- - 测试放 `tests/`,测试 target 链接被测库;测试不复制库的实现。
21
- - 只有一个可执行文件、没有复用需求时,可以不分库;一旦出现第二个调用方或测试需要直接调用,再拆库。
22
- - 抽共享库前先确认第二个真实使用方和相同语义;只是“看起来会复用”不构成理由。抽象理由写进项目技能或设计注释。
23
- - 第 2 步不写 `CMakeLists.txt`。只决定边界,构建 target 留给 `building-cpp` 的定构建。
24
- - 判断一个查询该成为成员还是同一命名空间的函数时,按 `references/project-design.md` 的「谁回答这个问题」:掌握数据的一方回答,只组合公开查询的函数不升级为成员。
14
+ - 库接收值、返回值,在无法完成任务时抛异常;终端、GUI、网络脚本与全局单例留在程序层。返回值与异常的取舍见 `references/exceptions.md`。
15
+ - 程序链接库,负责读参数、读标准输入、打印结果,并把退出码翻译成进程退出码。
16
+ - 依赖方向从程序指向库;组件之间只经由公共头互相依赖。
17
+ - 公共头放在组件的 `include/` 下,包含时写裸文件名;搜索路径由 target 的 `target_include_directories(... PUBLIC .../include)` 提供,写法见 `configuring-cmake` 的 `references/cmake.md`。
18
+ - 测试放 `tests/`,链接被测库,从公共头进入。
19
+ - 只有一个可执行文件、没有复用需求时不分库;出现第二个真实调用方,或测试需要直接调用时再拆。
20
+ - 抽共享抽象前先确认第二个真实使用方和相同语义;抽象理由写进项目技能或设计注释。
21
+ - 本步只决定边界:`CMakeLists.txt` 与 target 图由 `configuring-cmake` 的 `steps/01-targets.md` 写,构建与工具配置由 `building-cpp` 定。
22
+ - 一个查询该成为成员还是同命名空间的函数,按 `references/project-design.md` 的「谁回答这个问题」判:掌握数据的一方回答。
25
23
 
26
24
  ## 情况
27
25
 
28
- **单文件程序**:`src/main.cpp` 可以有 `main()` 和私有辅助函数;一旦辅助逻辑需要测试,把它移到库组件。
26
+ **单文件程序**:`src/main.cpp` 可以有 `main()` 和私有辅助函数;辅助逻辑需要测试时,把它移到库组件。
29
27
 
30
- **领域核心 + 外围**:规则核心(状态、合法性、结果)只依赖标准和领域类型;终端、GUI、Web、脚本都在适配层。依赖方向从外向内,核心不知道适配层存在。
28
+ **领域核心 + 外围**:规则核心(状态、合法性、结果)只依赖标准和领域类型;终端、GUI、Web、脚本都在适配层,依赖方向从外向内。
31
29
 
32
- **库 + 程序**:库放在 `<component>/`,程序放在 `app/` 或根 `src/main.cpp`;程序只包含库的公共头。公共头只暴露稳定契约,实现细节放 `.cpp` 或私有头。
30
+ **库 + 程序**:库放在 `<component>/`,程序放在 `app/` 或根 `src/main.cpp`;程序只包含库的公共头,实现细节留在 `.cpp` 或私有头。
33
31
 
34
- **静态库 / 动态库**:接口相同,差别在链接方式和运行时库路径;都遵守「库不依赖终端」。选哪种由 `building-cpp` 的定构建决定。
32
+ **静态库 / 动态库**:接口相同,差别在链接方式和运行时库路径;都遵守「库不依赖终端」。选哪种由 `configuring-cmake` 的定 target 步骤决定,运行时路径写法见其 `references/cmake.md`。
35
33
 
36
- **测试**:测试通过公共头调用库,不包含 `src/` 私有头;需要测私有逻辑时,先判断它是否应该成为公共行为。
34
+ **测试**:测试通过公共头调用库;需要测私有逻辑时,先判断它是否应该成为公共行为。
37
35
 
38
36
  ## 正反例指针
39
37