SuperAPI AI 工具集

settings-value-round-tripped-through-its-label

编程与开发

查看项目

更新于 10-17 16:17

技能介绍

用途

这一条讲的是 Kotlin/Compose 设置界面中一种常见但隐蔽的错误模式:把每个存储值与其翻译后的标签配对,界面上显示标签,用户选择后拿回标签,再用字符串相等反查出存储值并写回。正向映射(值到标签)用于行内副标题没有问题;问题出在返回的那一趟——它依赖“每个标签在每种语言中都唯一”,而这不是任何人会维护的性质。

主要问题

  • 两个选项翻译相同时,写入被静默禁用:反查失败时 ?.let 吞掉结果,对话框关闭、设置未变、无日志;更糟的是匹配到第一个同标签项,写入了错误的值。尾部空格、撇号差异、列表与对话框渲染不一致也会造成同样故障。
  • 正确做法是携带 id 而不是文本:选择 API 应返回索引或值本身;若对话框不由自己控制,则按位置对照构建标签时的同一列表实例映射。位置映射只在一个列表只构建一次时成立,因为选项列表常带条件项(某平台才支持的条目),在回调中重建会产生不同索引。应只保留一份 val,展示与回调共用。
  • 查找失败要显式暴露:必须成功的查找不应以 ?.let {} 结尾,应记录警告或在 debug 构建中抛出。设置静默写入失败往往数月后由某个用户、某种语言报告为“不保存”。
  • 默认值重复声明:存储层在键缺失时回退默认值,界面收集 flow 时又提供首帧初值,两者不一致会导致每次进入时短暂显示另一选项,“恢复默认”还可能写入存储层认为的值而界面显示的是收集器的值。应从两处引用同一个具名常量,优先使用带起始值的 flow 或 state flow。布尔版本同理,只是更容易发现;注意以字符串常量保存的标志其 false 是点分名称而非字面量 false,审计时两侧拼写不同。
  • 正向方向也有静默失败:行副标题从存储值查标签、无匹配时回退空串,旧版本写入或错误选项写入的值会渲染为空白副标题而非错误。应回退到默认选项标签并记录缺失。
  • 还需注意点击 lambda 中的其他操作:该模式里的对话框标题和按钮常用阻塞式资源读取;在事件处理器中尚可,在 composition 中调用则不行。

验证方法

页面给出若干 grep 审计命令(在项目根目录执行,排除 build 目录,仅匹配 .kt 文件):

  1. 查找所有按标签反查:
grep -rn --include='*.kt' --exclude-dir=build "it.second == \|\.second == selected" .

每处命中要么是 bug,要么需注释说明标签为何保证唯一。

  1. 查找以静默 ?.let 结尾的查找:
grep -rn --include='*.kt' --exclude-dir=build "firstOrNull {.*}?\..*?\.let {" .
  1. 查找非纯布尔的收集器默认值:
grep -rnE --include='*.kt' --exclude-dir=build \
  "collectAsState[A-Za-z]*\((initialValue *= *)?[A-Za-z_][A-Za-z0-9_.]*\)" . \
  | grep -vE "\((initialValue *= *)?(true|false|null|[A-Za-z0-9_.]*\.(TRUE|FALSE))\)"

命令中两半都关键:参数位置可选,因为默认值可按位置或具名传递,代码库常混用,只按 initialValue = 审计会漏掉另一拼写;排除项须覆盖布尔的两种拼写,否则匹配范围扩大后又会填入本应剔除的标志;null 被视为“尚未加载”的哨兵而非重复声明的默认值,故排除。每处命中需与存储层回退所用常量一致。

  1. 对比存储层自身回退:
grep -rn --include='*.kt' --exclude-dir=build "preferences\[.*\] ?: " .
  1. 手工验证(最能发现问题):把应用切到两个选项读法相同的语言,选择其中第二个,重新打开对话框。若勾选的是第一个或仍是旧值,说明往返经过标签。

适用场景与限制

适用于使用 Kotlin 与 Compose 编写设置界面、涉及本地化标签与持久化值的项目,可作为代码审查与静态审计的检查清单。资料未提供该技能对应的可执行工具、依赖或安装方式,也未涉及收费、平台支持范围等信息。