133 lines
8.9 KiB
Markdown
133 lines
8.9 KiB
Markdown
# 代码架构审查报告 — 代码组织与封装
|
||
|
||
> 日期:2026-09-13
|
||
> 载体:`game/`(9707 行 GDScript:core 12 文件 / data 11 / presentation 28 / smoke 10)
|
||
> 基线:`docs/architecture/architecture.md` v1.0 + ADR-0001~0005 + `docs/code-conventions.md`
|
||
> 触发:用户在 code-review 之后要求补充「代码组织问题 + 封装问题」专项审查
|
||
> 审查方式:core/presentation 关键文件全读 + S1–S8 静态规则逐条 grep 验证 + 跨层引用面扫描
|
||
>
|
||
> **Verdict: CONCERNS** — 分层纪律实测扎实(S1–S7 零违例、仅 2 处已登记例外),但封装收口存在
|
||
> 一条系统性欠账:核心模块内部状态以公有字段暴露,呈现层已形成链式穿透的既成事实。
|
||
> 原型期无害;是 ADR-0009「切片机制正式化批」应收口的主题。
|
||
|
||
---
|
||
|
||
## 1. 正面观察(实测合规项)
|
||
|
||
| 规则 | 结论 | 证据 |
|
||
|---|---|---|
|
||
| S1/S2 零随机 | ✅ | core 内唯一 `RandomNumberGenerator.new` 在 rng_service.gd:76(所有者本体);`stream.randf()`(reward_supply_controller.gd:32)取号经 B4 注入封闭流 |
|
||
| S3 零表加载 | ✅ | core 内唯一 `load()` 在 config_db.gd:81(单点本体) |
|
||
| S4 core 零 UI | ✅ | core/ 无 Control/Node2D/.tscn 引用(grep 命中均为 `RewardSupplyController` 类名误报与注释) |
|
||
| S5 零裸引用 autoload | ✅ | core/ 内 ConfigDB/RngService 命中均为日志前缀字符串 |
|
||
| S6 core↔core 方法 Callable | ✅ | level_runner.gd:130-134 全部方法转发;lambda 仅用于 sort_custom(不捕获 self) |
|
||
| S7 编排唯一性 | ✅ | B2/B3/B4 调用面 core 内收口于 battle_orchestrator.gd;仅 level_runner.gd:112(grant_node_active,已登记接口漂移)与 :168(in_stock_count 只读)两处例外 |
|
||
| 呈现层零核心引用 | ✅ | 28 个视图/装配器文件中仅 main.gd(组合根)与 hud_presenter.gd(授权翻译点)触 core |
|
||
| 封装典范 | — | status_container / reward_supply_controller / b4.query_inventory():私有状态 + 投影查询 + 授予时快照;battle_orchestrator.diagnostic_snapshot()(:660)为「成文只读出口」正确先例 |
|
||
|
||
---
|
||
|
||
## 2. 封装问题(按根因 → 表征排序)
|
||
|
||
### E1(根因)编排器协作对象全公开,呈现层链式穿透已成既成事实 — P1
|
||
|
||
- **位置**:core/battle_orchestrator.gd:20-27(`var a1/a2/a3/b2/b3/b4` 全公有)
|
||
- **表征**:`b1.a2.board`、`b1.b2.hero.hp`、`b1.a3.phase` 式层层下钻——main.gd 18 处、hud_presenter.gd 15 处。
|
||
- **影响**:组合根与 presenter 是授权翻译点,但访问形态是「恰好字段公开」而非「B1 提供成文读取面」。
|
||
S7 只能做文件级扫描(防另写调用方),防不了呈现层意外调用写 API,也防不了读取路径随重构漂移。
|
||
- **建议**:B1 补聚合呈现快照/查询面(`hud_state()` 一类,先例 = `diagnostic_snapshot()`);
|
||
a2–b4 下划线化后经命名代理暴露只读操作。
|
||
|
||
### E2(表征)视图绕过 A2 查询面直读原始状态字典 — P1
|
||
|
||
- **位置**:core/minesweeper_rules.gd:18-20(`board/revealed/flags` 公开可变);
|
||
presentation/views/board_view.gd:278、:303 直接 `_a2.revealed.get(c, {})`
|
||
- **矛盾点**:board_view.gd:7 头注自称「只读 A2 查询面」——直读 `.revealed` 恰是绕过查询面。
|
||
B1 自身亦直读(battle_orchestrator.gd:565-569 `a2.board.width/height`)。
|
||
- **建议**:`revealed/flags` 私有化;补 `reveal_record(c) -> Dictionary`、`board_dimensions()` 查询面。
|
||
调用点仅 B1 + board_view + main 三处,成本很低。
|
||
|
||
### E3(表征)B2 裸字典句柄跨模块传递,API 缺「含死亡残位」查询面 — P2
|
||
|
||
- **位置**:core/unit_container.gd:13-14(`hero/enemies` 公开;句柄 = Dictionary 本体,任何持有者可
|
||
`u.hp = 999` 绕过 apply_damage)
|
||
- **现症**:hud_presenter.gd:78-79 为渲染死亡残位直读 `b1.b2.enemies` 原始数组——
|
||
B2 的 `query_alive` 不含死亡单位,API 缺口迫使呈现层越界(B2 头注已登记此痛点)。
|
||
- **建议**:短期补 `query_all_units()` 投影面把 presenter 需求收进 API;
|
||
正式化(ADR-0007 两 face 归属裁决时)再决定是否升级 Unit 类型。
|
||
|
||
### E4 `db.hero` 表值运行时改写——测试覆盖通道用共享可变单例传参 — P2
|
||
|
||
- **位置**:presentation/main.gd:327-329、core/level_runner.gd:60-61,75-76 直接写 `db.hero.atk/max_hp`;
|
||
B2.spawn 经读表间接获得加成值
|
||
- **前科**:双写叠加 6→18→54 教训、双基线同步义务(main.gd:187-189 注释)——纪律已成文但机制脆弱。
|
||
- **建议**:切片保留;将「运行时表可变」登记进 ADR-0009 候选;正式化改参数传递(`spawn(atk_override=...)`)。
|
||
|
||
### E5 「只读」字段仅靠纪律,无机制约束 — P2
|
||
|
||
- **位置**:core/action_economy.gd:17-20(`phase/ap/rap/last_round_end` 架构文档定义为只读,实际公开可变);
|
||
core/rng_service.gd:19(`master_seed` 可绕过 set_master_seed 的计数器全零断言)
|
||
- **现状**:当前调用方全部合规(A2 经 try_spend/request_dig,B1 经 notify_*,呈现层只读)。
|
||
- **建议**:下划线化 + 查询方法(`phase()/ap()/rap()`),趁调用方少时收口,成本极低。
|
||
|
||
### E6 小项 — P3
|
||
|
||
| 项 | 位置 | 说明 |
|
||
|---|---|---|
|
||
| 返回类型多态 | core/reward_economy.gd(grant/stock/use 失败返 `false`,query_* 返 `null`) | B1 消费处被迫 `if not (snap is Dictionary)`;统一 `null` 更符合 GDScript 惯例 |
|
||
| 泄漏原始表行 | core/reward_economy.gd:256-265 `equivalence_supply` 返回 `db.reward.rows` 行引用(可变) | 工具观测面,建议 `duplicate()` |
|
||
| BoardData 全公开 | core/board_data.gd:8-18「生成后不可变」仅靠注释;视图持板即可读全部雷位(当前未作弊) | 正式化时 duplicate 冻结或下划线化 + 访问器 |
|
||
| 成员声明位置 | core/action_economy.gd:121 `var _next_round_mode` 声明在中部、使用在前 | 集中到顶部 |
|
||
|
||
---
|
||
|
||
## 3. 代码组织问题
|
||
|
||
### O1 main.gd 回涨至 667 行,再越 ~400 行审视线(规范 1.1)— P1
|
||
|
||
- **位置**:presentation/main.gd(667 行);ui_assembler.gd:3 自记「回涨 761 行触发审视线——拆出」历史
|
||
- **增长主因**:调试模式(`_start_single/_start_debug_levels/_start_battle/_on_battle_finished` 的
|
||
`_level_mode` 分支约 100+ 行)糊在组合根。
|
||
- **建议**:照 tune_sim_controller.gd 先例把调试模式抽成独立控制器,组合根回到纯接线。
|
||
|
||
### O2 调试模式与 LevelRunner 平行实现,语义漂移风险 — P1
|
||
|
||
- **位置**:「库存跨战斗延续 + HP 携带 + 层级推进」存在两份实现——core/level_runner.gd(正式)与
|
||
main.gd 调试分支(历史入口);`level_clear()` 调用时点已各有细节(main.gd:367,376 vs level_runner.gd:167)。
|
||
- **建议**:调试两层模式直接复用 LevelRunner(其设计目标即此),或抽 `DebugBattleSession`。
|
||
|
||
### O3 B1 体量与纯函数沉淀 — P2
|
||
|
||
- **位置**:core/battle_orchestrator.gd 718 行(审视线超 300+);
|
||
伤害公式族 `_resolve_damage/_charge_damage_component/_armor_effectiveness/_clamped_pool`
|
||
(约 80 行)为零状态纯算术。
|
||
- **建议**:按规范 1.2 抽 `DamageMath` 静态宿主类;段序骨架不动、可回放承诺无风险。
|
||
- **顺带**:core/board_generator.gd:50-55 对同一张合法板调用两次 `measure_inferability`
|
||
(全库最贵求解器)——合并为一次。
|
||
|
||
### O4 呈现层反向依赖 smoke 工具域 — P2
|
||
|
||
- **位置**:presentation/tune_sim_controller.gd:23 `load("res://smoke/level_autoplay.gd")`——
|
||
UI 控制器依赖测试工具目录。
|
||
- **建议**:「关卡自动模拟循环」上移为共享组件(core 或 lib),smoke 与 tune_sim 共同消费。
|
||
- **顺带**:main.gd:106,188 与 tune_sim_controller.gd:23 用运行时字符串 `load()`,
|
||
与 main.gd:16-19 的 `preload` 常量风格混用——统一 `preload`(编译期路径校验)。
|
||
|
||
### O5 头注溯源缺失(规范 1.4)— P3
|
||
|
||
core/ 12 文件中 4 个缺标准 `# 实现 GDD:...` 行:
|
||
`board_data.gd`(有 A1 描述但非标准格式)、`config_db.gd`、`rng_service.gd`、`reward_supply_controller.gd`。
|
||
|
||
---
|
||
|
||
## 4. 建议行动顺序
|
||
|
||
1. **P1 三连(合计约 2 天)**:
|
||
E2(A2 状态私有化 + 查询面)→ E1(B1 呈现快照面)→ O1+O2(调试模式抽离/复用 runner)。
|
||
完成后 board_view / hud_presenter / main 与 core 的接触面全部变为命名 API,封装收口完成。
|
||
2. **P2 项并入 ADR-0009「切片机制正式化批」裁决**:E4 表值改写参数化、E3 单位句柄形态、
|
||
O3 DamageMath 拆分、O4 模拟循环上移。
|
||
3. **P3 顺手清**(头注补齐、返回值统一、声明位置、preload 统一)。
|
||
|
||
> 复审触发:P1 三连落地后重跑本审查核对接触面收口;ADR-0009 落定后正式工程骨架迁移前复核 E3/E4 裁决结果。
|