一个大小写问题,让我 debug 了一整个下午
用 Tauri + Rust 做的 LoL 桌面助手,技能数值全部显示为 0。排查了数据层、前端、后端,最后发现 bug 藏在一行 HashMap 的 key 里。
一个大小写问题,让我 debug 了一整个下午
我做了一个英雄联盟桌面助手。Tauri + Rust + React,悬浮窗,实时显示英雄技能数据。
功能跑起来之后,技能描述是正常的,百分比也是对的。但是——所有具体数值,伤害、冷却、持续时间,全部是 0。
零。一个不剩。
第一步:先搞清楚数据从哪来
架构上有三条数据链路:
- LCU → 冷却、费用这些基础信息
- CDragon DataValues → 技能描述里的 token 解析
- CDragon DataValues → stats 面板上的数值
我先用 /grill-with-docs 把整个架构梳理了一遍,然后写单元测试验证 CDragon 解析层。
8 个测试,全绿。
解析层没问题。
第二步:看前端到底收到了什么
打开 devtools console,拿到 get_league_champion_details 返回的原始 JSON。
一看——所有技能的 cdragonAvailable: false。
数据根本没注入进去。
但问题是,解析层测试是通过的。那数据去哪了?
第三步:看后端日志
回到终端,翻 Rust 那边的日志。
bin_data=Some
CDragon 数据其实拉到了。后端有数据,但前端显示 false。
数据在中间丢了。
真正的 bug:一个大小写问题
CDragon 的 HashMap 存的 key 是大写:"Q"、"W"、"E"、"R"。
LCU 返回的 spellKey 是小写:"q"、"w"、"e"、"r"。
Rust 的 HashMap 是大小写敏感的。
所以 get_spell("q") 永远返回 None。
永远。
修复:一行代码
// 修复前
let slot_bin = bin_data.and_then(|bd| bd.get_spell(slot.as_str()));
// 修复后
let slot_bin = bin_data.and_then(|bd| bd.get_spell(slot.to_uppercase().as_str()));
加了一个 .to_uppercase()。
就这。
遗留的小问题
修完之后还有两个小毛病:
- E 技能圈数丢失:
NumTicks这个 token 没有正确渲染到描述文本里 - Stats 面板 key 格式:驼峰转换把
AD拆成了A D,看着很别扭
这两个不致命,但看着膈应人,后面慢慢修。
教训
单元测试绿不代表没 bug。
我的测试覆盖了解析层——HashMap 里存了什么、怎么解析的。但调用层的大小写规范化,没测到。bug 就藏在两层之间的缝隙里。
这种缝隙 bug 是最难找的。因为每一层单独看都是对的。
bin_data=Some 但显示 false——日志要分层看。
后端日志说数据有了,前端日志说数据没有。如果你只看一边,会觉得一切正常。两边对着看,才能发现数据在传递过程中丢了。
CDragon 是唯一选择。
Data Dragon 的数据不准确,LCU 没有计算数据,也没有现成的 token 解析库。所以 CDragon 是目前唯一靠谱的英雄联盟数据源。但它的数据格式和 LCU 不完全一致,大小写就是其中一个坑。
先写最小测试,再造大型 mock。
Claude Code 建议我先用真实数据写单元测试,而不是先造一个完整的 mock 数据集。结果证明这是对的——用真实数据测出来的 bug,mock 里根本测不出来。
一个大小写,一下午。
但至少现在我知道 HashMap 的 .get() 是大小写敏感的了。
在 Rust 里,这种教训你只需要吃一次。