Files
gplan-2/design/dev/code-conventions.md
T
DKChan db1bf42322 docs(迭代28 T6): 变异验证硬规则落文+机械面文档同步闸
规范落文三处(本批新硬规则「验收函数无变异红记录不算验收成立」):
- CLAUDE.md:批次工作流实施段新增第 9 条(原 9-14 顺延 10-15)
- design/dev/code-conventions.md:新增 §3.6 变异验证操作细则
  (注入→最窄闸见红→git 精确还原→记录四步;变异不红二选一处理;
  判据强弱分层:运行期断言/golden 逐像素/占比统计类三档)
- req-card 技能(仓外 ~/.workbuddy/skills/req-card/SKILL.md §7 验证闸,
  ZCode 副本同步;CodeBuddy/Qoder CN 无此技能副本,不动)

机械面文档同步:
- roadmap.md:迭代 28 行换代为已入库(冒烟 1086→1125/0)、近期主战场
  前沿改迭代 29+ M4
- design/gdd/06-待办清单.md:登记条销号改写为已入库收口 + 新增 GDD 反哺点
  三条专条(装配合同/boot 后置条件/生命周期矩阵,待用户逐点裁决)
- design/gdd/07-体验验证清单.md:新增 ㉗ 启动态菜单目检节五观测项

顺带清理(非本批引入,基线带入):07-体验验证清单.md v1.0 行处残留
<<<<<<</=======/>>>>>>> 合并冲突标记(iter27-techdebt-20261014 分支合并进
master 时漏解)——两侧均有效故全留(qa 收尾批人闸核销行 + ㉖ 目检节登记行
与正文节),版本号顺延 v1.1,清理注记入 v1.2 行。

doc 三闸对基线零新增(lint 三件/死链 3/术语 5 组均与 master 39c6544 同)。
2026-10-15 07:59:25 +08:00

17 KiB
Raw Blame History

代码规范(Dungeon Sweeper)

生效:2026-09-11 · 来源:用户在垂直切片评审中逐一指出的真实陋习与缺陷,全部成文。 每条规则附本项目真实反面案例(标注缺陷编号/日期)——规则不是抽象美德,是踩过的坑。 约束范围:game/(原 prototypes/ 垂直切片,2026-09-24 转正)代码同尺度;与 CLAUDE.md「硬性红线」节既有硬约束 (数值表单点 / RNG 封闭流 / Core 零 UI / fail-fast / spec-AC 一致性)并行生效,冲突以用户裁决为准。

写任何代码前必须通读本文件。


1. 结构纪律——高内聚、低耦合

1.1 禁止上帝对象

一个类只承载一个职责。场景主脚本(main)= 组合根:模式流转、信号接线、核心写调用的唯一出口; 渲染、文案拼装、状态装配一律拆分为独立视图/装配器类。单文件超过 ~400 行即触发审视。

反面案例(2026-09-11,用户:"一个工程师看了想吐"):main.gd 随功能追加长到 ~1100 行——棋盘渲染、 HUD、结算日志格式化、四种模态覆盖层、调整板、蒙特卡洛模拟器、关卡流程全部糊在一个类里。 已拆分为 9 个单一职责单元(views/ + hud_presenter),main 降到 433 行组合根。

1.2 一切功能以类封装,禁止裸函数堆

设计稿与实现同尺度:功能 = 类(状态 + 行为 + 信号),类是封装的最小交付单位。 工具性纯函数只允许挂在明确的宿主类下(静态装配器/工厂),不允许无主散落。

反面案例(2026-09-11,用户:"我看你都是函数的方式……没有对象封装吧"):供给控制器的伪代码 以裸函数呈现被否;正式实现按"B4 私有组合件 RewardSupplyController"封装。

1.3 依赖单向,核心写调用收口

依赖方向唯一:views →(信号)→ 组合根 →(写调用)→ core。 视图零核心写调用(纯 setter / 信号外发优先);"读核心状态 → 拼展示文案"集中于装配器(presenter); 核心写接口(settle_batch / submit_window_selection / runner 推进)只允许在组合根出现。

1.4 命名 = 领域命名,禁止系统 ID 前缀

文件名与类名禁止 a1_/b1_/c1_ 等系统 ID 前缀(a1_board_generator.gd → board_generator.gd)。 设计追溯不靠前缀:每个核心实现文件头注必须带一行 # 实现 GDD:design/gdd/systems/<循环>/<系统文档>.md(系统 ID Xn);系统 ID 只作为文档与 registry 的追溯锚。 豁免:D2 schema 类与 autoload(本就领域命名);文档/夹具登记名(如 b4_golden)属文档层。

反面案例(2026-09-11,用户:"是没有代码规范吗"):ID 前缀是跟随 GDD 文档名的事实约定, 从未成文;同目录 config_db/rng_service 无前缀与 A/B/C 系列并存——两套命名即规范缺失的铁证。

1.4.1 文件头 = ## 文档注释,≤8 行,白名单制(2026-10-15 第二轮立规)

文件头注释只用 ## 文档注释(Godot 官方载体,进编辑器 F1 帮助),总行数 ≤8 行(不含空行, 从文件首个非空行到首个非注释代码行)。写法权威 = Google Python Style Guide §3.8 + PEP 257 (GDScript 官方风格指南对文件头零条款,GDScript 语法近 Python,沿用其规范——不自造标准)。

白名单(只许写这五类,超出一类即违规):

  1. 一行职责摘要——这是什么;
  2. 用途/验证问题一行——它为什么存在;
  3. GDD 锚行(§1.4 格式);
  4. 硬使用约束——使用本文件前必须知道的禁令,每条一行;
  5. 指路行——架构文档/ADR/契约文档的位置。

黑名单(出现即违规):

  • 日期与变更叙事:任何日期、「XX批/T几/迭代N 做了什么」、迁移/改名/退役经过—— 改动史查 git log/git blame,变更背景读对应迭代实现报告;
  • 与 code-conventions、ADR、架构文档重复的段落——规范与架构各有唯一权威文档,文件头只指路不复制;
  • 场景结构、生命周期等代码机制描述——"Never describe the code"(pyguide §3.8.5),读者读代码或架构文档。

机器闸:tools/audit_file_headers.py(static_scan S10:行数上限 + 黑名单机械可判子集; 信号契约成文位置见 §2.1)。存量头注按白名单清偿后挂入收口闸序列;此间新增/改动的文件头必须即合规。

反面案例(main.gd,两轮,2026-10-15):第一轮头注累积 42 行——11+ 笔提交各自追加「本批做了什么」, 全部闸静默放行;清偿删叙事后仍剩 31 行——约 2/3 是对 code-conventions §1.3 与 ADR-0001 的整段复制、 场景结构/生命周期机制描述(对规约与代码零新信息),且第 4 行残留带日期批次叙事。第二轮立规后 全仓头注按白名单重写,机制事实归 design/dev/architecture。

1.5 禁止事实约定

任何"大家默认这么做"的惯例必须成文进本文件(或 CLAUDE.md)才算存在。 写代码时发现未成文但必须遵守的约定 → 当场补条文,不许靠惯性延续。


2. 契约纪律——跨层/跨类调用的语义

2.1 信号与事件契约成文

每个跨类信号:参数语义、发送时点、消费方依赖,必须在发送方 ## 文档注释成文,每信号一行 (参照 A2 三事件面先例);计入 §1.4.1 的 8 行上限,塞不下时迁契约文档并在文件头留指路行。 状态机的每次迁移路径都必须发出对应事件——缺一条路径的事件 = 消费方永久等待 = 软锁。

反面案例(playtest 缺陷②):A2 旗动作花完末点 AP 后相位翻 Waiting 但不发 round_end, B1 结算点依赖该事件 → 整局软锁。v1 冒烟没抓到是因为自动玩家不用旗——路径覆盖不全的代价。

2.2 跨层标识符语义单一且成文

位置(index)/ 句柄(handle)/ 键(key)是三种不同的东西。跨类传递标识符时, 传递方的语义必须与接收方契约逐字一致,禁止凭"当前恰好相等"传值。

反面案例(playtest 缺陷④):窗口选择契约 = 窗口条目位置,UI 却传库存数组句柄—— 未消耗时二者巧合相等;消耗一件后错位 → 选中件不消耗、邻件被误耗。

2.3 模态覆盖层层级规则

覆盖层遮挡关系由场景树加入顺序决定。打开任何模态覆盖层时,必须显式隐藏会遮挡它的全屏层; 新增覆盖层必须登记其在 z 序中的位置,并提供"打开可见性"的场景断言。

反面案例(playtest 缺陷⑤):数值调整板已实现,但打开时被不透明的开始覆盖层完全遮住 = "点击无反应"。功能存在 ≠ 功能可达。

2.4 状态字段语义显式

跨战斗/跨场景携带或恢复的状态字段,其语义来源必须显式(表值 / 明确入参),禁止用入参隐式定义。

反面案例:B2 spawn 把 hero 的 max_hp 设为入参 hero_hp——跨战斗携带 HP 时上限被压扁, 恢复点/治疗包的满血守卫全部失效。


3. 测试纪律——"存在"必须证明"可达且正确"

3.1 场景级探针标配

每新增一个可交互 UI 区域(视图/覆盖层/面板),必须配套场景级 headless 探针: 真实实例化场景 → 驱动真实输入路径 → 断言可见性与状态。只有 core 层 AC 通过不算有测试 (GDScript 编译不查字典字段与时序,呈现层错误只在运行时暴露)。

3.2 覆盖层/面板必测"打开后可见"

凡模态覆盖层,探针必须断言:打开后自身 visible 且遮挡物已隐藏(缺陷⑤回归断言)。

3.3 缺陷修复必须沉淀回归断言

每个缺陷修复必须在探针或冒烟中留下能抓住该缺陷的断言(旗动作耗尽 AP → 必须进入第 2 轮; 选用道具 → 库存态必须变 Used)。没有回归断言的修复视为未完成。

3.4 提交前全量验证

交付前必须全绿:smoke_all.gd + 全部场景探针(ui_probe / ui_click_probe / ui_tune_probe)。 行为探针使用随机化玩家行为(旗/停手/刷新/无效点击)——自动玩家不做的动作就是测试盲区。

3.5 测试函数怎么写——验收清单条目的落码规范(2026-10-11 用户立规)

需求卡片 §7「逐需求点验收清单」每一条都要落成可执行的验证;本节定统一写法,卡里定每批的具体内容(测什么/注入哪里)。写卡期查不出执行载体的条目=不可测,当场呈报,禁拖到实现期。

  1. 执行载体五选一,逐条声明:单元测试(tests/run_all)/冒烟断言(smoke_all.gd 对应场景段)/场景探针(smoke/ 下 *_probe)/MC 同种子对拍/人工核对(仅限无法自动化的纪律类需求点,如命名锁定、数值占位)。行为类条目以自动化载体为主。

  2. 粒度与对应关系:每条清单条目至少一个自动断言;多条条目可合用一个测试函数。断言失败消息末尾带编号尾缀〔卡短名 Vn〕(如〔秘宝批 V3〕)——断言与卡片清单可互相 grep 查找。

  3. 断言词汇按验证对象五选一:

    词汇 适用场景
    逐位相等 同种子下确定性公式与结算的精确对拍
    区间/差值带 概率与平衡读数(MC 同种子配对比较,出带即红)
    快照比对 存档域结构、配置表行结构
    重放 同种子断点回放、改前改后配对
    状态/计数器断言 条件解锁、限次、充能——置数据→走路径→断计数值
  4. 调试注入两层(测试方式两分法的落码面):自动化测试=测试代码直调核心层写口构造状态(直改数据表行/计数器/存档域,不经 UI、不走正常资源积累流程——冒烟现役做法);人工体验=DebugOverlay/DS_DEBUG 调试面,服务用户目检。清单条目的「怎么测」必须写到数据面粒度:改哪张表哪行、置哪个计数、调哪个核心写口。

  5. 换代批旧验证约束同生死:删除旧数据行/旧实现代码的批次,必须同批摘除只验证旧实现的断言与护栏校验,由新断言替代——旧件断言残留=冒烟假绿来源;合并后冒烟全绿且全仓无旧件残留引用为证。

3.6 变异验证——验收函数的拦截能力自证(2026-10-15 迭代 28 立)

测试绿只证明「当前代码没犯这个错」,不证明「犯了这个错会被抓住」。新增或改写的验收函数(冒烟断言/探针自检/闸机判据)必须配一次变异验证,把拦截能力自证一遍。无变异红记录的断言不写「已验收」,只写「已实现,拦截能力未证」。

操作四步(逐条落实现报告变异验证记录表):

  1. 注入——把被测件改坏成该类缺陷的最小形态(翻装配序、注释一行 add_child、把状态源取反、把层 visible 置 false)。一次只注入一处;可直接改源码,不必留长期开关。
  2. 跑最窄的那一道闸——只跑能覆盖该断言的最小单元(单冒烟脚本/单探针/单比对命令),禁跑全套闸(见 CLAUDE.md 闸机轮次纪律②)。判据:注入后该断言必须变红;红在别处而目标断言仍绿=判据没打在缺陷上,按第 4 条处理。
  3. 精确还原——git checkout -- <文件>(不用 git stash、不手工回改,防残留)。还原后复跑同一道闸确认回绿,才算本轮闭环。
  4. 记录——变异点(文件:行)、跑的闸、红输出摘要、还原结论,逐条进实现报告记录表。

变异不红怎么办(不许含糊带过,二选一):

  • 改判据——把断言打到真正的缺陷面上(例:断言「菜单标题可见」只查截图墨色占比时,把菜单层藏起来仍不红,因为底层棋盘透过标题框照样给出高反差像素;改查 is_visible_in_tree 或加 golden 逐像素比对才红)。
  • 如实标注拦截边界——判据本身只覆盖弱面的(如「只证伪整框空白/整框糊,不证明框里是何内容」),在断言注释与报告里写明边界,并说明强判据由哪一件承担。

判据强弱分层(迭代 28 实测结论):同一缺陷面往往需要多件配合——可见性/状态源类缺陷由 is_visible_in_tree、树序、状态源取值的运行期断言抓;内容/观感类缺陷由 golden 逐像素比对抓;占比/统计类判据(墨色占比、纹理幅度)只能兜「整框空白/整框糊/压 busy 纹理」,是补充不是主力。写卡时按此分层配齐,别指望一条断言包打。


4. 语言纪律——GDScript 4.x 实战坑(本项目踩实)

  1. 多行 lambda 内禁止嵌套 match / 复杂控制流(解析不可靠,报"Expected expression for match pattern")——信号处理体一律抽成命名方法 + bind 传状态。
  2. Dictionary 取值返回 Variant,无类型推断——参与算术或传参前显式声明类型(var x: float = ...)。
  3. RichTextLabel:append_text() 不回写 text 属性——读可见文本用 get_parsed_text()。
  4. lambda 捕获循环变量按创建时值——循环内先落地局部变量再捕获,或用 bind。
  5. 重命名脚本必须同步 .uid 文件(资源身份稳定);新增 class_name 后必须跑一次编辑器扫描 刷新全局类缓存,否则 --headless --script 模式解析失败。
  6. print 格式占位符与实参类型必须匹配(%d 配 String = 静默输出字面量)——工具输出错 等于没有输出。

5. 数值与汇报纪律

5.1 数值口径必须显式标注尺度

任何期望值/均值汇报必须写明口径:每格 / 每板 / 每战 / 每关。混用口径 = 误导 ("可选类 3.32"不加"每板",会被读成每格期望 >1)。

5.2 调参先测量,改后复测

改任何玩法数值前,先用扫描工具产出基线(通关率/难度类读数走 balance_verifier.py 冒险段——单关直入口口径已退役,机制类读数走 reward_expectation.gd 等); 改完必须复测回填数据。禁止拍脑袋调参。

5.3 数值设计必须跨尺度推演

在单一尺度校准的数值,必须推演其在更大尺度的复合结果(每战合理 × 关内囤积 = 关卡灾难)。

反面案例(2026-09-11):可选类 25% 格子配比在单战斗尺度校准(击杀需求 18/54), ×"库存关内延续"× 5 张板复合后 = 每关 24.8 件、刀总伤 180 > Boss 130 HP——预警⑥在关卡尺度坐实。

5.4 用户拍板的实现零发挥

拍板范围外的细节一律不自行补全——以"待办问题"清单列出等裁决。 (反面案例:清图奖励被自行设计成入库刀,用户纠正为"一次性高额伤害不入库"。)


6. 流程纪律

  1. 大改动三段走:设计稿/伪代码 → 用户审阅 → 实现 → 修改记录(README + session-state)。实现不得先于审阅。
  2. 实现批收尾双登记:切片 README 修改记录 + 根目录 active.md 条目(会话状态唯一状态源——2026-10-09 定性,路径自 production/session-state/ 迁根目录),缺一不可。
  3. 规范变更必须成文进本文件——发现未成文约定、或用户裁决产生新规则,当场补条文。
  4. 呈现层缺陷的教训回写 D1 GDD 设计输入(信号载荷 schema / 场景级测试 / 时点状态可见性)。

附:缺陷-规范对照表(本项目真实案例,2026-09-11)

缺陷 现象 根因 对应规则
① UI 抖动/"第3轮"消失 悬停文案换行推挤侧栏,末行被挤出视口 动态内容参与布局回流 1.1 拆分 + 视图定长槽(HUDView)
② Boss 战 AP 用完不结算(软锁) 插旗耗尽末点 AP 后整局冻结 旗动作路径缺轮末事件 2.1 事件发射完整性
④ 道具使用后不消失 选用件留存、邻件被误耗 窗口位置 vs 库存句柄错位 2.2 标识符语义单一
⑤ 调整板点击无反应 面板已实现但被开始层遮挡 模态打开未隐藏遮挡物 2.3 层级规则 + 3.2
上帝对象 main.gd ~1100 行 职责无边界持续堆积 1.1–1.3
ID 前缀命名 a1_/b1_ 与领域命名并存 事实约定未成文 1.4–1.5
可选类每关 24.8 件 速杀退化、囤刀秒 Boss 单尺度校准未做跨尺度推演 5.3

版本历史

版本 日期 说明
v1 2026-09-11 首版成文(垂直切片评审用户逐一指出,全部附真实反面案例)
v2 2026-10-09 文档正确性审查批·独立复核(worktree docaudit-2026-10-09):§6 流程纪律第 2 条「production/session-state/active.md」过期路径改「根目录 active.md」(会话状态唯一状态源,2026-10-09 用户定性为状态文件并迁根目录);本表系本批补建
v3 2026-10-11 新增 §3.5「测试函数怎么写——验收清单条目的落码规范」(2026-10-11 用户立规,需求卡片逻辑验收方法论批):执行载体五选一逐条声明/断言消息带〔卡短名 Vn〕编号尾缀/断言词汇五选一表/调试注入两层/换代批旧验证约束同生死