8.9 KiB
8.9 KiB
代码架构审查报告 — 代码组织与封装
日期:2026-09-13 载体:
game/(9707 行 GDScript:core 12 文件 / data 11 / presentation 28 / smoke 10) 基线:docs/architecture/architecture.mdv1.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-569a2.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. 建议行动顺序
- P1 三连(合计约 2 天): E2(A2 状态私有化 + 查询面)→ E1(B1 呈现快照面)→ O1+O2(调试模式抽离/复用 runner)。 完成后 board_view / hud_presenter / main 与 core 的接触面全部变为命名 API,封装收口完成。
- P2 项并入 ADR-0009「切片机制正式化批」裁决:E4 表值改写参数化、E3 单位句柄形态、 O3 DamageMath 拆分、O4 模拟循环上移。
- P3 顺手清(头注补齐、返回值统一、声明位置、preload 统一)。
复审触发:P1 三连落地后重跑本审查核对接触面收口;ADR-0009 落定后正式工程骨架迁移前复核 E3/E4 裁决结果。