一个大小写问题,让我 debug 了一整个下午

用 Tauri + Rust 做的 LoL 桌面助手,技能数值全部显示为 0。排查了数据层、前端、后端,最后发现 bug 藏在一行 HashMap 的 key 里。

· 1 min read

一个大小写问题,让我 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()

就这。


遗留的小问题

修完之后还有两个小毛病:

  1. E 技能圈数丢失NumTicks 这个 token 没有正确渲染到描述文本里
  2. 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 里,这种教训你只需要吃一次。