@chengzhao-dev/dsh-cpp-workflow 0.0.0-stage → 0.1.1

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 (72) hide show
  1. package/LICENSE +21 -0
  2. package/README.en.md +106 -0
  3. package/README.md +105 -2
  4. package/cordis.patch.yml +11 -0
  5. package/icon.svg +7 -0
  6. package/lib/index.js +203 -0
  7. package/lib/workspace.js +168 -0
  8. package/locale/en.json +6 -0
  9. package/locale/zh.json +6 -0
  10. package/package.json +80 -4
  11. package/skills/building-cpp/SKILL.md +44 -0
  12. package/skills/building-cpp/assets/cmake/applying-warnings.cmake +10 -0
  13. package/skills/building-cpp/assets/cmake/fetching-googletest.cmake +15 -0
  14. package/skills/building-cpp/assets/cmake/setting-rpath.cmake +8 -0
  15. package/skills/building-cpp/assets/config/.clang-format +40 -0
  16. package/skills/building-cpp/assets/config/.clang-tidy +92 -0
  17. package/skills/building-cpp/assets/config/.clangd +12 -0
  18. package/skills/building-cpp/assets/config/.vscode/extensions.json +6 -0
  19. package/skills/building-cpp/assets/config/.vscode/settings.json +34 -0
  20. package/skills/building-cpp/assets/templates/projects/common/running-build.sh +152 -0
  21. package/skills/building-cpp/assets/templates/projects/multi/CMakeLists.txt +18 -0
  22. package/skills/building-cpp/assets/templates/projects/multi/include/greeting.h +10 -0
  23. package/skills/building-cpp/assets/templates/projects/multi/src/greeting.cpp +13 -0
  24. package/skills/building-cpp/assets/templates/projects/multi/src/main.cpp +8 -0
  25. package/skills/building-cpp/assets/templates/projects/shared-library/CMakeLists.txt +24 -0
  26. package/skills/building-cpp/assets/templates/projects/shared-library/greeting/CMakeLists.txt +5 -0
  27. package/skills/building-cpp/assets/templates/projects/shared-library/greeting/include/greeting.h +10 -0
  28. package/skills/building-cpp/assets/templates/projects/shared-library/greeting/src/greeting.cpp +13 -0
  29. package/skills/building-cpp/assets/templates/projects/shared-library/src/main.cpp +8 -0
  30. package/skills/building-cpp/assets/templates/projects/single/CMakeLists.txt +14 -0
  31. package/skills/building-cpp/assets/templates/projects/single/src/main.cpp +9 -0
  32. package/skills/building-cpp/assets/templates/projects/static-library/CMakeLists.txt +18 -0
  33. package/skills/building-cpp/assets/templates/projects/static-library/greeting/CMakeLists.txt +5 -0
  34. package/skills/building-cpp/assets/templates/projects/static-library/greeting/include/greeting.h +10 -0
  35. package/skills/building-cpp/assets/templates/projects/static-library/greeting/src/greeting.cpp +13 -0
  36. package/skills/building-cpp/assets/templates/projects/static-library/src/main.cpp +8 -0
  37. package/skills/building-cpp/examples/cmake.md +150 -0
  38. package/skills/building-cpp/examples/environment.md +125 -0
  39. package/skills/building-cpp/examples/tooling.md +155 -0
  40. package/skills/building-cpp/references/build-script.md +63 -0
  41. package/skills/building-cpp/references/cmake.md +168 -0
  42. package/skills/building-cpp/references/environment.md +104 -0
  43. package/skills/building-cpp/references/linking.md +101 -0
  44. package/skills/building-cpp/references/static-analysis.md +136 -0
  45. package/skills/building-cpp/scripts/checking-environment.mjs +498 -0
  46. package/skills/building-cpp/steps/01-build.md +76 -0
  47. package/skills/building-cpp/steps/02-tooling.md +41 -0
  48. package/skills/building-cpp/steps/03-check.md +60 -0
  49. package/skills/developing-cpp/SKILL.md +42 -0
  50. package/skills/developing-cpp/examples/comments.md +231 -0
  51. package/skills/developing-cpp/examples/exceptions.md +134 -0
  52. package/skills/developing-cpp/examples/naming.md +157 -0
  53. package/skills/developing-cpp/examples/structure.md +79 -0
  54. package/skills/developing-cpp/references/comments.md +123 -0
  55. package/skills/developing-cpp/references/declaration-order.md +97 -0
  56. package/skills/developing-cpp/references/exceptions.md +79 -0
  57. package/skills/developing-cpp/references/glossary.md +88 -0
  58. package/skills/developing-cpp/references/google-cpp-style.md +109 -0
  59. package/skills/developing-cpp/references/language.md +119 -0
  60. package/skills/developing-cpp/references/layout.md +73 -0
  61. package/skills/developing-cpp/references/naming.md +62 -0
  62. package/skills/developing-cpp/references/performance.md +82 -0
  63. package/skills/developing-cpp/references/project-design.md +79 -0
  64. package/skills/developing-cpp/references/standard-library.md +100 -0
  65. package/skills/developing-cpp/references/types.md +147 -0
  66. package/skills/developing-cpp/steps/01-behavior.md +36 -0
  67. package/skills/developing-cpp/steps/02-boundary.md +40 -0
  68. package/skills/developing-cpp/steps/03-declaration.md +32 -0
  69. package/skills/developing-cpp/steps/04-implementation.md +33 -0
  70. package/templates/cpp-workflow-project/SKILL.md +49 -0
  71. package/templates/cpp-workflow-project/environment.json +5 -0
  72. package/templates/cpp-workflow-project/stages/stage-table.md +4 -0
@@ -0,0 +1,42 @@
1
+ ---
2
+ name: developing-cpp
3
+ description: 编写或修改 C++20 源码、头文件、命名、异常与源码注释时使用;按定行为、定边界、定声明、定实现四步推进。不用于只改文档、Python、CMake、构建脚本或与 C++ 无关的文件。
4
+ metadata:
5
+ short-description: C++20 源码四步
6
+ ---
7
+
8
+ # developing-cpp
9
+
10
+ ## 适用场景
11
+
12
+ 管 `.h` / `.cpp` 里换一台机器也不会改的内容:可观察行为、库与程序的边界、声明、命名、异常和源码注释。`CMakeLists.txt`、`cmake/`、`running-build.sh`、`.clang-*`、`.vscode/` 和 `environment.json` 只开 `building-cpp`;两边都要动时,先做完第 2 步定边界,再开 `building-cpp`。`.qmd`、仓库 README、Python 和非 C++ 文档不打开本技能。
13
+
14
+ 存在项目技能 `cpp-workflow-project` 时先读它的 `SKILL.md`:工程名、库与程序的边界、阶段表、术语增补、入口命令。没有项目技能时门禁不会放出本技能,不要猜项目约定。
15
+
16
+ ## 两种走法
17
+
18
+ | 走法 | 什么时候用 | 怎么做 |
19
+ | --- | --- | --- |
20
+ | 增量修正 | 用户点了一个函数、一条诊断或一个文件 | 沿本技能步骤走,只改判为不合格的那一处,其余保持原样 |
21
+ | 整段重构 | 新工程、新阶段、用户没点名范围,或改动跨过库与程序的边界 | 同样四步;第 1、2 步先定删留,不动函数体;第 3、4 步才改代码 |
22
+
23
+ 共同不变量:不发明阶段表和用户请求里没有的行为、类型和命令;只改注释或格式时不改可观察行为;不覆盖已有源码;不安装工具链;代理侧不写 `wsl` 或 `bash -lc`。后一步发现前一步没做完,退回那一步,不在当前步补结构。拿不准先用增量修正;做到一半发现边界被打破,退回整段重构,从被打破的那一步重走。
24
+
25
+ ## 步骤
26
+
27
+ | 步 | 名称 | 文件 | 一句话 |
28
+ | --- | --- | --- | --- |
29
+ | 1 | 定行为 | `steps/01-behavior.md` | 把这一处要能观察到的结果写成验收句 |
30
+ | 2 | 定边界 | `steps/02-boundary.md` | 决定哪些放库、哪些放程序,依赖朝哪个方向 |
31
+ | 3 | 定声明 | `steps/03-declaration.md` | 写命名、声明顺序、头文件契约和声明注释 |
32
+ | 4 | 定实现 | `steps/04-implementation.md` | 写 `.cpp` 的操作顺序、分组和实现注释 |
33
+
34
+ 每步只打开同号的一个步骤文件。判不准才打开 `examples/` 里与该步对应的案例;`references/` 保存完整条文,只在排错、整目录重构,或步骤里明确指向某一条文时按需打开。
35
+
36
+ ## 完成判据
37
+
38
+ - 第 1 步的行为句能被用户或测试验收;有阶段表时只覆盖对应行,没有时只覆盖用户点名的行为。
39
+ - 第 2 步的库 target 不依赖终端、GUI 或脚本;程序 target 只做装配和输入输出;共享抽象有第二个真实使用方。
40
+ - 第 3 步的公共头文件自包含,命名、声明顺序和异常合同符合 `references/` 的条文。
41
+ - 第 4 步的实现只兑现声明;资源由 RAII 管理;可预期失败用返回值,合同违反才抛异常。
42
+ - 没有改 `CMakeLists.txt`、`.clang-*`、构建脚本,也没有把外部资料路径或项目专属类名写进通用条文。
@@ -0,0 +1,231 @@
1
+ # 正反例:注释
2
+
3
+ ## 文件头
4
+
5
+ **差**:
6
+
7
+ ```cpp
8
+ // task_list.cpp
9
+ // 作者:张三
10
+ // 创建:2024-01-01
11
+ // 修改:2024-02-01 修复 bug
12
+ ```
13
+
14
+ **好**:
15
+
16
+ ```cpp
17
+ //===----------------------------------------------------------------------===//
18
+ // TaskList 的存储、优先级排序与稳定顺序。
19
+ //
20
+ // 不处理终端输入输出;程序层负责解析「优先级 名称」并打印。
21
+ //===----------------------------------------------------------------------===//
22
+ ```
23
+
24
+ 文件头回答「这个文件负责什么、不负责什么」,不写作者和变更史。
25
+
26
+ ## 声明注释
27
+
28
+ **差**:
29
+
30
+ ```cpp
31
+ /// 获取任务。
32
+ /// @param index 索引
33
+ /// @return 任务
34
+ Task GetTask(int index) const;
35
+ ```
36
+
37
+ 问题:没说越界行为,没说返回的是拷贝还是引用。
38
+
39
+ **好**:
40
+
41
+ ```cpp
42
+ /// 返回 index 处的任务副本。
43
+ ///
44
+ /// @param index 任务下标,必须在 [0, tasks_.size()) 内
45
+ /// @return 该位置的任务
46
+ /// @throws std::out_of_range index 越界
47
+ [[nodiscard]] Task GetTask(int index) const;
48
+ ```
49
+
50
+ 契约完整:范围、返回、异常。
51
+
52
+ ## 实现注释
53
+
54
+ **差**:
55
+
56
+ ```cpp
57
+ // 如果 tasks_ 为空
58
+ if (tasks_.empty()) {
59
+ // 返回空
60
+ return {};
61
+ }
62
+ // 遍历 tasks_
63
+ for (const auto& task : tasks_) {
64
+ // 比较优先级
65
+ if (task.priority < best.priority) {
66
+ // 更新 best
67
+ best = task;
68
+ }
69
+ }
70
+ ```
71
+
72
+ 每行都在复述代码。
73
+
74
+ **好**:
75
+
76
+ ```cpp
77
+ // 空列表是合法状态,调用方不应被迫先判空。
78
+ if (tasks_.empty()) {
79
+ return {};
80
+ }
81
+
82
+ // 优先级相同时保留先加入的任务:用严格小于,不交换等值元素。
83
+ for (const auto& task : tasks_) {
84
+ if (task.priority < best.priority) {
85
+ best = task;
86
+ }
87
+ }
88
+ ```
89
+
90
+ 解释「为什么」和「稳定排序的取舍」,不翻译语句。
91
+
92
+ ## TODO
93
+
94
+ **差**:
95
+
96
+ ```cpp
97
+ // TODO: 参考 xxx
98
+ // FIXME: 这里可能有问题
99
+ // TODO(张三): 优化
100
+ ```
101
+
102
+ **好**:
103
+
104
+ ```cpp
105
+ // TODO: 阶段 2 - 支持按截止日期排序
106
+ // TODO: issue #42 - 把优先级解析抽成 TryParsePriority
107
+ ```
108
+
109
+ 有定位、有动作、能搜索。
110
+
111
+ ## 配置注释
112
+
113
+ **差**:
114
+
115
+ ```yaml
116
+ # 这是 clang-format 配置
117
+ # 详见某文档
118
+ ColumnLimit: 100
119
+ ```
120
+
121
+ **好**:
122
+
123
+ ```yaml
124
+ # 100 列:模板和格式化串在 80 列下换行过碎。
125
+ ColumnLimit: 100
126
+ ```
127
+
128
+ 解释取值理由,不写策略头,不指文档路径。
129
+
130
+ ## 横幅
131
+
132
+ **差**:
133
+
134
+ ```cpp
135
+ // ===== 函数 =====
136
+ void AddTask();
137
+ void RemoveTask();
138
+ // ===== 结束 =====
139
+ ```
140
+
141
+ **好**:
142
+
143
+ ```cpp
144
+ //===----------------------------------------------------------------------===//
145
+ // 查询
146
+ //===----------------------------------------------------------------------===//
147
+
148
+ [[nodiscard]] std::size_t Size() const;
149
+ [[nodiscard]] bool IsEmpty() const;
150
+ ```
151
+
152
+ 横幅是跳转锚点,不是装饰;一组函数一个横幅,单函数不加。
153
+
154
+ ## 枚举行尾注释
155
+
156
+ **差**:
157
+
158
+ ```cpp
159
+ enum class TaskStatus : std::uint8_t {
160
+ kPending, // 待处理
161
+ kActive, // 处理中
162
+ kBlocked, // 等待外部输入
163
+ kDone // 已完成
164
+ };
165
+ ```
166
+
167
+ 问题:四个成员都写等于没写,且把名字重复了一遍。
168
+
169
+ **好**:
170
+
171
+ ```cpp
172
+ enum class TaskStatus : std::uint8_t {
173
+ kPending, ///< 已登记,尚未开始
174
+ kActive, ///< 正在处理
175
+ kBlocked, ///< 等待外部输入,不能继续
176
+ kDone ///< 已完成,不再改动
177
+ };
178
+ ```
179
+
180
+ 若只有部分成员需要解释,同一枚举内要写就写全,或全不写;不要半套。语义结果写在最前,判定条件放冒号后。
181
+
182
+ ## 声明注释的完整契约
183
+
184
+ **差**:
185
+
186
+ ```cpp
187
+ /// 修改任务优先级。
188
+ /// @param id 任务编号
189
+ /// @param priority 优先级
190
+ void SetPriority(TaskId id, int priority);
191
+ ```
192
+
193
+ **好**:
194
+
195
+ ```cpp
196
+ /// 修改任务优先级。
197
+ ///
198
+ /// @param id 任务编号,必须已存在
199
+ /// @param priority 新优先级,必须在 [0, 9] 内
200
+ /// @throws std::out_of_range 编号不在当前任务表内
201
+ /// @throws std::invalid_argument priority 越界
202
+ void SetPriority(TaskId id, int priority);
203
+ ```
204
+
205
+ 只写调用者无法从签名推出来的内容:范围、所有权、失败方式。`@brief` 不写,第一行就是摘要。
206
+
207
+ ## 相邻注释
208
+
209
+ **差**:
210
+
211
+ ```cpp
212
+ // 这里先检查存在性
213
+ // 然后检查状态
214
+ // 最后写入
215
+ if (!Contains(id)) return false;
216
+ if (tasks_[Index(id)].status == TaskStatus::kDone) return false;
217
+ tasks_[Index(id)].status = TaskStatus::kActive;
218
+ return true;
219
+ ```
220
+
221
+ **好**:
222
+
223
+ ```cpp
224
+ // 先检查再写入:失败时不留下半完成状态。
225
+ if (!Contains(id)) return false;
226
+ if (tasks_[Index(id)].status == TaskStatus::kDone) return false;
227
+ tasks_[Index(id)].status = TaskStatus::kActive;
228
+ return true;
229
+ ```
230
+
231
+ 一段连续的实现注释只讲一个判断或一个取舍;不是逐行字幕。
@@ -0,0 +1,134 @@
1
+ # 正反例:异常
2
+
3
+ ## 抛还是返回
4
+
5
+ **差**:用异常表达可预期的解析失败。
6
+
7
+ ```cpp
8
+ Task ParseTask(std::string_view line); // 格式错误时抛 std::runtime_error
9
+ ```
10
+
11
+ 调用方被迫用 `try/catch` 做正常分支,且无法从类型看出失败。
12
+
13
+ **好**:结果类型表达可预期失败,合同违反才抛。
14
+
15
+ ```cpp
16
+ enum class ParseError { kEmptyLine, kMissingPriority, kBadPriority };
17
+
18
+ struct ParseResult {
19
+ std::optional<Task> task;
20
+ ParseError error = ParseError::kEmptyLine;
21
+ };
22
+
23
+ [[nodiscard]] ParseResult TryParseTask(std::string_view line);
24
+
25
+ /// @throws std::out_of_range index 越界
26
+ [[nodiscard]] Task GetTask(int index) const;
27
+ ```
28
+
29
+ `TryParseTask` 的失败是调用方每天都要处理的;`GetTask` 越界是调用方违反合同。
30
+
31
+ ## 消息格式
32
+
33
+ **差**:
34
+
35
+ ```cpp
36
+ throw std::out_of_range("bad index");
37
+ ```
38
+
39
+ **好**:
40
+
41
+ ```cpp
42
+ throw std::out_of_range{
43
+ std::format("GetTask: index={} 超出 [0, {})", index, tasks_.size())};
44
+ ```
45
+
46
+ 消息含操作、关键值、期望范围;用 `std::format`,不拼字符串。
47
+
48
+ ## 边界
49
+
50
+ **差**:每层都捕获并打印,异常被吞掉。
51
+
52
+ ```cpp
53
+ void AddTask(Task task) {
54
+ try {
55
+ tasks_.push_back(task);
56
+ } catch (const std::exception& error) {
57
+ std::cout << error.what() << "\n"; // 库打印,且吞掉异常
58
+ }
59
+ }
60
+ ```
61
+
62
+ **好**:库让异常传播,入口统一翻译。
63
+
64
+ ```cpp
65
+ // main.cpp
66
+ int main() {
67
+ try {
68
+ return Run(std::cin, std::cout);
69
+ } catch (const std::exception& error) {
70
+ std::cerr << std::format("错误: {}\n", error.what());
71
+ return 1;
72
+ }
73
+ }
74
+ ```
75
+
76
+ ## 捕获
77
+
78
+ **差**:
79
+
80
+ ```cpp
81
+ try {
82
+ Load(path);
83
+ } catch (...) {
84
+ return; // 不知道发生了什么,状态可能半坏
85
+ }
86
+ ```
87
+
88
+ **好**:
89
+
90
+ ```cpp
91
+ try {
92
+ Load(path);
93
+ } catch (const std::filesystem::filesystem_error& error) {
94
+ return LoadResult::kNotFound;
95
+ }
96
+ ```
97
+
98
+ 只捕获能处理的类型;其他异常继续抛。
99
+
100
+ ## 析构
101
+
102
+ **差**:
103
+
104
+ ```cpp
105
+ ~Writer() {
106
+ if (file_) file_->Flush(); // 失败时抛?析构中抛会终止程序
107
+ }
108
+ ```
109
+
110
+ **好**:
111
+
112
+ ```cpp
113
+ ~Writer() {
114
+ if (file_) {
115
+ file_->Flush(); // 忽略失败:析构不抛,错误由 Close() 报告
116
+ }
117
+ }
118
+ ```
119
+
120
+ 需要报告关闭失败时,提供显式 `Close()` 返回结果,析构只做兜底。
121
+
122
+ ## `std::format` 与 `std::print`
123
+
124
+ C++20 没有 `std::print`。不要写:
125
+
126
+ ```cpp
127
+ std::print("任务: {}\n", task.name); // C++23
128
+ ```
129
+
130
+ 写:
131
+
132
+ ```cpp
133
+ std::cout << std::format("任务: {}\n", task.name);
134
+ ```
@@ -0,0 +1,157 @@
1
+ # 正反例:命名
2
+
3
+ ## 基本规则
4
+
5
+ | 差 | 好 | 原因 |
6
+ | --- | --- | --- |
7
+ | `class task_list` | `class TaskList` | 类型 PascalCase |
8
+ | `void add_task()` | `void AddTask()` | 函数 PascalCase |
9
+ | `int TaskCount` | `int task_count` | 变量 snake_case |
10
+ | `std::vector<Task> Tasks` | `std::vector<Task> tasks_` | 成员 snake_case + `_` |
11
+ | `constexpr int MAX = 10` | `constexpr int kMaxTasks = 10` | 常量 `k` 前缀 |
12
+ | `enum class Color { Red }` | `enum class Color { kRed }` | 枚举子 `k` 前缀 |
13
+
14
+ ## 访问器
15
+
16
+ **差**:
17
+
18
+ ```cpp
19
+ Order current_order() const; // 成员名直接当函数名
20
+ void set_current_order(Order order);
21
+ ```
22
+
23
+ **好**:
24
+
25
+ ```cpp
26
+ [[nodiscard]] Order GetCurrentOrder() const;
27
+ void AdvanceOrder(); // 领域动作,不是 setter
28
+ ```
29
+
30
+ 查询统一 `Get` + PascalCase;修改用领域动词。只有真正需要替换值时,才考虑 `Set` 形式,并说明为什么不是动作。
31
+
32
+ ## 语义角色
33
+
34
+ **差**:
35
+
36
+ ```cpp
37
+ bool Check(int id) const; // Check 什么?
38
+ Result DoSomething(Task task); // 做什么?
39
+ TaskState To(TaskState state); // 从什么到什么?
40
+ ```
41
+
42
+ **好**:
43
+
44
+ ```cpp
45
+ [[nodiscard]] bool Contains(int id) const;
46
+ [[nodiscard]] ParseResult TryParse(std::string_view text);
47
+ [[nodiscard]] TaskState StatusCodeToTaskState(int code);
48
+ ```
49
+
50
+ - 判断用 `Is` / `Has`。
51
+ - 可能失败但不抛用 `Try`。
52
+ - 映射名字含两端角色。
53
+
54
+ ## `current_` 前缀
55
+
56
+ **差**:
57
+
58
+ ```cpp
59
+ Order current_order_; // 只有一个订单时,current 是噪音
60
+ ```
61
+
62
+ **好**:
63
+
64
+ ```cpp
65
+ Order order_;
66
+ ```
67
+
68
+ **该用**:
69
+
70
+ ```cpp
71
+ Order current_order_; // 有 current_order_ 与 next_order_,不写会混
72
+ ```
73
+
74
+ ## 名字与职责
75
+
76
+ **差**:
77
+
78
+ ```cpp
79
+ class DataManager {}; // 管什么?
80
+ class StringHelper {}; // 为什么是类?
81
+ ```
82
+
83
+ **好**:
84
+
85
+ ```cpp
86
+ class Warehouse {}; // 持有库存,维持不变量
87
+ namespace string_utils { // 一组自由函数
88
+ [[nodiscard]] std::string Trim(std::string_view text);
89
+ }
90
+ ```
91
+
92
+ 避免 `Manager`、`Helper`、`Util`;名字要能回答「它负责什么」。一组无状态函数用命名空间,不要造类。
93
+
94
+ ## 查找与读取
95
+
96
+ **差**:
97
+
98
+ ```cpp
99
+ [[nodiscard]] Task GetTask(std::string_view name) const; // 可能找不到时返回什么?
100
+ [[nodiscard]] bool FindTask(std::string_view name) const; // 返回 bool 却叫 Find
101
+ ```
102
+
103
+ **好**:
104
+
105
+ ```cpp
106
+ [[nodiscard]] const Task& GetTask(std::string_view name) const; // 找不到抛 out_of_range
107
+ [[nodiscard]] const Task* FindTask(std::string_view name) const; // 找不到返回 nullptr
108
+ [[nodiscard]] bool HasTask(std::string_view name) const; // 只回答存在性
109
+ ```
110
+
111
+ `Get` 表示结果一定存在(或按合同失败),`Find` 表示可能找不到,`Has` / `Contains` 只回答是否存在。不要用 `Find` 返回 `bool`。
112
+
113
+ ## 谓词与 Lambda
114
+
115
+ **差**:
116
+
117
+ ```cpp
118
+ bool valid(int value);
119
+ auto check = [](const Task& task) { return task.done; };
120
+ ```
121
+
122
+ **好**:
123
+
124
+ ```cpp
125
+ [[nodiscard]] bool IsValid(int value);
126
+ auto is_done = [](const Task& task) { return task.done; };
127
+ auto has_owner = [](const Task& task) { return !task.owner.empty(); };
128
+ ```
129
+
130
+ 返回 `bool` 的自由函数用 `Is` / `Has` / `Can` / `Should` 开头;Lambda 用 snake_case 的 `is_` / `has_` / `can_`,读起来像条件。
131
+
132
+ ## 多返回值
133
+
134
+ **差**:
135
+
136
+ ```cpp
137
+ bool Parse(std::string_view text, int* out_value); // 出参 + 返回 bool
138
+ ```
139
+
140
+ **好**:
141
+
142
+ ```cpp
143
+ struct ParseResult {
144
+ bool ok{false};
145
+ int value{0};
146
+ };
147
+
148
+ [[nodiscard]] ParseResult TryParse(std::string_view text);
149
+ ```
150
+
151
+ 需要同时表达成功与结果时,用带字段的结构体或 `std::optional`,不要用输出参数加布尔返回。
152
+
153
+ ## 改名检查
154
+
155
+ - 搜全部调用点:声明、定义、测试、注释、文档。
156
+ - 先改声明,再改定义,再改调用点;每一步都能编译。
157
+ - 公共 API 改名保留旧名转发时,注释写弃用期限;不要静默删除。
@@ -0,0 +1,79 @@
1
+ # 正反例:行为与边界
2
+
3
+ ## 行为句
4
+
5
+ **差**:实现一个 `TaskList` 类,用 `std::vector` 保存任务,支持添加、删除、按优先级排序,并在终端打印表格。
6
+
7
+ 问题:把类型、容器、实现步骤混进行为;无法验收「表格」长什么样。
8
+
9
+ **好**:
10
+
11
+ ```text
12
+ 给定一组任务(每行「优先级 名称」),程序按优先级从高到低输出名称;
13
+ 输入为空时输出「没有任务」并返回 0;某行格式非法时输出行号并返回 1。
14
+ ```
15
+
16
+ 可以写成测试:输入三行,断言输出顺序;输入空,断言提示与退出码;输入坏行,断言行号。
17
+
18
+ ## 边界
19
+
20
+ **差**:库函数直接打印,程序只调用它。
21
+
22
+ ```cpp
23
+ // task_list.cpp(库)
24
+ void TaskList::Print() const {
25
+ for (const auto& task : tasks_) {
26
+ std::cout << task.priority << " " << task.name << "\n";
27
+ }
28
+ }
29
+ ```
30
+
31
+ 问题:库依赖终端,测试要捕获 `std::cout`,GUI 或文件输出无法复用。
32
+
33
+ **好**:库返回数据,程序决定怎么显示。
34
+
35
+ ```cpp
36
+ // task_list.h(库)
37
+ [[nodiscard]] std::vector<Task> SortedByPriority() const;
38
+
39
+ // app/cli.cpp(程序)
40
+ void PrintTasks(const std::vector<Task>& tasks, std::ostream& out) {
41
+ for (const auto& task : tasks) {
42
+ out << std::format("{} {}\n", task.priority, task.name);
43
+ }
44
+ }
45
+ ```
46
+
47
+ 测试直接断言 `SortedByPriority()`;输出格式由程序测试负责。
48
+
49
+ ## 头文件位置
50
+
51
+ **差**:
52
+
53
+ ```cpp
54
+ #include "task_list/task_list.h" // 公共根下又套一层组件名
55
+ ```
56
+
57
+ ```text
58
+ src/task_list.h // 公共头藏在 src,测试无法只依赖公共接口
59
+ ```
60
+
61
+ **好**:
62
+
63
+ ```text
64
+ task_list/include/task_list.h // 公共头
65
+ task_list/src/task_list.cpp // 实现
66
+ task_list/src/detail.h // 私有头,只被本组件包含
67
+ ```
68
+
69
+ ```cpp
70
+ #include "task_list.h" // 相对公共 include 根
71
+ ```
72
+
73
+ ## 单文件何时拆库
74
+
75
+ **先不拆**:一个 `main.cpp`,辅助函数在匿名命名空间,只有程序自己用。
76
+
77
+ **该拆**:测试需要直接调用排序逻辑;或第二个程序要复用同一逻辑;或逻辑已经超过一个屏幕。
78
+
79
+ 拆的时候先定公共头(只暴露 `SortedByPriority`),把实现移到 `src/`,行为句不变,测试先绿再改构建。