下载 App
开发者日志 02 · 把一千行拆开
警报:Simulation.lua 到 1000 行了
第 1 篇结尾提到,D6 结束时核心模拟文件已经超过一千行。当时的 Simulation.lua 什么都管:地块状态、水资源预算、天气推进、洪水、养殖试点、章节验收、存档校验——全塞在一个文件里。
这不是"能跑就行"的问题。真正的问题是:改任何一处都可能踩到别处。
举个当时真实发生过的 bug:我调整了水预算的计算顺序,结果章节验收的"全年非赤字"判定跟着变了,因为两者共用同一个 annualNet 字段但期望的口径不同。一个文件里,没有边界,就没有安全感。
拆法:按"谁依赖谁"切
我们没有按"功能"切,而是按依赖方向切:
Config.lua 纯数据,谁都能读,不依赖任何人
↓
Grid / Weather / Flood / Hatchery 独立规则模块,只依赖 Config
↓
Simulation.lua 业务状态机,组合上面的规则
↓
View / WaterPanel / ChapterGuide 只读展示层,不写业务
↓
Client.lua 装配与生命周期
关键约束只有一条:展示层只读,不写业务。 View.lua 可以调 sim:Work(),但不能自己改 sim.money。这条线一旦划清,UI 怎么改都不会污染模拟逻辑。
最终结构:
| 模块 | 行数 | 职责 |
|---|---|---|
| Simulation.lua | 1024 | 状态机、Tick、存档校验 |
| View.lua | 981 | 声明式 UI 树、交互 |
| Art.lua | 665 | 地图绘制、几何、命中判定 |
| SettingsPanel.lua | 490 | 设置弹窗 |
| LocalProgress.lua | 424 | 本地存档读写 |
| ChapterGuide.lua | 347 | 章节引导与回执 |
顺带一提:我们给自己定的红线是"单文件超过 1500 行必须拆"。Simulation.lua 停在 1024,是因为它的内聚性够高——所有字段都属于同一个状态机,硬拆反而会制造跨文件耦合。
测试:24 个文件替我们盯梢
拆完之后最怕的是"拆坏了没人知道"。所以我们把验收写成了 24 个测试文件,分两类:
Checks(纯逻辑) —— 不碰 UI,直接跑模拟:
- OasisGridChecks 网格索引与坐标映射
- OasisWaterChecks 水预算口径
- OasisFloodChecks 浸水积分守恒
- OasisSandChecks 覆沙压力
- OasisHatcheryChecks 养殖水账
Visual(真实 UI) —— 用引擎真跑一遍,模拟真实指针事件:
- OasisMapVisual 地图几何、命中、缩放、拖动
- OasisGuideVisual 章节引导逐层揭示
- OasisPlatformVisual 安全区、路线图、设置弹窗
每一条断言都是"可证伪"的具体事实,不是"看起来对":
lua复制
luaV.assert(map:Hit(cx + x * scale, cy + y * scale) == i, "菱形地块中心命中 " .. i) V.assert(math.abs(card:GetAbsoluteLayout().y - side:GetAbsoluteLayout().y - scroll) < 2, "真实试点入口切水页并定位独立卡片")
一次真实的回归
拆解过程中,我们把"地块中心命中"这条断言加进了测试。它立刻抓到一个隐蔽 bug:等距菱形的命中判定在格子共边处会重复归属——点到两格交界线时,返回的是后画的格子,而不是深度更靠前的那格。
这个 bug 在手动点击时几乎不可能复现(要正好点在 1 像素宽的边界上),但测试能稳定触发。修完之后,这条断言至今跑了 6512 次全绿。
这一篇的教训
重构的安全感,来自你重构前就写好的测试。
如果没有那 24 个文件,我们不敢动一千行的核心文件;不敢动,代码就会持续腐化。测试不是负担,是让你敢于重构的许可证。

