@chengzhao-dev/dsh-cpp-workflow 0.5.0 → 0.5.2

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 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.0
22
+ dsh plugin --profile web add npm:@chengzhao-dev/dsh-cpp-workflow@0.5.2
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.0
28
+ dsh plugin --profile web add github:chengzhao-dev/dsh-cpp-workflow#v0.5.2
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.0
22
+ dsh plugin --profile web add npm:@chengzhao-dev/dsh-cpp-workflow@0.5.2
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.0
28
+ dsh plugin --profile web add github:chengzhao-dev/dsh-cpp-workflow#v0.5.2
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.0",
3
+ "version": "0.5.2",
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",
@@ -38,6 +38,14 @@ metadata:
38
38
 
39
39
  每步只打开同号的一个步骤文件。判不准才打开 `examples/` 里与该步对应的案例;`references/` 保存完整条文,只在排错、整目录重构,或步骤里明确指向某一条文时按需打开。链接、rpath 与产物布局读 `references/linking.md`;CMake 的 target、标准和函数写法读 `../configuring-cmake/references/`,可复用片段在 `../configuring-cmake/assets/cmake/`。
40
40
 
41
+ 本轮激活按三条上限走:
42
+
43
+ - 只开当前步对应的一个步骤文件;本轮已经开了别的技能,就不再开第二个技能的步骤。
44
+ - 条文只开该步点名的一份;同号案例留到下一次激活。
45
+ - 标准档只打开项目技能声明的那一档,没有声明时用本技能已有的新工程默认,不把其余标准档读进来。
46
+
47
+ 项目技能列出了检查命令时只跑那一条,没有时跑本技能自己的 `scripts/checking-environment.mjs` 与 `scripts/checking-encoding.mjs`;退出码为 0 后在同一轮结束 goal,不再继续走没有失败的步骤。
48
+
41
49
  ### 四类改动
42
50
 
43
51
  先认这次改的是哪一类,再决定从哪一步进;一次改动混了两类时,按「写作 → 布局 → 排版 → 润色」的次序拆开做。
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bash
2
2
  #===------------------------------------------------------------------------===#
3
- # 配置、构建、格式化、静态检查、测试并运行当前 C++11–C++23 工程(默认 C++23)。
3
+ # 配置、构建、测试并运行当前 C++11–C++23 工程(默认 C++23)。
4
4
  #
5
5
  # 用法:./running-build.sh [clang|gcc];省略时用 clang。
6
6
  # 构建目录:build/<compiler>;
@@ -49,47 +49,6 @@ on_error() {
49
49
  }
50
50
  trap on_error ERR
51
51
 
52
- #===------------------------------------------------------------------------===#
53
- # 项目源码
54
- #===------------------------------------------------------------------------===#
55
-
56
- collect_sources() {
57
- find . \
58
- \( -type d \( -name .git -o -name .cache -o -name node_modules \
59
- -o -name third_party -o -name external -o -name vendor \
60
- -o -name build -o -name temp -o -name templates \) -prune \) -o \
61
- -type f \( -name '*.c' -o -name '*.cc' -o -name '*.cpp' \
62
- -o -name '*.cxx' -o -name '*.h' -o -name '*.hh' \
63
- -o -name '*.hpp' -o -name '*.hxx' \) -print0
64
- }
65
-
66
- # 只让项目自身目录的头文件诊断进入结果。
67
- # 顶层目录从项目文件推导,不硬编码 include/src;
68
- # 项目根同时覆盖 clang-tidy 输出的绝对路径。
69
- # 第三方头即使被包含,也不在项目根 + 顶层目录的白名单里。
70
- header_filter_for() {
71
- local tops=()
72
- local file rel top
73
- for file in "$@"; do
74
- rel="${file#./}"
75
- case "${rel}" in
76
- */*)
77
- top="${rel%%/*}"
78
- tops+=("${top}")
79
- ;;
80
- esac
81
- done
82
- local root
83
- root="$(pwd -P)"
84
- if [[ ${#tops[@]} -eq 0 ]]; then
85
- printf '^(%s/)?[^/]+$' "${root}"
86
- return 0
87
- fi
88
- local top_pattern
89
- top_pattern="$(printf '%s\n' "${tops[@]}" | sort -u | paste -sd '|' -)"
90
- printf '^(%s/)?((%s)/|[^/]+$)' "${root}" "${top_pattern}"
91
- }
92
-
93
52
  #===------------------------------------------------------------------------===#
94
53
  # 主流程
95
54
  #===------------------------------------------------------------------------===#
@@ -110,46 +69,6 @@ main() {
110
69
  ln -s "${COMPILER}/compile_commands.json" \
111
70
  "${BUILD_ROOT}/compile_commands.json"
112
71
 
113
- local format_sources=()
114
- local tidy_sources=()
115
- local file
116
- while IFS= read -r -d '' file; do
117
- format_sources+=("${file}")
118
- case "${file}" in
119
- *.c|*.cc|*.cpp|*.cxx) tidy_sources+=("${file}") ;;
120
- esac
121
- done < <(collect_sources)
122
-
123
- header "换行检查"
124
- if [[ ${#format_sources[@]} -gt 0 ]]; then
125
- local crlf
126
- crlf=$(grep -Il $'\r' "${format_sources[@]}" || true)
127
- if [[ -n "${crlf}" ]]; then
128
- err "以下文件含 CRLF,请改成 LF:${crlf}"
129
- return 1
130
- fi
131
- fi
132
-
133
- header "格式检查"
134
- if [[ ${#format_sources[@]} -gt 0 ]]; then
135
- clang-format --dry-run --Werror "${format_sources[@]}"
136
- else
137
- err "没有找到项目源码,跳过 clang-format"
138
- fi
139
-
140
- header "静态检查"
141
- if [[ ${#tidy_sources[@]} -gt 0 ]]; then
142
- local header_filter
143
- local tidy_args=(--warnings-as-errors='*')
144
- header_filter="$(header_filter_for "${format_sources[@]}")"
145
- if [[ -n "${header_filter}" ]]; then
146
- tidy_args+=(--header-filter="${header_filter}")
147
- fi
148
- clang-tidy -p "${BUILD_DIR}" "${tidy_args[@]}" "${tidy_sources[@]}"
149
- else
150
- err "没有找到 C++ 源文件,跳过 clang-tidy"
151
- fi
152
-
153
72
  header "测试"
154
73
  ctest --test-dir "${BUILD_DIR}" --output-on-failure
155
74
 
@@ -1,6 +1,6 @@
1
1
  # 构建脚本
2
2
 
3
- `assets/templates/projects/common/running-build.sh` 是 POSIX build-host 脚本,适用于 Linux、macOS 和 Windows + WSL。`host=windows` + `build_host=native` 不是本版本支持的组合:声明校验会直接拒绝,脚本也不会在原生 Windows 壳里运行。
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
5
  ## 用法
6
6
 
@@ -22,10 +22,8 @@
22
22
  4. **configure**:每次运行都重新 configure,不复用缓存;传 `-G Ninja`、`-DCMAKE_BUILD_TYPE=Debug`、`-DCMAKE_C_COMPILER`、`-DCMAKE_CXX_COMPILER`、`-DCMAKE_EXPORT_COMPILE_COMMANDS=ON`。C 与 C++ 使用同一套编译器;没有 C 语言的工程会忽略 C 编译器设置。
23
23
  5. **build**:`cmake --build "$BUILD_DIR"`。
24
24
  6. **编译数据库链接**:先 `rm -f build/compile_commands.json`,再 `ln -s "${COMPILER}/compile_commands.json" build/compile_commands.json`;不用 `ln -n`,它在 BSD/macOS 上不可移植。
25
- 7. **format**:收集项目源码,`clang-format --dry-run --Werror`;没有源文件时跳过。
26
- 8. **tidy**:`clang-tidy -p "$BUILD_DIR" --warnings-as-errors='*'` 扫描项目源文件;`--header-filter` 由脚本从项目根和顶层目录生成,绝对路径和相对路径都只放行项目自身头文件。不要用 clang-tidy 19 才加入的 `--exclude-header-filter`,最低支持版本是 16。
27
- 9. **test**:`ctest --test-dir "$BUILD_DIR" --output-on-failure`;没有测试时 CTest 成功。
28
- 10. **run**:`(cd "$BUILD_DIR/bin" && "./$APP_NAME")`;共享库的运行时路径由定构建的 CMake RPATH 负责,不在脚本里设全局 `LD_LIBRARY_PATH`。
25
+ 7. **test**:`ctest --test-dir "$BUILD_DIR" --output-on-failure`;没有测试时 CTest 成功。
26
+ 8. **run**:`(cd "$BUILD_DIR/bin" && "./$APP_NAME")`;共享库的运行时路径由定构建的 CMake RPATH 负责,不在脚本里设全局 `LD_LIBRARY_PATH`。
29
27
 
30
28
  最后一行是 `main "$@"`;函数定义在 `main()` 之前,`set` / `readonly` 在最前。
31
29
 
@@ -34,7 +32,6 @@
34
32
  - 只用 POSIX shell + Bash 数组;不要依赖 `mapfile`(macOS 自带 Bash 3.2 没有)。
35
33
  - 路径含空格时全部加引号;`find -print0` 配 `read -d ''`。
36
34
  - `set -u` 下空数组展开会报错;先判 `${#sources[@]}` 再传给工具。
37
- - 不把 `clang-format -i` 放进脚本;检查阶段只读。
38
35
  - 不在脚本里设全局 `LD_LIBRARY_PATH`;共享库的运行时路径由 CMake 的 RPATH 设置。
39
36
  - 不 `sudo`、不 `apt install`、不 `pip install`。
40
37
  - 失败时保留原始命令输出;不要在脚本里 `2>/dev/null` 吞掉诊断。
@@ -59,8 +56,6 @@
59
56
  | `ninja: error: ... missing and no known rule` | 源文件是否在 CMake 中列出 | 定构建 |
60
57
  | `fatal error: ... No such file` | `target_include_directories` | 定构建 |
61
58
  | `undefined reference` | `target_link_libraries`、库 target 名 | 定构建 |
62
- | `clang-format` 差异 | 源码排版 | 定工具配置 |
63
- | `clang-tidy` 命名/类型诊断 | 声明或实现 | `developing-cpp` 的定声明 / 定实现 |
64
59
  | `CTest` 失败 | 测试断言、行为句 | `developing-cpp` 的定行为 / 定实现 |
65
60
  | 运行时报找不到 `.so` | RPATH / `LD_LIBRARY_PATH` | 定构建 |
66
61
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## 产出
4
4
 
5
- 一次可复现的通过记录:环境校验、配置、构建、格式化、静态检查、测试、运行都通过;失败时能指出是哪一步、哪个角色、哪条诊断。
5
+ 一次可复现的通过记录:环境校验、配置、构建、测试、运行都通过;失败时能指出是哪一步、哪个角色、哪条诊断。
6
6
 
7
7
  ## 通用情况
8
8
 
@@ -11,7 +11,7 @@
11
11
  1. **校验编码与行尾**:在仓库根跑编码检查。默认 UTF-8 无 BOM、LF;MSVC 原生工程加 `--profile msvc`。有问题先按 `references/encoding.md` 修正,不带着 BOM 或混用行尾继续。
12
12
  2. **校验环境声明(不带 `--live`)**:读 `environment.json`,检查三个环境轴、`standard` 和组合是否合法。环境轴为空、`standard` 非法或组合非法时,停在这里,不找可执行文件。
13
13
  3. **活动校验**:在声明的 build host 上跑 `--live`,核对 PATH 中的角色和版本。`windows` + `native` 在声明校验阶段就返回失败,不会进入这一步。
14
- 4. **跑项目入口命令**:项目技能里写的入口命令(如 `./running-build.sh clang`)。入口命令仍是 `<entry-command>` 或空白时,停在这里,不要猜命令;是真实命令才跑。入口内部依次 configure → build → format → tidy → ctest → run;每一步失败都保留原始输出。
14
+ 4. **跑项目入口命令**:项目技能里写的入口命令(如 `./running-build.sh clang`)。入口命令仍是 `<entry-command>` 或空白时,停在这里,不要猜命令;是真实命令才跑。入口内部依次 configure → build → ctest → run;每一步失败都保留原始输出。
15
15
  5. **核对产物**:`build/<compiler>/compile_commands.json` 存在;`build/compile_commands.json` 指向当前编译器;`app` 可执行文件在 `build/<compiler>/bin`。
16
16
  6. **记录结果**:通过时记录编译器版本、构建目录、测试数量;失败时记录命令、退出码、第一条根因诊断。
17
17
 
@@ -32,9 +32,6 @@ node "<skill-dir>/scripts/checking-environment.mjs" --project . --live # 活
32
32
  | 轴为空、组合非法、版本不匹配 | `environment.json`、`--live` 输出 | 定构建,先修声明 |
33
33
  | 找不到头文件、链接失败、target 不存在 | `CMakeLists.txt`、`target_include_directories`、`target_link_libraries` | 定构建 |
34
34
  | 没有 `compile_commands.json`、clangd 跳转失败 | 生成器、`CMAKE_EXPORT_COMPILE_COMMANDS`、`.clangd` 路径 | 定构建 / 定工具配置 |
35
- | `clang-format` 报格式 | 源码排版 | 定工具配置;只改格式,不改行为 |
36
- | `clang-tidy` 报命名/类型/现代 C++ 用法 | 源码声明或实现 | `developing-cpp` 的定声明 / 定实现 |
37
- | 编译错误指向声明或类型 | 头文件、类型定义、函数签名 | `developing-cpp` 的定声明 |
38
35
  | 编译错误指向函数体 | 实现 | `developing-cpp` 的定实现;先修语义,再重跑 |
39
36
  | CTest 失败 | 测试断言、行为句 | `developing-cpp` 的定行为 / 定实现 |
40
37
  | `app` 启动失败、找不到共享库 | 输出目录、RPATH / 运行时库路径 | 定构建 |
@@ -45,9 +42,9 @@ node "<skill-dir>/scripts/checking-environment.mjs" --project . --live # 活
45
42
 
46
43
  **第一次接工程**:先跑声明校验,再跑 `--live`;两者都过再跑入口命令。环境没声明对时,构建脚本再完美也会失败。
47
44
 
48
- **只改源码**:仍跑完整入口命令;格式化和静态检查可能暴露声明层问题,不要只看编译通过。
45
+ **只改源码**:仍跑完整入口命令;CTest 可能暴露声明层问题,不要只看编译通过。
49
46
 
50
- **只改配置**:至少跑一次完整构建 + 格式化 + 静态检查;配置错误常常在 clangd 或 tidy 阶段才暴露。
47
+ **只改配置**:至少跑一次完整构建 + CTest;配置错误常常在链接或测试阶段才暴露。
51
48
 
52
49
  **没有测试**:CTest 显示没有测试是合法结果;不要为了「全绿」造空测试。行为验收由定行为的验收句负责。
53
50
 
@@ -34,6 +34,14 @@ metadata:
34
34
 
35
35
  每步只打开同号的一个步骤文件。判不准才打开 `examples/cmake.md`;`references/cmake.md` 保存 target、链接、输出、RPATH、依赖与反模式的完整条文,`references/cxx-standard.md` 保存 C++11–C++23 的标准映射,`references/modules.md` 保存函数与模块契约。可复制的函数在 `assets/cmake/`:`CompileSettings.cmake` 放标准与警告,`LinkSettings.cmake` 放构建期 RPATH 与 GoogleTest 依赖;一个文件按性质收一组函数。
36
36
 
37
+ 本轮激活按三条上限走:
38
+
39
+ - 只开当前步对应的一个步骤文件;本轮已经开了别的技能,就不再开第二个技能的步骤。
40
+ - 条文只开该步点名的一份;同号案例留到下一次激活。
41
+ - 标准档只打开项目技能声明的那一档,没有声明时用本技能已有的新工程默认,不把其余标准档读进来。
42
+
43
+ 项目技能列出了检查命令时只跑那一条,没有时跑插件根目录的 `npm test`;退出码为 0 后在同一轮结束 goal,不再继续走没有失败的步骤。
44
+
37
45
  五档标准的语言设施、工具映射与降档仍由 `building-cpp/references/standards/` 维护;本技能只负责这些标准在 CMake 里怎么写。
38
46
 
39
47
  ### 四类改动
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## 产出
4
4
 
5
- 一次干净的 configure、一次构建、一份与 `environment.json` 一致的编译数据库。检查只回答「target 契约是否成立」,完整的格式化、静态检查、CTest 与运行由 `building-cpp` 的通过检查接手。
5
+ 一次干净的 configure、一次构建、一份与 `environment.json` 一致的编译数据库。检查只回答「target 契约是否成立」,完整的 CTest 与运行由 `building-cpp` 的通过检查接手。
6
6
 
7
7
  ## 命令
8
8
 
@@ -32,8 +32,8 @@ GCC 模式把目录和编译器换成 `build/gcc`、`g++`。每次检查都重
32
32
  | 找不到 target、链接失败、include 路径不对 | 第 1 步 |
33
33
  | 目标标准没有生效、`-std` 与声明不一致 | 第 1 步,必要时重读 `references/cxx-standard.md` |
34
34
  | 函数参数没校验、模块重复导入、旧模块残留 | 第 2 步 |
35
- | 只是 clang-format、clang-tidy、CTest 或运行失败 | 转 `building-cpp` 的通过检查 |
35
+ | 只是 CTest 或运行失败 | 转 `building-cpp` 的通过检查 |
36
36
 
37
37
  ## 边界
38
38
 
39
- 本步不替代 `building-cpp`:不安装工具链,不改 `running-build.sh`,不跑 `clang-tidy` 或 CTest。检查通过的判据是 configure 与构建成功、数据库标准正确、结构规则无命中;完整的工具链检查仍由 `building-cpp` 的通过检查给出。
39
+ 本步不替代 `building-cpp`:不安装工具链,不改 `running-build.sh`,不跑 `clang-tidy` 或 CTest。检查通过的判据是 configure 与构建成功、数据库标准正确、结构规则无命中;完整的构建、测试与运行检查仍由 `building-cpp` 的通过检查给出。
@@ -35,6 +35,14 @@ metadata:
35
35
 
36
36
  每步只打开同号的一个步骤文件。判不准才打开 `examples/` 里与该步对应的案例;`references/` 保存完整条文,只在排错、整目录重构,或步骤里明确指向某一条文时按需打开。性能问题读 `references/performance.md`,标准库设施选型读 `references/standard-library.md`,链接与产物的细节读 `building-cpp` 的 `references/linking.md`。
37
37
 
38
+ 本轮激活按三条上限走:
39
+
40
+ - 只开当前步对应的一个步骤文件;本轮已经开了别的技能,就不再开第二个技能的步骤。
41
+ - 条文只开该步点名的一份;同号案例留到下一次激活。
42
+ - 标准档只打开项目技能声明的那一档,没有声明时用本技能已有的新工程默认,不把其余标准档读进来。
43
+
44
+ 项目技能列出了检查命令时只跑那一条,没有时跑本技能自己的 `scripts/checking-comments.mjs`;退出码为 0 后在同一轮结束 goal,不再继续走没有失败的步骤。
45
+
38
46
  ### 四类改动
39
47
 
40
48
  先认这次改的是哪一类,再决定从哪一步进;一次改动混了两类时,按「写作 → 布局 → 排版 → 润色」的次序拆开做,前一类落定并经检查再进下一类。
@@ -1,4 +1,4 @@
1
- # 正反例:注释
1
+ # 正反例:声明注释与文件头
2
2
 
3
3
  ## 文件头
4
4
 
@@ -48,18 +48,25 @@ class Board {
48
48
  class Board {
49
49
  ```
50
50
 
51
- 第二段说的是 `Game` 负责什么,读者读完仍不知道 `Board` 保证什么,重构一次这句话就过期;后两段里的可拷贝、`const` 查询在类声明里已经写清,「搜索要读同一棋盘」还不是现在的调用方。读者真正需要的是这个类型自己的不变量与失败方式。
51
+ 第二段说的是 `Game` 负责什么,读者读完仍不知道 `Board` 是什么,重构一次这句话就过期;后两段里的可拷贝、`const` 查询在类声明里已经写清,「搜索要读同一棋盘」还不是现在的调用方。读者真正需要的是这个类型是什么;格子数由成员声明表达,失败方式写在各自函数上。
52
52
 
53
- **好**:
53
+ **好**(纯数据与尺寸由成员表达):
54
54
 
55
55
  ```cpp
56
56
  /// 井字棋棋盘。
57
- ///
58
- /// 3×3 个格子,每格取空、X 或 O;越界坐标不会写入棋盘。
59
57
  class Board {
60
58
  ```
61
59
 
62
- 两句都是本类型能兑现、读代码又不容易一眼看全的事,函数各自的契约下沉到函数注释;设计理由与分层不写进注释,写进书稿。
60
+ **好**(类有不变量,第二句才写限制):
61
+
62
+ ```cpp
63
+ /// 一局井字棋。
64
+ ///
65
+ /// 棋盘、当前玩家和落子历史同步推进;落子被拒时三者都不变。
66
+ class Game {
67
+ ```
68
+
69
+ 纯数据结构和枚举只写一句名词短语;类有不变量,且第二句不是重复 `cells_`、`kBoardRows` 这类领域尺寸时,才保留第二段。函数各自的契约下沉到函数注释,设计理由与分层写进书稿。
63
70
 
64
71
  ## 风格理由不进注释
65
72
 
@@ -84,7 +91,7 @@ class Board {
84
91
 
85
92
  命名与风格约定写进项目文档和读者正文,注释里不解释「为什么用这种命名」。
86
93
 
87
- ## 第一句:先写结果或限制
94
+ ## 第一句:结果、限制或检查项
88
95
 
89
96
  **差**:
90
97
 
@@ -106,7 +113,23 @@ CellType GetCellType(CellPosition position) const;
106
113
  CellType GetCellType(CellPosition position) const;
107
114
  ```
108
115
 
109
- 第一句就是结果或失败方式;后面的标签只补参数与异常细节,不再重复这句话。
116
+ 第一句就是结果、失败方式或检查项;后面的标签只补参数与异常细节,不再重复这句话。
117
+
118
+ **差**(比较函数却先写“比较”):
119
+
120
+ ```cpp
121
+ /// 比较两个坐标。
122
+ friend bool operator==(const CellPosition&, const CellPosition&) = default;
123
+ ```
124
+
125
+ **好**:
126
+
127
+ ```cpp
128
+ /// 检查两个坐标的行与列是否都相同。
129
+ friend bool operator==(const CellPosition&, const CellPosition&) = default;
130
+ ```
131
+
132
+ 布尔查询和 `operator==` 的第一句都用「检查…是否…」,让读者先看到被检查的条件,而不是一个同义反复的动词。
110
133
 
111
134
  ## 函数契约
112
135
 
@@ -197,62 +220,11 @@ GameResult result_;
197
220
  **好**:
198
221
 
199
222
  ```cpp
200
- Board board_; // 对局棋盘。
201
223
  Player current_player_ = Player::kCross; // 当前轮到的玩家,开局时 X 先手。
202
224
  GameResult result_ = GameResult::kInProgress; // 对局结果;落子被拒时不改写。
203
225
  ```
204
226
 
205
- 行尾写「存什么 + 初值含义 + 什么时候变」,同类成员对齐。
206
-
207
- ## 实现注释
208
-
209
- **差**:
210
-
211
- ```cpp
212
- // 遍历所有空格
213
- for (const auto& position : empty_cells) {
214
- // 尝试落子
215
- const bool wins = game.Board().CompletesLine(position, cell_type);
216
- // 如果能赢
217
- if (wins) {
218
- // 返回这一手
219
- return position;
220
- }
221
- }
222
- ```
223
-
224
- 每行都在复述代码。
225
-
226
- **好**:
227
-
228
- ```cpp
229
- // 先找能立刻赢的格子:启发式只在「马上赢」和「马上挡」之间做选择,
230
- // 不在这里做更深的前瞻,深搜留给 MinimaxAi。
231
- for (const auto& position : empty_cells) {
232
- if (game.CompletesLine(position, CellType::kX)) {
233
- return position;
234
- }
235
- }
236
- ```
237
-
238
- 解释「为什么停在这一层」,不翻译语句。
239
-
240
- ## TODO
241
-
242
- **差**:
243
-
244
- ```cpp
245
- // TODO: 参考 xxx
246
- // FIXME: 这里可能有问题
247
- ```
248
-
249
- **好**:
250
-
251
- ```cpp
252
- // TODO: 阶段 5 - 给 MinimaxAi 加置换表,减少重复局面的搜索。
253
- ```
254
-
255
- 有定位、有动作、能搜索。
227
+ 行尾只写名字和类型看不出的范围、初值含义与改写时机,同类成员对齐;`board_`、`cells_` 这类不复述。
256
228
 
257
229
  ## 配置注释
258
230
 
@@ -0,0 +1,53 @@
1
+ # 正反例:实现注释
2
+
3
+ 实现注释只写本层为什么停在这里;声明注释、文件头与配置注释见 `comments-declaration.md`。
4
+
5
+ ## 实现注释
6
+
7
+ **差**:
8
+
9
+ ```cpp
10
+ // 遍历所有空格
11
+ for (const auto& position : empty_cells) {
12
+ // 尝试落子
13
+ const bool wins = game.Board().CompletesLine(position, cell_type);
14
+ // 如果能赢
15
+ if (wins) {
16
+ // 返回这一手
17
+ return position;
18
+ }
19
+ }
20
+ ```
21
+
22
+ 每行都在复述代码。
23
+
24
+ **好**:
25
+
26
+ ```cpp
27
+ // 先找能立刻赢的格子:启发式只在「马上赢」和「马上挡」之间做选择,
28
+ // 不在这里做更深的前瞻,深搜留给 MinimaxAi。
29
+ for (const auto& position : empty_cells) {
30
+ if (game.CompletesLine(position, CellType::kX)) {
31
+ return position;
32
+ }
33
+ }
34
+ ```
35
+
36
+ 解释「为什么停在这一层」,不翻译语句。
37
+
38
+ ## TODO
39
+
40
+ **差**:
41
+
42
+ ```cpp
43
+ // TODO: 参考 xxx
44
+ // FIXME: 这里可能有问题
45
+ ```
46
+
47
+ **好**:
48
+
49
+ ```cpp
50
+ // TODO: 阶段 5 - 给 MinimaxAi 加置换表,减少重复局面的搜索。
51
+ ```
52
+
53
+ 有定位、有动作、能搜索。
@@ -95,10 +95,10 @@ std::vector<std::vector<CellPosition>> BuildLines() {
95
95
  }
96
96
  ```
97
97
 
98
- **好**:固定坐标写成匿名命名空间里的常量表,`HasLine` 与 `IsLineComplete` 共用;格子用 `[]`,整条线 `all_of`、八条线 `any_of`。
98
+ **好**:固定坐标写成具名命名空间里的 `constexpr` 常量表,`HasLine` 与 `IsLineComplete` 共用,所以不放进任何一个函数体;格子用 `at()`,整条线 `all_of`、八条线 `any_of`,函数不标 `noexcept`。
99
99
 
100
100
  ```cpp
101
- // board.cpp 的匿名命名空间:行、列、对角线三组,坐标固定。
101
+ // board.cpp 的 namespace tictactoe:行、列、对角线三组,坐标固定。
102
102
  constexpr std::array<std::array<CellPosition, 3>, 8> kLines{{
103
103
  {{{0, 0}, {0, 1}, {0, 2}}}, // 行 0
104
104
  {{{1, 0}, {1, 1}, {1, 2}}}, // 行 1
@@ -126,11 +126,11 @@ return hypothetical.HasLine(cell_type);
126
126
  **好**:判断「这一格换成某种棋子后会不会成线」不需要第二份棋盘。越界、空格、已占用仍按 `PlaceCell` 的拒绝条件返回 `false`;通过之后只在包含该坐标的线上把这一格算作 `cell_type`,其余格子读原棋盘。
127
127
 
128
128
  ```cpp
129
- bool Board::IsLineComplete(CellPosition position, CellType cell_type) const noexcept {
129
+ bool Board::IsLineComplete(CellPosition position, CellType cell_type) const {
130
130
  if (!IsInRange(position) || cell_type == CellType::kEmpty) {
131
131
  return false;
132
132
  }
133
- if (cells_[position.row][position.col] != CellType::kEmpty) {
133
+ if (cells_.at(position.row).at(position.col) != CellType::kEmpty) {
134
134
  return false;
135
135
  }
136
136
 
@@ -138,7 +138,7 @@ bool Board::IsLineComplete(CellPosition position, CellType cell_type) const noex
138
138
  if (cell.row == position.row && cell.col == position.col) {
139
139
  return true;
140
140
  }
141
- return cells_[cell.row][cell.col] == cell_type;
141
+ return cells_.at(cell.row).at(cell.col) == cell_type;
142
142
  };
143
143
 
144
144
  return std::ranges::any_of(kLines, [&](const auto& line) {
@@ -150,4 +150,4 @@ bool Board::IsLineComplete(CellPosition position, CellType cell_type) const noex
150
150
  }
151
151
  ```
152
152
 
153
- 只用 `[]` 与 `IsInRange`,不调用 `at()`,头文件上的 `noexcept` 可以留下;原棋盘不被写入。
153
+ 先用 `IsInRange` 拒绝越界,再用 `at()` 读取;失败路径不碰数组,`noexcept` 也就不再成立。
@@ -4,7 +4,7 @@
4
4
 
5
5
  ## 总则
6
6
 
7
- - **第一句先写结果或限制**:类型给调用方什么,函数正常返回什么、失败时返回什么或抛什么,调用方必须守住哪条边界。第二句以后才写条件、参数与实现理由。
7
+ - **第一句先写结果、限制或检查项**:类型用名词短语写它是什么;普通函数写正常返回什么、失败时返回什么或抛什么;布尔查询与 `operator==` 写「检查…是否…」,不写「返回 true/false」。条件、参数与实现理由放第二句以后。
8
8
  - 公共注释回答「这是什么」和「调用方必须遵守什么」;实现注释回答「为什么这样写」和「边界在哪里」。设计取舍、方案对比与演进历史写到书稿、README 或提交说明。
9
9
  - 用中文散文写完整句子,句号结尾;标识符、标准库类型、`@param` 这类标签保持原文。
10
10
  - 行宽跟项目 `.clang-format` 的 `ColumnLimit` 走(Google 基线是 80 列,中文按两列计);横幅与列宽同宽,不另设数字。
@@ -23,9 +23,9 @@
23
23
  | 层 | 行数 | 第一句 | 写什么 |
24
24
  | --- | --- | --- | --- |
25
25
  | 文件头 | 3–6 | 这个文件是什么 | 由哪几部分组成、不负责什么 |
26
- | 类型注释 | 6–14 | 这个类型给调用方什么 | 概念、使用约定 |
27
- | 函数注释 | 3–10 | 正常返回什么、失败时返回或抛什么 | 参数与失败方式 |
28
- | 数据成员 | 行尾 1 句 | 存什么 | 范围或单位、初值的含义 |
26
+ | 类型注释 | 2–14 | 名词短语:这个类型或取值是什么 | 只有类的不变量 |
27
+ | 函数注释 | 3–10 | 正常返回什么、失败时返回或抛什么、检查什么 | 参数与失败方式 |
28
+ | 数据成员 | 行尾 1 句,名字与类型已自明时不写 | 名字和类型看不出的范围、初值含义 | 什么时候变 |
29
29
  | 实现注释 | 1–3 | 为什么这样写或边界在哪里 | 代价 |
30
30
 
31
31
 
@@ -47,16 +47,13 @@
47
47
 
48
48
  ## 类型注释
49
49
 
50
- 对象是类、结构体、枚举和类型别名。写两段,段间空一行:
50
+ 对象是类、结构体、枚举和类型别名。第一句是名词短语:类写「这个类型是什么」,纯数据结构和枚举写「这个取值表示什么」。
51
51
 
52
- 1. 结果:这个类型是什么、给谁用、保证什么。
53
- 2. 限制:本类型不变量、调用方必须遵守的条件、失败方式与所有权。
52
+ 只有类有不变量,而且不重复领域尺寸时,才加第二段「限制」。纯数据结构和枚举不写第二句。
54
53
 
55
54
 
56
55
  ```cpp
57
- /// 井字棋棋盘:保存九格状态,并回答满盘、成线与胜负。
58
- ///
59
- /// 格子数量固定为 3×3;落子只改变单个格子,不移动其他格子。
56
+ /// 井字棋棋盘。
60
57
  class Board {
61
58
  ```
62
59
 
@@ -67,13 +64,15 @@ class Board {
67
64
  - 不写跨类分工。边界靠类型与所有权表达,注释只写本类型保证什么。
68
65
  - 不写类型系统已经表达的性质(`值语义`、`所有查询都是 const`)。
69
66
  - 不写尚未发生的用法、方案对比与取舍论证、广告句。
70
- - 枚举与只承载数据的结构体:第一句写这个取值表示什么,需要时用冒号补一小句可核实的条件。
67
+ - 纯数据结构不写「只保存…,不拥有…,不含…」这类否定式边界;这些由成员类型和所有权表达。
68
+ - 领域尺寸不写进类型注释:格子数由 `cells_` 的声明表达,棋盘边长由 `kBoardRows`、`kBoardCols` 表达。
69
+ - 枚举与只承载数据的结构体:第一句是名词短语,写这个取值表示什么;需要条件时用 `///<` 补一句可核实的条件。
71
70
 
72
71
  ## 函数注释
73
72
 
74
73
  两段式,短函数可以只留第一段:
75
74
 
76
- 1. 结果或限制:这个函数给调用方什么结果;失败时返回什么、抛什么,或声明它不抛。
75
+ 1. 结果、限制或检查项:普通函数写返回什么、失败时返回或抛什么;布尔查询与 `operator==` 写「检查…是否…」。
77
76
  2. 契约:`@param`、`@return`、`@throws`,写范围、单位、所有权和失败方式。
78
77
 
79
78
 
@@ -85,7 +84,7 @@ class Board {
85
84
  [[nodiscard]] bool PlaceCell(CellPosition position, CellType cell_type);
86
85
  ```
87
86
 
88
- - 布尔查询:`@return` 写「什么情况下为 true」,再补一句「不检查什么」。
87
+ - 布尔查询与 `operator==`:第一句写「检查…是否…」;`@return` 只写「什么情况下为 true」,再补一句「不检查什么」。
89
88
  - `@param` 要么全写,要么全不写;写范围、单位或所有权,不写「索引」这类同义反复。
90
89
  - `@return` 在返回类型自明时可以省略;`[[nodiscard]]` 已表达「必须使用」时不再重复。
91
90
  - `@throws` 必须与 `references/exceptions.md` 一致;不抛异常的函数不写。
@@ -93,14 +92,14 @@ class Board {
93
92
 
94
93
  ## 数据成员与实现注释
95
94
 
96
- 数据成员用行尾注释,写「存什么 + 范围或单位 + 初值的含义」,同类成员对齐。不复述 `cells_[row][col]` 这类存储布局:数组维度和访问方式由声明表达。
95
+ 数据成员在名字和类型已经说清时不写行尾注释。只保留需要说明单位、哨兵值、外部约定或所有权的成员:写「单位或哨兵 + 外部约定 + 什么时候改写」,同类成员对齐。`cells_`、`count` 这类不复述存储布局;`current_player_` 与 `result_` 这类需要读者知道取值含义或改写时机的保留。
97
96
 
98
97
  ```cpp
99
- Board board_; // 对局棋盘。
100
- Player current_player_ = Player::kCross; // 当前轮到的玩家,开局时 X 先手。
98
+ Player current_player_ = Player::kCross; // 开局 X 先手;每次落子后切换。
99
+ GameResult result_ = GameResult::kInProgress; // 终局后不再改写。
101
100
  ```
102
101
 
103
- 实现注释只写三样东西:为什么这样写、这里的边界条件、这样做的代价。1–3 行,放在被解释语句上方;第一句先写理由或边界,不复述代码。
102
+ 实现注释只写读者看不出、但下一次修改会踩到的三样东西:为什么这样写(而不是另一种写法)、这里的边界条件、这样做的代价。1–3 行,放在被解释语句上方;第一句先写理由或边界,不复述代码,也不写变更史。
104
103
 
105
104
  `.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) 同样要求写意图而不是逐步翻译。文件头横幅保留,它对应两家的文件说明,不是函数体解说。
106
105
 
@@ -119,7 +118,7 @@ Player current_player_ = Player::kCross; // 当前轮到的玩家,开局时 X
119
118
 
120
119
  `.clang-format`、`.clang-tidy`、`.clangd`、CMake 的配置项旁边写「为什么这样设」,不写「这一项是什么」;配置注释贴在被解释的键上方。
121
120
 
122
- 只改注释时仍走第 3、4 步,然后回读:文件头 → 类型(结果/限制 + 约定)→ 函数(结果/失败方式 + 参数)→ 数据成员(存什么/约束/初值)→ 实现(为什么/边界)→ 术语与新符号是否真实存在 → 运行 `checking-comments.mjs` 兜底。
121
+ 只改注释时仍走第 3、4 步,然后回读:文件头 → 类型(名词短语 + 只有类的不变量)→ 函数(结果/失败方式/检查项 + 参数)→ 数据成员(名字类型看不出的含义)→ 实现(为什么/边界)→ 术语与新符号是否真实存在 → 运行 `checking-comments.mjs` 兜底。
123
122
 
124
123
  - 注释写给维护者,正文写给读者;设计理由、方案比较与算法取舍写在正文或实现注释里,公开接口只留契约。
125
124
  - `///`、`@param`、横幅不进入 `.qmd`、YAML、CSS。
@@ -84,7 +84,7 @@ class TaskTable {
84
84
  ## `.cpp` 的定义顺序
85
85
 
86
86
  - 与头文件声明同序:先类型和常量,再自由函数,再成员函数;成员函数按类内声明顺序。
87
- - 文件头注释之后先放本文件的 `namespace taskflow {`,再放公共定义;不需要外部引用的实现放匿名命名空间,或声明 `static`;按是否需要内部链接选择,不要为了「整洁」强行加。
87
+ - 文件头注释之后先放本文件的 `namespace taskflow {`,再放公共定义;常量仍排在具名命名空间内、成员函数之前;内部链接的写法按 `google-cpp-style.md`,本文不复述。
88
88
  - 构造函数、析构函数、拷贝/移动定义放在成员函数组的最前;`= default` 的定义放 `.cpp` 时紧跟声明顺序。
89
89
  - 不要为了「先编译」重排定义;C++ 不要求定义顺序,读者顺序优先。
90
90
 
@@ -69,7 +69,7 @@ E.31:`catch` 子句从派生到基类、从具体到通用排序,`catch (...
69
69
 
70
70
  ## 不抛的边界
71
71
 
72
- E.12:在不可能或不可接受抛异常的地方用 `noexcept`。声明前先读实现:函数体里出现 `at()` 或其他可能抛出的调用,就去掉 `noexcept`。正例是只比较两个整数的 `IsInRange`;反例是用 `at()` 写入并可能抛 `std::out_of_range` 的 `PlaceCell`。E.13:直接拥有资源的函数不要抛,资源释放应交给 RAII。E.16:析构、`deallocate`、`swap` 以及异常类型的拷贝/移动构造不得失败;析构和 `swap` 应当是 `noexcept`。
72
+ E.12:在不可能或不可接受抛异常的地方用 `noexcept`。声明前先读实现:函数体里出现 `at()` 或其他可能抛出的调用,就去掉 `noexcept`;不要再包一层只转发下标的私有函数来维持 `noexcept`,直接写 `cells_.at(row).at(col)` 并去掉 `noexcept`。正例是只比较两个整数的 `IsInRange`;反例是用 `at()` 写入并可能抛 `std::out_of_range` 的 `PlaceCell`。E.13:直接拥有资源的函数不要抛,资源释放应交给 RAII。E.16:析构、`deallocate`、`swap` 以及异常类型的拷贝/移动构造不得失败;析构和 `swap` 应当是 `noexcept`。
73
73
 
74
74
  E.30:不要写异常规范(`throw(...)` 之类);C++11 之后用 `noexcept` 表达「不抛」。需要无异常构建时,由项目单独决定,不要把这套异常指导改写成两面都对的模糊话。
75
75
 
@@ -57,7 +57,8 @@
57
57
  - 查询成员函数标尾部 `const`;返回结果对象的 API 加 `[[nodiscard]]`,不要给所有非 `void` 函数机械添加。
58
58
  - 所有权优先 `std::unique_ptr`;没有共享所有权的理由时不用 `std::shared_ptr`。禁止裸 `new` / `delete`。
59
59
  - 禁止 C 风格强制转换;用 `static_cast`、`const_cast`、`reinterpret_cast`、`std::bit_cast`,并让 `clang-tidy` 的 `google-readability-casting` 兜底。
60
- - `.cpp` 里不需要外部引用的定义放匿名命名空间或声明 `static`;不要在生产 `.cpp` 里为了「整洁」强行用 `static`,按是否需要内部链接决定。
60
+ - `.cpp` 里不需要外部引用的 `constexpr` 直接放具名命名空间;仅一个函数使用时才用函数内 `static constexpr`;函数和类型仍放匿名命名空间。
61
+ - 常量要出 `.cpp` 时按四步升:1) 其他翻译单元要的是算出来的答案,给类加查询、表留 `.cpp`;2) 要同一份坐标但仍不是公开契约,放组件 `src/` 的私有头;3) 对方必须写 `Board::kLines` 且这是类型的稳定事实,才用类内 `public static constexpr`;不依赖某个对象的公开常量则用命名空间 `inline constexpr`;4) 第二使用方复用同一套坐标时再抽共享组件。
61
62
 
62
63
  ## 函数
63
64
 
@@ -44,7 +44,7 @@ auto total = 0.0; // auto 只能用 =,不能用 {}
44
44
  | `constinit` | 静态存储期变量在编译期完成初始化 | 全局/静态变量 |
45
45
  | `inline constexpr` | 头文件里可共享的编译期常量,避免 ODR 问题 | 命名空间作用域常量 |
46
46
 
47
- - 共享常量写在头文件的命名空间作用域:`inline constexpr int kMaxRows = 3;`,不要用 `#define`。
47
+ - 共享常量写在头文件的命名空间作用域:`inline constexpr int kMaxRows = 3;`,不要用 `#define`。`.cpp` 里非 `inline`、非 `extern` 的命名空间作用域 `constexpr` 已有内部链接。
48
48
  - `constexpr` 函数的定义要放在头文件里,否则其他翻译单元无法在编译期调用。
49
49
  - 不要给所有函数加 `constexpr`;只有确实需要在编译期使用、且实现满足约束时才加。
50
50
  - `constinit` 只保证初始化时机,不保证不可修改;需要不可修改时同时写 `const`。
@@ -124,7 +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` 或先做边界检查。坐标类型只保存位置,不拥有容器区间;区间判断属于持有尺寸的类型(如 `Board::IsInRange`)。`std::span` 在 C++20 没有 `at`,`span::at` 到 C++26 才进入标准。
127
+ - 只在调用点已经证明下标合法时用 `[]`;否则用 `at` 或先做边界检查。坐标类型不拥有容器区间;区间判断属于持有尺寸的类型(如 `Board::IsInRange`)。`std::span` 在 C++20 没有 `at`,`span::at` 到 C++26 才进入标准。
128
128
  - `map::operator[]` 会插入,不是“读取可能缺失的键”;只读查询用 `find` / `contains` / `at`。
129
129
  - 契约失败的消息用 `std::format`,见 `references/exceptions.md`。
130
130
 
@@ -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
  // 用法:
@@ -31,6 +31,9 @@ const MEMBER_LIST = /提供[^。]{0,40}、[^。]{0,40}(的功能|的能力|的
31
31
  const RATIONALE = /而不是|而非|代价.{0,4}忽略|只留一份|与其|比.{0,12}更|更(?:安全|快|清楚|直接|好写|难写)/
32
32
  const FORWARD_REF = /(?:留在|归|交给|由)\s*`?[A-Z][A-Za-z0-9_]*`?/
33
33
  const TYPE_SYSTEM = /值语义|自由拷贝/
34
+ const NEGATED_BOUNDARY = /只保存|不拥有|不含/
35
+ const DOMAIN_SIZE = /\d+\s*[×xX]\s*\d+|九格|九个格子/
36
+ const PREDICATE_DECL = /^(?:\[\[nodiscard\]\]\s*)?(?:static\s+)?bool\s+(?:Is|Has|Can|Completes)[A-Za-z0-9_]*\s*\(|operator==/
34
37
  const DECL_KEYWORDS = /^(class|struct|union|enum|namespace|using|typedef|template|#|if|else|for|while|switch|return|case|default)\b/
35
38
 
36
39
  function usage(message) {
@@ -227,6 +230,16 @@ export function checkText(text, options = {}) {
227
230
  if (isType && TYPE_SYSTEM.test(block)) {
228
231
  add(start + 1, 'type-system', '类型注释在复述类型系统已表达的性质:可拷贝与 const 查询由声明给出,删掉这句')
229
232
  }
233
+ if (isType && NEGATED_BOUNDARY.test(block)) {
234
+ add(start + 1, 'negated-boundary', '类型注释用「只保存/不拥有/不含」写否定式边界:改成名词短语,边界交给成员类型表达')
235
+ }
236
+ if (isType && DOMAIN_SIZE.test(block)) {
237
+ add(start + 1, 'domain-size', '类型注释在写领域尺寸:格子数由 cells_ 之类成员声明表达,删掉这句')
238
+ }
239
+ const firstDocLine = lines[start].trim().replace(/^\/\/\/\s*/, '')
240
+ if (!isType && PREDICATE_DECL.test(declaration) && !firstDocLine.startsWith('检查')) {
241
+ add(start + 1, 'predicate-first', '布尔查询或 operator== 的第一句应写「检查…是否…」')
242
+ }
230
243
  const params = functionParams(declaration)
231
244
  if (!params) continue
232
245
  const documented = [...block.matchAll(/@param(?:\[[^\]]*\])?\s+(\w+)/g)].map((match) => match[1])
@@ -13,7 +13,7 @@
13
13
  - 单参数构造函数写 `explicit`;可查询的成员函数加 `[[nodiscard]]`;默认成员初始化写在声明处,不在构造函数里重复。
14
14
  - 头文件只就地定义大约 10 行以内的短函数;更长或需要完整定义的普通函数只在 `.cpp` 定义,模板和 `constexpr` 例外。
15
15
  - 失败是调用方的正常结果时,用结果类型、`bool` + 输出参数或 `std::optional` 表达;函数无法完成自己的任务、或构造函数无法建立 invariant 时,才在声明注释里写清抛出的异常类型;越界用 `std::out_of_range`,前置条件被破坏用 `std::logic_error`,只有调用方必须区分时才新增派生类型;Debug/Release 的抛出条件相同。判据见 `references/exceptions.md`。
16
- - 声明注释按 `references/comments.md` 分四层写:文件头的范围与边界、类型注释的结果/限制与约定、函数注释的第一句结果或失败方式与契约、数据成员的行尾说明。类型注释不罗列成员功能、不写方案对比与设计论证;全项目的风格理由写进项目文档,不写进注释。
16
+ - 声明注释按 `references/comments.md` 分四层写:文件头的范围与边界、类型注释的名词短语(纯数据与枚举一句,类只在不变量不重复领域尺寸时加第二段)、函数注释的第一句结果或失败方式或检查项与契约、数据成员只在名字和类型看不出的行尾说明。类型注释不罗列成员功能、不写领域尺寸、不写方案对比与设计论证;全项目的风格理由写进项目文档,不写进注释。
17
17
  - `std::format` 用于组合消息;声明里不要承诺 `std::print`(C++23)。
18
18
 
19
19
  ## 情况
@@ -30,4 +30,4 @@
30
30
 
31
31
  - 命名、访问器、语义角色:`examples/naming.md`。
32
32
  - 异常声明与返回值选择:`examples/exceptions.md`。
33
- - 声明注释写成了实现说明:`examples/comments.md`「声明注释」。
33
+ - 声明注释写成了实现说明:`examples/comments-declaration.md`。
@@ -33,6 +33,7 @@
33
33
 
34
34
  ## 正反例指针
35
35
 
36
- - 实现注释复述代码、分组混乱:`examples/comments.md`「实现注释」。
36
+ - 实现注释复述代码、分组混乱:`examples/comments-implementation.md`「实现注释」。
37
+ - 为了保住 `noexcept` 再包一层只转发下标的私有函数:`references/exceptions.md`「不抛的边界」。
37
38
  - 异常类型与结果类型混用:`examples/exceptions.md`「什么时候抛」。
38
39
  - `std::format` 与流输出混用得当:`examples/exceptions.md`「异常类型与消息」。