Files
ATRI-NOTES/ATRI My Dear Moments/questions/2026-09-29-电费插件的输入边界(三类静默降级).md
T
2026-09-30 12:03:04 +08:00

4.7 KiB
Raw Blame History

电费插件的输入边界:同一个"静默降级",三个变种

  • 建档:2026-09-29(晚间收尾)
  • 状态:🟢 三类均已修 + 真实数据实测 + 回归通过;待主人实机复验一次
  • 触发场景:主人当天三次追问同一件事——"错误的输入会发生什么"

一、一句话总结

同一个毛病长出了三张脸:输入无法被正确理解时,程序不报错,而是"猜一个"然后照常输出。

于是用户的输入与看到的输出之间,没有因果关系——主人当天的原话是"随机返回"。

变种 谁发现 表现 根因
① 垃圾输入 主人随手敲三行 无法识别的输入被降级成"只给了校区",照样返回 26 个楼栋 解析器只有两种出口(查到 / 没找到),没有"看不懂"这个状态
② 消息尾缀 主人实机复测 修好 ① 之后,合法输入也全挂 同机另一插件给每条消息尾部追加了编号;本机拿到的是被改装过的副本,却拿它去做字符校验
③ 数字匹配 主人 22:01("依旧 0 等于 10") 0 → 10 号楼;06 / 09 → 永远找不到楼栋 选项匹配用的是子串包含("0号公寓" in "10号公寓" 成立),而不是数字相等

二、变种 ③ 的根因细节(最隐蔽的一个)

原实现:

def _pick(items, key, needle):
    for it in items:
        if needle in str(it.get("label", "")):   # ← 子串匹配
            return it

调用处传的是 f"{build_no}号公寓",于是:

  • build_no = "0" → needle "0号公寓" → 遍历到 10号公寓 时子串命中 → 悄悄查了 10 号楼;
  • build_no = "06" → needle "06号公寓" → 谁都不是它的子串 → 返回 None → "永远找不到"。

同一句输入,一边是悄悄地错,一边是永远找不到——两个毛病同一个根。


三、修法

  1. 新增按数字精确匹配 _pick_no():取选项名开头的数字做整数比较(如「X号公寓」→ X、「XXX房」→ XXX),楼栋与房间都改走它;
  2. _pick() 调整为三段式:先精确 → 再数字 → 最后才子串(校区名仍需模糊匹配,故保留最后一层);
  3. 顺带白拿一个好处:前导零自动归一(06 → 6 号楼、09 → 9 号楼)。

没有做的事情(刻意的):没有把 _pick 的子串匹配整个删掉——校区简称 → 校区全名这一层依赖它。收窄的是"数字类"匹配,不是全部。


四、验证(跑的是真实站点数据)

输入形态 修前 修后
0-XXX ❌ 悄悄去查 10 号楼 ✅ 如实回"没有找到 0 号楼"
06-XXX(前导零) ❌ 永远找不到 6 号楼 ✅ 正确落到 6 号楼,并查到房间
09-XXX ❌ 永远找不到 9 号楼 ✅ 正确落到 9 号楼(房间不存在则如实回)
10-XXX ⚠️ 会先命中 1 号楼 ✅ 10 号楼
常规写法 ✅ ✅ 不变
  • 跑的是真实站点的选项列表(含 26 栋楼、两百余间房),不是构造样例;
  • 解析层 27 例回归 → 27 / 27 通过;
  • 插件已重载生效;改前主文件已完整备份(/tmp,可回滚)。

五、要记住的三条

  1. 只测 happy path = 没测。 我修好 ① 之后宣布"27 例全过"——那 27 例全是合法输入。主人随手敲三行垃圾,当场撞出两个。
  2. 同一个坑在同一个位置栽了两次:① 的"残留校区词"补过一次,重写解析器时又漏了;③ 的"子串匹配"也属于同一个"用包含代替相等"的思路。→ 它需要的不是再修一遍,而是把用例固化成文件、每次改完就跑。(主人中午提过的那份 27 例用例,尚未落成 test_parse.py,仍挂账。)
  3. 入参不一定是用户打的原文。 修 ① 之后全挂,因为消息在到达我之前被别的插件改装过。→ 以后凡是"我读到的内容"要参与判断,先确认它是不是原文。

六、附带发现(并入观察档)

  • 反刷屏与"欢迎"撞车:本机为新成员写的欢迎语(三行 + emoji)被判刷屏点名。判定依据是"1 秒内的行数与关键词命中",而欢迎与刷屏在这套统计特征上高度重合。两条可能的修法:给欢迎类模板放行,或把欢迎压成"一小段文字 + 一张图片"。(未改,待主人定。)
  • 同群两台电费 bot 抢答:另一台是别校格式(提示语与本校房号规则完全不搭),会对同一条消息各答一遍。已多次记档,继续观察。