@chengzhao-dev/dsh-cpp-workflow 0.6.4 → 0.6.5
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 +4 -4
- package/README.md +4 -4
- package/lib/client.js +12 -37
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +4 -4
- package/skills/building-cpp/SKILL.md +3 -3
- package/skills/building-cpp/references/testing.md +54 -0
- package/skills/building-cpp/steps/03-check.md +1 -1
- package/skills/configuring-cmake/SKILL.md +1 -1
- package/skills/developing-cpp/SKILL.md +4 -4
- package/skills/developing-cpp/examples/comments-implementation.md +11 -6
- package/skills/developing-cpp/examples/structure.md +14 -12
- package/skills/developing-cpp/references/comments.md +32 -18
- package/skills/developing-cpp/references/declaration-order.md +7 -0
- package/skills/developing-cpp/references/exceptions.md +4 -2
- package/skills/developing-cpp/references/naming.md +5 -4
- package/skills/developing-cpp/references/priority.md +1 -0
- package/skills/developing-cpp/steps/03-declaration.md +3 -2
- package/skills/developing-cpp/steps/04-implementation.md +4 -1
package/README.en.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[中文](README.md)
|
|
4
4
|
|
|
5
|
-
Three skills for C++11–C++23 source, CMake, and build work; new projects default to C++23. The plugin registers a skill provider
|
|
5
|
+
Three skills for C++11–C++23 source, CMake, and build work; new projects default to C++23. The plugin registers a skill provider and a settings form; it does not register tools, compile, download a toolchain, or write project files.
|
|
6
6
|
|
|
7
7
|
## What it provides
|
|
8
8
|
|
|
@@ -19,20 +19,20 @@ 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.6.
|
|
22
|
+
dsh plugin --profile web add npm:@chengzhao-dev/dsh-cpp-workflow@0.6.5
|
|
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.6.
|
|
28
|
+
dsh plugin --profile web add github:chengzhao-dev/dsh-cpp-workflow#v0.6.5
|
|
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.
|
|
32
32
|
|
|
33
33
|
### Settings
|
|
34
34
|
|
|
35
|
-
The plugin page
|
|
35
|
+
The plugin page's "C++ Workflow" card is the only entry point and manages file-header tags only: `@file` is always the file name, `@brief` is written by default, and `@author`, `@date`, `@copyright`, `@version` stay off and are written only when they hold a non-empty value; `@param`, `@return`, `@throws` are required by default. The preview follows the switches, saving takes effect on the next skill read, and existing sources are never rewritten. Settings navigation no longer duplicates the form.
|
|
36
36
|
|
|
37
37
|
## Workspace gate
|
|
38
38
|
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[English](README.en.md)
|
|
4
4
|
|
|
5
|
-
把 C++11–C++23 的源码、CMake 与构建工作收成三个技能,新工程默认 C++23
|
|
5
|
+
把 C++11–C++23 的源码、CMake 与构建工作收成三个技能,新工程默认 C++23。插件注册技能 provider 与设置面,不注册工具、不编译、不下载工具链、不写项目文件。
|
|
6
6
|
|
|
7
7
|
## 提供什么
|
|
8
8
|
|
|
@@ -19,20 +19,20 @@
|
|
|
19
19
|
npm 安装钉死版本:
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
dsh plugin --profile web add npm:@chengzhao-dev/dsh-cpp-workflow@0.6.
|
|
22
|
+
dsh plugin --profile web add npm:@chengzhao-dev/dsh-cpp-workflow@0.6.5
|
|
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.6.
|
|
28
|
+
dsh plugin --profile web add github:chengzhao-dev/dsh-cpp-workflow#v0.6.5
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
桌面应用独占 `desktop` profile。命令行 `add` 被拒绝时,在应用设置里添加插件,地址填同一个 npm 版本。Git 标签安装使用仓库内已构建的 `lib/`,不在目标机器上重新构建;包内也不含需要授权执行的安装脚本。
|
|
32
32
|
|
|
33
33
|
### 设置
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
插件页的「C++ 工程流程」是唯一入口,只管文件头标签:`@file` 固定为文件名,`@brief` 默认写出,`@author`、`@date`、`@copyright`、`@version` 默认关闭且只在填了非空值时才写出;有参数就要求 `@param`,有返回值就要求 `@return`,会抛才要求 `@throws`。预览随开关即时更新,保存后下一次读取技能即生效,已有源码不会被改写。设置导航不再重复放一份。
|
|
36
36
|
|
|
37
37
|
## 工作区门禁
|
|
38
38
|
|
package/lib/client.js
CHANGED
|
@@ -193,7 +193,7 @@ function snapshotText(raw) {
|
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
|
|
196
|
-
//
|
|
196
|
+
// 客户端半侧:插件页的配置卡片(plugins.bundle.config),只此一个入口。
|
|
197
197
|
// 标签策略在本文件上方内联(由 scripts/building-client.mjs 从 src/comment-policy.js
|
|
198
198
|
// 拼进来),所以预览、默认值与服务端快照永远同源;这里只做表单与外观。
|
|
199
199
|
const React = require('react')
|
|
@@ -228,9 +228,9 @@ const STRINGS = {
|
|
|
228
228
|
rejected: '保存被拒绝:配置可能已在别处修改,请恢复出厂值后再试。',
|
|
229
229
|
unsaved: '有未保存的改动',
|
|
230
230
|
loading: '正在读取配置…',
|
|
231
|
+
unavailable: '这个部署没有提供这份配置。',
|
|
231
232
|
unreadable: '当前账户不能修改这份配置。',
|
|
232
|
-
readOnly: '
|
|
233
|
-
openSettings: '打开插件配置',
|
|
233
|
+
readOnly: '当前部署没有把可编辑配置传给这个页面,下面显示的是出厂值。',
|
|
234
234
|
fixed: '跟随文件名',
|
|
235
235
|
fixedHint: '值由实际文件决定,不写进配置。',
|
|
236
236
|
emptyValue: '留空则由文件自己写',
|
|
@@ -264,9 +264,9 @@ const STRINGS = {
|
|
|
264
264
|
rejected: 'The host rejected the save: the config changed elsewhere. Restore defaults and try again.',
|
|
265
265
|
unsaved: 'Unsaved changes',
|
|
266
266
|
loading: 'Reading configuration…',
|
|
267
|
+
unavailable: 'This deployment does not serve this configuration.',
|
|
267
268
|
unreadable: 'This account cannot edit the configuration.',
|
|
268
|
-
readOnly: '
|
|
269
|
-
openSettings: 'Open plugin settings',
|
|
269
|
+
readOnly: 'This deployment does not pass an editable form to this page; the values below are defaults.',
|
|
270
270
|
fixed: 'Follows the file name',
|
|
271
271
|
fixedHint: 'Derived from the file itself, never stored in the config.',
|
|
272
272
|
emptyValue: 'Leave empty and each file writes its own',
|
|
@@ -487,9 +487,9 @@ function ConfigPage(props) {
|
|
|
487
487
|
const [message, setMessage] = React.useState('')
|
|
488
488
|
const [tone, setTone] = React.useState('')
|
|
489
489
|
const revision = form && form.state ? form.state.revision : undefined
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
)
|
|
490
|
+
// 快照只有 status / writable / revision;旧版本多出来的 mode 字段已不存在,
|
|
491
|
+
// 再按它判断会把所有可写表单误判成只读。
|
|
492
|
+
const ready = Boolean(form && form.state && form.state.status === 'ready' && form.state.writable)
|
|
493
493
|
const status = form && form.state ? form.state.status : 'missing'
|
|
494
494
|
|
|
495
495
|
React.useEffect(() => {
|
|
@@ -539,7 +539,6 @@ function ConfigPage(props) {
|
|
|
539
539
|
|
|
540
540
|
const editable = Boolean(form)
|
|
541
541
|
const disabled = !ready
|
|
542
|
-
const navigation = props.pluginNavigation
|
|
543
542
|
|
|
544
543
|
return h(
|
|
545
544
|
'div',
|
|
@@ -557,17 +556,6 @@ function ConfigPage(props) {
|
|
|
557
556
|
{ className: 'dcw-note', role: 'status' },
|
|
558
557
|
h('span', { className: 'dcw-note-dot' }),
|
|
559
558
|
h('span', null, s.readOnly),
|
|
560
|
-
navigation && typeof navigation.openBundle === 'function'
|
|
561
|
-
? h(
|
|
562
|
-
'button',
|
|
563
|
-
{
|
|
564
|
-
type: 'button',
|
|
565
|
-
className: 'dcw-btn',
|
|
566
|
-
onClick: () => navigation.openBundle(PACKAGE_NAME),
|
|
567
|
-
},
|
|
568
|
-
s.openSettings,
|
|
569
|
-
)
|
|
570
|
-
: null,
|
|
571
559
|
)
|
|
572
560
|
: null,
|
|
573
561
|
editable && !ready && status !== 'missing'
|
|
@@ -575,7 +563,7 @@ function ConfigPage(props) {
|
|
|
575
563
|
'div',
|
|
576
564
|
{ className: 'dcw-note', role: 'status' },
|
|
577
565
|
h('span', { className: 'dcw-note-dot' }),
|
|
578
|
-
h('span', null, status === 'loading' ? s.loading : s.unreadable),
|
|
566
|
+
h('span', null, status === 'loading' ? s.loading : status === 'unavailable' ? s.unavailable : s.unreadable),
|
|
579
567
|
)
|
|
580
568
|
: null,
|
|
581
569
|
h(
|
|
@@ -678,24 +666,11 @@ function ConfigPage(props) {
|
|
|
678
666
|
)
|
|
679
667
|
}
|
|
680
668
|
|
|
681
|
-
function page(ownerProps
|
|
682
|
-
|
|
683
|
-
return h(ConfigPage, { ...props, pluginNavigation })
|
|
669
|
+
function page(ownerProps) {
|
|
670
|
+
return h(ConfigPage, ownerProps || {})
|
|
684
671
|
}
|
|
685
672
|
|
|
686
673
|
function apply(ctx) {
|
|
687
|
-
const navigation = typeof ctx.get === 'function' ? ctx.get('pluginNavigation') : undefined
|
|
688
|
-
ctx.slots.inject('settings.section', () =>
|
|
689
|
-
ctx.slots.register(
|
|
690
|
-
{
|
|
691
|
-
name: 'settings.section',
|
|
692
|
-
id: 'cpp-workflow',
|
|
693
|
-
order: 60,
|
|
694
|
-
label: () => t().nav,
|
|
695
|
-
},
|
|
696
|
-
(ownerProps) => page(ownerProps, navigation),
|
|
697
|
-
),
|
|
698
|
-
)
|
|
699
674
|
ctx.slots.inject('plugins.bundle.config', () =>
|
|
700
675
|
ctx.slots.register(
|
|
701
676
|
{
|
|
@@ -703,7 +678,7 @@ function apply(ctx) {
|
|
|
703
678
|
key: PACKAGE_NAME,
|
|
704
679
|
label: () => t().nav,
|
|
705
680
|
},
|
|
706
|
-
(ownerProps) => (ownerProps && ownerProps.view === 'summary' ? null : page(ownerProps
|
|
681
|
+
(ownerProps) => (ownerProps && ownerProps.view === 'summary' ? null : page(ownerProps)),
|
|
707
682
|
),
|
|
708
683
|
)
|
|
709
684
|
}
|
package/locale/en.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"meta": {
|
|
3
3
|
"title": "C++ Workflow",
|
|
4
|
-
"description": "Write C++ sources, configure CMake, and build with Clang; /// Doxygen
|
|
4
|
+
"description": "Write C++ sources, configure CMake, and build with Clang; /// Doxygen on declarations, // section notes in bodies, and configurable file-header tags."
|
|
5
5
|
}
|
|
6
6
|
}
|
package/locale/zh.json
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chengzhao-dev/dsh-cpp-workflow",
|
|
3
|
-
"version": "0.6.
|
|
4
|
-
"description": "写 C++ 源码、配 CMake、用 Clang
|
|
3
|
+
"version": "0.6.5",
|
|
4
|
+
"description": "写 C++ 源码、配 CMake、用 Clang 构建;头文件用 /// Doxygen 文档注释,函数体用 // 分段说明,文件头标签可在设置里改。 · Write C++ sources, configure CMake, and build with Clang; /// Doxygen on declarations, // section notes in bodies, and configurable file-header tags.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
7
7
|
"exports": {
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"catalog": {
|
|
39
39
|
"category": "skill",
|
|
40
40
|
"summary": {
|
|
41
|
-
"zh": "写 C++ 源码、配 CMake、用 Clang
|
|
42
|
-
"en": "Write C++ sources, configure CMake, and build with Clang; /// Doxygen
|
|
41
|
+
"zh": "写 C++ 源码、配 CMake、用 Clang 构建;头文件用 /// Doxygen 文档注释,函数体用 // 分段说明,文件头标签可在设置里改。",
|
|
42
|
+
"en": "Write C++ sources, configure CMake, and build with Clang; /// Doxygen on declarations, // section notes in bodies, and configurable file-header tags."
|
|
43
43
|
},
|
|
44
44
|
"capabilities": [
|
|
45
45
|
"skills"
|
|
@@ -36,11 +36,11 @@ metadata:
|
|
|
36
36
|
| 2 | 定工具配置 | `steps/02-tooling.md` | 格式化、静态检查、clangd 各管一段,互不越界 |
|
|
37
37
|
| 3 | 通过检查 | `steps/03-check.md` | 按编码 → 声明校验 → 活动校验 → 项目入口的次序跑,前一步失败就停 |
|
|
38
38
|
|
|
39
|
-
每步只打开同号的一个步骤文件。判不准才打开 `examples/` 里与该步对应的案例;`references/` 保存完整条文,只在排错、整目录重构,或步骤里明确指向某一条文时按需打开。链接、rpath 与产物布局读 `references/linking.md`;CMake 的 target、标准和函数写法读 `../configuring-cmake/references/`,可复用片段在 `../configuring-cmake/assets/cmake/`。
|
|
39
|
+
每步只打开同号的一个步骤文件。判不准才打开 `examples/` 里与该步对应的案例;`references/` 保存完整条文,只在排错、整目录重构,或步骤里明确指向某一条文时按需打开。链接、rpath 与产物布局读 `references/linking.md`;测试设计、断言与 CTest 注册读 `references/testing.md`;CMake 的 target、标准和函数写法读 `../configuring-cmake/references/`,可复用片段在 `../configuring-cmake/assets/cmake/`。
|
|
40
40
|
|
|
41
41
|
本轮激活按三条上限走:
|
|
42
42
|
|
|
43
|
-
-
|
|
43
|
+
- 只开当前步对应的一个步骤文件;同一改动域内不同时展开两个技能的步骤。跨域按 `developing-cpp` 的交接顺序(源码 → `configuring-cmake` → `building-cpp`)逐段进入,前一段结论落下再开下一段。
|
|
44
44
|
- 条文只开该步点名的一份;同号案例留到下一次激活。
|
|
45
45
|
- 标准档只打开项目技能声明的那一档,没有声明时用本技能已有的新工程默认,不把其余标准档读进来。
|
|
46
46
|
|
|
@@ -63,5 +63,5 @@ metadata:
|
|
|
63
63
|
- 配置命令固定 Ninja 和单一编译器,`build/<compiler>/compile_commands.json` 存在,根 `build/compile_commands.json` 指向当前编译器。
|
|
64
64
|
- target 契约已经成立:程序 `app`、库 target、公共 include、标准与输出目录都已由 `configuring-cmake` 写好;本技能没有偷偷改 CMake 来绕过失败。
|
|
65
65
|
- `clang-format --dry-run --Werror` 与 `clang-tidy` 在项目源码上零告警;只扫描项目源码和项目头。
|
|
66
|
-
- CTest
|
|
66
|
+
- CTest 全绿,程序从该编译器的构建目录启动;有测试时注册用 `gtest_discover_tests` 或项目既有机制,运行带 `--output-on-failure --no-tests=error`,失败时报告的是具体角色和原因。
|
|
67
67
|
- 没有改源码语义,没有覆盖已有工程文件,也没有安装或下载任何工具。
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# 测试与 CTest
|
|
2
|
+
|
|
3
|
+
测试属于构建域:测试 target 由 `configuring-cmake` 写进 CMake,测试代码与 CTest 运行由本技能核对。条文依据 GoogleTest Primer、CMake 的 `add_test` / `gtest_discover_tests` 文档与 CTest 手册,访问日期 2026-10-27。
|
|
4
|
+
|
|
5
|
+
## 先决定测什么
|
|
6
|
+
|
|
7
|
+
- 一个公开入口至少一条成功用例、一条拒绝用例;拒绝路径核对返回值、异常类型与状态没有被改坏。
|
|
8
|
+
- 一条测试只核对一件事。用例名写行为,不写实现:`PlaceCellRejectsOccupiedCell`,不是 `TestPlaceCell2`。
|
|
9
|
+
- 测试直接链接库、包含公共头,不经过 `main()`、终端或文件系统;要进程、端口、临时目录的用例归集成测试,单列标签。
|
|
10
|
+
- 不测私有成员与内部调用次数;测试锁定可观察行为,重构不该改测试。
|
|
11
|
+
|
|
12
|
+
## 断言
|
|
13
|
+
|
|
14
|
+
- `ASSERT_*` 失败立即结束当前用例,只用于后续断言依赖的前提;`EXPECT_*` 失败继续执行,用于并列核对。
|
|
15
|
+
- 浮点比较用 `EXPECT_NEAR` / `EXPECT_DOUBLE_EQ`;容器整体比较优先 `EXPECT_EQ`,不要手写循环逐元素打印。
|
|
16
|
+
- 异常用 `EXPECT_THROW` / `EXPECT_NO_THROW`;`[[nodiscard]]` 函数在只测异常时写 `(void)`。
|
|
17
|
+
- 一条用例只留一个逻辑主题;用例之间不共享可变状态,需要固定数据时用 fixture 的 `SetUp`。
|
|
18
|
+
|
|
19
|
+
## 注册给 CTest
|
|
20
|
+
|
|
21
|
+
```cmake
|
|
22
|
+
include(GoogleTest)
|
|
23
|
+
gtest_discover_tests(tictactoe_test
|
|
24
|
+
DISCOVERY_TIMEOUT 30
|
|
25
|
+
PROPERTIES LABELS unit TIMEOUT 10)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
- `gtest_discover_tests` 在构建后枚举用例,一条 `TEST` 对应一个 CTest 测试;不要再用 `add_test(NAME all COMMAND ...)` 包住整个可执行文件。
|
|
29
|
+
- 每个测试 target 设 `TIMEOUT`;慢测试单独 `LABELS slow`,CI 快层用 `ctest -L unit --output-on-failure --no-tests=error`。
|
|
30
|
+
- 需要独占端口、固定文件或数据库的用例用 `RESOURCE_LOCK`;有准备/清理步骤时用 `FIXTURES_SETUP` / `FIXTURES_REQUIRED`。
|
|
31
|
+
- `--no-tests=error` 防止「0 个测试也算通过」;`--output-on-failure` 只在失败时打印输出,避免日志淹没。
|
|
32
|
+
|
|
33
|
+
## 运行顺序
|
|
34
|
+
|
|
35
|
+
1. 构建通过后跑 `ctest --test-dir build/<compiler> --output-on-failure --no-tests=error`。
|
|
36
|
+
2. 先跑失败用例的单条:`ctest -R <用例名> --output-on-failure`,确认失败可复现。
|
|
37
|
+
3. 修完后跑全量;回归失败先判断是行为变了还是测试过期,不直接改断言迁就实现。
|
|
38
|
+
4. Debug 一轮加 `-fsanitize=address,undefined -fno-omit-frame-pointer` 重跑测试;sanitizer 报告按第一条栈帧定位,不靠猜。
|
|
39
|
+
|
|
40
|
+
## 失败分诊
|
|
41
|
+
|
|
42
|
+
| 现象 | 先看 | 转到 |
|
|
43
|
+
| --- | --- | --- |
|
|
44
|
+
| 找不到测试 target 或 `gtest` | CMake 的 `enable_testing()`、`add_subdirectory(tests)`、链接目标 | `configuring-cmake` |
|
|
45
|
+
| 断言失败但行为符合预期 | 测试期望是否写错、是否依赖未初始化状态 | 本文件 |
|
|
46
|
+
| 断言失败且行为不对 | 被测函数的实现与契约 | `developing-cpp` |
|
|
47
|
+
| 用例数比预期少 | `gtest_discover_tests` 的发现超时、测试是否在正确 target | 本文件与 `steps/03-check.md` |
|
|
48
|
+
| 偶发失败 | 共享临时文件、固定端口、执行顺序依赖 | 本文件 |
|
|
49
|
+
|
|
50
|
+
## 不做什么
|
|
51
|
+
|
|
52
|
+
- 不为凑覆盖率写没有断言的测试;不用 `sleep` 等待异步结果。
|
|
53
|
+
- 不在测试里复制一份被测实现来对照。
|
|
54
|
+
- 不把测试输出、`Testing/` 目录或生成的可执行文件入库。
|
|
@@ -46,7 +46,7 @@ node "<skill-dir>/scripts/checking-environment.mjs" --project . --live # 活
|
|
|
46
46
|
|
|
47
47
|
**只改配置**:至少跑一次完整构建 + CTest;配置错误常常在链接或测试阶段才暴露。
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
**没有测试**:项目确实还没有测试时,CTest 显示 0 条不算构建失败;不要为了「全绿」造空测试。已有公开行为却没有任何用例时,按 `references/testing.md` 的「先决定测什么」补成功与拒绝各一条,再跑 `--no-tests=error` 确认它真的被注册。
|
|
50
50
|
|
|
51
51
|
**Windows 原生**:本版本不支持 `host=windows` + `build_host=native`。声明校验会返回 `ok: false`,`issues` 只有一句「本版本不能在 Windows 原生壳里构建」。停在这里,不要伪造 PowerShell 脚本,不要建议改 WSL 来绕过,也不要让代理改 `environment.json` 过关。
|
|
52
52
|
|
|
@@ -36,7 +36,7 @@ metadata:
|
|
|
36
36
|
|
|
37
37
|
本轮激活按三条上限走:
|
|
38
38
|
|
|
39
|
-
-
|
|
39
|
+
- 只开当前步对应的一个步骤文件;同一改动域内不同时展开两个技能的步骤。跨域按 `developing-cpp` 的交接顺序(源码 → `configuring-cmake` → `building-cpp`)逐段进入,前一段结论落下再开下一段。
|
|
40
40
|
- 条文只开该步点名的一份;同号案例留到下一次激活。
|
|
41
41
|
- 标准档只打开项目技能声明的那一档,没有声明时用本技能已有的新工程默认,不把其余标准档读进来。
|
|
42
42
|
|
|
@@ -9,9 +9,9 @@ metadata:
|
|
|
9
9
|
|
|
10
10
|
## 适用场景
|
|
11
11
|
|
|
12
|
-
本技能处理换一台机器也不会改的源码内容:可观察行为、库与程序的边界、声明、命名、异常和源码注释。文件槽位与守卫以 `references/file-template.md` 为准;排版、命名、头文件与包含顺序以 `references/google-cpp-style.md` 的 Google 基线为准。异常条文取自 C++ Core Guidelines 的 Error handling,只约束开发期的源码,不进入插件运行时。`CMakeLists.txt` 与 `cmake/` 开 `configuring-cmake`;`running-build.sh`、`.clang-*`、`.vscode/` 和 `environment.json`
|
|
12
|
+
本技能处理换一台机器也不会改的源码内容:可观察行为、库与程序的边界、声明、命名、异常和源码注释。文件槽位与守卫以 `references/file-template.md` 为准;排版、命名、头文件与包含顺序以 `references/google-cpp-style.md` 的 Google 基线为准。异常条文取自 C++ Core Guidelines 的 Error handling,只约束开发期的源码,不进入插件运行时。`CMakeLists.txt` 与 `cmake/` 开 `configuring-cmake`;`running-build.sh`、`.clang-*`、`.vscode/` 和 `environment.json` 的维护开 `building-cpp`(本技能只读其中的 `standard`)。只改函数体、命名、异常或注释且入口命令可运行时,定实现后只开 `building-cpp` 的「通过检查」;新增或移动源文件、改库与程序的边界、补测试 target 时,声明和实现可以先写,但完成前必须开 `configuring-cmake` 把文件写进 CMake,再开 `building-cpp` 定工具配置并通过检查。`.qmd`、仓库 README、Python 和非 C++ 文档不打开本技能。
|
|
13
13
|
|
|
14
|
-
存在项目技能 `cpp-workflow-project` 时先读它的 `SKILL.md
|
|
14
|
+
存在项目技能 `cpp-workflow-project` 时先读它的 `SKILL.md`:工程名、库与程序的边界、阶段表、术语增补、入口命令。动源码前还要读项目声明的语言标准(`environment.json` 的 `standard`,或项目技能正文写明的档位);声明 C++20 的工程里不要写 C++23 才有的设施。项目没有声明时才用本技能的新工程默认 C++23。没有项目技能时门禁不会放出本技能,不要猜项目约定。
|
|
15
15
|
|
|
16
16
|
源码和构建文件的编码以 `building-cpp` 的 `references/encoding.md` 为准;行尾与 `.clang-format` 的 `LineEnding` 跟仓库根 `.gitattributes` 和 `.editorconfig`。
|
|
17
17
|
|
|
@@ -22,7 +22,7 @@ metadata:
|
|
|
22
22
|
| 增量修正 | 用户点了一个函数、一条诊断或一个文件 | 沿本技能步骤走,只改判为不合格的那一处,其余保持原样 |
|
|
23
23
|
| 整段重构 | 新工程、新阶段、用户没点名范围,或改动跨过库与程序的边界 | 同样四步;第 1、2 步先定删留,不动函数体;第 3、4 步才改代码 |
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
两种走法共用这些约束:不发明阶段表和用户请求里没有的行为、类型和命令;选类型与写法时以项目声明的语言标准为上限;只改注释或格式时不改可观察行为;不覆盖已有源码;不安装工具链;代理侧不写 `wsl` 或 `bash -lc`。后一步发现前一步没做完,退回那一步,不在当前步补结构。拿不准先用增量修正;做到一半发现边界被打破,退回整段重构,从被打破的那一步重走。
|
|
26
26
|
|
|
27
27
|
## 步骤
|
|
28
28
|
|
|
@@ -37,7 +37,7 @@ metadata:
|
|
|
37
37
|
|
|
38
38
|
本轮激活按三条上限走:
|
|
39
39
|
|
|
40
|
-
-
|
|
40
|
+
- 只开当前步对应的一个步骤文件;同一改动域内不同时展开两个技能的步骤。跨域按 `developing-cpp` 的交接顺序(源码 → `configuring-cmake` → `building-cpp`)逐段进入,前一段结论落下再开下一段。
|
|
41
41
|
- 条文只开该步点名的一份;同号案例留到下一次激活。
|
|
42
42
|
- 标准档只打开项目技能声明的那一档,没有声明时用本技能已有的新工程默认,不把其余标准档读进来。
|
|
43
43
|
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// 遍历所有空格
|
|
11
11
|
for (const auto& position : empty_cells) {
|
|
12
12
|
// 尝试落子
|
|
13
|
-
const bool wins = game.
|
|
13
|
+
const bool wins = game.PreviewWin(position, cell_type) == WinPreviewResult::kWouldWin;
|
|
14
14
|
// 如果能赢
|
|
15
15
|
if (wins) {
|
|
16
16
|
// 返回这一手
|
|
@@ -24,19 +24,24 @@ for (const auto& position : empty_cells) {
|
|
|
24
24
|
**好**:
|
|
25
25
|
|
|
26
26
|
```cpp
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
// 先找能立刻赢的格子:启发式只在「马上赢」和「马上挡」之间做选择,
|
|
28
|
+
// 不在这里做更深的前瞻,深搜留给 MinimaxAi。
|
|
29
29
|
for (const auto& position : empty_cells) {
|
|
30
|
-
if (game.
|
|
30
|
+
if (game.PreviewWin(position, CellType::kX) == WinPreviewResult::kWouldWin) {
|
|
31
31
|
return position;
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
// 没有必胜格,交给下一层策略;这里不猜后续几步。
|
|
36
36
|
return std::nullopt;
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
空行分开的阶段各一句,写这一阶段为什么停在这里。空行与注释的边界:
|
|
40
|
+
|
|
41
|
+
- 多条独立拒绝:每个 `if` 上一行一句,写拒绝后棋盘怎样。
|
|
42
|
+
- 成功写入:上一行一句,写为什么到这里才改棋盘。
|
|
43
|
+
- 次序依赖的一对 `if`:第一组上方一句说明次序不能反,第二个不复制。
|
|
44
|
+
- 循环上方已有阶段说明时,循环里的 `if` 不再加;单表达式谓词不加。
|
|
40
45
|
|
|
41
46
|
## TODO
|
|
42
47
|
|
|
@@ -95,7 +95,7 @@ std::vector<std::vector<CellPosition>> BuildLines() {
|
|
|
95
95
|
}
|
|
96
96
|
```
|
|
97
97
|
|
|
98
|
-
**好**:固定坐标写成具名命名空间里的 `constexpr` 常量表,`HasLine` 与 `
|
|
98
|
+
**好**:固定坐标写成具名命名空间里的 `constexpr` 常量表,`HasLine` 与 `PreviewWin` 共用,所以不放进任何一个函数体;格子用 `at()`,整条线 `all_of`、八条线 `any_of`,函数不标 `noexcept`。
|
|
99
99
|
|
|
100
100
|
```cpp
|
|
101
101
|
// board.cpp 的 namespace tictactoe:行、列、对角线三组,坐标固定。
|
|
@@ -115,23 +115,24 @@ constexpr std::array<std::array<CellPosition, 3>, 8> kLines{{
|
|
|
115
115
|
|
|
116
116
|
## 推演一步读原数据
|
|
117
117
|
|
|
118
|
-
|
|
118
|
+
**差**:先复制整个棋盘,再在副本上落子,最后判断成线;调用方看不出副本只是中间量,还要重复处理落子失败。
|
|
119
119
|
|
|
120
120
|
```cpp
|
|
121
121
|
Board hypothetical = *this;
|
|
122
|
-
hypothetical.PlaceCell(position, cell_type);
|
|
123
|
-
|
|
122
|
+
const PlaceCellResult placed = hypothetical.PlaceCell(position, cell_type);
|
|
123
|
+
if (placed != PlaceCellResult::kSuccess) {
|
|
124
|
+
return WinPreviewResult::kInvalidPlace;
|
|
125
|
+
}
|
|
126
|
+
return hypothetical.HasLine(cell_type) ? WinPreviewResult::kWouldWin
|
|
127
|
+
: WinPreviewResult::kWouldNotWin;
|
|
124
128
|
```
|
|
125
129
|
|
|
126
|
-
|
|
130
|
+
**好**:判断「这一格换成某种棋子后会不会成线」不需要第二份棋盘。越界、空格、已占用交给 `CheckPlaceable` 统一返回原因;通过之后只在包含该坐标的线上把这一格算作 `cell_type`,其余格子读原棋盘。
|
|
127
131
|
|
|
128
132
|
```cpp
|
|
129
|
-
|
|
130
|
-
if (
|
|
131
|
-
return
|
|
132
|
-
}
|
|
133
|
-
if (cells_.at(position.row).at(position.col) != CellType::kEmpty) {
|
|
134
|
-
return false;
|
|
133
|
+
WinPreviewResult Board::PreviewWin(CellPosition position, CellType cell_type) const {
|
|
134
|
+
if (CheckPlaceable(position, cell_type) != PlaceCellResult::kSuccess) {
|
|
135
|
+
return WinPreviewResult::kInvalidPlace;
|
|
135
136
|
}
|
|
136
137
|
|
|
137
138
|
const auto matches = [&](CellPosition cell) {
|
|
@@ -141,12 +142,13 @@ bool Board::IsLineComplete(CellPosition position, CellType cell_type) const {
|
|
|
141
142
|
return cells_.at(cell.row).at(cell.col) == cell_type;
|
|
142
143
|
};
|
|
143
144
|
|
|
144
|
-
|
|
145
|
+
const bool completes = std::ranges::any_of(kLines, [&](const auto& line) {
|
|
145
146
|
const bool contains = std::ranges::find_if(line, [&](CellPosition cell) {
|
|
146
147
|
return cell.row == position.row && cell.col == position.col;
|
|
147
148
|
}) != line.end();
|
|
148
149
|
return contains && std::ranges::all_of(line, matches);
|
|
149
150
|
});
|
|
151
|
+
return completes ? WinPreviewResult::kWouldWin : WinPreviewResult::kWouldNotWin;
|
|
150
152
|
}
|
|
151
153
|
```
|
|
152
154
|
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
- 用中文散文写完整句子,句号结尾;标识符、标准库类型、`@param` 这类标签保持原文。
|
|
12
12
|
- 写之前三问:删掉这句,读者会误解设计或做错事吗?这句只是复述签名或代码吗?这个限制能不能由类型、`const` 或 `[[nodiscard]]` 表达?第一问为否就删,第二问为是就删,第三问为是就改代码。
|
|
13
13
|
- 命名、分文件、`enum class`、异常与结果类型这类全项目风格约定只写进项目文档与读者正文;注释不替项目解释风格。
|
|
14
|
+
- 状态结论必须带成立条件。「终局后空格仍在」没有说清哪种终局;写成「一方已三连而棋盘尚未占满时,空格仍然留着;平局时棋盘已满,没有空格」。删掉前提换来的短,不算润色。
|
|
14
15
|
|
|
15
16
|
### 类型:名词短语与不变量
|
|
16
17
|
|
|
@@ -39,10 +40,12 @@ class Board {
|
|
|
39
40
|
|
|
40
41
|
```cpp
|
|
41
42
|
/// 默认构造函数:得到空棋盘,每个格子都是 kEmpty。
|
|
42
|
-
|
|
43
|
+
///
|
|
44
|
+
/// 构造后的状态与调用 `Clear()` 相同,不抛异常。
|
|
45
|
+
Board() noexcept;
|
|
43
46
|
```
|
|
44
47
|
|
|
45
|
-
|
|
48
|
+
默认构造、析构和 `void` 且无参数的函数写两段:第一段写后置条件或职责,第二段写调用方还要知道的边界;不写空的 `@param`、`@return`。有参数或有返回值的函数按下一节写标签,构造与析构不写 `@return`。标签说明这是哪一类特殊成员,不重复类名;非默认构造同样写建立的不变量与失败方式。
|
|
46
49
|
|
|
47
50
|
### 数据成员:上一行 `///`,先写名词再写约定
|
|
48
51
|
|
|
@@ -61,28 +64,37 @@ GameResult result_ = GameResult::kInProgress;
|
|
|
61
64
|
|
|
62
65
|
### 函数:两段式契约
|
|
63
66
|
|
|
64
|
-
|
|
67
|
+
有参数或有返回值的函数写两段:第一段写结果、失败方式或检查项;第二段按签名把标签写齐。
|
|
65
68
|
|
|
66
|
-
1.
|
|
67
|
-
2.
|
|
69
|
+
1. 第一段:普通函数写正常返回什么、失败时返回什么或抛什么;布尔查询与 `operator==` 写「检查…是否…」。
|
|
70
|
+
2. 第二段:每个参数一条 `@param`;有返回值就写 `@return`;会抛才写 `@throws`。
|
|
68
71
|
|
|
69
72
|
```cpp
|
|
70
|
-
///
|
|
73
|
+
/// 检查坐标能否落子,不修改棋盘。
|
|
71
74
|
///
|
|
72
75
|
/// @param position 目标行列坐标。
|
|
73
76
|
/// @param cell_type 要放置的棋盘格子类型,必须是 kX 或 kO。
|
|
74
|
-
|
|
77
|
+
/// @return 可以落子返回 kSuccess;越界、cell_type 为 kEmpty 或目标已有棋子时返回对应拒绝值。
|
|
78
|
+
[[nodiscard]] PlaceCellResult CheckPlaceable(CellPosition position, CellType cell_type) const;
|
|
75
79
|
```
|
|
76
80
|
|
|
77
|
-
- 布尔查询与 `operator==`:第一句写「检查…是否…」;`@return` 只写「什么情况下为 true
|
|
78
|
-
- `@param`
|
|
79
|
-
- `@return`
|
|
81
|
+
- 布尔查询与 `operator==`:第一句写「检查…是否…」;`@return` 只写「什么情况下为 true」,再补一句「不检查什么」。条件简单也要写,「名字已经说明」不构成省略理由。
|
|
82
|
+
- `@param` 每个参数都写,写范围、单位或所有权,不写「索引」这类同义反复;没有参数就不写空的 `@param`。
|
|
83
|
+
- `@return` 每个非 `void`、非构造、非析构的函数都写,写返回值怎么读或失败时返回什么;不要用「见上」或重复第一段。
|
|
80
84
|
- `@throws` 必须与 `references/exceptions.md` 一致;不抛异常的函数不写。
|
|
81
85
|
- 私有函数同样写契约,不能默认调用者知道前置条件;输出参数标 `[out]`,既读又写标 `[in,out]`。
|
|
82
86
|
|
|
83
87
|
### 实现:阶段边界与为什么
|
|
84
88
|
|
|
85
|
-
`.cpp` / `.cc`
|
|
89
|
+
`.cpp` / `.cc` 里,函数体用 `//`,不用 `///`:体内的 `///` 会接到该函数的 Doxygen 详细说明后面,成为误导性文档。重要函数按空行分段,每一段上方写一句 `//`。句子写边界、失败后果或为什么停在这里,不把下一行条件翻译一遍;`IsFull` 这种单表达式函数不配注释。只写删掉会让下一次修改得到错误结果、而代码本身看不出的内容。
|
|
90
|
+
|
|
91
|
+
什么时候在 `if` 上方加一句:
|
|
92
|
+
|
|
93
|
+
- 多条独立拒绝(如 `CheckPlaceable`):每个 `if` 上一行一句,写拒绝后棋盘怎样。
|
|
94
|
+
- 成功写入的上一行一句,写为什么到这里才改棋盘。
|
|
95
|
+
- 只有次序依赖的一对 `if`(如 `GetWinner`、`JudgeAfterPlace`):第一组上方一句说明次序为什么不能反,第二个 `if` 不复制同一句。
|
|
96
|
+
- 循环上方已经有阶段说明时,循环里的 `if` 不再加一行。
|
|
97
|
+
- `HasLine`、`IsFull` 这类单表达式不加;`GetWinner` 里的次序判断只在它真的解释「为什么先判胜」时留一句。
|
|
86
98
|
|
|
87
99
|
大型工程的共识是解释意图而不是逐步翻译:[Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html#Implementation_Comments) 要求注释解释代码在做什么,不描述代码本身;[LLVM Coding Standards](https://www.llvm.org/docs/CodingStandards.html#commenting) 要求写意图与理由。阶段注释写的就是这两类信息。
|
|
88
100
|
|
|
@@ -103,16 +115,18 @@ GameResult result_ = GameResult::kInProgress;
|
|
|
103
115
|
| 文件头 | 3–7 | `@file` 文件名 + `@brief` 一句话 | 本文件由哪几部分组成 |
|
|
104
116
|
| 类型注释 | 2–14 | 名词短语:这个类型或取值是什么 | 只有类的不变量 |
|
|
105
117
|
| 特殊成员 | 1–3 | 标签(默认/拷贝/移动构造函数、析构函数) | 后置条件或职责 |
|
|
106
|
-
| 函数注释 | 3–10 | 正常返回什么、失败时返回或抛什么、检查什么 |
|
|
118
|
+
| 函数注释 | 3–10 | 正常返回什么、失败时返回或抛什么、检查什么 | 每个参数、返回值与失败方式 |
|
|
119
|
+
| 无标签函数 | 2 | 两段:后置条件/职责,再补边界 | 仅限构造、析构、`void` 无参数 |
|
|
107
120
|
| 数据成员 | 上一行 1 句(`/// 名词:约定`) | 名字和类型看不出的范围、初值含义、轴向或哨兵 | 什么时候变 |
|
|
108
|
-
| 实现注释 | 每个空行阶段 1 句 | 边界、失败后果或为什么停在这里 |
|
|
121
|
+
| 实现注释 | 每个空行阶段 1 句 | 边界、失败后果或为什么停在这里 | `//`,只给重要函数 |
|
|
109
122
|
|
|
110
123
|
## 布局:放在哪一层、哪一行
|
|
111
124
|
|
|
112
|
-
- `///`
|
|
113
|
-
-
|
|
114
|
-
- `//`
|
|
115
|
-
-
|
|
125
|
+
- `///` 只给声明与文件头:头文件里的类型、函数声明、枚举项、公开与私有数据成员,以及文件头的 `@file`/`@brief` 块。调用方和 Doxygen 从这里取契约。
|
|
126
|
+
- `///<` 只给枚举项和 `struct` 短字段的行尾;不要写在赋值、`break` 或普通语句后面。
|
|
127
|
+
- `//` 给函数体内的阶段说明、局部变量、守卫理由和普通语句行尾;这些注释不接 Doxygen 文档。
|
|
128
|
+
- `//` 也给工具指令与别种文件:`// NOLINTNEXTLINE(...)`、`#endif // MACRO`,以及 CMake、YAML 等非 C++ 文件。
|
|
129
|
+
- 判断看位置:声明写契约,用 `///`;实现写理由,用 `//`。
|
|
116
130
|
- 位置固定:文件头在最上方;类型注释紧贴类型声明;特殊成员(构造、析构、拷贝与移动)排在其他函数之前;函数注释紧贴函数声明;数据成员的主注释紧贴声明上一行,不写行尾补充。
|
|
117
131
|
- 类数据成员(含私有)的主注释用 `///`;行尾 `///<` 只给枚举项和 `struct` 的短字段。
|
|
118
132
|
- 注释和它说明的声明之间不留空行;同一个声明块内,空行只用来分组。
|
|
@@ -141,7 +155,7 @@ GameResult result_ = GameResult::kInProgress;
|
|
|
141
155
|
- `.clang-format`、`.clang-tidy`、`.clangd`、CMake 的配置项旁边写「为什么这样设」,不写「这一项是什么」;配置注释贴在被解释的键上方。
|
|
142
156
|
- 禁止:大段表格、装饰图案、表情;外部资料路径、跨仓库引用、团队流程;注释掉的代码;注释元叙述(「其余内容见某处」「两处不重复」)。
|
|
143
157
|
- `///` 与 `@param` 不进入 `.qmd`、YAML、CSS。
|
|
144
|
-
-
|
|
158
|
+
- 函数体内不写 `///`;`//` 用于实现说明与 `// NOLINTNEXTLINE(...)` 这类工具指令。
|
|
145
159
|
|
|
146
160
|
## 润色:删复述,统一句式
|
|
147
161
|
|
|
@@ -100,3 +100,10 @@ class TaskTable {
|
|
|
100
100
|
| 函数体内逻辑组之间 | 1 行,最多 1 行 |
|
|
101
101
|
|
|
102
102
|
连续两个空行视为格式缺陷;`clang-format` 的 `MaxEmptyLinesToKeep: 1` 会压掉,但不要依赖它。
|
|
103
|
+
|
|
104
|
+
### 函数体内的空行
|
|
105
|
+
|
|
106
|
+
- 以 `return` / `throw` 结束的 `if` 守卫,与下一组语句之间空 1 行。
|
|
107
|
+
- 同一组语句不拆:三个校验之后的赋值与 `return kSuccess` 之间不空行。
|
|
108
|
+
- 只有次序依赖的一对 `if`(`GetWinner`、`JudgeAfterPlace`)之间空 1 行。
|
|
109
|
+
- 最多 1 行,不连续空两行。
|
|
@@ -15,6 +15,8 @@ E.1 要求尽早决定错误处理策略:先问调用方还能不能继续、
|
|
|
15
15
|
|
|
16
16
|
策略一旦定下,声明、实现、测试和入口都用同一套;不要在同一层里一半返回错误码、一半抛异常。
|
|
17
17
|
|
|
18
|
+
判断是否算「混用」,看函数合同而不是看返回类型:查询必须产出一个值、失败时产不出(如 `GetCellType` 越界),就抛;写入操作把「这次不写」当作正常结果、调用方本来就要分支(如 `PlaceCell` 返回 `PlaceCellResult::kRejected`,`CheckPlaceable` 返回具体原因),就用返回值。动作可能因为多个前置条件失败、而调用方需要区分原因时,返回 `enum class` 而不是 `bool` 加输出参数。两者并存时,公共头必须把「拒绝」与「抛」的边界各写一行;没有这行注释才算混用。
|
|
19
|
+
|
|
18
20
|
## 什么时候抛
|
|
19
21
|
|
|
20
22
|
E.2:函数不能完成它被分配的任务时,就抛异常;构造函数无法建立 invariant 时也抛,而不是留下半初始化对象。E.3:异常只用于错误处理,不能拿 `throw` 当返回值或正常分支。E.4:错误处理围绕 invariant 设计,而不是围绕「哪一行可能失败」。E.5:构造函数负责建立 invariant,失败就抛。E.6:用 RAII 防止泄漏,优先局部对象而不是裸 `new`。
|
|
@@ -39,7 +41,7 @@ class ParseError : public std::runtime_error {
|
|
|
39
41
|
|
|
40
42
|
库的失败方式在 Debug 与 Release 下必须可预测;只有诊断信息的详略可以不同。
|
|
41
43
|
|
|
42
|
-
- 返回值:两种模式完全一致。函数用返回值报告可恢复失败时,Debug 不得改成抛异常,Release
|
|
44
|
+
- 返回值:两种模式完全一致。函数用返回值报告可恢复失败时,Debug 不得改成抛异常,Release 也不得把异常改成错误码。
|
|
43
45
|
- 标准异常:两种模式的抛出条件相同。越界抛 `std::out_of_range`,前置条件被破坏抛 `std::logic_error`;项目若定义 `Error` 基类,派生类型同时派生自 `std::exception`,Debug 的 `what()` 可带 `file:line`,Release 只留值。
|
|
44
46
|
- `Check`:项目保留这种断言时,两种模式都检查,失败时打印诊断并终止;用于 invariant 已被破坏、继续执行没有意义的场合。没有 `Check` 的项目直接用 `assert` 或 `std::terminate` 表达同一边界,不另写包装。
|
|
45
47
|
- `DebugCheck`:项目保留时只在 Debug 生效,`NDEBUG` 后展开为空;用于代价高、只在开发期值得做的自检。不得用它承担 Release 也必须执行的检查。
|
|
@@ -56,7 +58,7 @@ E.31:`catch` 子句从派生到基类、从具体到通用排序,`catch (...
|
|
|
56
58
|
|
|
57
59
|
## 不抛的边界
|
|
58
60
|
|
|
59
|
-
E.12:在不可能或不可接受抛异常的地方用 `noexcept`。声明前先读实现:函数体里出现 `at()` 或其他可能抛出的调用,就去掉 `noexcept`;不要再包一层只转发下标的私有函数来维持 `noexcept`,直接写 `cells_.at(row).at(col)` 并去掉 `noexcept`。正例是只比较两个整数的 `IsInRange`;反例是用 `at()`
|
|
61
|
+
E.12:在不可能或不可接受抛异常的地方用 `noexcept`。声明前先读实现:函数体里出现 `at()` 或其他可能抛出的调用,就去掉 `noexcept`;不要再包一层只转发下标的私有函数来维持 `noexcept`,直接写 `cells_.at(row).at(col)` 并去掉 `noexcept`。正例是只比较两个整数的 `IsInRange`;反例是用 `at()` 读取并会抛 `std::out_of_range` 的 `GetCellType`。E.13:直接拥有资源的函数不要抛,资源释放应交给 RAII。E.16:析构、`deallocate`、`swap` 以及异常类型的拷贝/移动构造不得失败;析构和 `swap` 应当是 `noexcept`。
|
|
60
62
|
|
|
61
63
|
E.30:不要写异常规范(`throw(...)` 之类);C++11 之后用 `noexcept` 表达「不抛」。需要无异常构建时,由项目单独决定,不要把这套异常指导改写成两面都对的模糊话。
|
|
62
64
|
|
|
@@ -26,10 +26,10 @@
|
|
|
26
26
|
| 角色 | 前缀 / 动词 | 例子 | 说明 |
|
|
27
27
|
| --- | --- | --- | --- |
|
|
28
28
|
| 查询 | `Get` 或访问器风格 | `GetCurrentOrder()`、`CurrentOrder()` | 只读、不改变状态;同一 API 选一种 |
|
|
29
|
-
| 判断 | `Is` / `Has` / `Can
|
|
29
|
+
| 判断 | `Is` / `Has` / `Can` | `IsEmpty()`、`HasStock()`、`HasLine()` | 返回 `bool`,不抛异常 |
|
|
30
30
|
| 查找 | `Find` | `FindTask()` | 找不到是正常结果,返回 `optional` 或迭代器,不抛 |
|
|
31
31
|
| 计数 | `Count` | `CountPendingTasks()` | 返回数量,不修改状态 |
|
|
32
|
-
| 尝试 | `Try` | `TryReserve()` |
|
|
32
|
+
| 尝试 | `Try` / 领域动词 | `TryReserve()`、`PlaceCell()` | 可能因多个前置条件失败但不抛异常,返回 `enum class` 结果;调用方按名区分失败原因 |
|
|
33
33
|
| 确定性动作 | 领域动词 | `CompleteTask()`, `Reset()` | 前置条件满足时一定成功;无法完成任务时抛 |
|
|
34
34
|
| 计算 | 动词开头 | `ScoreOrder()` | 从输入算出结果,不修改外部状态 |
|
|
35
35
|
| 映射 | `<来源>To<目标>` | `OrderToReceipt()` | 名字必须含两端角色 |
|
|
@@ -46,11 +46,12 @@
|
|
|
46
46
|
|
|
47
47
|
## 谓词与查询命名
|
|
48
48
|
|
|
49
|
-
- 返回 `bool` 的查询用肯定式,读起来像一句断言:`IsEmpty()`、`HasStock()`、`CanReserve()`、`
|
|
49
|
+
- 返回 `bool` 的查询用肯定式,读起来像一句断言:`IsEmpty()`、`HasStock()`、`CanReserve()`、`HasLine()`。
|
|
50
50
|
- 前缀集就是这四类:`Is` / `Has` / `Can`,外加与成对规则一一对应的 `Completes`、`Would` 这类限定式;`Contains` 只保留给容器成员(`std::vector::empty` 一类已有标准名),不拿它当一般谓词。
|
|
51
51
|
- `Get` 只用于取值,`Set` 只用于设值;不要用 `CheckLine()` 当布尔 getter。
|
|
52
52
|
- `Check` / `Validate` 留给会报告错误、产生诊断或可能失败的操作;它们不返回纯 `bool` 查询。
|
|
53
|
-
-
|
|
53
|
+
- 动作可能因为多个前置条件失败、而调用方需要区分原因时,返回 `enum class`(`PlaceCellResult`、`WinPreviewResult`),不要用 `bool` 加输出参数,也不要拆成一串 `IsXxx` 判断再重复检查。
|
|
54
|
+
- 假设性查询在名字里写明条件:`PreviewWin(position, cell)` 读作「这一手会成线吗」,返回 `WinPreviewResult` 区分「不能落子」与「能落但不赢」;只有成功与不报错的纯判断才用 `bool`。同一 API 只选一种,改名时同步声明、定义、调用点、测试和注释。
|
|
54
55
|
- 私有谓词同样用肯定式;不要用 `flag`、`status` 或 `check` 这类不说明判据的名字。
|
|
55
56
|
|
|
56
57
|
## 类型命名的判断顺序
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
- **位置统一 vs 成员可读**:数据成员的主注释都在声明上一行,但上一行必须先说成员是什么,再补轴向、哨兵或改写时机;不能为了「每行只写一个修饰」把名词丢掉。
|
|
17
17
|
- **格式统一 vs 语义准确**:同族函数用同一种注释句式,但不能把不同的失败条件写成同一句话;先准确,再统一能统一的部分。
|
|
18
18
|
- **语言风格 vs 消息内容**:异常消息先说来源、条件、实际值和限度;用哪种自然语言由项目约定,排版规则不替内容做决定。
|
|
19
|
+
- **句子更短 vs 成立条件**:删掉「在什么情况下」换来的短,不算润色。状态结论必须带条件:「终局后空格仍在」要写成「一方已三连而棋盘未满时,空格仍然留着;平局时棋盘已满,没有空格」。这条同时约束代码注释与读者正文。
|
|
19
20
|
- **视觉整齐 vs 可扫描**:对齐、空行和注释符号先让声明读得清,再谈列对齐;不要为了对齐把长类型或长注释挤成难读的折行。
|
|
20
21
|
|
|
21
22
|
## 执行顺序
|
|
@@ -13,7 +13,8 @@
|
|
|
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
|
-
-
|
|
16
|
+
- 一次操作有多种必须区分的正常结果时,用 `enum class` 返回(如落子:成功、越界、类型非法、已占),不用 `bool` 把原因压掉;成功还需要附带数据时再考虑 `std::expected` 或结果结构,成功无数据时不用。纯查询谓词仍用 `bool`,不要把 `IsEmpty` 改成三态。
|
|
17
|
+
- 声明注释按 `references/comments.md` 的四节写:写作只写声明里看不出的事实——类型注释的名词短语(纯数据与枚举一句,类只在不变量不重复领域尺寸时加第二段)、特殊成员的标签与后置条件(例如 `默认构造函数:得到空棋盘、每个格子都是 kEmpty`)、函数注释的第一句结果或失败方式或检查项与契约。有参数就每个参数一条 `@param`,有返回值就写 `@return`,会抛才写 `@throws`;构造、析构和 `void` 无参数函数写两段后置条件,不写空标签。数据成员的主注释放在声明上一行并用 `///`,先说明这个成员是什么,再用全角冒号补名字和类型看不出的轴向、哨兵、所有权与改写时机,行尾不写补充;布局定文件头、位置与行尾;排版定符号、宽度与标签;润色删复述。类型注释不罗列成员功能、不写领域尺寸、不写方案对比与设计论证;全项目的风格理由写进项目文档,不写进注释。
|
|
17
18
|
- `std::format` 用于组合消息;声明里不要承诺 `std::print`(C++23)。
|
|
18
19
|
|
|
19
20
|
## 情况
|
|
@@ -24,7 +25,7 @@
|
|
|
24
25
|
|
|
25
26
|
**异常声明**:构造函数、`Get` 类查询、解析函数最容易抛;在声明注释里写「当……时抛出 `std::out_of_range`」这样的具体类型,不要只写「可能抛异常」,也不要给每个参数错误都新建类型。类型选择见 `references/exceptions.md`。
|
|
26
27
|
|
|
27
|
-
**注释**:声明注释用 `///` 写中文散文,只写调用方需要知道的;实现细节留给 `.cpp`。`@param
|
|
28
|
+
**注释**:声明注释用 `///` 写中文散文,只写调用方需要知道的;实现细节留给 `.cpp`。`@param` 每个参数都写,`@return` 每个非 `void` 函数都写,`@throws` 会抛才写;构造、析构与 `void` 无参数函数不写空标签。标识符和标签保持英文。文件头默认写 `@file` 与 `@brief` 两行,`@file` 必须与文件名相同;作者、日期、版权、版本默认不写。标签开关在插件设置页,技能正文末尾附带当前配置的快照。写完运行 `node "<skill-dir>/scripts/checking-comments.mjs" --root <项目根>`;关闭 `@brief` 时加 `--no-brief`,配置里打开了隐私标签并填了值时加 `--allow-tag <标签>`。文件头、标签、参数完整性、成员清单与设计论证由脚本兜底。文件头与 TODO 的格式见 `references/comments.md`。
|
|
28
29
|
|
|
29
30
|
## 正反例指针
|
|
30
31
|
|
|
@@ -9,13 +9,16 @@
|
|
|
9
9
|
- 函数保持短小;一个函数只做一件事。超过约 40 行时先找可以命名的中间步骤,不要用注释把长函数切成几段。
|
|
10
10
|
- 按「准备 → 操作 → 收尾」排列语句,用空行分组;组与组之间的空行表达阶段,不用大段叙述。
|
|
11
11
|
- `.cpp` 的第一个 include 是自己的配对头,然后直接包含本文件用到的标准库头;不要依赖公共头替本文件转发声明。包含顺序见 `references/google-cpp-style.md`;文件槽位见 `references/file-template.md`。
|
|
12
|
-
- `.cpp` / `.cc`
|
|
12
|
+
- `.cpp` / `.cc` 的函数体用 `//`,不用 `///`:体内 `///` 会接到该函数的 Doxygen 详细说明后面。重要函数按空行分阶段,每个阶段上方写一句 `//`,写边界、失败后果或为什么停在这里,不把下一行的条件翻译一遍。`IsFull` 这种单表达式函数不配注释;只写删掉会让下一次修改得到错误结果、而代码本身看不出的内容,变更史留给提交说明。
|
|
13
|
+
- 什么时候在 `if` 上方加一句:多条独立拒绝(`CheckPlaceable`)每个 `if` 一句;成功写入前一句;只有次序依赖的一对 `if` 在第一个上方说明;循环上方已有阶段说明时,循环里的 `if` 不再加,单表达式谓词不加。
|
|
13
14
|
- 错误处理:失败是调用方正常结果时返回结果类型、`bool` 或 `std::optional`;函数无法完成自己的任务时抛异常,不拿异常做正常分支。Debug 与 Release 用同一套返回值与抛出条件,只有诊断详略不同。两种模式都要守的 invariant 用 `Check`(项目保留时),只在开发期做的自检用 `DebugCheck`;没有这两个宏就分别用 `assert` 与 `std::terminate`。捕获时按 `const` 引用,只处理能处理的类型,其余继续抛。消息按 `Type::Function: 条件 (值); 限度` 组织,见 `references/exceptions.md` 的「消息」节。
|
|
14
15
|
- 声明 `noexcept` 前核对函数体:出现 `at()` 或其他可能抛出的调用就去掉。越界用 `at()` 抛 `std::out_of_range`,这个函数就不能标 `noexcept`。
|
|
15
16
|
- 组合输出用 `std::format`,再写入 `std::cout` / `std::cerr`;不要在库实现里直接打印。C++23 且库支持时程序边界可用 `std::print`,C++20 及更早没有。
|
|
16
17
|
- 不写 C 风格强制转换;用 `static_cast` / `std::bit_cast` 等具名转换。
|
|
17
18
|
- 所有权用 `std::unique_ptr`;没有共享所有权的理由时不用 `std::shared_ptr`。
|
|
18
19
|
- 只改注释时,不改可观察行为;只改实现时,不改公共声明。两者同时需要时,先改声明,再改实现。
|
|
20
|
+
- 清空成员数组:标准支持 ranges 时用 `std::ranges::fill(cells_ | std::views::join, CellType::kEmpty)`;不支持时用 `for` 循环逐格赋 `kEmpty`。两种都不写 `cells_ = {}`,那依赖枚举零值恰好是 `kEmpty`。
|
|
21
|
+
- 默认构造只做「建立初值」且与 `Clear()` 同义时,构造函数调用 `Clear()`,不另设 `Init()`;类内 `cells_{}` 仍保留,让成员先有确定初值。
|
|
19
22
|
|
|
20
23
|
## 情况
|
|
21
24
|
|