@chengzhao-dev/dsh-cpp-workflow 0.3.1 → 0.5.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.
- package/README.en.md +10 -9
- package/README.md +10 -9
- package/lib/index.js +4 -4
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +2 -2
- package/skills/building-cpp/SKILL.md +24 -11
- package/skills/building-cpp/references/build-script.md +11 -0
- package/skills/building-cpp/references/linking.md +1 -1
- package/skills/building-cpp/references/standards/README.md +1 -1
- package/skills/building-cpp/steps/01-build.md +22 -35
- package/skills/configuring-cmake/SKILL.md +57 -0
- package/skills/configuring-cmake/assets/cmake/CompileSettings.cmake +126 -0
- package/skills/configuring-cmake/assets/cmake/LinkSettings.cmake +48 -0
- package/skills/{building-cpp → configuring-cmake}/examples/cmake.md +84 -10
- package/skills/{building-cpp → configuring-cmake}/references/cmake.md +49 -43
- package/skills/configuring-cmake/references/cxx-standard.md +67 -0
- package/skills/configuring-cmake/references/modules.md +105 -0
- package/skills/configuring-cmake/steps/01-targets.md +53 -0
- package/skills/configuring-cmake/steps/02-modules.md +37 -0
- package/skills/configuring-cmake/steps/03-check.md +39 -0
- package/skills/developing-cpp/SKILL.md +1 -1
- package/skills/developing-cpp/examples/comments.md +24 -0
- package/skills/developing-cpp/examples/exceptions.md +38 -19
- package/skills/developing-cpp/examples/structure.md +71 -0
- package/skills/developing-cpp/references/comments.md +49 -137
- package/skills/developing-cpp/references/exceptions.md +12 -13
- package/skills/developing-cpp/references/language.md +1 -0
- package/skills/developing-cpp/references/naming.md +2 -2
- package/skills/developing-cpp/references/standard-library.md +1 -0
- package/skills/developing-cpp/references/types.md +1 -2
- package/skills/developing-cpp/scripts/checking-comments.mjs +57 -2
- package/skills/developing-cpp/steps/03-declaration.md +3 -3
- package/skills/developing-cpp/steps/04-implementation.md +4 -3
- package/skills/building-cpp/assets/cmake/applying-warnings.cmake +0 -10
- package/skills/building-cpp/assets/cmake/fetching-googletest.cmake +0 -15
- package/skills/building-cpp/assets/cmake/setting-rpath.cmake +0 -8
|
@@ -84,6 +84,30 @@ class Board {
|
|
|
84
84
|
|
|
85
85
|
命名与风格约定写进项目文档和读者正文,注释里不解释「为什么用这种命名」。
|
|
86
86
|
|
|
87
|
+
## 第一句:先写结果或限制
|
|
88
|
+
|
|
89
|
+
**差**:
|
|
90
|
+
|
|
91
|
+
```cpp
|
|
92
|
+
/// 检查坐标。
|
|
93
|
+
/// 返回格子类型;越界会抛异常。
|
|
94
|
+
CellType GetCellType(CellPosition position) const;
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
前两句都没有直接回答“合法时拿到什么、越界时发生什么”,读者要读完三句才拼出合同。
|
|
98
|
+
|
|
99
|
+
**好**:
|
|
100
|
+
|
|
101
|
+
```cpp
|
|
102
|
+
/// 坐标越界时抛出 std::out_of_range;合法坐标返回该格类型。
|
|
103
|
+
///
|
|
104
|
+
/// @param position 行列坐标。
|
|
105
|
+
/// @throws std::out_of_range 坐标不在棋盘范围内。
|
|
106
|
+
CellType GetCellType(CellPosition position) const;
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
第一句就是结果或失败方式;后面的标签只补参数与异常细节,不再重复这句话。
|
|
110
|
+
|
|
87
111
|
## 函数契约
|
|
88
112
|
|
|
89
113
|
**差**:
|
|
@@ -22,11 +22,11 @@ struct ParseResult {
|
|
|
22
22
|
|
|
23
23
|
[[nodiscard]] ParseResult TryParseTask(std::string_view line);
|
|
24
24
|
|
|
25
|
-
/// @throws
|
|
25
|
+
/// @throws std::out_of_range index 不在 [0, size()) 内。
|
|
26
26
|
[[nodiscard]] Task GetTask(int index) const;
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
`TryParseTask` 把失败作为正常结果返回。`GetTask`
|
|
29
|
+
`TryParseTask` 把失败作为正常结果返回。`GetTask` 的前提是调用方给出有效下标;越界时函数无法返回一个任务,抛标准库已有的 `std::out_of_range`。如果越界其实是正常输入,应该改成返回 `std::optional<Task>`,而不是让调用方捕获异常;只有项目需要把这种越界和别处的 `out_of_range` 区分开时,才定义派生类。
|
|
30
30
|
|
|
31
31
|
## 异常类型与消息
|
|
32
32
|
|
|
@@ -36,34 +36,53 @@ struct ParseResult {
|
|
|
36
36
|
throw "bad index";
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
**好**:语义已经由标准类型给出时直接用标准类型;需要额外携带领域信息时,再派生自它。
|
|
40
40
|
|
|
41
41
|
```cpp
|
|
42
|
-
|
|
43
|
-
public:
|
|
44
|
-
explicit TaskIndexError(const std::string& message)
|
|
45
|
-
: std::out_of_range(message) {}
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
throw TaskIndexError{
|
|
42
|
+
throw std::out_of_range{
|
|
49
43
|
std::format("GetTask: index={} 超出 [0, {})", index, tasks_.size())};
|
|
50
44
|
```
|
|
51
45
|
|
|
52
|
-
消息写操作和关键值,不写「出错了」;`std::format`
|
|
46
|
+
消息写操作和关键值,不写「出错了」;`std::format` 只是构造消息的方式,不强制固定模板。项目若定义 `Error` 基类,按 `references/exceptions.md` 先定基类再派生,调用方捕获基类接住整族。
|
|
47
|
+
|
|
48
|
+
## Debug 与 Release 的三层合同
|
|
49
|
+
|
|
50
|
+
**差**:同一个函数在 Debug 抛异常、在 Release 返回 false;调用方被迫按构建模式写两套分支。
|
|
51
|
+
|
|
52
|
+
```cpp
|
|
53
|
+
#ifdef NDEBUG
|
|
54
|
+
return false;
|
|
55
|
+
#else
|
|
56
|
+
throw std::out_of_range{row, col};
|
|
57
|
+
#endif
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**好**:返回值在两种模式完全一致;需要异常时两种模式都抛,只有 `what()` 的详略不同。
|
|
53
61
|
|
|
54
|
-
|
|
62
|
+
```cpp
|
|
63
|
+
// 返回值:失败是可恢复结果,两种模式都返回 false。
|
|
64
|
+
[[nodiscard]] bool TryPlace(CellPosition position, Player player) noexcept;
|
|
65
|
+
|
|
66
|
+
// 越界:两种模式都抛 std::out_of_range;Debug 消息可带 file:line。
|
|
67
|
+
void Place(CellPosition position, Player player);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
越界用标准库已有的 `std::out_of_range`,调用方不需要认识项目私有类型;只有调用方必须把它和其它 `out_of_range` 区分开时,才定义派生类并在 `what()` 里补位置信息。
|
|
71
|
+
|
|
72
|
+
**差**:用 `assert` 承担 Release 也必须执行的 invariant 检查;`NDEBUG` 后检查消失。
|
|
73
|
+
|
|
74
|
+
```cpp
|
|
75
|
+
assert(game.GetResult() == GameResult::kInProgress);
|
|
76
|
+
```
|
|
55
77
|
|
|
56
|
-
|
|
78
|
+
**好**:两种模式都必须检查的 invariant 用 `Check`(项目保留时),失败时打印诊断并终止;只在开发期值得做、Release 可以省掉的自检用 `DebugCheck`。没有这两个宏时分别用 `assert` 与 `std::terminate`。
|
|
57
79
|
|
|
58
80
|
```cpp
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
struct file_error : virtual io_error {};
|
|
62
|
-
struct read_error : virtual io_error {};
|
|
63
|
-
struct file_read_error : virtual file_error, virtual read_error {};
|
|
81
|
+
Check(game.GetResult() == GameResult::kInProgress, "Ai::Choose: game is already over");
|
|
82
|
+
DebugCheck(index < history_.size(), "history index out of range");
|
|
64
83
|
```
|
|
65
84
|
|
|
66
|
-
|
|
85
|
+
`Check` 不是 `[[noreturn]]`:条件成立时正常返回。`DebugCheck` 在 `NDEBUG` 后展开为空,参数不能带副作用。
|
|
67
86
|
|
|
68
87
|
## 捕获
|
|
69
88
|
|
|
@@ -80,3 +80,74 @@ task_list/src/detail.h // 私有头,只被本组件包含
|
|
|
80
80
|
**该拆**:测试需要直接调用排序逻辑;或第二个程序要复用同一逻辑;或逻辑已经超过一个屏幕。
|
|
81
81
|
|
|
82
82
|
拆的时候先定公共头(只暴露 `SortedByPriority`),把实现移到 `src/`,行为句不变,测试先绿再改构建。
|
|
83
|
+
|
|
84
|
+
## 固定坐标用表
|
|
85
|
+
|
|
86
|
+
**差**:每次判断成线,都用循环把八条线的坐标重新拼一遍;行、列、对角线混在一个循环里,读者要跟着跑两遍才知道哪八条。
|
|
87
|
+
|
|
88
|
+
```cpp
|
|
89
|
+
// 差:用循环现写八条线,坐标藏在控制流里。
|
|
90
|
+
std::vector<std::vector<CellPosition>> BuildLines() {
|
|
91
|
+
std::vector<std::vector<CellPosition>> lines;
|
|
92
|
+
for (int row = 0; row < kRows; ++row) { /* ... */ }
|
|
93
|
+
// 列与两条对角线再各写一段
|
|
94
|
+
return lines;
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**好**:固定坐标写成匿名命名空间里的常量表,`HasLine` 与 `IsLineComplete` 共用;格子用 `[]`,整条线 `all_of`、八条线 `any_of`。
|
|
99
|
+
|
|
100
|
+
```cpp
|
|
101
|
+
// board.cpp 的匿名命名空间:行、列、对角线三组,坐标固定。
|
|
102
|
+
constexpr std::array<std::array<CellPosition, 3>, 8> kLines{{
|
|
103
|
+
{{{0, 0}, {0, 1}, {0, 2}}}, // 行 0
|
|
104
|
+
{{{1, 0}, {1, 1}, {1, 2}}}, // 行 1
|
|
105
|
+
{{{2, 0}, {2, 1}, {2, 2}}}, // 行 2
|
|
106
|
+
{{{0, 0}, {1, 0}, {2, 0}}}, // 列 0
|
|
107
|
+
{{{0, 1}, {1, 1}, {2, 1}}}, // 列 1
|
|
108
|
+
{{{0, 2}, {1, 2}, {2, 2}}}, // 列 2
|
|
109
|
+
{{{0, 0}, {1, 1}, {2, 2}}}, // 对角线
|
|
110
|
+
{{{0, 2}, {1, 1}, {2, 0}}}, // 反对角线
|
|
111
|
+
}};
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
每个格子要分别处理时仍写 `row` / `col` 循环,例如收集空格、终端打印。
|
|
115
|
+
|
|
116
|
+
## 推演一步读原数据
|
|
117
|
+
|
|
118
|
+
**差**:先复制整盘,再在副本上落子,最后判断成线;调用方看不出副本只是中间量。
|
|
119
|
+
|
|
120
|
+
```cpp
|
|
121
|
+
Board hypothetical = *this;
|
|
122
|
+
hypothetical.PlaceCell(position, cell_type);
|
|
123
|
+
return hypothetical.HasLine(cell_type);
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**好**:判断「这一格换成某种棋子后会不会成线」不需要第二份棋盘。越界、空格、已占用仍按 `PlaceCell` 的拒绝条件返回 `false`;通过之后只在包含该坐标的线上把这一格算作 `cell_type`,其余格子读原棋盘。
|
|
127
|
+
|
|
128
|
+
```cpp
|
|
129
|
+
bool Board::IsLineComplete(CellPosition position, CellType cell_type) const noexcept {
|
|
130
|
+
if (!IsInRange(position) || cell_type == CellType::kEmpty) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
if (cells_[position.row][position.col] != CellType::kEmpty) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const auto matches = [&](CellPosition cell) {
|
|
138
|
+
if (cell.row == position.row && cell.col == position.col) {
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
return cells_[cell.row][cell.col] == cell_type;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
return std::ranges::any_of(kLines, [&](const auto& line) {
|
|
145
|
+
const bool contains = std::ranges::find_if(line, [&](CellPosition cell) {
|
|
146
|
+
return cell.row == position.row && cell.col == position.col;
|
|
147
|
+
}) != line.end();
|
|
148
|
+
return contains && std::ranges::all_of(line, matches);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
只用 `[]` 与 `IsInRange`,不调用 `at()`,头文件上的 `noexcept` 可以留下;原棋盘不被写入。
|
|
@@ -1,38 +1,37 @@
|
|
|
1
1
|
# 注释
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
注释分四层:文件头、类型注释、函数注释、实现注释;配置文件的注释另算。层级越靠外,越讲概念与契约;越靠里,越讲实现理由。同一件事只写一次。
|
|
4
4
|
|
|
5
5
|
## 总则
|
|
6
6
|
|
|
7
|
-
-
|
|
7
|
+
- **第一句先写结果或限制**:类型给调用方什么,函数正常返回什么、失败时返回什么或抛什么,调用方必须守住哪条边界。第二句以后才写条件、参数与实现理由。
|
|
8
|
+
- 公共注释回答「这是什么」和「调用方必须遵守什么」;实现注释回答「为什么这样写」和「边界在哪里」。设计取舍、方案对比与演进历史写到书稿、README 或提交说明。
|
|
8
9
|
- 用中文散文写完整句子,句号结尾;标识符、标准库类型、`@param` 这类标签保持原文。
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
- 命名、分文件、`enum class`、异常与结果类型这类全项目风格约定只写进项目文档与读者正文;注释不替项目解释风格,也不写「用 X 而不是 Y」的理由。
|
|
10
|
+
- 行宽跟项目 `.clang-format` 的 `ColumnLimit` 走(Google 基线是 80 列,中文按两列计);横幅与列宽同宽,不另设数字。
|
|
11
|
+
- 写之前三问:删掉这句,读者会误解设计或做错事吗?这句只是复述签名或代码吗?这个限制能不能由类型、`const` 或 `[[nodiscard]]` 表达?第一问为否就删,第二问为是就删,第三问为是就改代码。
|
|
12
|
+
- 命名、分文件、`enum class`、异常与结果类型这类全项目风格约定只写进项目文档与读者正文;注释不替项目解释风格。
|
|
13
13
|
|
|
14
14
|
## `///` 与 `//` 的分工
|
|
15
15
|
|
|
16
16
|
- `///` 只给声明:头文件里的类型、函数、枚举值与公开数据成员(行尾补一句用 `///<`)。调用方和 Doxygen 从这里取契约。
|
|
17
|
-
- `//`
|
|
17
|
+
- `//` 给实现:函数体内的步骤与边界说明、局部变量、文件头横幅、配置文件的说明。
|
|
18
18
|
- 不给局部变量和函数体里的语句写 `///`;文件头也不写成 `///`,否则编辑器会把它挂到紧随其后的第一个声明上。
|
|
19
|
-
-
|
|
19
|
+
- 判断看一句话写的是「调用方要遵守的承诺」还是「维护者要知道的写法」:前者用 `///`,后者用 `//`。
|
|
20
20
|
|
|
21
21
|
## 长度预算
|
|
22
22
|
|
|
23
|
-
| 层 | 行数 | 写什么 |
|
|
24
|
-
| --- | --- | --- |
|
|
25
|
-
| 文件头 | 3–6 |
|
|
26
|
-
| 类型注释 | 6–14 | 概念、使用约定 |
|
|
27
|
-
| 函数注释 | 3–10 |
|
|
28
|
-
| 数据成员 | 行尾 1 句 |
|
|
29
|
-
| 实现注释 | 1–3 |
|
|
23
|
+
| 层 | 行数 | 第一句 | 写什么 |
|
|
24
|
+
| --- | --- | --- | --- |
|
|
25
|
+
| 文件头 | 3–6 | 这个文件是什么 | 由哪几部分组成、不负责什么 |
|
|
26
|
+
| 类型注释 | 6–14 | 这个类型给调用方什么 | 概念、使用约定 |
|
|
27
|
+
| 函数注释 | 3–10 | 正常返回什么、失败时返回或抛什么 | 参数与失败方式 |
|
|
28
|
+
| 数据成员 | 行尾 1 句 | 存什么 | 范围或单位、初值的含义 |
|
|
29
|
+
| 实现注释 | 1–3 | 为什么这样写或边界在哪里 | 代价 |
|
|
30
30
|
|
|
31
|
-
行数只是防止失控的锚点,不是配额。内容不够就少写,像「检查棋盘是否已满」这种名字已经说清的,一句契约即可。
|
|
32
31
|
|
|
33
32
|
## 文件头
|
|
34
33
|
|
|
35
|
-
|
|
34
|
+
每个手写源文件开头写一段文件头:这个文件负责什么、分成哪几块、不负责什么。不写作者、版权年份、变更日志和外部资料路径。
|
|
36
35
|
|
|
37
36
|
```cpp
|
|
38
37
|
//===----------------------------------------------------------------------===//
|
|
@@ -43,172 +42,85 @@
|
|
|
43
42
|
```
|
|
44
43
|
|
|
45
44
|
- C++ 用 `//`,CMake 用 `#`,YAML/JSON 用 `#` 或 `//`。
|
|
46
|
-
- 横幅宽度等于 `ColumnLimit`:`//===` 起头、`===//` 收尾,中间补
|
|
47
|
-
- 超过约 200 行的文件用章节横幅分组,组名是读者要跳转的锚点;一个文件不超过 8
|
|
48
|
-
- 生成文件、第三方文件、纯数据文件不写文件头。
|
|
45
|
+
- 横幅宽度等于 `ColumnLimit`:`//===` 起头、`===//` 收尾,中间补 `-`;章节横幅同一格式。
|
|
46
|
+
- 超过约 200 行的文件用章节横幅分组,组名是读者要跳转的锚点;一个文件不超过 8 个横幅。
|
|
49
47
|
|
|
50
48
|
## 类型注释
|
|
51
49
|
|
|
52
50
|
对象是类、结构体、枚举和类型别名。写两段,段间空一行:
|
|
53
51
|
|
|
54
|
-
1.
|
|
55
|
-
2.
|
|
52
|
+
1. 结果:这个类型是什么、给谁用、保证什么。
|
|
53
|
+
2. 限制:本类型不变量、调用方必须遵守的条件、失败方式与所有权。
|
|
56
54
|
|
|
57
|
-
不写设计段落:为什么这样切分职责、为什么不选另一种写法,属于书稿的设计讨论,注释里不出现。分层说明放文件头,类注释只讲本类型。
|
|
58
55
|
|
|
59
56
|
```cpp
|
|
60
|
-
///
|
|
57
|
+
/// 井字棋棋盘:保存九格状态,并回答满盘、成线与胜负。
|
|
61
58
|
///
|
|
62
|
-
/// 3×3
|
|
59
|
+
/// 格子数量固定为 3×3;落子只改变单个格子,不移动其他格子。
|
|
63
60
|
class Board {
|
|
64
61
|
```
|
|
65
62
|
|
|
66
|
-
判据:删掉这句,读者会不会做错事、或把它用到别的类型上?读签名、`const`、`[[nodiscard]]` 或成员类型就知道的事,写出来只是重复一遍,删。
|
|
67
|
-
|
|
68
|
-
按下面的次序取内容,够了就停,不必每段都凑满:
|
|
69
|
-
|
|
70
|
-
1. 一句话职责:这个类型是什么、给谁用。
|
|
71
|
-
2. 使用约定:什么时候该用它、失败与前置条件是什么。
|
|
72
|
-
3. 非显然保证:所有权、生命周期、异常保证、复杂度;签名里看得出的不写。
|
|
73
|
-
4. 线程规则:只有实例会被多个线程访问时才写,写清哪些操作可以并发、哪些要调用方自己同步。
|
|
74
|
-
5. 不变式:几条始终成立的条件;能写成 `assert` 或 `static_assert` 的,写断言而不是注释。
|
|
75
63
|
|
|
76
64
|
禁止事项:
|
|
77
65
|
|
|
78
|
-
-
|
|
79
|
-
-
|
|
80
|
-
-
|
|
81
|
-
-
|
|
82
|
-
-
|
|
83
|
-
- 不写方案对比与取舍论证:`用 X 而不是 Y`、`比……更……`、`复制的代价可以忽略`、`只留一份实现` 这类句子属于设计讨论,不进注释。
|
|
84
|
-
- 不写「本类使用快速排序所以很快」这种广告句;要写清楚代价与适用规模。
|
|
85
|
-
|
|
86
|
-
枚举与只承载数据的结构体可以短一些:概念一段,`///<` 先写这个取值表示什么,需要时用冒号补一小句读者能核实的补充(条件、动作或结果);两段都要短,不重复名字,不写「因为……所以……」的长句。
|
|
87
|
-
|
|
88
|
-
```cpp
|
|
89
|
-
/// 对局结果。
|
|
90
|
-
enum class GameResult : std::uint8_t {
|
|
91
|
-
kInProgress = 0, ///< 游戏进行中:还可以落子。
|
|
92
|
-
kCrossWin = 1, ///< X 获胜:达成三连。
|
|
93
|
-
kNoughtWin = 2, ///< O 获胜:达成三连。
|
|
94
|
-
kDraw = 3, ///< 平局:棋盘已满,双方都没有三连。
|
|
95
|
-
};
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
冒号后的补充只要是读者扫一眼就能核实的条件就够了;要展开判定过程或推导,留给函数实现注释或正文。
|
|
66
|
+
- 不写成员清单,不按声明顺序复述接口。函数各自的契约写在函数上。
|
|
67
|
+
- 不写跨类分工。边界靠类型与所有权表达,注释只写本类型保证什么。
|
|
68
|
+
- 不写类型系统已经表达的性质(`值语义`、`所有查询都是 const`)。
|
|
69
|
+
- 不写尚未发生的用法、方案对比与取舍论证、广告句。
|
|
70
|
+
- 枚举与只承载数据的结构体:第一句写这个取值表示什么,需要时用冒号补一小句可核实的条件。
|
|
99
71
|
|
|
100
72
|
## 函数注释
|
|
101
73
|
|
|
102
74
|
两段式,短函数可以只留第一段:
|
|
103
75
|
|
|
104
|
-
1.
|
|
76
|
+
1. 结果或限制:这个函数给调用方什么结果;失败时返回什么、抛什么,或声明它不抛。
|
|
105
77
|
2. 契约:`@param`、`@return`、`@throws`,写范围、单位、所有权和失败方式。
|
|
106
78
|
|
|
79
|
+
|
|
107
80
|
```cpp
|
|
108
|
-
///
|
|
109
|
-
///
|
|
110
|
-
/// 越界、已占或要写空格子都返回 false,不抛异常。
|
|
81
|
+
/// 放置成功返回 true;坐标越界、目标已占或要写空格子时返回 false,不抛异常。
|
|
111
82
|
///
|
|
112
83
|
/// @param position 目标行列坐标。
|
|
113
84
|
/// @param cell_type 要放置的棋盘格子类型,必须是 kX 或 kO。
|
|
114
|
-
/// @return 放置成功返回 true;坐标越界、要写的是空格子或目标已有棋子时返回 false,棋盘不变。
|
|
115
85
|
[[nodiscard]] bool PlaceCell(CellPosition position, CellType cell_type);
|
|
116
86
|
```
|
|
117
87
|
|
|
118
|
-
- 布尔查询:`@return` 写「什么情况下为 true
|
|
119
|
-
- `@param`
|
|
88
|
+
- 布尔查询:`@return` 写「什么情况下为 true」,再补一句「不检查什么」。
|
|
89
|
+
- `@param` 要么全写,要么全不写;写范围、单位或所有权,不写「索引」这类同义反复。
|
|
120
90
|
- `@return` 在返回类型自明时可以省略;`[[nodiscard]]` 已表达「必须使用」时不再重复。
|
|
121
|
-
- `@throws` 必须与 `references/exceptions.md`
|
|
122
|
-
-
|
|
123
|
-
- 参数是输出参数时标 `[out]`,既读又写时标 `[in,out]`。
|
|
91
|
+
- `@throws` 必须与 `references/exceptions.md` 一致;不抛异常的函数不写。
|
|
92
|
+
- 私有函数同样写契约,不能默认调用者知道前置条件;输出参数标 `[out]`,既读又写标 `[in,out]`。
|
|
124
93
|
|
|
125
94
|
## 数据成员与实现注释
|
|
126
95
|
|
|
127
|
-
数据成员用行尾注释,写「存什么 + 范围或单位 +
|
|
96
|
+
数据成员用行尾注释,写「存什么 + 范围或单位 + 初值的含义」,同类成员对齐。不复述 `cells_[row][col]` 这类存储布局:数组维度和访问方式由声明表达。
|
|
128
97
|
|
|
129
98
|
```cpp
|
|
130
99
|
Board board_; // 对局棋盘。
|
|
131
100
|
Player current_player_ = Player::kCross; // 当前轮到的玩家,开局时 X 先手。
|
|
132
101
|
```
|
|
133
102
|
|
|
134
|
-
实现注释只写三样东西:为什么这样写、这里的边界条件、这样做的代价。1–3
|
|
135
|
-
|
|
136
|
-
```cpp
|
|
137
|
-
// 只有对手能立刻成线时才挡:先看自己的胜手,避免把能赢的一步让给对方。
|
|
138
|
-
if (CanWinNow(game, player)) {
|
|
139
|
-
return win_move;
|
|
140
|
-
}
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
- 不逐行翻译代码,不在 `if` 与 `else` 之间挂注释,分支理由写进各自分支内部。
|
|
144
|
-
- 允许写局部取舍(为什么这里接受 O(n²)、为什么排序要稳定、为什么缓存这一层);不写变更史(「原来……现在……」)和外部资料路径。
|
|
145
|
-
- 注释掉的代码不保留,需要的从版本历史取。
|
|
103
|
+
实现注释只写三样东西:为什么这样写、这里的边界条件、这样做的代价。1–3 行,放在被解释语句上方;第一句先写理由或边界,不复述代码。
|
|
146
104
|
|
|
147
|
-
|
|
105
|
+
`.cpp` / `.cc` 的函数体默认**不写**注释:公开契约已经在头文件的 `///` 里,实现再写一遍只会随代码漂移。大型工程的共识是把接口说明留在声明上——[Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html#Implementation_Comments) 只在难懂、不明显的地方写实现注释,并明确不要描述代码本身;[LLVM Coding Standards](https://www.llvm.org/docs/CodingStandards.html#commenting) 同样要求写意图而不是逐步翻译。文件头横幅保留,它对应两家的文件说明,不是函数体解说。
|
|
148
106
|
|
|
149
|
-
|
|
150
|
-
- 类型收窄:从宽类型读入再检查能否收进窄类型,或 `static_cast` 之前的范围判断。
|
|
151
|
-
- 输入与解析:为什么清失败位、为什么丢弃整行、为什么按 64 位读再收窄。
|
|
152
|
-
- 外部格式:手写 JSON、协议字段顺序、编码处理的约定,以及改动时要同步的文件。
|
|
153
|
-
- 回退路径:`switch` 覆盖全部枚举后仍留的 `return`、断言拦不住的兜底分支、`continue` 而不是报错的场合。
|
|
154
|
-
- 状态不变量:写入后哪些成员已经一致、失败时为什么不需要撤销。
|
|
155
|
-
- 刻意重复或刻意不共享:为了可读性重复一小段判断、为了零依赖不复用库函数。
|
|
107
|
+
唯一保留的一类:删掉这句话,下一次修改会得到错误结果,而且代码本身看不出那个结果。句子要写出错误结果,不写「省掉一次扫线」这类口号。典型位置:
|
|
156
108
|
|
|
157
|
-
|
|
109
|
+
- 次序依赖:先判胜再判满,否则最后一子会变成平局。
|
|
110
|
+
- 类型收窄与解析:先按 64 位读再收窄;解析失败后必须丢掉整行。
|
|
111
|
+
- 状态不变量、外部格式、回退路径、刻意重复。
|
|
112
|
+
- 共享方式:`thread_local` 之类的可见性。
|
|
158
113
|
|
|
159
|
-
|
|
114
|
+
循环体里逐行复述 `push_back`、`return` 的注释一律删;不写变更史、外部资料路径和注释掉的代码。CMake 的契约注释不在这里,写法见 `../../configuring-cmake/references/cmake.md`。
|
|
160
115
|
|
|
161
|
-
|
|
162
|
-
// TODO: 阶段 3 - 支持撤销任务状态更新
|
|
163
|
-
// TODO: issue #42 - 把编号检查抽到 TaskTable
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
`TODO:` 后先写定位(阶段名或 issue 号),再写一个短横线,再写动作。不写 `TODO(某人)`,不写没有动作的 TODO。
|
|
167
|
-
|
|
168
|
-
## 配置注释
|
|
169
|
-
|
|
170
|
-
`.clang-format`、`.clang-tidy`、`.clangd`、CMake 的配置项旁边写「为什么这样设」,不写「这一项是什么」。
|
|
171
|
-
|
|
172
|
-
```yaml
|
|
173
|
-
# 列宽、缩进、包含顺序都取 Google 预设,不逐项覆盖。
|
|
174
|
-
Language: Cpp
|
|
175
|
-
BasedOnStyle: Google
|
|
176
|
-
Standard: Latest
|
|
177
|
-
```
|
|
116
|
+
## TODO、配置、改写次序与边界
|
|
178
117
|
|
|
179
|
-
|
|
180
|
-
- `.clang-format` 按逻辑组分段,每组一句说明这一组在管什么,不逐键写「设置 X 为 Y」。
|
|
181
|
-
- `.clangd` 每个键组一句话,说明为什么需要这个数据库或参数。
|
|
182
|
-
- `.clang-tidy` 的每个关闭项写一句理由;没有理由的关闭项删掉。同一个理由只写一次。
|
|
118
|
+
`TODO:` 后先写定位(阶段名或 issue 号),再写一个短横线,再写动作;不写 `TODO(某人)`,不写没有动作的 TODO。
|
|
183
119
|
|
|
184
|
-
|
|
120
|
+
`.clang-format`、`.clang-tidy`、`.clangd`、CMake 的配置项旁边写「为什么这样设」,不写「这一项是什么」;配置注释贴在被解释的键上方。
|
|
185
121
|
|
|
186
|
-
-
|
|
187
|
-
- 同一个概念只用一种译法,术语表见 `references/glossary.md`;中文与英文、数字之间留一个空格。
|
|
188
|
-
- 不写「显然」「众所周知」这类评判读者的词,也不写「注意」「重要」这类没有信息量的加重。
|
|
122
|
+
只改注释时仍走第 3、4 步,然后回读:文件头 → 类型(结果/限制 + 约定)→ 函数(结果/失败方式 + 参数)→ 数据成员(存什么/约束/初值)→ 实现(为什么/边界)→ 术语与新符号是否真实存在 → 运行 `checking-comments.mjs` 兜底。
|
|
189
123
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
只改注释时仍走第 3、4 步,然后按这个次序回读:
|
|
193
|
-
|
|
194
|
-
1. 文件头:是什么、由哪几块组成、不负责什么。
|
|
195
|
-
2. 类型注释:概念、约定;删掉成员清单与设计论证。
|
|
196
|
-
3. 函数注释:承诺、参数与失败方式;与 `references/exceptions.md` 一致。
|
|
197
|
-
4. 数据成员:存什么、约束、初值。
|
|
198
|
-
5. 实现注释:只留为什么和边界,逐行字幕全部删掉。
|
|
199
|
-
6. 回读:同一概念一种译法;注释里出现的新函数名、新常量是否真的存在。
|
|
200
|
-
7. 机械化兜底:`node "<skill-dir>/scripts/checking-comments.mjs" --root <项目根>` 检查横幅宽度、禁止标签、注释与声明的贴合、类型成员清单、设计论证、`@param` 完整性与枚举行尾注释;它不判断内容好坏,只守住格式底线。
|
|
201
|
-
|
|
202
|
-
## 与读者正文的边界
|
|
203
|
-
|
|
204
|
-
- 注释写给维护者,正文写给读者;两边各自写自己的文件,不互相抄。
|
|
205
|
-
- 设计理由、方案比较与算法取舍写在正文或实现注释里,公开接口注释只留契约。
|
|
124
|
+
- 注释写给维护者,正文写给读者;设计理由、方案比较与算法取舍写在正文或实现注释里,公开接口只留契约。
|
|
206
125
|
- `///`、`@param`、横幅不进入 `.qmd`、YAML、CSS。
|
|
207
|
-
|
|
208
|
-
## 禁止
|
|
209
|
-
|
|
210
|
-
- 用注释代替类型、`const`、`[[nodiscard]]` 能表达的限制。
|
|
211
|
-
- 大段 ASCII 表格、装饰图案、表情符号,以及把注释当提交说明或变更日志。
|
|
212
|
-
- 写外部资料路径、跨仓库引用、团队流程;这些属于文档。
|
|
213
|
-
- 写成员功能清单(`提供……、……的功能`)与按声明顺序复述接口。
|
|
214
|
-
- 写注释的元叙述:不交代「其余内容见某处注释」「两处不重复」「成员用法写在行尾注释里」这类注释布局;读者要的事实直接写出来,不写「哪里写了什么」。
|
|
126
|
+
- 禁止:用注释代替类型/`const`/`[[nodiscard]]`;大段表格、装饰图案、表情;外部资料路径、跨仓库引用、团队流程;成员功能清单;注释元叙述(「其余内容见某处」「两处不重复」)。
|
|
@@ -37,21 +37,18 @@ class ParseError : public std::runtime_error {
|
|
|
37
37
|
};
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
标准库已经命名的失败直接用标准类型:坐标越界用 `std::out_of_range`,调用时不变量已破坏用 `std::logic_error`,正常输入里的解析失败可以用 `std::runtime_error`。只有调用方必须把这些失败和上述类型区分开时,才新增派生自它们的类型;不要为了「看起来规范」给每个参数错误都建一支。项目若定义自己的异常基类(如 `Error`),先定基类再按领域派生,调用方捕获基类就能接住整族异常。
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
## 两种构建模式的三层合同
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
库的失败方式在 Debug 与 Release 下必须可预测;只有诊断信息的详略可以不同。
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
struct read_error : virtual io_error {};
|
|
51
|
-
struct file_read_error : virtual file_error, virtual read_error {};
|
|
52
|
-
```
|
|
46
|
+
- 返回值:两种模式完全一致。函数用返回值报告可恢复失败时,Debug 不得改成抛异常,Release 也不得把异常改成 false。
|
|
47
|
+
- 标准异常:两种模式的抛出条件相同。越界抛 `std::out_of_range`,前置条件被破坏抛 `std::logic_error`;项目若定义 `Error` 基类,派生类型同时派生自 `std::exception`,Debug 的 `what()` 可带 `file:line`,Release 只留值。
|
|
48
|
+
- `Check`:项目保留这种断言时,两种模式都检查,失败时打印诊断并终止;用于 invariant 已被破坏、继续执行没有意义的场合。没有 `Check` 的项目直接用 `assert` 或 `std::terminate` 表达同一边界,不另写包装。
|
|
49
|
+
- `DebugCheck`:项目保留时只在 Debug 生效,`NDEBUG` 后展开为空;用于代价高、只在开发期值得做的自检。不得用它承担 Release 也必须执行的检查。
|
|
53
50
|
|
|
54
|
-
|
|
51
|
+
`Check` 不是 `[[noreturn]]`:条件成立时正常返回。`DebugCheck` 的参数在 Release 不求值,不能放入有副作用的表达式。
|
|
55
52
|
|
|
56
53
|
## 抛出与捕获
|
|
57
54
|
|
|
@@ -72,7 +69,7 @@ E.31:`catch` 子句从派生到基类、从具体到通用排序,`catch (...
|
|
|
72
69
|
|
|
73
70
|
## 不抛的边界
|
|
74
71
|
|
|
75
|
-
E.12:在不可能或不可接受抛异常的地方用 `noexcept`。E.13:直接拥有资源的函数不要抛,资源释放应交给 RAII。E.16:析构、`deallocate`、`swap` 以及异常类型的拷贝/移动构造不得失败;析构和 `swap` 应当是 `noexcept`。
|
|
72
|
+
E.12:在不可能或不可接受抛异常的地方用 `noexcept`。声明前先读实现:函数体里出现 `at()` 或其他可能抛出的调用,就去掉 `noexcept`。正例是只比较两个整数的 `IsInRange`;反例是用 `at()` 写入并可能抛 `std::out_of_range` 的 `PlaceCell`。E.13:直接拥有资源的函数不要抛,资源释放应交给 RAII。E.16:析构、`deallocate`、`swap` 以及异常类型的拷贝/移动构造不得失败;析构和 `swap` 应当是 `noexcept`。
|
|
76
73
|
|
|
77
74
|
E.30:不要写异常规范(`throw(...)` 之类);C++11 之后用 `noexcept` 表达「不抛」。需要无异常构建时,由项目单独决定,不要把这套异常指导改写成两面都对的模糊话。
|
|
78
75
|
|
|
@@ -113,4 +110,6 @@ Config ReadConfig(const std::filesystem::path& path);
|
|
|
113
110
|
- [ ] 析构、`swap`、异常类型的拷贝/移动不抛。
|
|
114
111
|
- [ ] 没有用异常做正常分支,也没有在每个函数里捕获所有异常。
|
|
115
112
|
- [ ] 入口有统一捕获,消息可定位,退出码符合项目合同。
|
|
116
|
-
- [ ] 公共声明的 `@throws`
|
|
113
|
+
- [ ] 公共声明的 `@throws` 与实际抛出类型一致,且 Debug/Release 的抛出条件相同。
|
|
114
|
+
- [ ] 每个 `noexcept` 函数的函数体不调用 `at()` 或其他会抛的接口。
|
|
115
|
+
- [ ] 项目若保留 `Check` / `DebugCheck`:`Check` 两模式都生效,`DebugCheck` 只在 Debug 生效;没有让 Debug 抛、Release 返回 false。
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
- 变量有名字、类型、存储期和作用域。局部变量在离开作用域时销毁;`static` 局部变量在首次执行到声明时初始化,程序结束时销毁。
|
|
9
9
|
- 声明让名字可见,定义分配存储或给出函数体。一个实体可以声明多次,但只能定义一次;头文件只放声明,定义放 `.cpp`。
|
|
10
10
|
- 引用是已有对象的别名,必须初始化,不能重新绑定;指针可以为空,也可以改指向。接口优先用引用表达“必须存在”,用指针表达“可以不存在”。
|
|
11
|
+
- `*this` 不用于复制当前对象:要一份副本就直说语义(如“推演落子”只读原数据,或显式写一个复制函数)。`return *this` 仍只属于赋值运算符,井字棋没有这种运算符。
|
|
11
12
|
|
|
12
13
|
## 初始化与列表初始化
|
|
13
14
|
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
- 布尔变量用 `is_` / `has_` / `can_` 开头:`is_valid`、`has_stock`;不要用 `flag`、`status`。
|
|
20
20
|
- 容器用复数:`tasks`、`orders`;单个元素用单数。
|
|
21
21
|
- 缩写按单词处理:`url`、`id`、`http`,不要写 `URL` 混进 snake_case;类型名里可以 `HttpClient`。
|
|
22
|
-
-
|
|
22
|
+
- 不用单字母名:坐标用 `position`,行、列用 `row`、`col`;`i` / `j` 只用于没有领域名字的计数(如遍历固定长度的下标)。也不要用 `data`、`info`、`tmp` 这类不说明内容的名字。
|
|
23
23
|
|
|
24
24
|
## 语义角色
|
|
25
25
|
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
| 访问器 | `Get` + PascalCase,或 snake_case | `GetHistory()`、`history()` | Google 允许两种;同一 API 只选一种,不用成员名直接当函数名 |
|
|
38
38
|
|
|
39
39
|
- 返回 `bool` 的 lambda 也用 `is_` / `has_` / `can_` 命名:`auto is_empty = [](const Order& order) { ... };`。
|
|
40
|
-
- `Find` 不把“没找到”当异常;`Get`
|
|
40
|
+
- `Find` 不把“没找到”当异常;`Get` 在前置条件不满足时抛标准异常:坐标越界用 `std::out_of_range`,调用时不变量已破坏用 `std::logic_error`;只有调用方必须把这些类型区分开时,才新增派生自它们的类型。两种构建模式的抛出条件相同。两者不要互换。
|
|
41
41
|
- 查询返回多个值时用 `struct`,不要用三个 `GetXxx()` 加输出参数。
|
|
42
42
|
|
|
43
43
|
- 禁止裸 `To()` / `Of()`:调用点看不出方向。
|
|
@@ -62,6 +62,7 @@ std::erase_if(values, [](int value) { return value % 2 == 0; });
|
|
|
62
62
|
| 二分查找 | `std::lower_bound` / `std::upper_bound`(要求已排序) |
|
|
63
63
|
|
|
64
64
|
- 算法接收迭代器区间,不接收容器;调用时写 `values.begin(), values.end()` 或 C++20 `std::ranges::sort(values)`。
|
|
65
|
+
- 嵌套数组整体赋同一个值时用 `std::ranges::fill` 与 `std::views::join`,例见 `examples/structure.md`;逐格做不同处理时仍用 `row` / `col` 循环。
|
|
65
66
|
- 谓词只做判断,不改元素;需要修改时用 `transform`。
|
|
66
67
|
- 捕获列表只捕获需要的变量;默认 `[&]` 容易悬空,默认 `[=]` 容易复制大对象。
|
|
67
68
|
- 不在比较函数里写有副作用的逻辑;排序要求严格弱序,违反会导致未定义行为。
|
|
@@ -124,8 +124,7 @@ class TaskBoard {
|
|
|
124
124
|
| 按键 | `map`、`unordered_map` | 缺键则插入默认值 | 缺键则抛 `std::out_of_range` |
|
|
125
125
|
| 无下标 | `set`、`unordered_set`、`multimap` | 无 `operator[]` / `at` | — |
|
|
126
126
|
|
|
127
|
-
- 只在调用点已经证明下标合法时用 `[]`;否则用 `at`
|
|
128
|
-
- `std::span` 在 C++20 没有 `at`;`span::at` 到 C++26 才进入标准,不要假称 C++20 可用。
|
|
127
|
+
- 只在调用点已经证明下标合法时用 `[]`;否则用 `at` 或先做边界检查。坐标类型只保存位置,不拥有容器区间;区间判断属于持有尺寸的类型(如 `Board::IsInRange`)。`std::span` 在 C++20 没有 `at`,`span::at` 到 C++26 才进入标准。
|
|
129
128
|
- `map::operator[]` 会插入,不是“读取可能缺失的键”;只读查询用 `find` / `contains` / `at`。
|
|
130
129
|
- 契约失败的消息用 `std::format`,见 `references/exceptions.md`。
|
|
131
130
|
|