@chengzhao-dev/dsh-cpp-workflow 0.1.1 → 0.3.0

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 (82) hide show
  1. package/README.en.md +18 -15
  2. package/README.md +18 -15
  3. package/lib/index.js +7 -8
  4. package/lib/workspace.js +14 -14
  5. package/locale/en.json +1 -1
  6. package/locale/zh.json +1 -1
  7. package/package.json +7 -5
  8. package/skills/building-cpp/SKILL.md +8 -6
  9. package/skills/building-cpp/assets/config/.clang-format +5 -38
  10. package/skills/building-cpp/assets/config/.clang-tidy +8 -39
  11. package/skills/building-cpp/assets/config/.clangd +2 -5
  12. package/skills/building-cpp/assets/config/.vscode/extensions.json +2 -1
  13. package/skills/building-cpp/assets/config/.vscode/settings.json +16 -1
  14. package/skills/building-cpp/assets/templates/projects/common/running-build.sh +29 -18
  15. package/skills/building-cpp/assets/templates/projects/multi/CMakeLists.txt +9 -5
  16. package/skills/building-cpp/assets/templates/projects/multi/include/greeting.h +5 -3
  17. package/skills/building-cpp/assets/templates/projects/multi/src/greeting.cc +18 -0
  18. package/skills/building-cpp/assets/templates/projects/multi/src/main.cc +8 -0
  19. package/skills/building-cpp/assets/templates/projects/shared-library/CMakeLists.txt +8 -3
  20. package/skills/building-cpp/assets/templates/projects/shared-library/greeting/CMakeLists.txt +15 -5
  21. package/skills/building-cpp/assets/templates/projects/shared-library/greeting/include/greeting.h +5 -3
  22. package/skills/building-cpp/assets/templates/projects/shared-library/greeting/src/greeting.cc +18 -0
  23. package/skills/building-cpp/assets/templates/projects/shared-library/src/main.cc +8 -0
  24. package/skills/building-cpp/assets/templates/projects/single/CMakeLists.txt +7 -3
  25. package/skills/building-cpp/assets/templates/projects/single/src/main.cc +15 -0
  26. package/skills/building-cpp/assets/templates/projects/static-library/CMakeLists.txt +8 -3
  27. package/skills/building-cpp/assets/templates/projects/static-library/greeting/CMakeLists.txt +15 -5
  28. package/skills/building-cpp/assets/templates/projects/static-library/greeting/include/greeting.h +5 -3
  29. package/skills/building-cpp/assets/templates/projects/static-library/greeting/src/greeting.cc +18 -0
  30. package/skills/building-cpp/assets/templates/projects/static-library/src/main.cc +8 -0
  31. package/skills/building-cpp/examples/cmake.md +17 -17
  32. package/skills/building-cpp/examples/environment.md +4 -3
  33. package/skills/building-cpp/examples/tooling.md +31 -16
  34. package/skills/building-cpp/references/build-script.md +2 -2
  35. package/skills/building-cpp/references/cmake.md +35 -17
  36. package/skills/building-cpp/references/encoding.md +49 -0
  37. package/skills/building-cpp/references/environment.md +17 -13
  38. package/skills/building-cpp/references/linking.md +6 -6
  39. package/skills/building-cpp/references/standards/README.md +165 -0
  40. package/skills/building-cpp/references/standards/c++11/README.md +50 -0
  41. package/skills/building-cpp/references/standards/c++11/cmake-snippet.cmake +7 -0
  42. package/skills/building-cpp/references/standards/c++14/README.md +48 -0
  43. package/skills/building-cpp/references/standards/c++14/cmake-snippet.cmake +7 -0
  44. package/skills/building-cpp/references/standards/c++17/README.md +49 -0
  45. package/skills/building-cpp/references/standards/c++17/cmake-snippet.cmake +7 -0
  46. package/skills/building-cpp/references/standards/c++20/README.md +49 -0
  47. package/skills/building-cpp/references/standards/c++20/cmake-snippet.cmake +7 -0
  48. package/skills/building-cpp/references/standards/c++23/README.md +50 -0
  49. package/skills/building-cpp/references/standards/c++23/cmake-snippet.cmake +7 -0
  50. package/skills/building-cpp/references/static-analysis.md +27 -15
  51. package/skills/building-cpp/scripts/checking-encoding.mjs +241 -0
  52. package/skills/building-cpp/scripts/checking-environment.mjs +21 -21
  53. package/skills/building-cpp/steps/01-build.md +10 -8
  54. package/skills/building-cpp/steps/02-tooling.md +9 -7
  55. package/skills/building-cpp/steps/03-check.md +9 -7
  56. package/skills/developing-cpp/SKILL.md +8 -6
  57. package/skills/developing-cpp/examples/comments.md +39 -8
  58. package/skills/developing-cpp/examples/exceptions.md +64 -45
  59. package/skills/developing-cpp/examples/naming.md +2 -2
  60. package/skills/developing-cpp/examples/structure.md +12 -9
  61. package/skills/developing-cpp/references/comments.md +28 -8
  62. package/skills/developing-cpp/references/declaration-order.md +7 -4
  63. package/skills/developing-cpp/references/exceptions.md +83 -46
  64. package/skills/developing-cpp/references/glossary.md +6 -6
  65. package/skills/developing-cpp/references/google-cpp-style.md +27 -37
  66. package/skills/developing-cpp/references/language.md +4 -4
  67. package/skills/developing-cpp/references/layout.md +14 -14
  68. package/skills/developing-cpp/references/naming.md +15 -6
  69. package/skills/developing-cpp/references/project-design.md +12 -3
  70. package/skills/developing-cpp/references/standard-library.md +1 -1
  71. package/skills/developing-cpp/steps/02-boundary.md +8 -7
  72. package/skills/developing-cpp/steps/03-declaration.md +9 -8
  73. package/skills/developing-cpp/steps/04-implementation.md +11 -7
  74. package/templates/cpp-workflow-project/SKILL.md +1 -1
  75. package/templates/cpp-workflow-project/environment.json +2 -1
  76. package/skills/building-cpp/assets/templates/projects/multi/src/greeting.cpp +0 -13
  77. package/skills/building-cpp/assets/templates/projects/multi/src/main.cpp +0 -8
  78. package/skills/building-cpp/assets/templates/projects/shared-library/greeting/src/greeting.cpp +0 -13
  79. package/skills/building-cpp/assets/templates/projects/shared-library/src/main.cpp +0 -8
  80. package/skills/building-cpp/assets/templates/projects/single/src/main.cpp +0 -9
  81. package/skills/building-cpp/assets/templates/projects/static-library/greeting/src/greeting.cpp +0 -13
  82. package/skills/building-cpp/assets/templates/projects/static-library/src/main.cpp +0 -8
@@ -1,44 +1,84 @@
1
1
  # 异常与错误
2
2
 
3
- 本技能对 Google「不使用异常」做书面偏离:**异常可用,但只用于合同违反和程序边界**。可预期的失败用返回值表达。这样既保留 C++ 的清晰错误路径,又不让异常成为控制流。
3
+ 本文件是**开发阶段**的源码写作指导,只在 `developing-cpp` 被打开时阅读。插件运行时不编译 C++、不执行示例,也不把这里的规定送进任何运行时流程;它只是一份随技能分发的 Markdown 参考。
4
4
 
5
- ## 抛还是返回
5
+ Google C++ Style Guide 明确写「We do not use C++ exceptions」。本工作流在书面规范上做一次有边界、可追溯的偏离:**源码示例和异常指导采用 C++ Core Guidelines 的 Error handling 规则**,因为这套规则由 Bjarne Stroustrup 与 Herb Sutter 编辑,代表现代 C++ 社区对异常安全、RAII 和 invariant 的共识。偏离只覆盖异常这一节;其他排版、命名、类型和注释规则仍以 Google 为准。不要在工程里同时引入 LLVM、Chromium 或 Abseil 的异常政策作为第二套标准。
6
6
 
7
- | 情形 | 做法 | 例子 |
8
- | --- | --- | --- |
9
- | 用户输入非法、文件不存在、解析失败 | 返回结果类型 / `bool` / `std::optional` | `TryParse()` → `ParseResult` |
10
- | 查询越界、构造参数不满足 invariant | 抛合同异常 | `GetTaskState(TaskId)` 越界抛 `std::out_of_range` |
11
- | 程序入口、CLI、工具边界 | 捕获 `std::exception`,打印并返回非零 | `main()` 捕获后 `std::format("错误: {}", e.what())` |
12
- | 内存不足、系统调用失败 | 让标准异常传播,或转成结果类型;不要吞掉 | `std::bad_alloc` 不捕获 |
13
- | 析构函数 | 不抛;失败只记录或忽略 | `~File()` 不抛 |
14
- | 可恢复的业务失败 | 不抛;用结果类型让调用方分支 | `ReserveResult::kOccupied` |
7
+ 规范来源:
15
8
 
16
- 判断句:**调用方能否在不看实现的情况下合理避免?** 能避免的是合同违反(抛);不能避免的是可预期失败(返回)。
9
+ - [C++ Core Guidelines: Error handling](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#S-errors)(E.1–E.31)。
10
+ - [Boost.Exception](https://www.boost.org/doc/libs/latest/libs/exception/doc/boost-exception.html):异常类型层级的工程典范,只借它的结构,不把 Boost 变成依赖。
17
11
 
18
- ## 合同异常
12
+ ## 先定错误策略
19
13
 
20
- - 在声明注释里写清:什么条件抛、抛什么类型、调用方应如何避免。
21
- - 类型选择:参数非法 `std::invalid_argument`;越界 `std::out_of_range`;状态不允许 `std::logic_error`;资源失败用标准库已有类型。
22
- - 异常消息用 `std::format` 组合,包含操作、关键值和期望范围:
14
+ E.1 要求尽早决定错误处理策略。写接口前先回答:
15
+
16
+ - 这个函数不能完成自己的任务时,调用方还能继续吗?
17
+ - 失败是「正常的另一种结果」,还是「invariant 被破坏、继续执行没有意义」?
18
+ - 失败信息需要跨层携带,还是只需在边界打印?
19
+
20
+ 策略一旦定下,声明、实现、测试和入口都用同一套;不要在同一层里一半返回错误码、一半抛异常。
21
+
22
+ ## 什么时候抛
23
+
24
+ E.2:函数不能完成它被分配的任务时,就抛异常;构造函数无法建立 invariant 时也抛,而不是留下半初始化对象。E.3:异常只用于错误处理,不能拿 `throw` 当返回值或正常分支。E.4:错误处理围绕 invariant 设计,而不是围绕「哪一行可能失败」。E.5:构造函数负责建立 invariant,失败就抛。E.6:用 RAII 防止泄漏,优先局部对象而不是裸 `new`。
25
+
26
+ 判断句可以写成:**调用方是否还能履行自己的合同?** 不能,就让异常向上传播;能,并且失败是接口的正常结果,就用返回值、`std::optional` 或项目自己的结果类型表达。不要为了「统一」把可恢复的业务分支包成异常,也不要把已经无法维持 invariant 的状态压成错误码继续跑。
27
+
28
+ ## 异常类型
29
+
30
+ E.14:用专门的用户定义类型表示异常,不要抛内建类型、`std::string` 或字符串字面量。类型本身可以只表达语义,数据放在成员里;也可以完全不带数据,让 `catch` 只按类型选择。
31
+
32
+ ```cpp
33
+ // 领域错误:类型即语义,调用方可以只捕获自己认识的那一支。
34
+ class ParseError : public std::runtime_error {
35
+ public:
36
+ explicit ParseError(const std::string& message) : std::runtime_error(message) {}
37
+ };
38
+ ```
39
+
40
+ 标准库异常只做两种用途:作为基类,或在确实没有更多信息时直接使用通用类型(如 `std::runtime_error`)。不要为了「看起来规范」而强制每个参数错误都抛 `std::invalid_argument`、每个越界都抛 `std::out_of_range`;有领域语义时就定义自己的类型,让调用方按语义捕获。
41
+
42
+ ### Boost.Exception 的类型层级
43
+
44
+ Boost.Exception 展示了「异常类型只做语义标签」的做法:类型不带数据,用多继承表达一个错误同时属于多个类别,数据由 `error_info` 在抛出点或传播途中携带。异常类型之间用 virtual 继承,避免 `catch (const std::exception&)` 在多继承下出现二义性。
23
45
 
24
46
  ```cpp
25
- throw std::out_of_range{
26
- std::format("GetTaskState: id={} 超出 [0, {})", id, kTaskCount)};
47
+ struct exception_base : virtual std::exception, virtual boost::exception {};
48
+ struct io_error : virtual exception_base {};
49
+ struct file_error : virtual io_error {};
50
+ struct read_error : virtual io_error {};
51
+ struct file_read_error : virtual file_error, virtual read_error {};
27
52
  ```
28
53
 
29
- - 不要抛裸 `std::string`、`const char*` 或自定义无继承的类型;从 `std::exception` 派生时实现 `what()`。
30
- - 不要用异常做正常分支;循环里用异常控制流程视为缺陷。
54
+ 这段代码只作为**设计典范**,不要求工程引入 Boost。若项目已有自己的异常基类,照同样的原则做:语义标签用类型表达,共享数据放在基类或错误信息容器里,多继承时用 virtual 基类。`boost::exception` 不直接派生 `std::exception`,正是为了让用户类型能以 virtual 基类方式同时派生两者;这是 Koenig 的洞察,异常处理路径上的虚继承开销可以忽略。
31
55
 
32
- ## 捕获
56
+ ## 抛出与捕获
33
57
 
34
- - 按 `const` 引用捕获:`catch (const std::exception& error)`。
35
- - 只捕获能处理的类型;不认识的异常继续抛,不要 `catch (...)` 后静默返回。
36
- - 捕获后要么恢复成有效状态,要么返回错误;不要带着半初始化对象继续运行。
37
- - `noexcept` 只写在确实不抛的函数上;析构函数、移动操作默认不抛,除非有明确理由。
58
+ E.15:按值抛出、按引用捕获(通常是 `const` 引用);不要抛裸指针;重新抛出用 `throw;`,不要 `throw error;` 丢类型。
38
59
 
39
- ## 程序边界
60
+ ```cpp
61
+ try {
62
+ ReadConfig(path);
63
+ } catch (const FileReadError& error) {
64
+ // 能补偿才捕获;不能补偿就让异常继续向上。
65
+ throw;
66
+ }
67
+ ```
68
+
69
+ E.17:不要在每个函数里捕获所有异常。E.18:少用显式 `try/catch`,优先 RAII 和 `finally` 风格的清理对象。E.19:没有资源句柄时,用 `final_action` 之类的 scope guard 做清理。
70
+
71
+ E.31:`catch` 子句从派生到基类、从具体到通用排序,`catch (...)` 只能放最后。不要 `catch (...)` 后静默返回,除非当前层真的能把错误翻译成调用方合同的一部分。
72
+
73
+ ## 不抛的边界
74
+
75
+ E.12:在不可能或不可接受抛异常的地方用 `noexcept`。E.13:直接拥有资源的函数不要抛,资源释放应交给 RAII。E.16:析构、`deallocate`、`swap` 以及异常类型的拷贝/移动构造不得失败;析构和 `swap` 应当是 `noexcept`。
40
76
 
41
- `main()` 是异常边界:库让合同异常向上传播,入口统一翻译成用户可读的消息和非零退出码。
77
+ E.30:不要写异常规范(`throw(...)` 之类);C++11 之后用 `noexcept` 表达「不抛」。需要无异常构建时,由项目单独决定,不要把这套异常指导改写成两面都对的模糊话。
78
+
79
+ ## 程序入口
80
+
81
+ 库让异常向上传播,入口是翻译边界:捕获 `std::exception`,输出可理解的诊断,返回项目约定的非零退出码。退出码的具体取值属于项目合同,本节不规定 0/1/2。
42
82
 
43
83
  ```cpp
44
84
  int main() {
@@ -51,29 +91,26 @@ int main() {
51
91
  }
52
92
  ```
53
93
 
54
- - 消息面向用户,不打印栈、不暴露内部路径;调试信息写日志或测试输出。
55
- - 退出码:成功 `0`;可预期失败 `1`;用法错误可以用 `2`。同一个程序保持一致。
56
- - 不要在每个函数里 `try/catch` 后打印;让异常走到边界,中间层只在能补偿时捕获。
57
-
58
- ## 消息文案与日志
94
+ 消息要能让调用者定位操作和关键值;不强制固定消息模板,也不要求库代码打印日志。`std::format` 只是构造 `what()` 或边界消息的一种方式。C++20 及更早没有 `std::print`;默认 C++23 且标准库支持时,可在程序边界用 `std::print`,库代码仍只组合消息、不直接打印。
59
95
 
60
- 异常消息是给调用者和用户看的诊断,不是日志流:
96
+ ## 声明注释
61
97
 
62
- - 格式固定为 `<ApiName>: <主体> <原因>`,例如 `GetTaskState: id=3 超出 [0, 3)`。
63
- - 写操作名和关键值,不写「出错了」「非法」这类没有信息量的词。
64
- - 不引入日志框架,不在库代码里打印;需要诊断上下文时由边界层记录,异常本身只携带消息。
65
- - 同一条消息在测试里断言稳定的前缀,不断言完整路径或平台相关文本。
98
+ 在公共声明的注释里写清:什么条件下抛、抛哪一类、调用方如何避免。实现文件里的内部异常不必逐条写进公共头。
66
99
 
67
- ## 与 `std::format` 的关系
68
-
69
- - 异常消息、用户提示、测试断言消息优先 `std::format`。
70
- - C++20 没有 `std::print`;格式化后写入流。
71
- - 格式串里的类型要匹配;不要把用户输入直接拼进格式串(格式串必须是可信字面量)。
100
+ ```cpp
101
+ /// 读取配置文件。
102
+ /// @throws FileReadError 文件不存在或无法读取。
103
+ /// @throws ParseError 内容不是合法配置。
104
+ Config ReadConfig(const std::filesystem::path& path);
105
+ ```
72
106
 
73
107
  ## 检查清单
74
108
 
75
- - [ ] 每个抛异常的函数在声明注释里写了条件和类型。
76
- - [ ] 可预期失败没有用异常。
77
- - [ ] 捕获按 `const` 引用,未吞掉未知异常。
78
- - [ ] 析构函数不抛。
79
- - [ ] 入口有统一捕获,消息用 `std::format`,退出码一致。
109
+ - [ ] 错误策略在接口设计时已确定,同一层没有混用错误码和异常。
110
+ - [ ] 构造函数失败时抛异常,没有留下半初始化对象。
111
+ - [ ] 异常类型有语义,没有抛内建类型、字符串或裸指针。
112
+ - [ ] 按值抛出、按 `const` 引用捕获;重抛用 `throw;`。
113
+ - [ ] 析构、`swap`、异常类型的拷贝/移动不抛。
114
+ - [ ] 没有用异常做正常分支,也没有在每个函数里捕获所有异常。
115
+ - [ ] 入口有统一捕获,消息可定位,退出码符合项目合同。
116
+ - [ ] 公共声明的 `@throws` 与实际抛出类型一致。
@@ -6,15 +6,15 @@
6
6
  | --- | --- | --- |
7
7
  | declaration | 声明 | 告诉编译器名字和类型,不分配存储 |
8
8
  | definition | 定义 | 分配存储或给出函数体 |
9
- | translation unit | 翻译单元 | 一个 `.cpp` 加上它包含的头,预处理后的整体 |
9
+ | translation unit | 翻译单元 | 一个 `.cc` 加上它包含的头,预处理后的整体 |
10
10
  | header | 头文件 | `.h` / `.hpp`,放声明和可内联实现 |
11
- | source file | 源文件 | `.cpp`,放普通函数体 |
11
+ | source file | 源文件 | `.cc`,放普通函数体 |
12
12
  | target | 目标 | CMake 的构建单元,如 `app`、`greeting` |
13
13
  | observable behavior | 可观察行为 | 用户或测试能看到的输出、返回值、退出码、文件内容 |
14
14
  | boundary | 边界 | 库与程序、模块与模块之间的职责分界 |
15
15
  | invariant | 不变量 | 对象在任何公共操作后都必须成立的条件 |
16
16
  | contract | 契约 | 调用方必须满足的前置条件,以及被调用方承诺的结果 |
17
- | precondition | 前置条件 | 调用前必须为真;违反通常抛合同异常 |
17
+ | precondition | 前置条件 | 调用前必须为真;违反时函数通常抛出声明过的异常 |
18
18
  | postcondition | 后置条件 | 调用成功后保证为真 |
19
19
  | exception safety | 异常安全 | 抛出后对象仍有效、资源不泄漏的保证 |
20
20
  | ownership | 所有权 | 谁负责释放资源;`unique_ptr` 表达独占 |
@@ -46,7 +46,7 @@
46
46
  | qualified name | 限定名 | 带命名空间或类前缀的名字,如 `inventory::Item` |
47
47
  | namespace | 命名空间 | 给名字分组、避免冲突的作用域 |
48
48
  | forward declaration | 前向声明 | 只声明名字不给出定义,减少头文件依赖 |
49
- | include guard | 包含守卫 | 防止头文件重复展开;本项目用 `#pragma once` |
49
+ | include guard | 包含守卫 | 防止头文件重复展开;本项目用 `#define` 守卫,名字由源码树全路径生成 |
50
50
  | ODR | 单一定义规则 | 一个实体在整个程序中至多一个定义,内联实体例外 |
51
51
  | overload | 重载 | 同名但参数不同的函数 |
52
52
  | template | 模板 | 按类型或值参数生成代码的机制 |
@@ -83,6 +83,6 @@
83
83
 
84
84
  - **声明 vs 定义**:头文件里多是声明;函数体、全局变量初始化是定义。
85
85
  - **参数 vs 实参**:声明里叫参数(parameter),调用点叫实参(argument);中文口语都叫参数时,写文档要区分。
86
- - **接口 vs 实现**:公共头是接口,`.cpp` 是实现;不要用「接口」指整个类。
87
- - **异常 vs 错误**:异常是语言机制;错误是结果。可预期错误用返回值,不叫「异常」。
86
+ - **接口 vs 实现**:公共头是接口,`.cc` 是实现;不要用「接口」指整个类。
87
+ - **异常 vs 错误**:异常是语言机制,错误是结果;是否用异常表达错误由接口合同和 invariant 决定,不能把两者当同义词。规则见 `references/exceptions.md`。
88
88
  - **格式化 vs 排版**:`std::format` 组合消息;clang-format 排版源码。不要混用「格式化」指代两者。
@@ -1,38 +1,43 @@
1
1
  # Google C++ 风格:本技能采用的版本
2
2
 
3
- 以 [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html) 为基线,按 C++20 工程的需要做了少量书面偏离。偏离必须写在这里,不能只存在于某个人的习惯里。
3
+ 以 [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html) 为基线。除「异常政策」和「新工程默认 C++23」两处明确偏离外,排版、命名、头文件、包含顺序、类型和注释都按 Google 原文执行;本地习惯不能悄悄覆盖它。
4
4
 
5
5
  ## 语言版本
6
6
 
7
- - 目标标准是 C++20。不使用 C++23 的 `std::print`、`std::expected`、`std::stacktrace` 等设施。
7
+ - 上游 [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html) 仍写 “Currently, code should target C++20, i.e., should not use C++23 features”。本工作流把**新工程默认标准**定为 C++23,并把标准放进项目声明 `environment.json` 的 `standard`(`c++11`–`c++23`)。这是项目级语言版本覆盖,不改变 Google 的排版、命名、头文件和包含顺序规则。
8
+ - `standard=c++23` 时才使用 `std::print`、`std::expected`、`std::stacktrace` 等 C++23 设施;`c++20` 及更早的项目不得使用。C++11/14/17 项目用各自可用的替代写法,不能因为本机编译器支持就偷偷升级。
9
+ - Google/LLVM 的公开基线仍集中在 C++17–C++20;默认 C++23 是本工作流的产品选择,不声称 Google 已改口。标准由项目声明和 CMake target 决定,同一仓库不要混用多个标准。
8
10
  - 不使用编译器扩展;需要扩展时先停下来,改成标准写法。
9
11
  - 一个工程只用一个编译器配置;不要在同一棵源码树上混用 Clang 与 GCC 的扩展。
10
12
 
11
13
  ## 头文件
12
14
 
13
- - 每个头文件用 `#pragma once`。这是对 Google「使用 `#define` 守卫」的偏离;项目统一用前者,避免守卫名漂移。
15
+ - 每个头文件用 `#define` 守卫,符号由项目源码树全路径生成:`<PROJECT>_<PATH>_<FILE>_H_`,例如 `FOO_BAR_BAZ_H_`。不要用 `#pragma once`。
14
16
  - 头文件必须自包含:自己用到的类型、宏、标准库头都直接包含,不依赖调用方的包含顺序。
15
- - `.cpp` 第一个包含自己的配对头,然后直接包含它用到的每个标准库头;不要依赖公共头替它转包含。
16
- - 包含顺序:相关头文件 → C 系统头 → C++ 标准库 → 其他库 → 本项目。每组之间空一行,组内按字母序;`clang-format` 的 `IncludeCategories` 负责维持。
17
- - 本项目头文件用引号加裸文件名:`#include "inventory.h"`,路径相对于公共 include 根。不要写 `#include "component/inventory.h"`,也不要用尖括号包含项目头。
18
- - 公共头文件直接放在组件的 `include/` 根下;不要为了「整齐」再加一层命名空间目录。
17
+ - `.cc` 第一个包含自己的配对头,然后按下面的顺序包含。
18
+ - 包含顺序:配对头 → C 系统头 → C++ 标准库头 → 其他库头 → 本项目头。每组之间空一行,组内按字母序;不要依赖 `clang-format` 的 `IncludeCategories` 自定义分组,本项目不再覆盖 Google 预设。
19
+ - 本项目头用引号,路径从项目源码根开始,例如 `#include "base/logging.h"`。构建时把源码根加入 include 搜索路径,不要只把组件的 `include/` 设为根再写裸文件名。
20
+ - 公共头文件直接放在组件的 `include/` 下;不要为了「整齐」再加一层命名空间目录。
19
21
  - 跨文件共享的常量用 `inline constexpr`,放在命名空间里并带 `k` 前缀;头文件里不要定义非 inline 的普通全局变量。
20
- - 头文件里只放模板、`constexpr`、类内联小函数和声明;普通函数的函数体放 `.cpp`。
22
+ - 头文件里只放模板、`constexpr`、类内联小函数和声明;普通函数的函数体放 `.cc`。
23
+ - 头文件里不要用匿名命名空间或 `static` 隐藏定义;内部链接的实现放 `.cc`。
21
24
 
22
25
  ## 命名
23
26
 
24
27
  | 种类 | 规则 | 例子 |
25
28
  | --- | --- | --- |
26
- | 类型、类、结构、枚举、别名 | PascalCase | `TaskTable`, `OrderResult`, `TaskState` |
29
+ | 类型、类、结构、枚举、别名、模板参数 | PascalCase | `TaskTable`, `OrderResult`, `TaskState` |
27
30
  | 函数、成员函数 | PascalCase | `Contains`, `GetCurrentOrder` |
31
+ | 访问器、修改器 | Google 允许 PascalCase 或 snake_case | `Count()` / `count()`、`SetCount()` / `set_count()`;同一 API 只选一种,不要混用 |
28
32
  | 变量、参数、局部变量 | snake_case | `task_count`, `current_order` |
29
33
  | 类数据成员 | snake_case + 尾随 `_` | `tasks_`, `orders_` |
34
+ | 结构体数据成员 | snake_case,无尾随下划线 | `name`, `priority` |
30
35
  | 常量、枚举子 | `k` + PascalCase | `kTaskCount`, `Order::kDraft` |
31
36
  | 命名空间 | snake_case | `taskflow`, `warehouse` |
32
37
  | 宏 | 尽量不用;必须用时全大写并带项目前缀 | `CPP_WORKFLOW_ASSERT` |
33
38
 
34
39
  - 一个 API 不混用两套命名。对外函数用 PascalCase,成员变量用 snake_case,不要出现 `get_task` 与 `GetTask` 并存。
35
- - 查询用 `Get` + PascalCase:`GetCurrentOrder()`、`GetHistory()`;不要用成员名直接当访问器(`current_order()`)。
40
+ - 查询函数遵循同一 API 选定的访问器约定:Google 允许 `Count()` 或 `count()`,也允许 `GetCurrentOrder()`;同一 API 内不要混用两套。
36
41
  - 谓词用 `Is` / `Has`:`Contains()`、`HasStock()`。返回 `bool` 但语义不是判断的函数,换成领域动词。
37
42
  - 尝试用 `Try`:`TryReserve()` 返回结果类型;确定性动作直接用领域动词:`CompleteTask()`、`Reset()`。
38
43
  - 映射函数的名字要含来源与目标角色:`OrderToReceipt()`、`TaskTableToString()`;禁止裸 `To()` / `Of()`。
@@ -45,22 +50,23 @@
45
50
 
46
51
  - 纯数据聚合用 `struct`;有不变量的类型用 `class`,不变量在构造函数和公共成员函数里维持。
47
52
  - 单参数构造函数写 `explicit`;零参数构造函数不写。
48
- - 默认成员初始化写在声明处(NSDMI),不要在构造函数初始化列表里重复。容器和有默认构造的子对象不写 `{}`。
53
+ - 默认成员初始化写在声明处(NSDMI),不要在构造函数初始化列表里重复。
49
54
  - 默认用花括号初始化:`int count{};`、`std::vector<int> values{1, 2, 3};`。只有需要对比、`auto` 推导或赋值语义时用 `=`;不要用 `()` 初始化普通对象。
50
- - 类声明顺序:类型与别名 → 静态常量 → 工厂 → 构造/赋值/析构 → 其他函数 → 数据成员。
51
- - 用户声明的拷贝/移动/析构函数在头文件声明、在 `.cpp` 定义;只有零参数且默认实现足够时才写 `= default`。
55
+ - 类声明顺序:public → protected → private;类型与别名 → 静态常量 → 工厂 → 构造/赋值/析构 → 其他函数 → 数据成员。
56
+ - 用户声明的拷贝/移动/析构函数在头文件声明、在 `.cc` 定义;只有零参数且默认实现足够时才写 `= default`。
52
57
  - 查询成员函数标尾部 `const`;返回结果对象的 API 加 `[[nodiscard]]`,不要给所有非 `void` 函数机械添加。
53
58
  - 所有权优先 `std::unique_ptr`;没有共享所有权的理由时不用 `std::shared_ptr`。禁止裸 `new` / `delete`。
54
59
  - 禁止 C 风格强制转换;用 `static_cast`、`const_cast`、`reinterpret_cast`、`std::bit_cast`,并让 `clang-tidy` 的 `google-readability-casting` 兜底。
55
- - 生产代码不用匿名命名空间隐藏实现;私有实现放 `.cpp` 的 `static` 函数或独立组件,测试通过公共头使用。
60
+ - `.cc` 里不需要外部引用的定义放匿名命名空间或声明 `static`;不要在生产 `.cc` 里为了「整洁」强行用 `static`,按是否需要内部链接决定。
56
61
 
57
62
  ## 函数
58
63
 
59
64
  - 优先返回值,不用输出参数:返回 `std::string`、`std::vector<T>`、结果结构体。
60
65
  - 已有成员按 `const` 引用返回,例如 `GetHistory()`;不要返回内部容器的可变引用。
61
- - 函数保持短小;超过约 40 行时先拆出有名字的步骤,而不是加注释分节。
66
+ - 函数保持短小;没有硬上限,超过约 40 行时先拆出有名字的步骤,而不是加注释分节。
67
+ - 头文件里只把大约 10 行以内、确实适合内联的短函数就地定义;更长或需要完整定义的函数放 `.cc`,避免头文件膨胀和重复编译。
62
68
  - 虚函数不用默认参数;默认参数只用于非虚的便利接口。
63
- - 除 lambda 和必须推导的模板外,不写尾置返回类型。
69
+ - 尾置返回类型只在普通语法不可行或明显更难读时使用,例如返回类型依赖参数;不要为了风格统一全面改写。
64
70
  - 只对自己的类型重载运算符;不重载 `&&`、`||`、`,`、一元 `&`;不定义用户字面量。
65
71
 
66
72
  ## 表达式与整数
@@ -68,22 +74,22 @@
68
74
  - 比较不写 Yoda 形式:`x == 0`、`task == TaskState::kPending`;不要写 `0 == x`、`nullptr == p`。
69
75
  - 半开区间写成两个「变量在左」的比较:`id >= 0 && id < kTaskCount`;不要写 `0 <= id`。
70
76
  - 混合优先级算术加显式括号,消除 `readability-math-missing-parentheses`。
71
- - 业务编号、尺寸用 `std::int32_t`;容器长度和索引用 `std::size_t`。越界由边界检查函数统一判断,负值和上界都算非法。
72
- - 封闭状态码可以用 `enum class Name : std::uint8_t`,只为缩小存储;普通枚举用默认底层类型。枚举值总是带作用域:`OrderResult::kSuccess`。
77
+ - 内建整数默认用 `int`;需要确定宽度时用 `<cstdint>` 的 `std::int32_t`、`std::int64_t` 等(可省略 `std::` 前缀),可能到 2^31 的值用 64 位;`std::size_t` / `std::ptrdiff_t` 只用于大小和指针差。不要用无符号类型表达「这个值不会为负」。
78
+ - 封闭状态码用 `enum class`;底层类型默认 `int`,只有确实需要缩小存储时才写固定底层类型并说明理由。枚举值总是带作用域:`OrderResult::kSuccess`。
73
79
  - 枚举的尾随注释要么全写要么全不写;语义结果写在最前,`///<` 对齐。
74
80
  - 浮点不用 `==` 比较计算结果;按量纲给容差,金额用最小货币单位的整数。
75
81
  - 数值宽度、符号、枚举、强类型、`const` 成员和下标访问的完整规则见 `references/types.md`;初始化、RAII、移动和模板见 `references/language.md`。
76
82
 
77
83
  ## 异常
78
84
 
79
- Google 原文说「我们不使用 C++ 异常」。本技能按使用方要求做书面偏离:**可以在合适的地方使用异常**,但只用于合同违反和程序边界,不用异常表达可预期的业务失败。完整规则见 `references/exceptions.md`。
85
+ Google 原文不使用 C++ 异常;本工作流只在开发阶段的源码指导中按 `references/exceptions.md` 采用 C++ Core Guidelines 的 Error handling,插件运行时不编译、不执行这套策略。其余条目仍以 Google 原文为准。
80
86
 
81
87
  ## 输出与格式化
82
88
 
83
89
  - 组合消息以 `std::format` 为主:`std::format("任务 {} 进入状态 {}", task, state)`。
84
- - C++20 没有 `std::print`;格式化结果写到 `std::cout` / `std::cerr` / `std::ostringstream`。
90
+ - `std::print` 只在 C++23 且标准库支持时使用,通常放在程序边界;库代码仍用 `std::format` 组合消息。C++20 及更早把格式化结果写到 `std::cout` / `std::cerr` / `std::ostringstream`。
85
91
  - 简单字面量输出可以直接用 `std::cout << "..."`;需要拼接变量、对齐、数值格式时用 `std::format`。
86
- - 列宽上限 100(`ColumnLimit: 100`),由 `clang-format` 执行;不要手工调换行。
92
+ - 列宽上限 80,每级缩进 2 个空格,代码不用 tab;这些都来自 Google 预设,不要在 `.clang-format` 里覆盖。
87
93
  - 浮点输出固定精度,整数不依赖隐式格式;`std::format` 的格式串写全。
88
94
 
89
95
  ## 注释
@@ -91,19 +97,3 @@ Google 原文说「我们不使用 C++ 异常」。本技能按使用方要求
91
97
  - 注释写意图、契约、取舍,不复述代码。
92
98
  - 文件头、章节横幅、声明注释、实现注释、配置注释的格式见 `references/comments.md`。
93
99
  - TODO 统一写 `// TODO: <阶段或 issue> - <要做什么>`;不要写 `@todo`、`FIXME` 或只有「参考」没有动作的占位。
94
-
95
- ## 对 Google 的书面偏离
96
-
97
- | 主题 | Google | 本项目 | 理由 |
98
- | --- | --- | --- | --- |
99
- | 头文件守卫 | `#define` 守卫 | `#pragma once` | 统一、无守卫名漂移;主流编译器都支持 |
100
- | 异常 | 不使用 | 合同违反/程序边界可用 | 用户明确要求「能使用异常的地方使用合理的异常」 |
101
- | 访问器 | 可用 snake_case | `Get` + PascalCase | 与函数命名统一,调用点可搜索 |
102
- | 列宽 | 80 | 100 | 现代屏幕与 C++ 模板可读性;团队统一 |
103
- | 项目头包含 | 相对源根 | 公共 include 根 + 裸文件名 | 公共头直接放 `include/`,减少路径噪音 |
104
- | 源文件扩展名 | `.cc` | `.h` / `.cpp` | 现有工程、编辑器和材料统一用 `.cpp` |
105
- | 风格检查器 | `cpplint.py` | `clang-format` + `clang-tidy` | 与编译数据库集成,可自动修复;不再维护 Python 检查器 |
106
- | 枚举底层类型 | 默认 | 封闭状态码可 `: std::uint8_t` | 缩小存储,边界明确 |
107
- | 整数宽度 | `int` / 实现定义宽度 | 业务编号 `std::int32_t`,容器下标 `std::size_t`,状态码 `std::uint8_t` | 协议、布局和负编号需要可预测宽度 |
108
-
109
- 偏离之外仍以 Google 原文为准;新增偏离必须先写进本表,再改配置或代码。
@@ -1,12 +1,12 @@
1
1
  # 语言基础与对象语义
2
2
 
3
- 这里只写“换一台机器、换一个编译器仍然成立”的 C++20 语言规则。项目专属类型、协议字段和构建目录不在本文件。
3
+ 这里只写“换一台机器、换一个编译器仍然成立”的 C++11–C++23 语言规则;示例默认 C++23,C++11/14/17/20 项目按声明降级。项目专属类型、协议字段和构建目录不在本文件。
4
4
 
5
5
  ## 值与对象
6
6
 
7
7
  - 类型决定允许的操作和表示;对象是某类型的一块存储;值是可以复制、比较、传递的内容。
8
8
  - 变量有名字、类型、存储期和作用域。局部变量在离开作用域时销毁;`static` 局部变量在首次执行到声明时初始化,程序结束时销毁。
9
- - 声明让名字可见,定义分配存储或给出函数体。一个实体可以声明多次,但只能定义一次;头文件只放声明,定义放 `.cpp`。
9
+ - 声明让名字可见,定义分配存储或给出函数体。一个实体可以声明多次,但只能定义一次;头文件只放声明,定义放 `.cc`。
10
10
  - 引用是已有对象的别名,必须初始化,不能重新绑定;指针可以为空,也可以改指向。接口优先用引用表达“必须存在”,用指针表达“可以不存在”。
11
11
 
12
12
  ## 初始化与列表初始化
@@ -72,7 +72,7 @@ auto total = 0.0; // auto 只能用 =,不能用 {}
72
72
  - `struct` 用于公开数据的聚合;`class` 用于维护不变量、隐藏表示。两者只差默认访问权限,按语义选。
73
73
  - 数据成员默认私有;需要暴露只读视图时提供 `const` 成员函数,不返回可变内部引用。
74
74
  - 单参数构造函数用 `explicit`;构造函数失败时抛异常,不留下半初始化对象。
75
- - 特殊成员(构造、析构、拷贝、移动)在头文件声明,在 `.cpp` 定义;只有默认实现足够且零参数时才写 `= default`。
75
+ - 特殊成员(构造、析构、拷贝、移动)在头文件声明,在 `.cc` 定义;只有默认实现足够且零参数时才写 `= default`。
76
76
  - 定义了析构函数、拷贝构造或拷贝赋值之一时,认真考虑其余特殊成员;用 `= delete` 明确禁止不需要的操作。
77
77
  - 多态基类析构函数必须是 `virtual` 或 `protected`;否则通过基类指针删除派生对象是未定义行为。
78
78
  - 不在构造函数里调用虚函数;此时派生部分还没构造完。
@@ -89,7 +89,7 @@ auto total = 0.0; // auto 只能用 =,不能用 {}
89
89
 
90
90
  ## 模板、概念与 CTAD
91
91
 
92
- - 模板定义放头文件;`.cpp` 里的模板实例化只对当前翻译单元可见。
92
+ - 模板定义放头文件;`.cc` 里的模板实例化只对当前翻译单元可见。
93
93
  - C++20 用概念约束模板参数,把错误从实例化深处提前到调用点:
94
94
 
95
95
  ```cpp
@@ -7,15 +7,15 @@
7
7
  ```text
8
8
  <component>/
9
9
  include/
10
- <name>.h 公共接口,裸文件名包含
10
+ <name>.h 公共接口,从源码根写完整路径包含
11
11
  src/
12
- <name>.cpp 实现,可以包含私有头
12
+ <name>.cc 实现,可以包含私有头
13
13
  <detail>.h 私有头,只被本组件 src 包含
14
14
  tests/
15
- <name>_test.cpp 通过公共接口测试
15
+ <name>_test.cc 通过公共接口测试
16
16
  ```
17
17
 
18
- - 公共头直接放 `include/` 根下;需要分组时用子目录,但包含路径仍从公共根写起。
18
+ - 公共头直接放 `include/` 下;包含时从仓库根写完整路径,例如 `#include "task_list/include/task_list.h"`,不要写裸文件名或 `../`。
19
19
  - 私有头放 `src/`,不进入公共 include 根;测试不得包含私有头。
20
20
  - 一个组件一个职责;组件名用 snake_case,和命名空间一致。
21
21
  - 组件之间通过公共头依赖;不要用 `../other/src/private.h` 跨组件包含私有实现。
@@ -27,21 +27,21 @@ app/
27
27
  include/
28
28
  cli.h 输入输出与参数解析的私有接口
29
29
  src/
30
- cli.cpp 终端交互
31
- main.cpp 只做装配:构造依赖、调用 Run、翻译退出码
30
+ cli.cc 终端交互
31
+ main.cc 只做装配:构造依赖、调用 Run、翻译退出码
32
32
  ```
33
33
 
34
- - `main.cpp` 不写业务规则;它只解析参数、打开流、调用库、捕获边界异常。
34
+ - `main.cc` 不写业务规则;它只解析参数、打开流、调用库、捕获边界异常。
35
35
  - 终端输入输出集中在 `app/`;库不包含 `<iostream>`,也不打印。
36
- - 只有一个程序时 `app/` 可以退化成根 `src/main.cpp`;一旦需要测试交互逻辑,再拆 `cli.h` / `cli.cpp`。
36
+ - 只有一个程序时 `app/` 可以退化成根 `src/main.cc`;一旦需要测试交互逻辑,再拆 `cli.h` / `cli.cc`。
37
37
  - 程序可以依赖库;库不能反向依赖程序。
38
38
 
39
39
  ## 单文件与多文件
40
40
 
41
41
  | 形态 | 何时用 | 布局 |
42
42
  | --- | --- | --- |
43
- | 单文件 | 一次性脚本、极小示例 | `src/main.cpp`,辅助函数放匿名命名空间 |
44
- | 多文件单程序 | 逻辑开始分块,但还没有复用方 | `src/*.cpp` + `include/*.h`,一个 `app` target |
43
+ | 单文件 | 一次性脚本、极小示例 | `src/main.cc`,辅助函数放匿名命名空间 |
44
+ | 多文件单程序 | 逻辑开始分块,但还没有复用方 | `src/*.cc` + `include/*.h`,一个 `app` target |
45
45
  | 静态库 + 程序 | 需要测试、需要被多个程序链接 | `<component>/` + `app/`,库 target + `app` target |
46
46
  | 动态库 + 程序 | 需要运行时替换实现、插件式加载 | 同上,库 target 改 `SHARED`,注意运行时库路径 |
47
47
 
@@ -49,15 +49,15 @@ app/
49
49
 
50
50
  ## 头文件契约
51
51
 
52
- - 公共头自包含:单独编译一个只包含它的 `.cpp` 必须通过。
52
+ - 公共头自包含:单独编译一个只包含它的 `.cc` 必须通过。
53
53
  - 头文件不定义非 inline 的普通全局变量;共享常量用 `inline constexpr`。
54
54
  - 头文件不写 `using namespace`;不污染全局命名空间。
55
- - 模板和 `constexpr` 的实现可以留在头文件;普通函数体放 `.cpp`。
56
- - 头文件不包含 `<iostream>`;需要流时在 `.cpp` 或程序层包含。
55
+ - 模板和 `constexpr` 的实现可以留在头文件;普通函数体放 `.cc`。
56
+ - 头文件不包含 `<iostream>`;需要流时在 `.cc` 或程序层包含。
57
57
 
58
58
  ## 测试
59
59
 
60
- - 测试文件放组件 `tests/` 或顶层 `tests/`,命名 `<被测>_test.cpp`。
60
+ - 测试文件放组件 `tests/` 或顶层 `tests/`,命名 `<被测>_test.cc`。
61
61
  - 测试只包含公共头;需要测私有逻辑时,先判断它是否应该成为公共行为。
62
62
  - 一个测试文件对应一个行为组;测试名描述行为,不描述实现。
63
63
  - 测试不复制实现,不依赖执行顺序,不读写固定绝对路径。
@@ -10,11 +10,12 @@
10
10
  | 函数 / 成员函数 | PascalCase | `Contains`, `GetTaskState` | `contains`, `gettaskstate` |
11
11
  | 变量 / 参数 | snake_case | `task_count`, `order` | `TaskCount`, `o` |
12
12
  | 类成员 | snake_case + `_` | `tasks_`, `orders_` | `m_tasks`, `tasks` |
13
+ | 结构体成员 | snake_case,无尾随 `_` | `name`, `priority` | `name_`, `priority_` |
13
14
  | 常量 / 枚举子 | `k` + PascalCase | `kTaskCount`, `Order::kDraft` | `TASK_COUNT`, `Draft` |
14
15
  | 命名空间 | snake_case | `taskflow`, `warehouse` | `Taskflow`, `Warehouse` |
15
16
  | 宏 | 全大写 + 项目前缀,尽量不用 | `CPP_WORKFLOW_ASSERT` | `ASSERT` |
16
17
 
17
- - 文件名用 snake_case:`order_result.h`、`task_table.cpp`;测试文件 `task_table_test.cpp`。
18
+ - 文件名用 snake_case:`order_result.h`、`task_table.cc`;测试文件 `task_table_test.cc`。
18
19
  - 布尔变量用 `is_` / `has_` / `can_` 开头:`is_valid`、`has_stock`;不要用 `flag`、`status`。
19
20
  - 容器用复数:`tasks`、`orders`;单个元素用单数。
20
21
  - 缩写按单词处理:`url`、`id`、`http`,不要写 `URL` 混进 snake_case;类型名里可以 `HttpClient`。
@@ -24,25 +25,33 @@
24
25
 
25
26
  | 角色 | 前缀 / 动词 | 例子 | 说明 |
26
27
  | --- | --- | --- | --- |
27
- | 查询 | `Get` | `GetCurrentOrder()` | 只读、不改变状态;返回成员时按 `const` 引用 |
28
+ | 查询 | `Get` 或访问器风格 | `GetCurrentOrder()`、`CurrentOrder()` | 只读、不改变状态;同一 API 选一种 |
28
29
  | 判断 | `Is` / `Has` / `Can` | `Contains()`, `HasStock()` | 返回 `bool`,不抛异常 |
29
30
  | 查找 | `Find` | `FindTask()` | 找不到是正常结果,返回 `optional` 或迭代器,不抛 |
30
31
  | 计数 | `Count` | `CountPendingTasks()` | 返回数量,不修改状态 |
31
32
  | 尝试 | `Try` | `TryReserve()` | 可能失败但不抛异常,返回结果类型 |
32
- | 确定性动作 | 领域动词 | `CompleteTask()`, `Reset()` | 前置条件满足时一定成功;违反合同才抛 |
33
+ | 确定性动作 | 领域动词 | `CompleteTask()`, `Reset()` | 前置条件满足时一定成功;无法完成任务时抛 |
33
34
  | 计算 | 动词开头 | `ScoreOrder()` | 从输入算出结果,不修改外部状态 |
34
35
  | 映射 | `<来源>To<目标>` | `OrderToReceipt()` | 名字必须含两端角色 |
35
36
  | 读写 | `Read` / `Write` | `ReadTaskTable()`, `WriteTaskTable()` | 涉及 I/O 时用;不要在库核心用 |
36
- | 访问器 | `Get` + PascalCase | `GetHistory()` | 不用成员名直接当函数名 |
37
+ | 访问器 | `Get` + PascalCase,或 snake_case | `GetHistory()`、`history()` | Google 允许两种;同一 API 只选一种,不用成员名直接当函数名 |
37
38
 
38
39
  - 返回 `bool` 的 lambda 也用 `is_` / `has_` / `can_` 命名:`auto is_empty = [](const Order& order) { ... };`。
39
- - `Find` 不把“没找到”当异常;`Get` 在前置条件不满足时可以抛 `std::out_of_range`。两者不要互换。
40
+ - `Find` 不把“没找到”当异常;`Get` 在前置条件不满足时可以抛带领域语义的异常(如 `TaskIndexError`),而不是机械地抛 `std::out_of_range`。两者不要互换。
40
41
  - 查询返回多个值时用 `struct`,不要用三个 `GetXxx()` 加输出参数。
41
42
 
42
43
  - 禁止裸 `To()` / `Of()`:调用点看不出方向。
43
- - 禁止 `Set` + 成员名的伪访问器;需要修改时用领域动作(`CompleteTask` 而不是 `SetItem`)。
44
+ - 修改器用 `Set` + 属性名(`SetCount()` / `set_count()`)或领域动作(`CompleteTask()`);同一 API 只选一种,不要用 `SetItem` 这类暴露存储细节的名字。
44
45
  - `current_` 前缀只在有多个候选、且「当前」可能混淆时使用:`current_order_`。只有一个当前对象时不要加。
45
46
 
47
+ ## 谓词与查询命名
48
+
49
+ - 返回 `bool` 的查询用肯定式,读起来像一句断言:`IsEmpty()`、`HasStock()`、`CanReserve()`、`CompletesLine()`。
50
+ - `Get` 只用于取值,`Set` 只用于设值;不要用 `CheckLine()` 当布尔 getter。
51
+ - `Check` / `Validate` 留给会报告错误、产生诊断或可能失败的操作;它们不返回纯 `bool` 查询。
52
+ - 假设性查询在名字里写明条件:`CompletesLine(position, cell)` 读作「这一手会成线吗」;需要更强语气时用 `WouldCompleteLine()`。同一 API 只选一种,改名时同步声明、定义、调用点、测试和注释。
53
+ - 私有谓词同样用肯定式;不要用 `flag`、`status` 或 `check` 这类不说明判据的名字。
54
+
46
55
  ## 类型命名的判断顺序
47
56
 
48
57
  1. 纯数据、无 invariant、可以直接聚合初始化 → `struct`,名字是名词:`Order`, `TaskRecord`。
@@ -23,7 +23,7 @@
23
23
  - 状态用连续取值的 `enum class`:`kPending`、`kActive`、`kDone`。
24
24
  - 状态转换写成显式函数或规则对象,不用一串 `if` 直接改整数。
25
25
  - “动作是否合法”和“动作执行结果”分开:合法性查询返回 `bool` 或原因,执行返回结果对象。
26
- - 可预期失败(编号不存在、输入格式不对)用返回值表达;违反合同(下标越界、状态机不可能分支)才抛异常。
26
+ - 失败是调用方的正常结果(编号不存在、输入格式不对)时用返回值表达;函数无法完成自己的任务、或状态机进入不可能分支时抛异常,规则见 `references/exceptions.md`。
27
27
  - 结果对象携带足够信息让调用方决定下一步,不要只返回 `bool` 再让调用方猜原因。
28
28
 
29
29
  ## 规则核心与外围
@@ -44,6 +44,15 @@
44
44
 
45
45
  依赖方向从外向内;核心不知道适配层存在。
46
46
 
47
+ ## 谁回答这个问题
48
+
49
+ 判断一个操作该落在哪个类型,先看回答它需要哪些数据:
50
+
51
+ - 需要的数据在哪个类型里,就由那个类型回答(Information Expert)。
52
+ - 只有直接碰到内部表示的函数才做成成员;只组合公开查询的函数留在同一命名空间(C.4、C.5)。调用方不另写一份。
53
+ - 例外是虚函数、`=`、`()`、`[]`、`->` 这类必须为成员的运算符,以及同一组重载或链式调用里为了接口一致而留下的函数。使用例外时在声明旁写一句原因。
54
+ - 示例不指向任何仓库的类:一次落子仍由对局类型接收,因为它掌握当前参与者、历史和结果;它不重新计算棋盘已经能回答的占格、成线和终局。棋盘类型不包含参与者类型和结果类型。
55
+
47
56
  ## 边界的划分理由
48
57
 
49
58
  按“变化原因”划分边界,不按文件数量:
@@ -53,7 +62,7 @@
53
62
  - 平台变化(Windows/WSL/macOS 路径、动态库查找)放构建脚本或适配层,不进核心。
54
63
  - 测试需要替换的外部依赖(时钟、随机源、输入输出)通过接口注入,不在核心内部直接调用。
55
64
 
56
- 公共头文件只暴露稳定契约;实现细节放 `.cpp` 或私有头。一个组件一个 `CMakeLists.txt`,组件的 `include/` 是对外接口,`src/` 是实现。
65
+ 公共头文件只暴露稳定契约;实现细节放 `.cc` 或私有头。一个组件一个 `CMakeLists.txt`,组件的 `include/` 是对外接口,`src/` 是实现。
57
66
 
58
67
  ## 什么时候抽共享库
59
68
 
@@ -73,7 +82,7 @@
73
82
 
74
83
  - [ ] 领域值有明确类型,封闭集合用 `enum class`。
75
84
  - [ ] 规则核心不依赖终端、GUI、Web、Python 或当前环境。
76
- - [ ] 可预期失败用返回值,合同违反才抛异常。
85
+ - [ ] 失败是正常结果时用返回值,函数无法完成任务时抛异常;异常类型和捕获方式符合 `references/exceptions.md`。
77
86
  - [ ] 边界按变化原因划分,依赖方向从外向内。
78
87
  - [ ] 共享抽象有第二个真实使用方和明确理由。
79
88
  - [ ] 规则歧义已问清或记录了保守假设。
@@ -77,7 +77,7 @@ std::erase_if(values, [](int value) { return value % 2 == 0; });
77
77
  ## `std::format` 与输出
78
78
 
79
79
  - 组合消息优先 `std::format`;它返回 `std::string`,不直接写流。
80
- - 输出仍走 `std::cout` / `std::cerr` / 项目传入的 `std::ostream&`;C++20 没有 `std::print`。
80
+ - 输出仍走 `std::cout` / `std::cerr` / 项目传入的 `std::ostream&`;C++23 且库支持时程序边界可用 `std::print`,C++20 及更早没有。
81
81
  - 格式串里的 `{}` 按顺序取参数;需要位置时写 `{0}`、`{1}`。
82
82
  - 自定义类型要格式化时,特化 `std::formatter<T>`;不要在 `operator<<` 里塞业务格式。
83
83
  - 浮点默认格式可能与预期不同;需要固定精度时写 `{:.2f}`,需要科学计数时写 `{:e}`。
@@ -6,29 +6,30 @@
6
6
 
7
7
  ```text
8
8
  <component>/include/<name>.h 公共接口
9
- <component>/src/<name>.cpp 实现
9
+ <component>/src/<name>.cc 实现
10
10
  tests/ 测试
11
11
  app 只做参数、输入输出和装配
12
12
  ```
13
13
 
14
14
  ## 通用情况
15
15
 
16
- - 库 target 不依赖终端、GUI、网络脚本或全局单例;它只接收值、返回值、抛合同异常。
16
+ - 库 target 不依赖终端、GUI、网络脚本或全局单例;它只接收值、返回值、在无法完成任务时抛异常。
17
17
  - 程序 target 链接库,负责读参数、读标准输入、打印结果、把退出码翻译成进程退出码。
18
- - 公共头文件直接放在组件的 `include/` 根下,用裸文件名包含;不要写 `#include "<component>/x.h"`。
19
- - 依赖方向从程序指向库。库不反向包含程序的 `cli.h`,也不引用 `main.cpp`。
18
+ - 公共头文件直接放在组件的 `include/` 下,从仓库根写完整路径包含,例如 `#include "task_list/include/task_list.h"`;不要写裸文件名或 `../` 相对路径。
19
+ - 依赖方向从程序指向库。库不反向包含程序的 `cli.h`,也不引用 `main.cc`。
20
20
  - 测试放 `tests/`,测试 target 链接被测库;测试不复制库的实现。
21
21
  - 只有一个可执行文件、没有复用需求时,可以不分库;一旦出现第二个调用方或测试需要直接调用,再拆库。
22
22
  - 抽共享库前先确认第二个真实使用方和相同语义;只是“看起来会复用”不构成理由。抽象理由写进项目技能或设计注释。
23
23
  - 第 2 步不写 `CMakeLists.txt`。只决定边界,构建 target 留给 `building-cpp` 的定构建。
24
+ - 判断一个查询该成为成员还是同一命名空间的函数时,按 `references/project-design.md` 的「谁回答这个问题」:掌握数据的一方回答,只组合公开查询的函数不升级为成员。
24
25
 
25
26
  ## 情况
26
27
 
27
- **单文件程序**:`src/main.cpp` 可以有 `main()` 和私有辅助函数;一旦辅助逻辑需要测试,把它移到库组件。
28
+ **单文件程序**:`src/main.cc` 可以有 `main()` 和私有辅助函数;一旦辅助逻辑需要测试,把它移到库组件。
28
29
 
29
30
  **领域核心 + 外围**:规则核心(状态、合法性、结果)只依赖标准和领域类型;终端、GUI、Web、脚本都在适配层。依赖方向从外向内,核心不知道适配层存在。
30
31
 
31
- **库 + 程序**:库放在 `<component>/`,程序放在 `app/` 或根 `src/main.cpp`;程序只包含库的公共头。公共头只暴露稳定契约,实现细节放 `.cpp` 或私有头。
32
+ **库 + 程序**:库放在 `<component>/`,程序放在 `app/` 或根 `src/main.cc`;程序只包含库的公共头。公共头只暴露稳定契约,实现细节放 `.cc` 或私有头。
32
33
 
33
34
  **静态库 / 动态库**:接口相同,差别在链接方式和运行时库路径;都遵守「库不依赖终端」。选哪种由 `building-cpp` 的定构建决定。
34
35
 
@@ -37,4 +38,4 @@ app 只做参数、输入输出和装配
37
38
  ## 正反例指针
38
39
 
39
40
  - 终端输出被塞进库、程序反向依赖:`examples/structure.md`「边界」。
40
- - 公共头放在 `src/`、用带组件前缀的包含路径:`examples/structure.md`「头文件位置」。
41
+ - 公共头放在 `src/`、测试被迫包含私有头:`examples/structure.md`「头文件位置」。