@chengzhao-dev/dsh-cpp-workflow 0.5.2 → 0.5.4
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 +2 -2
- package/README.md +2 -2
- package/package.json +5 -3
- package/skills/building-cpp/references/build-script.md +13 -1
- package/skills/building-cpp/steps/02-tooling.md +1 -1
- package/skills/developing-cpp/SKILL.md +10 -8
- package/skills/developing-cpp/examples/comments-declaration.md +60 -11
- package/skills/developing-cpp/examples/comments-implementation.md +4 -1
- package/skills/developing-cpp/examples/exceptions.md +6 -5
- package/skills/developing-cpp/references/comments.md +92 -60
- package/skills/developing-cpp/references/exceptions.md +21 -16
- package/skills/developing-cpp/references/file-template.md +5 -3
- package/skills/developing-cpp/references/google-cpp-style.md +2 -0
- package/skills/developing-cpp/references/priority.md +29 -0
- package/skills/developing-cpp/references/standard-library.md +1 -1
- package/skills/developing-cpp/scripts/checking-comments.mjs +73 -6
- package/skills/developing-cpp/steps/03-declaration.md +2 -2
- package/skills/developing-cpp/steps/04-implementation.md +2 -2
package/README.en.md
CHANGED
|
@@ -19,13 +19,13 @@ The exception material follows the C++ Core Guidelines' Error handling section a
|
|
|
19
19
|
Pinned npm install:
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
dsh plugin --profile web add npm:@chengzhao-dev/dsh-cpp-workflow@0.5.
|
|
22
|
+
dsh plugin --profile web add npm:@chengzhao-dev/dsh-cpp-workflow@0.5.4
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
The matching Git tag:
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
dsh plugin --profile web add github:chengzhao-dev/dsh-cpp-workflow#v0.5.
|
|
28
|
+
dsh plugin --profile web add github:chengzhao-dev/dsh-cpp-workflow#v0.5.4
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
The desktop app owns the `desktop` profile. If the command-line `add` is rejected, add the plugin in the app settings and use the same pinned npm version. A Git tag install uses the prebuilt `lib/` committed in the repository and runs no build on the target machine; the package has no install script that needs approval.
|
package/README.md
CHANGED
|
@@ -19,13 +19,13 @@
|
|
|
19
19
|
npm 安装钉死版本:
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
dsh plugin --profile web add npm:@chengzhao-dev/dsh-cpp-workflow@0.5.
|
|
22
|
+
dsh plugin --profile web add npm:@chengzhao-dev/dsh-cpp-workflow@0.5.4
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
同版本的 Git 标签:
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
dsh plugin --profile web add github:chengzhao-dev/dsh-cpp-workflow#v0.5.
|
|
28
|
+
dsh plugin --profile web add github:chengzhao-dev/dsh-cpp-workflow#v0.5.4
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
桌面应用独占 `desktop` profile。命令行 `add` 被拒绝时,在应用设置里添加插件,地址填同一个 npm 版本。Git 标签安装使用仓库内已构建的 `lib/`,不在目标机器上重新构建;包内也不含需要授权执行的安装脚本。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chengzhao-dev/dsh-cpp-workflow",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
4
4
|
"description": "C++11–C++23 工程流程技能包:源码四步、CMake 三步与构建三步,按项目声明的环境组合校验 · C++11–C++23 workflow skills for DeepSeek Harness",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -42,8 +42,9 @@
|
|
|
42
42
|
"icon": "./icon.svg",
|
|
43
43
|
"scripts": {
|
|
44
44
|
"build": "tsc -p tsconfig.json",
|
|
45
|
-
"test": "npm run build && node scripts/checking-skill-links.mjs && node skills/building-cpp/scripts/checking-encoding.mjs --quiet && node --test tests/*.test.mjs",
|
|
45
|
+
"test": "npm run build && node scripts/checking-skill-links.mjs && node skills/building-cpp/scripts/checking-encoding.mjs --quiet && node scripts/checking-github-workflows.mjs && node --test tests/*.test.mjs",
|
|
46
46
|
"check:encoding": "node skills/building-cpp/scripts/checking-encoding.mjs",
|
|
47
|
+
"check:workflows": "node scripts/checking-github-workflows.mjs",
|
|
47
48
|
"test:matrix": "bash scripts/checking-standard-matrix.sh",
|
|
48
49
|
"pack:check": "npm pack --dry-run"
|
|
49
50
|
},
|
|
@@ -79,6 +80,7 @@
|
|
|
79
80
|
"devDependencies": {
|
|
80
81
|
"@deepseek-ai/dsh-skill": ">=0.2.0-rc.1 <0.3.0-0",
|
|
81
82
|
"@types/node": "^22.20.5",
|
|
82
|
-
"typescript": "^5.9.3"
|
|
83
|
+
"typescript": "^5.9.3",
|
|
84
|
+
"yaml": "^2.9.1"
|
|
83
85
|
}
|
|
84
86
|
}
|
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
`assets/templates/projects/common/running-build.sh` 是 POSIX build-host 脚本,适用于 Linux、macOS 和 Windows + WSL。`host=windows` + `build_host=native` 不是本版本支持的组合:声明校验会直接拒绝,脚本也不会在原生 Windows 壳里运行。脚本只做配置、构建、CTest 与运行;格式化与静态检查在编辑时手动跑,不混进构建入口。
|
|
4
4
|
|
|
5
|
+
## 格式化与静态检查
|
|
6
|
+
|
|
7
|
+
不在 `running-build.sh` 里跑。构建脚本只做配置、构建、CTest 与运行;格式化是编辑时动作,不能让构建顺手改动源码。检查放在编辑器保存时或 CI 的独立步骤里。
|
|
8
|
+
|
|
9
|
+
检查(按工程实际源码根替换 `src/`、`include/`、`apps/`、`tests/`):
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
clang-format --dry-run --Werror src/*.cpp include/*.h
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
需要修复时单独运行 `clang-format -i`,再回看 diff。`clang-format` 只排版,不会把行尾注释搬到上一行;数据成员注释位置按 `developing-cpp/references/comments.md` 的规则和 `checking-comments.mjs` 检查。
|
|
16
|
+
|
|
5
17
|
## 用法
|
|
6
18
|
|
|
7
19
|
```bash
|
|
@@ -39,7 +51,7 @@
|
|
|
39
51
|
|
|
40
52
|
## 构建模式的错误合同
|
|
41
53
|
|
|
42
|
-
|
|
54
|
+
工程需要 Release 时另建构建目录;模式只改变诊断详略,不改变失败方式:
|
|
43
55
|
|
|
44
56
|
- 返回值:两种模式的返回值相同,调用方不按 `NDEBUG` 分支。
|
|
45
57
|
- 自定义异常:两种模式都抛;Debug 的 `what()` 可以带 `file:line`,Release 只留值。
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
- 配置注释写「为什么这样设」,不写「这一项是什么」;不写策略头、不指向文档路径。
|
|
21
21
|
- 格式化与静态检查只扫描项目源码:`src/`、`include/`、`apps/`、`tests/` 等实际存在的源码根;不扫描 `build/`、第三方目录、生成文件。
|
|
22
22
|
- `clang-tidy` 用 `-p build/<compiler>` 读取编译数据库,`--header-filter` 只列项目根下的顶层目录,避免把第三方头纳入;不要加 `--system-headers`,也不要用 clang-tidy 19 才加入、16 没有的 `--exclude-header-filter`。
|
|
23
|
-
- 格式化用 `--dry-run --Werror` 做检查;需要自动修复时单独运行 `-i
|
|
23
|
+
- 格式化用 `--dry-run --Werror` 做检查;需要自动修复时单独运行 `-i`,不要让构建脚本悄悄改源码。`clang-format` 只排版,不会把行尾注释搬到上一行;数据成员注释位置按 `developing-cpp/references/comments.md` 检查。
|
|
24
24
|
- 工程模板里不预置 `compile_commands.json`;它由定构建的 configure 生成,是构建产物。
|
|
25
25
|
|
|
26
26
|
## 情况
|
|
@@ -47,14 +47,16 @@ metadata:
|
|
|
47
47
|
|
|
48
48
|
先认这次改的是哪一类,再决定从哪一步进;一次改动混了两类时,按「写作 → 布局 → 排版 → 润色」的次序拆开做,前一类落定并经检查再进下一类。
|
|
49
49
|
|
|
50
|
-
| 类型 | 典型请求 |
|
|
51
|
-
| --- | --- | --- |
|
|
52
|
-
| 写作 |
|
|
53
|
-
| 布局 | 换文件位置、拆头文件、调声明顺序、补测试 target |
|
|
54
|
-
| 排版 |
|
|
55
|
-
| 润色 | 只改命名、只改注释措辞 |
|
|
50
|
+
| 类型 | 典型请求 | 入口步骤 |
|
|
51
|
+
| --- | --- | --- |
|
|
52
|
+
| 写作 | 新增类型、函数或文件;改可观察行为;改库与程序的边界;补构造后置条件与成员事实 | `steps/01-behavior.md` |
|
|
53
|
+
| 布局 | 换文件位置、拆头文件、调声明顺序、补测试 target;改注释位置与行尾形态 | `steps/03-declaration.md` |
|
|
54
|
+
| 排版 | 只改缩进、行宽、包含顺序或注释符号 | `steps/03-declaration.md` |
|
|
55
|
+
| 润色 | 只改命名、只改注释措辞 | `steps/03-declaration.md` |
|
|
56
|
+
|
|
57
|
+
只改格式时不动行为,只改注释时不动签名;条文由入口步骤按需打开。
|
|
56
58
|
|
|
57
|
-
|
|
59
|
+
同一处命中多条规则时,先按 `references/priority.md` 的五级次序判:行为与契约 > 结构 > 清晰 > 一致 > 形式。高层未解决,不先做低层润色;低层改动碰到高层事实就停手退回。
|
|
58
60
|
|
|
59
61
|
## 完成判据
|
|
60
62
|
|
|
@@ -62,5 +64,5 @@ metadata:
|
|
|
62
64
|
- 第 2 步的库 target 不依赖终端、GUI 或脚本;程序 target 只做装配和输入输出;共享抽象有第二个真实使用方。
|
|
63
65
|
- 第 3 步的公共头文件自包含,命名、声明顺序和异常声明符合 `references/` 的条文。
|
|
64
66
|
- 第 4 步的实现只兑现声明;资源由 RAII 管理;失败是正常结果时用返回值,函数无法完成任务时抛异常,不拿异常做正常分支。
|
|
65
|
-
- 源码注释符合 `references/comments.md`
|
|
67
|
+
- 源码注释符合 `references/comments.md` 的四节规范,`node "<skill-dir>/scripts/checking-comments.mjs" --root <项目根>` 无输出。
|
|
66
68
|
- 没有改 `CMakeLists.txt`、`.clang-*`、构建脚本,也没有把外部资料路径或项目专属类名写进通用条文。
|
|
@@ -15,13 +15,13 @@
|
|
|
15
15
|
|
|
16
16
|
```cpp
|
|
17
17
|
//===----------------------------------------------------------------------===//
|
|
18
|
-
// board.h -
|
|
18
|
+
// board.h - 定义井字棋棋盘、格子坐标与格子类型。
|
|
19
19
|
//
|
|
20
|
-
//
|
|
20
|
+
// 棋盘保存九个棋盘格子的状态,提供只读查询、落子与整盘清空。
|
|
21
21
|
//===----------------------------------------------------------------------===//
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
文件头只介绍这个文件里有什么,不写作者和变更史,也不把读者指去另一个文件。
|
|
25
25
|
|
|
26
26
|
## 类型注释:概念与约定,不是功能清单
|
|
27
27
|
|
|
@@ -68,6 +68,29 @@ class Game {
|
|
|
68
68
|
|
|
69
69
|
纯数据结构和枚举只写一句名词短语;类有不变量,且第二句不是重复 `cells_`、`kBoardRows` 这类领域尺寸时,才保留第二段。函数各自的契约下沉到函数注释,设计理由与分层写进书稿。
|
|
70
70
|
|
|
71
|
+
## 构造函数与析构函数:标签 + 职责
|
|
72
|
+
|
|
73
|
+
**差**:
|
|
74
|
+
|
|
75
|
+
```cpp
|
|
76
|
+
/// 构造函数:创建一个空棋盘。
|
|
77
|
+
Board() = default;
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
「创建」复述了声明,读者仍不知道构造完成后棋盘里是什么、`cells_` 的每个元素是否已经有值。
|
|
81
|
+
|
|
82
|
+
**好**:
|
|
83
|
+
|
|
84
|
+
```cpp
|
|
85
|
+
/// 默认构造函数:得到空棋盘,每个格子都是 kEmpty。
|
|
86
|
+
Board() = default;
|
|
87
|
+
|
|
88
|
+
/// 析构函数:保证通过接口指针正确删除派生类对象。
|
|
89
|
+
virtual ~Ai();
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`= default` 本身不说明成员被初始化成什么;`cells_{}` 把每个 `CellType` 初始化成 `kEmpty`,这条后置条件从签名上看不出来。标签说明这是哪一类特殊成员,冒号后写它建立的状态或承担的职责。
|
|
93
|
+
|
|
71
94
|
## 风格理由不进注释
|
|
72
95
|
|
|
73
96
|
**差**(在十几个函数上各写一遍):
|
|
@@ -212,19 +235,45 @@ enum class GameResult : std::uint8_t {
|
|
|
212
235
|
**差**:
|
|
213
236
|
|
|
214
237
|
```cpp
|
|
215
|
-
Board board_;
|
|
216
|
-
|
|
217
|
-
GameResult result_;
|
|
238
|
+
Board board_; // 对局棋盘。
|
|
239
|
+
std::array<std::array<CellType, kBoardCols>, kBoardRows> cells_{}; // 存储棋盘格子。
|
|
218
240
|
```
|
|
219
241
|
|
|
242
|
+
前一句把名字翻译了一遍;后一句只复述声明,没有说清两层下标各是什么。
|
|
243
|
+
|
|
220
244
|
**好**:
|
|
221
245
|
|
|
222
246
|
```cpp
|
|
223
|
-
|
|
224
|
-
|
|
247
|
+
/// 棋盘格子数组:外层下标是行,内层下标是列。
|
|
248
|
+
std::array<std::array<CellType, kBoardCols>, kBoardRows> cells_{};
|
|
249
|
+
/// 当前玩家:开局由 X 先手。
|
|
250
|
+
Player current_player_ = Player::kCross;
|
|
251
|
+
/// 对局结果:落子被拒时保持原值。
|
|
252
|
+
GameResult result_ = GameResult::kInProgress;
|
|
253
|
+
/// 落子历史:按落子次序保存。
|
|
254
|
+
std::vector<Move> history_;
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
主注释写在声明上一行,类数据成员(含私有)统一用 `///`;第一句先说是哪个成员,再用全角冒号补名字和类型看不出的范围、初值含义、轴向、哨兵与改写时机。行尾不写补充;`///<` 只给枚举项和 `struct` 短字段。名字已经说明存放对象的 `board_` 不写「对局棋盘」。
|
|
258
|
+
|
|
259
|
+
## 完整声明:后置条件与轴向
|
|
260
|
+
|
|
261
|
+
把上面的规则放在一起,`Board` 的声明是这样:
|
|
262
|
+
|
|
263
|
+
```cpp
|
|
264
|
+
/// 井字棋棋盘。
|
|
265
|
+
class Board {
|
|
266
|
+
public:
|
|
267
|
+
/// 默认构造函数:得到空棋盘,每个格子都是 kEmpty。
|
|
268
|
+
Board() = default;
|
|
269
|
+
|
|
270
|
+
private:
|
|
271
|
+
/// 棋盘格子数组:外层下标是行,内层下标是列。
|
|
272
|
+
std::array<std::array<CellType, kBoardCols>, kBoardRows> cells_{};
|
|
273
|
+
};
|
|
225
274
|
```
|
|
226
275
|
|
|
227
|
-
|
|
276
|
+
类注释只说这个类型是什么;构造注释补的是 `= default` 配合 `cells_{}` 才建立的后置条件;`cells_` 的上一行先说是棋盘格子数组,再用全角冒号补轴向。行尾不写补充;`{}` 带来的空格子已经由构造注释表达。三处各说一件事,没有一句复述签名。
|
|
228
277
|
|
|
229
278
|
## 配置注释
|
|
230
279
|
|
|
@@ -264,8 +313,8 @@ Standard: Latest
|
|
|
264
313
|
//===----------------------------------------------------------------------===//
|
|
265
314
|
// board.h - 棋盘状态与规则判断
|
|
266
315
|
//
|
|
267
|
-
//
|
|
316
|
+
// 定义 Board 的查询、落子与整盘清空。
|
|
268
317
|
//===----------------------------------------------------------------------===//
|
|
269
318
|
```
|
|
270
319
|
|
|
271
|
-
|
|
320
|
+
变更史进提交说明,外部资料进文档,团队流程进规范文件;注释只写这个文件或这个类型自己的内容,不把读者指去别的文件。
|
|
@@ -30,20 +30,21 @@ struct ParseResult {
|
|
|
30
30
|
|
|
31
31
|
## 异常类型与消息
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
**差**:抛字符串,或把来源、条件和值揉成一句口语;调用方接到的 `what()` 既不稳定,也无法定位是哪一步失败。
|
|
34
34
|
|
|
35
35
|
```cpp
|
|
36
36
|
throw "bad index";
|
|
37
|
+
throw std::out_of_range{std::format("GetTask: 下标 {} 越界", index)};
|
|
37
38
|
```
|
|
38
39
|
|
|
39
|
-
|
|
40
|
+
**好**:语义已经由标准类型给出时直接用标准类型;消息按「来源、条件、值、限度」组织,语言由项目约定,入口需要面向用户的前缀时只加前缀。
|
|
40
41
|
|
|
41
42
|
```cpp
|
|
42
|
-
throw std::out_of_range{
|
|
43
|
-
|
|
43
|
+
throw std::out_of_range{std::format(
|
|
44
|
+
"GetTask: index is out of range (index={})", index)};
|
|
44
45
|
```
|
|
45
46
|
|
|
46
|
-
|
|
47
|
+
消息写操作和关键值;示例中的英文消息条件小写、句末不加句号,中文消息按中文标点,两者槽位顺序不变;有实际值才写括号,有分号时条件必须是小句。`std::format` 只是构造消息的方式,句式见 `references/exceptions.md` 的「消息」节。项目若定义 `Error` 基类,按该文件先定基类再派生,调用方捕获基类接住整族。
|
|
47
48
|
|
|
48
49
|
## Debug 与 Release 的三层合同
|
|
49
50
|
|
|
@@ -1,63 +1,28 @@
|
|
|
1
1
|
# 注释
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
注释按写作、布局、排版、润色四节组织;文件头、类型、函数与实现注释是注释所在的位置,配置文件的注释另算。位置越靠外,越讲概念与契约;越靠里,越讲实现理由。同一件事只写一次。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
按四类改动查:写作决定写什么,布局决定放哪一层哪一行,排版决定符号、宽度与标签,润色删复述、统一句式。四步都可能删句子,但后置条件和成员约定只有写作能定,润色不得以「精简」为由删掉。
|
|
6
|
+
|
|
7
|
+
## 写作:只写声明里看不出的事实
|
|
6
8
|
|
|
7
9
|
- **第一句先写结果、限制或检查项**:类型用名词短语写它是什么;普通函数写正常返回什么、失败时返回什么或抛什么;布尔查询与 `operator==` 写「检查…是否…」,不写「返回 true/false」。条件、参数与实现理由放第二句以后。
|
|
8
10
|
- 公共注释回答「这是什么」和「调用方必须遵守什么」;实现注释回答「为什么这样写」和「边界在哪里」。设计取舍、方案对比与演进历史写到书稿、README 或提交说明。
|
|
9
11
|
- 用中文散文写完整句子,句号结尾;标识符、标准库类型、`@param` 这类标签保持原文。
|
|
10
|
-
- 行宽跟项目 `.clang-format` 的 `ColumnLimit` 走(Google 基线是 80 列,中文按两列计);横幅与列宽同宽,不另设数字。
|
|
11
12
|
- 写之前三问:删掉这句,读者会误解设计或做错事吗?这句只是复述签名或代码吗?这个限制能不能由类型、`const` 或 `[[nodiscard]]` 表达?第一问为否就删,第二问为是就删,第三问为是就改代码。
|
|
12
13
|
- 命名、分文件、`enum class`、异常与结果类型这类全项目风格约定只写进项目文档与读者正文;注释不替项目解释风格。
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
- `///` 只给声明:头文件里的类型、函数、枚举值与公开数据成员(行尾补一句用 `///<`)。调用方和 Doxygen 从这里取契约。
|
|
17
|
-
- `//` 给实现:函数体内的步骤与边界说明、局部变量、文件头横幅、配置文件的说明。
|
|
18
|
-
- 不给局部变量和函数体里的语句写 `///`;文件头也不写成 `///`,否则编辑器会把它挂到紧随其后的第一个声明上。
|
|
19
|
-
- 判断看一句话写的是「调用方要遵守的承诺」还是「维护者要知道的写法」:前者用 `///`,后者用 `//`。
|
|
20
|
-
|
|
21
|
-
## 长度预算
|
|
22
|
-
|
|
23
|
-
| 层 | 行数 | 第一句 | 写什么 |
|
|
24
|
-
| --- | --- | --- | --- |
|
|
25
|
-
| 文件头 | 3–6 | 这个文件是什么 | 由哪几部分组成、不负责什么 |
|
|
26
|
-
| 类型注释 | 2–14 | 名词短语:这个类型或取值是什么 | 只有类的不变量 |
|
|
27
|
-
| 函数注释 | 3–10 | 正常返回什么、失败时返回或抛什么、检查什么 | 参数与失败方式 |
|
|
28
|
-
| 数据成员 | 行尾 1 句,名字与类型已自明时不写 | 名字和类型看不出的范围、初值含义 | 什么时候变 |
|
|
29
|
-
| 实现注释 | 1–3 | 为什么这样写或边界在哪里 | 代价 |
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
## 文件头
|
|
33
|
-
|
|
34
|
-
每个手写源文件开头写一段文件头:这个文件负责什么、分成哪几块、不负责什么。不写作者、版权年份、变更日志和外部资料路径。
|
|
35
|
-
|
|
36
|
-
```cpp
|
|
37
|
-
//===----------------------------------------------------------------------===//
|
|
38
|
-
// board.h - 定义井字棋棋盘。
|
|
39
|
-
//
|
|
40
|
-
// 棋盘只保存格子状态与规则判断,不含对局流程;对局流程在 game.h。
|
|
41
|
-
//===----------------------------------------------------------------------===//
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
- C++ 用 `//`,CMake 用 `#`,YAML/JSON 用 `#` 或 `//`。
|
|
45
|
-
- 横幅宽度等于 `ColumnLimit`:`//===` 起头、`===//` 收尾,中间补 `-`;章节横幅同一格式。
|
|
46
|
-
- 超过约 200 行的文件用章节横幅分组,组名是读者要跳转的锚点;一个文件不超过 8 个横幅。
|
|
47
|
-
|
|
48
|
-
## 类型注释
|
|
15
|
+
### 类型:名词短语与不变量
|
|
49
16
|
|
|
50
17
|
对象是类、结构体、枚举和类型别名。第一句是名词短语:类写「这个类型是什么」,纯数据结构和枚举写「这个取值表示什么」。
|
|
51
18
|
|
|
52
19
|
只有类有不变量,而且不重复领域尺寸时,才加第二段「限制」。纯数据结构和枚举不写第二句。
|
|
53
20
|
|
|
54
|
-
|
|
55
21
|
```cpp
|
|
56
22
|
/// 井字棋棋盘。
|
|
57
23
|
class Board {
|
|
58
24
|
```
|
|
59
25
|
|
|
60
|
-
|
|
61
26
|
禁止事项:
|
|
62
27
|
|
|
63
28
|
- 不写成员清单,不按声明顺序复述接口。函数各自的契约写在函数上。
|
|
@@ -68,14 +33,39 @@ class Board {
|
|
|
68
33
|
- 领域尺寸不写进类型注释:格子数由 `cells_` 的声明表达,棋盘边长由 `kBoardRows`、`kBoardCols` 表达。
|
|
69
34
|
- 枚举与只承载数据的结构体:第一句是名词短语,写这个取值表示什么;需要条件时用 `///<` 补一句可核实的条件。
|
|
70
35
|
|
|
71
|
-
|
|
36
|
+
### 构造函数与析构函数:先写标签,再写职责
|
|
37
|
+
|
|
38
|
+
特殊成员用标签开头:`默认构造函数:`、`拷贝构造函数:`、`移动构造函数:`、`析构函数:`;其他构造函数写 `构造函数:`。标签后写构造完成后的后置条件,或析构函数负责什么。`= default` 配合成员初始化器建立的不变量,签名上看不见,必须写成后置条件。只写读者能观察到的事实,不写「创建对象」「初始化成员」这类复述。
|
|
39
|
+
|
|
40
|
+
```cpp
|
|
41
|
+
/// 默认构造函数:得到空棋盘,每个格子都是 kEmpty。
|
|
42
|
+
Board() = default;
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`cells_{}` 把每个 `CellType` 初始化成 `kEmpty`,这条后置条件不能从 `Board() = default` 读出。标签说明这是哪一类特殊成员,不重复类名;非默认构造同样写建立的不变量与失败方式。
|
|
46
|
+
|
|
47
|
+
### 数据成员:上一行 `///`,先写名词再写约定
|
|
48
|
+
|
|
49
|
+
类数据成员的主注释写在声明上一行,统一用 `///`,私有成员也一样。第一句先写这个成员是什么,再在同一句里补类型和名字看不出的约定:轴向、单位、哨兵值、所有权、外部约定、初值含义或改写时机与次序。有补充时写成一句 `/// 名词:约定。`,冒号用全角 `:`,句号结束;修饰必须依附于被修饰的名词,不能单独占一行。行尾不写 `//` 或 `///<`;`///<` 只留给枚举项和 `struct` 的短字段。名字和类型已经说清时不写。
|
|
72
50
|
|
|
73
|
-
|
|
51
|
+
```cpp
|
|
52
|
+
/// 棋盘格子数组:外层下标是行,内层下标是列。
|
|
53
|
+
std::array<std::array<CellType, kBoardCols>, kBoardRows> cells_{};
|
|
54
|
+
/// 当前玩家:开局由 X 先手。
|
|
55
|
+
Player current_player_ = Player::kCross;
|
|
56
|
+
/// 对局结果:落子被拒时保持原值。
|
|
57
|
+
GameResult result_ = GameResult::kInProgress;
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`board_` 这类名字已经说明存放对象的成员不写「对局棋盘」;`cells_` 不写「存储…」这种把声明读一遍的句子,但上一行要先说它是棋盘格子数组,再补轴向、哨兵和改写次序。`clang-format` 不会把行尾注释搬到上一行,位置由本规则和检查器保证。
|
|
61
|
+
|
|
62
|
+
### 函数:两段式契约
|
|
63
|
+
|
|
64
|
+
短函数可以只留第一段:
|
|
74
65
|
|
|
75
66
|
1. 结果、限制或检查项:普通函数写返回什么、失败时返回或抛什么;布尔查询与 `operator==` 写「检查…是否…」。
|
|
76
67
|
2. 契约:`@param`、`@return`、`@throws`,写范围、单位、所有权和失败方式。
|
|
77
68
|
|
|
78
|
-
|
|
79
69
|
```cpp
|
|
80
70
|
/// 放置成功返回 true;坐标越界、目标已占或要写空格子时返回 false,不抛异常。
|
|
81
71
|
///
|
|
@@ -90,36 +80,78 @@ class Board {
|
|
|
90
80
|
- `@throws` 必须与 `references/exceptions.md` 一致;不抛异常的函数不写。
|
|
91
81
|
- 私有函数同样写契约,不能默认调用者知道前置条件;输出参数标 `[out]`,既读又写标 `[in,out]`。
|
|
92
82
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
数据成员在名字和类型已经说清时不写行尾注释。只保留需要说明单位、哨兵值、外部约定或所有权的成员:写「单位或哨兵 + 外部约定 + 什么时候改写」,同类成员对齐。`cells_`、`count` 这类不复述存储布局;`current_player_` 与 `result_` 这类需要读者知道取值含义或改写时机的保留。
|
|
83
|
+
### 实现:阶段边界与为什么
|
|
96
84
|
|
|
97
|
-
|
|
98
|
-
Player current_player_ = Player::kCross; // 开局 X 先手;每次落子后切换。
|
|
99
|
-
GameResult result_ = GameResult::kInProgress; // 终局后不再改写。
|
|
100
|
-
```
|
|
85
|
+
`.cpp` / `.cc` 里,重要函数按空行分段,每一段上方写一句 `//`。句子写边界、失败后果或为什么停在这里,不把下一行条件翻译一遍;`IsWin` 这种单行函数不配注释。只写删掉会让下一次修改得到错误结果、而代码本身看不出的内容。
|
|
101
86
|
|
|
102
|
-
|
|
87
|
+
大型工程的共识是解释意图而不是逐步翻译:[Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html#Implementation_Comments) 要求注释解释代码在做什么,不描述代码本身;[LLVM Coding Standards](https://www.llvm.org/docs/CodingStandards.html#commenting) 要求写意图与理由。阶段注释写的就是这两类信息。
|
|
103
88
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
唯一保留的一类:删掉这句话,下一次修改会得到错误结果,而且代码本身看不出那个结果。句子要写出错误结果,不写「省掉一次扫线」这类口号。典型位置:
|
|
89
|
+
典型位置:
|
|
107
90
|
|
|
108
91
|
- 次序依赖:先判胜再判满,否则最后一子会变成平局。
|
|
109
92
|
- 类型收窄与解析:先按 64 位读再收窄;解析失败后必须丢掉整行。
|
|
93
|
+
- 三种拒绝都在赋值前返回,棋盘保持原样。
|
|
110
94
|
- 状态不变量、外部格式、回退路径、刻意重复。
|
|
111
95
|
- 共享方式:`thread_local` 之类的可见性。
|
|
112
96
|
|
|
113
97
|
循环体里逐行复述 `push_back`、`return` 的注释一律删;不写变更史、外部资料路径和注释掉的代码。CMake 的契约注释不在这里,写法见 `../../configuring-cmake/references/cmake.md`。
|
|
114
98
|
|
|
115
|
-
|
|
99
|
+
### 长度预算
|
|
116
100
|
|
|
117
|
-
|
|
101
|
+
| 层 | 行数 | 第一句 | 写什么 |
|
|
102
|
+
| --- | --- | --- | --- |
|
|
103
|
+
| 文件头 | 3–6 | 文件名 - 这个文件定义或实现什么 | 本文件由哪几部分组成 |
|
|
104
|
+
| 类型注释 | 2–14 | 名词短语:这个类型或取值是什么 | 只有类的不变量 |
|
|
105
|
+
| 特殊成员 | 1–3 | 标签(默认/拷贝/移动构造函数、析构函数) | 后置条件或职责 |
|
|
106
|
+
| 函数注释 | 3–10 | 正常返回什么、失败时返回或抛什么、检查什么 | 参数与失败方式 |
|
|
107
|
+
| 数据成员 | 上一行 1 句(`/// 名词:约定`) | 名字和类型看不出的范围、初值含义、轴向或哨兵 | 什么时候变 |
|
|
108
|
+
| 实现注释 | 每个空行阶段 1 句 | 边界、失败后果或为什么停在这里 | 只给重要函数 |
|
|
118
109
|
|
|
119
|
-
|
|
110
|
+
## 布局:放在哪一层、哪一行
|
|
120
111
|
|
|
121
|
-
|
|
112
|
+
- `///` 只给声明:头文件里的类型、函数、枚举值、公开与私有数据成员。数据成员的主注释写在上一行;行尾 `///<` 只用于枚举项和 `struct` 的短字段。调用方和 Doxygen 从这里取契约。
|
|
113
|
+
- `//` 给实现:函数体内的阶段与边界说明、局部变量、文件头横幅、配置文件的说明。
|
|
114
|
+
- 不给局部变量和函数体里的语句写 `///`;文件头也不写成 `///`,否则编辑器会把它挂到紧随其后的第一个声明上。
|
|
115
|
+
- 判断看一句话写的是「调用方要遵守的承诺」还是「维护者要知道的写法」:前者用 `///`,后者用 `//`。
|
|
116
|
+
- 位置固定:文件头在最上方;类型注释紧贴类型声明;特殊成员(构造、析构、拷贝与移动)排在其他函数之前;函数注释紧贴函数声明;数据成员的主注释紧贴声明上一行,不写行尾补充。
|
|
117
|
+
- 类数据成员(含私有)的主注释用 `///`;行尾 `///<` 只给枚举项和 `struct` 的短字段。
|
|
118
|
+
- 注释和它说明的声明之间不留空行;同一个声明块内,空行只用来分组。
|
|
122
119
|
|
|
123
|
-
|
|
120
|
+
### 文件头
|
|
121
|
+
|
|
122
|
+
每个手写源文件开头写一段文件头:第一行是「文件名 - 本文件定义或实现什么」,后面只写本文件自己的组成部分或边界。删掉把读者指去另一个头文件、另一个目录或另一层的句子,也删掉「这一层不负责什么」。不写作者、版权年份、变更日志和外部资料路径。
|
|
123
|
+
|
|
124
|
+
```cpp
|
|
125
|
+
//===----------------------------------------------------------------------===//
|
|
126
|
+
// board.h - 定义井字棋棋盘、格子坐标与格子类型。
|
|
127
|
+
//
|
|
128
|
+
// 棋盘保存九个棋盘格子的状态,提供只读查询、落子与整盘清空。
|
|
129
|
+
//===----------------------------------------------------------------------===//
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
- C++ 用 `//`,CMake 用 `#`,YAML/JSON 用 `#` 或 `//`。
|
|
133
|
+
- 超过约 200 行的文件用章节横幅分组,组名是读者要跳转的锚点;一个文件不超过 8 个横幅。
|
|
134
|
+
- 文件头 3–6 行;第一行点文件名,后面只写本文件的范围与组成,不写实现步骤、作者和变更史。
|
|
135
|
+
|
|
136
|
+
## 排版:符号、宽度、横幅与标签
|
|
137
|
+
|
|
138
|
+
- 行宽跟项目 `.clang-format` 的 `ColumnLimit` 走(Google 基线是 80 列,中文按两列计);横幅与列宽同宽,不另设数字。
|
|
139
|
+
- 横幅宽度等于 `ColumnLimit`:`//===` 起头、`===//` 收尾,中间补 `-`;章节横幅同一格式。
|
|
140
|
+
- 行尾注释与代码之间空两格;`///<` 只接在枚举项和 `struct` 短字段声明行尾。
|
|
141
|
+
- `@param`、`@return`、`@throws` 标签保持英文;`@param` 后先写参数名,再写一句。
|
|
142
|
+
- `TODO:` 后先写定位(阶段名或 issue 号),再写一个短横线,再写动作;不写 `TODO(某人)`,不写没有动作的 TODO。
|
|
143
|
+
- `.clang-format`、`.clang-tidy`、`.clangd`、CMake 的配置项旁边写「为什么这样设」,不写「这一项是什么」;配置注释贴在被解释的键上方。
|
|
144
|
+
- 禁止:大段表格、装饰图案、表情;外部资料路径、跨仓库引用、团队流程;注释掉的代码;注释元叙述(「其余内容见某处」「两处不重复」)。
|
|
124
145
|
- `///`、`@param`、横幅不进入 `.qmd`、YAML、CSS。
|
|
125
|
-
|
|
146
|
+
|
|
147
|
+
## 润色:删复述,统一句式
|
|
148
|
+
|
|
149
|
+
- 类型第一句是名词短语;布尔查询与 `operator==` 用「检查…是否…」;普通函数第一句写结果、失败方式或检查项。
|
|
150
|
+
- 特殊成员保留标签(`默认构造函数:`、`析构函数:` 等),但禁止只写「构造函数:创建对象」这类没有后置条件或职责的句子,也禁止复述类名、签名、成员清单。
|
|
151
|
+
- 禁止在类型注释里写领域尺寸(3×3、九个格子、边长);尺寸由成员声明和常量表达。
|
|
152
|
+
- 禁止把成员功能清单按声明顺序抄一遍;函数契约写在各自函数上。
|
|
153
|
+
- 同一事实只写一处:声明注释、实现注释、书稿和 README 不各讲一遍。
|
|
154
|
+
- 删掉注释后读者不会做错事、或注释只重复签名时,删;但写作节要求保留的后置条件、轴向、哨兵和改写时机不在此列。
|
|
155
|
+
- 只改注释时仍走第 3、4 步,然后回读:文件头 → 类型(名词短语 + 只有类的不变量)→ 特殊成员(标签 + 后置条件/职责)→ 函数(结果/失败方式/检查项 + 参数)→ 数据成员(只读上一行 `///`:名词 + 全角冒号 + 约定;行尾不应有注释)→ 实现(每个空行阶段的边界或理由)→ 术语与新符号是否真实存在 → 运行 `checking-comments.mjs` 兜底。
|
|
156
|
+
- 注释写给维护者,正文写给读者;设计理由、方案比较与算法取舍写在正文或实现注释里,公开接口只留契约。
|
|
157
|
+
- 禁止用注释代替类型、`const` 或 `[[nodiscard]]`。
|
|
@@ -11,11 +11,7 @@ Google C++ Style Guide 明确写「We do not use C++ exceptions」。本工作
|
|
|
11
11
|
|
|
12
12
|
## 先定错误策略
|
|
13
13
|
|
|
14
|
-
E.1
|
|
15
|
-
|
|
16
|
-
- 这个函数不能完成自己的任务时,调用方还能继续吗?
|
|
17
|
-
- 失败是「正常的另一种结果」,还是「invariant 被破坏、继续执行没有意义」?
|
|
18
|
-
- 失败信息需要跨层携带,还是只需在边界打印?
|
|
14
|
+
E.1 要求尽早决定错误处理策略:先问调用方还能不能继续、失败是正常结果还是 invariant 已破坏、消息是否需要跨层携带,再写接口。
|
|
19
15
|
|
|
20
16
|
策略一旦定下,声明、实现、测试和入口都用同一套;不要在同一层里一半返回错误码、一半抛异常。
|
|
21
17
|
|
|
@@ -52,16 +48,7 @@ class ParseError : public std::runtime_error {
|
|
|
52
48
|
|
|
53
49
|
## 抛出与捕获
|
|
54
50
|
|
|
55
|
-
E.15:按值抛出、按引用捕获(通常是 `const` 引用);不要抛裸指针;重新抛出用 `throw;`,不要 `throw error;`
|
|
56
|
-
|
|
57
|
-
```cpp
|
|
58
|
-
try {
|
|
59
|
-
ReadConfig(path);
|
|
60
|
-
} catch (const FileReadError& error) {
|
|
61
|
-
// 能补偿才捕获;不能补偿就让异常继续向上。
|
|
62
|
-
throw;
|
|
63
|
-
}
|
|
64
|
-
```
|
|
51
|
+
E.15:按值抛出、按引用捕获(通常是 `const` 引用);不要抛裸指针;重新抛出用 `throw;`,不要 `throw error;` 丢类型。捕获后能补偿才捕获,不能补偿就让异常继续向上。
|
|
65
52
|
|
|
66
53
|
E.17:不要在每个函数里捕获所有异常。E.18:少用显式 `try/catch`,优先 RAII 和 `finally` 风格的清理对象。E.19:没有资源句柄时,用 `final_action` 之类的 scope guard 做清理。
|
|
67
54
|
|
|
@@ -88,7 +75,25 @@ int main() {
|
|
|
88
75
|
}
|
|
89
76
|
```
|
|
90
77
|
|
|
91
|
-
|
|
78
|
+
消息要能让调用者定位操作和关键值,句式见下节;`std::format` 只是构造 `what()` 或边界消息的方式。C++20 及更早没有 `std::print`;默认 C++23 且标准库支持时,可在程序边界用 `std::print`,库代码仍只组合消息、不直接打印。
|
|
79
|
+
|
|
80
|
+
## 消息
|
|
81
|
+
|
|
82
|
+
`what()` 是给程序员看的诊断文本,句式是「标签: 条件 (值); 限度」,四个槽位不动。标签是标识符,成员写 `Type::Function`,自由函数只写函数名,冒号后一个空格。先写清哪一步失败、失败条件是什么、手上有什么值,再写允许的限度;语言由项目约定,下面的示例用英文。条件不写 Error / Failed,不写空泛的「失败」;有分号时条件必须是带动词的小句,写 `board position is out of range`,不写无动词片段。括号只放导致失败的实际值,写 `name=value`,逗号后一个空格,等号两侧不空格;没有实际值就不写括号。分号后只放限度,且必须自成小句,如 `board is {}x{}`;没有限度就不写分号。
|
|
83
|
+
|
|
84
|
+
下面的英文示例只用 `:`、`()`、`,`、`=`、`;` 五个 ASCII 符号:冒号紧贴标签、后接一个空格;左括号前一个空格、内侧不空;逗号前不空、后一个空格;等号两侧不空;分号写在右括号外、前不空、后一个空格。不用第二个冒号、句号、问号、感叹号、破折号或 `×`;尺寸写 `3x3`,`out of range` 不加连字符。中文消息按中文标点,但槽位顺序和「值只放实际值」不变。
|
|
85
|
+
|
|
86
|
+
有插值才用 `std::format`,没有洞的消息用字符串字面量。`std::out_of_range` 与 `std::logic_error` 在构造时复制消息,不能把格式化推迟到 `what()`。格式串只填手头的整数和短文本;格式化若抛出,调用方看到 `std::format_error` 或 `std::bad_alloc`,所以不在抛出表达式里做 I/O。自有异常类型才在 `what()` 里惰性格式化并兜底,本仓库不为此新增类型。
|
|
87
|
+
|
|
88
|
+
`what()` 不是分支接口;调用方要数字就放进类型成员,不解析字符串。入口可以在消息前加面向用户的前缀,库代码不打印日志。
|
|
89
|
+
|
|
90
|
+
```cpp
|
|
91
|
+
throw std::out_of_range{std::format(
|
|
92
|
+
"Board::GetCellType: board position is out of range (row={}, col={}); board is {}x{}",
|
|
93
|
+
position.row, position.col, kBoardRows, kBoardCols)};
|
|
94
|
+
throw std::logic_error{"Ai::Choose: game is already over"};
|
|
95
|
+
throw std::logic_error{"EmptyCellsOrThrow: no empty cell left to place"};
|
|
96
|
+
```
|
|
92
97
|
|
|
93
98
|
## 声明注释
|
|
94
99
|
|
|
@@ -47,7 +47,9 @@
|
|
|
47
47
|
|
|
48
48
|
```cpp
|
|
49
49
|
//===----------------------------------------------------------------------===//
|
|
50
|
-
//
|
|
50
|
+
// thing_table.h - 定义 ThingTable 与它用到的状态。
|
|
51
|
+
//
|
|
52
|
+
// 头文件放常量、枚举、结构体与类声明。
|
|
51
53
|
//===----------------------------------------------------------------------===//
|
|
52
54
|
|
|
53
55
|
#ifndef PROJECT_PATH_FILE_H_
|
|
@@ -81,7 +83,7 @@ class ThingTable {
|
|
|
81
83
|
#endif // PROJECT_PATH_FILE_H_
|
|
82
84
|
```
|
|
83
85
|
|
|
84
|
-
-
|
|
86
|
+
- 文件头不写版权、作者或变更日志;守卫外只放文件头,文件头只介绍本文件。
|
|
85
87
|
- 头文件自包含:自己用到的类型、宏和标准库头都直接包含,不依赖调用方先包含什么。
|
|
86
88
|
- 不把 `using namespace`、非 inline 普通全局变量、匿名命名空间或 `static` 定义放进头文件。
|
|
87
89
|
- 模板、`constexpr` 和类内短函数可以留在头文件;普通函数体放实现文件。
|
|
@@ -98,7 +100,7 @@ class ThingTable {
|
|
|
98
100
|
|
|
99
101
|
```cpp
|
|
100
102
|
//===----------------------------------------------------------------------===//
|
|
101
|
-
//
|
|
103
|
+
// thing_table.cpp - 实现 ThingTable 的添加与查询。
|
|
102
104
|
//===----------------------------------------------------------------------===//
|
|
103
105
|
|
|
104
106
|
#include "project/path/thing.h"
|
|
@@ -85,6 +85,8 @@
|
|
|
85
85
|
|
|
86
86
|
Google 原文不使用 C++ 异常;本工作流只在开发阶段的源码指导中按 `references/exceptions.md` 采用 C++ Core Guidelines 的 Error handling,插件运行时不编译、不执行这套策略。其余条目仍以 Google 原文为准。
|
|
87
87
|
|
|
88
|
+
异常消息的组织方式见 `references/exceptions.md` 的「消息」节。
|
|
89
|
+
|
|
88
90
|
## 输出与格式化
|
|
89
91
|
|
|
90
92
|
- 组合消息以 `std::format` 为主:`std::format("任务 {} 进入状态 {}", task, state)`。
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# 规则冲突时的优先级
|
|
2
|
+
|
|
3
|
+
源码规则不是同一层的检查表。两条规则冲突时,先保住更高层,再谈低层的「更短」「更统一」「更整齐」。
|
|
4
|
+
|
|
5
|
+
| 级别 | 保住什么 | 典型判断 | 冲突时让位给谁 |
|
|
6
|
+
| --- | --- | --- | --- |
|
|
7
|
+
| 1 行为与契约 | 可观察行为、不变量、所有权、异常保证不走样 | 这个后置条件删掉后,调用方还知道构造出来的对象是什么状态吗? | 不让位。为短、为顺、为统一删掉契约,都是失败 |
|
|
8
|
+
| 2 结构 | 库与程序的边界、依赖方向、头文件自包含 | 这个 include 把哪一层带进了公共头? | 让位给行为;高于清晰、一致、形式 |
|
|
9
|
+
| 3 清晰 | 名字、签名、注释能直接读出用途与限制 | 读者要不要猜 `cells_` 的两层下标哪层是行? | 让位给行为与结构;高于一致、形式 |
|
|
10
|
+
| 4 一致 | 同类声明用同一种句式、位置和术语 | 两个成员的注释是否都在说同一类事实? | 让位给行为、结构、清晰;高于形式 |
|
|
11
|
+
| 5 形式 | 行宽、缩进、空行、符号 | 这行能不能再短一点? | 前四级都通过后才动 |
|
|
12
|
+
|
|
13
|
+
## 常见冲突
|
|
14
|
+
|
|
15
|
+
- **注释更短 vs 契约完整**:删掉构造后置条件、参数范围或失败方式换来的短,不算润色;先补回契约,再压句子。
|
|
16
|
+
- **位置统一 vs 成员可读**:数据成员的主注释都在声明上一行,但上一行必须先说成员是什么,再补轴向、哨兵或改写时机;不能为了「每行只写一个修饰」把名词丢掉。
|
|
17
|
+
- **格式统一 vs 语义准确**:同族函数用同一种注释句式,但不能把不同的失败条件写成同一句话;先准确,再统一能统一的部分。
|
|
18
|
+
- **语言风格 vs 消息内容**:异常消息先说来源、条件、实际值和限度;用哪种自然语言由项目约定,排版规则不替内容做决定。
|
|
19
|
+
- **视觉整齐 vs 可扫描**:对齐、空行和注释符号先让声明读得清,再谈列对齐;不要为了对齐把长类型或长注释挤成难读的折行。
|
|
20
|
+
|
|
21
|
+
## 执行顺序
|
|
22
|
+
|
|
23
|
+
1. 先写清这次改动要保住的行为、不变量和调用方契约,列出不能丢的事实。
|
|
24
|
+
2. 再查边界、依赖和声明位置;结构没定,不改句子。
|
|
25
|
+
3. 再查名字、签名、注释和消息是否说清用途、条件与值。
|
|
26
|
+
4. 再统一同类声明的句式、位置和术语。
|
|
27
|
+
5. 最后才调行宽、缩进、空行和符号。低层改动如果碰到高层,停手退回。
|
|
28
|
+
|
|
29
|
+
同级冲突时,选更靠近调用方契约的那条;仍分不出,保留原文,不凭风格偏好重写。
|
|
@@ -82,7 +82,7 @@ std::erase_if(values, [](int value) { return value % 2 == 0; });
|
|
|
82
82
|
- 格式串里的 `{}` 按顺序取参数;需要位置时写 `{0}`、`{1}`。
|
|
83
83
|
- 自定义类型要格式化时,特化 `std::formatter<T>`;不要在 `operator<<` 里塞业务格式。
|
|
84
84
|
- 浮点默认格式可能与预期不同;需要固定精度时写 `{:.2f}`,需要科学计数时写 `{:e}`。
|
|
85
|
-
- 异常消息、日志、用户界面文本都通过 `std::format`
|
|
85
|
+
- 异常消息、日志、用户界面文本都通过 `std::format` 组合,不手工拼接数字和字符串。异常消息的组织方式见 `references/exceptions.md` 的「消息」节;面向用户的中文只留在程序入口的前缀里。
|
|
86
86
|
|
|
87
87
|
## 时间、随机与文件
|
|
88
88
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// 只读注释审计:按 references/comments.md 的硬规则检查 C/C++ 源文件。
|
|
3
3
|
//
|
|
4
4
|
// 检查项:文件头横幅、横幅宽度、注释行宽度、Doxygen 标签、注释与声明的贴合、
|
|
5
|
-
// 类型注释存在性、成员功能清单、设计论证(方案对比)、否定式边界、领域尺寸、谓词首句、@param
|
|
5
|
+
// 类型注释存在性、成员功能清单、设计论证(方案对比)、否定式边界、领域尺寸、谓词首句、@param 完整性、枚举成员注释一致性、数据成员主注释位置。
|
|
6
6
|
// 不重写任何文件。
|
|
7
7
|
//
|
|
8
8
|
// 用法:
|
|
@@ -170,6 +170,30 @@ export function functionParams(declaration) {
|
|
|
170
170
|
return splitParams(decl.slice(open + 1, close)).map(paramName).filter(Boolean)
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
+
/** 标记每一行是否位于 class/struct 体内,供数据成员注释位置检查使用。 */
|
|
174
|
+
function classMemberLines(lines) {
|
|
175
|
+
const flags = new Array(lines.length).fill(false)
|
|
176
|
+
const starts = []
|
|
177
|
+
let depth = 0
|
|
178
|
+
let pending = false
|
|
179
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
180
|
+
const code = lines[index].replace(/\/\/.*$/, '').trim()
|
|
181
|
+
if (/^(?:template\s*<[^>]*>\s*)?(?:class|struct)\b/.test(code)) pending = true
|
|
182
|
+
const opens = (code.match(/\{/g) ?? []).length
|
|
183
|
+
const closes = (code.match(/\}/g) ?? []).length
|
|
184
|
+
if (pending && opens > 0) {
|
|
185
|
+
starts.push(depth)
|
|
186
|
+
pending = false
|
|
187
|
+
} else if (pending && opens === 0 && code.endsWith(';')) {
|
|
188
|
+
pending = false
|
|
189
|
+
}
|
|
190
|
+
flags[index] = starts.length > 0 && depth > starts[starts.length - 1]
|
|
191
|
+
depth += opens - closes
|
|
192
|
+
while (starts.length > 0 && depth <= starts[starts.length - 1]) starts.pop()
|
|
193
|
+
}
|
|
194
|
+
return flags
|
|
195
|
+
}
|
|
196
|
+
|
|
173
197
|
/** 检查一段文本,返回问题列表;每个问题带 1 起始的行号。 */
|
|
174
198
|
export function checkText(text, options = {}) {
|
|
175
199
|
const width = options.width ?? 80
|
|
@@ -178,11 +202,24 @@ export function checkText(text, options = {}) {
|
|
|
178
202
|
const lines = text.split(/\r?\n/)
|
|
179
203
|
const issues = []
|
|
180
204
|
const add = (line, rule, message) => issues.push({ line, rule, message })
|
|
205
|
+
const inClass = classMemberLines(lines)
|
|
181
206
|
|
|
182
207
|
const firstContent = lines.findIndex((line) => line.trim() !== '')
|
|
183
208
|
if (requireHeader && firstContent !== -1 && !BANNER.test(lines[firstContent].trim())) {
|
|
184
209
|
add(firstContent + 1, 'header-missing', '文件开头缺少文件头横幅(//===...===//)')
|
|
185
210
|
}
|
|
211
|
+
if (firstContent !== -1 && BANNER.test(lines[firstContent].trim())) {
|
|
212
|
+
const own = options.fileName ? options.fileName.split(/[\\/]/).pop() : ''
|
|
213
|
+
for (let index = firstContent + 1; index < lines.length; index += 1) {
|
|
214
|
+
const trimmed = lines[index].trim()
|
|
215
|
+
if (BANNER.test(trimmed) || !trimmed.startsWith('//')) break
|
|
216
|
+
const names = trimmed.match(/\b[A-Za-z0-9_-]+\.(?:h|hpp|cpp|cc)\b/g) ?? []
|
|
217
|
+
for (const name of names) {
|
|
218
|
+
if (name !== own) add(index + 1, 'header-cross-file', `文件头提到了别的源文件 ${name}:只介绍本文件,删掉跨文件句子`)
|
|
219
|
+
}
|
|
220
|
+
if (/\bapps\//.test(trimmed)) add(index + 1, 'header-cross-file', '文件头指向了另一个目录:只介绍本文件')
|
|
221
|
+
}
|
|
222
|
+
}
|
|
186
223
|
|
|
187
224
|
for (let index = 0; index < lines.length; index += 1) {
|
|
188
225
|
const line = lines[index]
|
|
@@ -225,7 +262,7 @@ export function checkText(text, options = {}) {
|
|
|
225
262
|
add(start + 1, 'member-list', '类型注释在罗列成员功能,应改写为概念与使用约定')
|
|
226
263
|
}
|
|
227
264
|
if (isType && FORWARD_REF.test(block)) {
|
|
228
|
-
add(start + 1, 'forward-ref', '
|
|
265
|
+
add(start + 1, 'forward-ref', '类型注释在交代别的类型负责什么:只写本类型的契约,删掉这句,不要挪到文件头')
|
|
229
266
|
}
|
|
230
267
|
if (isType && TYPE_SYSTEM.test(block)) {
|
|
231
268
|
add(start + 1, 'type-system', '类型注释在复述类型系统已表达的性质:可拷贝与 const 查询由声明给出,删掉这句')
|
|
@@ -257,10 +294,40 @@ export function checkText(text, options = {}) {
|
|
|
257
294
|
if (!trimmed.startsWith('//') || trimmed.startsWith('///') || trimmed.startsWith('//!')) continue
|
|
258
295
|
const next = lines[index + 1].trim()
|
|
259
296
|
if (next === '' || next.startsWith('//')) continue
|
|
260
|
-
if (!/(
|
|
297
|
+
if (!/(下标|存储|对应坐标|行优先|存放)/.test(trimmed)) continue
|
|
261
298
|
const identifiers = next.match(/[A-Za-z_]\w*/g) ?? []
|
|
262
|
-
if (identifiers.some((identifier) => trimmed.includes(identifier)))
|
|
263
|
-
|
|
299
|
+
if (!identifiers.some((identifier) => trimmed.includes(identifier))) continue
|
|
300
|
+
let residue = trimmed
|
|
301
|
+
for (const identifier of identifiers) residue = residue.split(identifier).join('')
|
|
302
|
+
residue = residue.replace(
|
|
303
|
+
/(对应坐标|行优先|下标|存储|存放|声明|类型|棋盘|格子|元素|坐标|行列|行|列|当前|每个|所有|用来|用于|表示|保存|初始化|的是|的了|在|中|里|和|与|及|或|按|class|struct|enum|const|auto|row|col|index|indices|cell|cells|board|data|value|values)/g,
|
|
304
|
+
'',
|
|
305
|
+
)
|
|
306
|
+
residue = residue.replace(/[\s\p{P}\p{S}\p{N}]/gu, '')
|
|
307
|
+
if (residue === '') {
|
|
308
|
+
add(index + 1, 'restates-next', '实现注释只重复下一行声明的标识符或类型:删掉这句,让声明自己表达')
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
313
|
+
if (!inClass[index]) continue
|
|
314
|
+
const line = lines[index]
|
|
315
|
+
const commentAt = line.indexOf('//')
|
|
316
|
+
const code = (commentAt === -1 ? line : line.slice(0, commentAt)).trim()
|
|
317
|
+
if (!code.endsWith(';') || code.includes('(')) continue
|
|
318
|
+
const names = code.match(/\b[A-Za-z_]\w*_\b/g)
|
|
319
|
+
if (!names) continue
|
|
320
|
+
const member = names.at(-1)
|
|
321
|
+
const previous = index > 0 ? lines[index - 1].trim() : ''
|
|
322
|
+
if (commentAt !== -1) {
|
|
323
|
+
if (line.slice(commentAt).startsWith('///<')) continue
|
|
324
|
+
if (!previous.startsWith('//')) {
|
|
325
|
+
add(index + 1, 'member-comment-missing', `数据成员 ${member} 只有行尾注释:主注释写在声明上一行,行尾不写补充`)
|
|
326
|
+
} else {
|
|
327
|
+
add(index + 1, 'member-comment-trailing', `数据成员 ${member} 的行尾注释应删掉:主注释只写在声明上一行,行尾只允许 ///< 用于枚举项或 struct 短字段`)
|
|
328
|
+
}
|
|
329
|
+
} else if (previous.startsWith('//') && !previous.startsWith('///')) {
|
|
330
|
+
add(index + 1, 'member-comment-slash', `数据成员 ${member} 的主注释用了 //:类数据成员统一用 ///,写在声明上一行`)
|
|
264
331
|
}
|
|
265
332
|
}
|
|
266
333
|
|
|
@@ -363,7 +430,7 @@ function main(argv) {
|
|
|
363
430
|
process.stderr.write(`错误:读取 ${file} 失败:${error.message}\n`)
|
|
364
431
|
return EXIT.usage
|
|
365
432
|
}
|
|
366
|
-
for (const issue of checkText(text, options)) results.push({ file: path.relative(root, file) || file, ...issue })
|
|
433
|
+
for (const issue of checkText(text, { ...options, fileName: path.relative(root, file) || file })) results.push({ file: path.relative(root, file) || file, ...issue })
|
|
367
434
|
}
|
|
368
435
|
if (options.json) {
|
|
369
436
|
process.stdout.write(`${JSON.stringify({ root, files: files.length, issues: results }, null, 2)}\n`)
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
- 公共头文件自包含:自身需要的头都直接包含,不依赖调用方的包含顺序。
|
|
13
13
|
- 单参数构造函数写 `explicit`;可查询的成员函数加 `[[nodiscard]]`;默认成员初始化写在声明处,不在构造函数里重复。
|
|
14
14
|
- 头文件只就地定义大约 10 行以内的短函数;更长或需要完整定义的普通函数只在 `.cpp` 定义,模板和 `constexpr` 例外。
|
|
15
|
-
- 失败是调用方的正常结果时,用结果类型、`bool` + 输出参数或 `std::optional` 表达;函数无法完成自己的任务、或构造函数无法建立 invariant 时,才在声明注释里写清抛出的异常类型;越界用 `std::out_of_range`,前置条件被破坏用 `std::logic_error`,只有调用方必须区分时才新增派生类型;Debug/Release
|
|
16
|
-
- 声明注释按 `references/comments.md`
|
|
15
|
+
- 失败是调用方的正常结果时,用结果类型、`bool` + 输出参数或 `std::optional` 表达;函数无法完成自己的任务、或构造函数无法建立 invariant 时,才在声明注释里写清抛出的异常类型;越界用 `std::out_of_range`,前置条件被破坏用 `std::logic_error`,只有调用方必须区分时才新增派生类型;Debug/Release 的抛出条件相同。消息句式见 `references/exceptions.md` 的「消息」节。
|
|
16
|
+
- 声明注释按 `references/comments.md` 的四节写:写作只写声明里看不出的事实——类型注释的名词短语(纯数据与枚举一句,类只在不变量不重复领域尺寸时加第二段)、特殊成员的标签与后置条件(例如 `默认构造函数:得到空棋盘、每个格子都是 kEmpty`)、函数注释的第一句结果或失败方式或检查项与契约、数据成员的主注释放在声明上一行并用 `///`,先说明这个成员是什么,再用全角冒号补名字和类型看不出的轴向、哨兵、所有权与改写时机,行尾不写补充;布局定文件头、位置与行尾;排版定符号、宽度与标签;润色删复述。类型注释不罗列成员功能、不写领域尺寸、不写方案对比与设计论证;全项目的风格理由写进项目文档,不写进注释。
|
|
17
17
|
- `std::format` 用于组合消息;声明里不要承诺 `std::print`(C++23)。
|
|
18
18
|
|
|
19
19
|
## 情况
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
- 函数保持短小;一个函数只做一件事。超过约 40 行时先找可以命名的中间步骤,不要用注释把长函数切成几段。
|
|
10
10
|
- 按「准备 → 操作 → 收尾」排列语句,用空行分组;组与组之间的空行表达阶段,不用大段叙述。
|
|
11
11
|
- `.cpp` 的第一个 include 是自己的配对头,然后直接包含本文件用到的标准库头;不要依赖公共头替本文件转发声明。包含顺序见 `references/google-cpp-style.md`;文件槽位见 `references/file-template.md`。
|
|
12
|
-
- `.cpp`
|
|
13
|
-
- 错误处理:失败是调用方正常结果时返回结果类型、`bool` 或 `std::optional`;函数无法完成自己的任务时抛异常,不拿异常做正常分支。Debug 与 Release 用同一套返回值与抛出条件,只有诊断详略不同。两种模式都要守的 invariant 用 `Check`(项目保留时),只在开发期做的自检用 `DebugCheck`;没有这两个宏就分别用 `assert` 与 `std::terminate`。捕获时按 `const`
|
|
12
|
+
- `.cpp` / `.cc` 里,重要函数按空行分阶段,每个阶段上方写一句 `//`:写边界、失败后果或为什么停在这里,不把下一行的条件翻译一遍。`IsWin` 这种单行函数不配注释;只写删掉会让下一次修改得到错误结果、而代码本身看不出的内容,变更史留给提交说明。
|
|
13
|
+
- 错误处理:失败是调用方正常结果时返回结果类型、`bool` 或 `std::optional`;函数无法完成自己的任务时抛异常,不拿异常做正常分支。Debug 与 Release 用同一套返回值与抛出条件,只有诊断详略不同。两种模式都要守的 invariant 用 `Check`(项目保留时),只在开发期做的自检用 `DebugCheck`;没有这两个宏就分别用 `assert` 与 `std::terminate`。捕获时按 `const` 引用,只处理能处理的类型,其余继续抛。消息按 `Type::Function: 条件 (值); 限度` 组织,见 `references/exceptions.md` 的「消息」节。
|
|
14
14
|
- 声明 `noexcept` 前核对函数体:出现 `at()` 或其他可能抛出的调用就去掉。越界用 `at()` 抛 `std::out_of_range`,这个函数就不能标 `noexcept`。
|
|
15
15
|
- 组合输出用 `std::format`,再写入 `std::cout` / `std::cerr`;不要在库实现里直接打印。C++23 且库支持时程序边界可用 `std::print`,C++20 及更早没有。
|
|
16
16
|
- 不写 C 风格强制转换;用 `static_cast` / `std::bit_cast` 等具名转换。
|