风控的作用是在钱不够的时候停下来。但如果它把「查不到」当成「是 0」, 它就会在钱够的时候也停下来 —— 而且你会以为风控起作用了。 这一篇讲两次真实的误触,以及它们指向的同一条规矩: 「未知」两个方向都不能错。
我们有一条真钱线,规则是「钱包现金低于某个地板就停止下单」。 开闸约五个半小时后,它读到现金 $145.41,低于 $150 的地板,落下停机标记。
看起来完全正确。实际上不是:
| 金额 | |
|---|---|
| 钱包现金(风控看到的) | $145.41 |
| 已经赢了、正在赎回途中的 | $94.8 |
| 真实净值 | 约 $240(地板的一倍半) |
病根:地板用的是纯现金口径。这个口径是从一条 4 小时线继承来的 —— 在 4 小时一个窗的节奏上,赎回慢几分钟无所谓。搬到 5 分钟一个窗的节奏上, 赎回滞后(实测六到十一分钟)永远盖在头上,钱包现金系统性地低报自己。
同一段代码,在慢节奏上是对的,搬到快节奏上会被结算延迟顶穿。
更一般地:任何依赖「资金已到账」的判断,都要问一句 「到账需要多久,而我多久检查一次」。 后者小于前者,就一定会误判。
另一处是展示层的同一个错误。一个监控面板上,某个指标查不到数据时, 代码写的是「取到就用,取不到用 0」—— 于是页面上显示一个漂亮的 $0。
但 $0 和「不知道」是完全不同的两件事:
| 页面显示 | 可能的真相 | 你会怎么做 |
|---|---|---|
| $0 | 确实是 0 | 正确反应 |
| 查不到 / 接口挂了 / 字段改名了 | 基于假数据做决定 |
这个坑特别阴险,因为 $0 看起来是个正常的数字。 如果显示的是「—」或者「读取失败」,任何人一眼就知道该去查; 显示 $0,你会当成一个事实去用它。
| 把未知当成 | 后果 | 例子 |
|---|---|---|
| 0 | 假停机:明明有钱却停了 | 在途资金没算进去 |
| 正常 | 盲跑:真穿了地板也不会停 | 数据源挂了,风控读到旧值一路放行 |
正确做法是把「未知」当成第三种状态,单独处理:
| 场景 | 该怎么办 |
|---|---|
| 风控读不到余额 | 停下来但不落死标记 —— 等下一轮,别做不可逆的动作 |
| 展示层读不到 | 显示「—」或「读取失败」,绝不显示 0 |
| 在途资金 | 显式建模,不要靠「反正一会儿就到账」 |
| 数据陈旧 | 加陈旧告警:多久没更新就报警 |
第一行的「停下来但不落死标记」值得展开:停机要分可逆和不可逆。 因为读不到数据而暂停一轮是可逆的;落下一个需要人工清除的硬停标记是不可逆的。 在信息不足的时候,只做可逆的动作。
我们在自己的一个监控页上发现过这个洞:页面有一个「数据陈旧」的提示位, 样式也写好了,但从来没有任何代码往里写内容 —— 也就是说这个页面根本没有数据陈旧告警。
后果是:如果数据源静默挂掉,页面会一直显示最后一次成功读到的数字, 看起来一切正常。一个永远显示「正常」的监控,比没有监控更危险, 因为它会让你停止怀疑。
| 问 | 不合格的答案 |
|---|---|
| 读不到数据时,这个变量是什么值? | 「默认 0」 |
| 风控的口径含在途资金吗? | 「应该会很快到账吧」 |
| 数据多久没更新会报警? | 「没设」 |
| 这条风控是从哪抄来的? | 「另一条线」——那条线是什么节奏? |
| 停机动作可逆吗? | 不知道 = 大概率不可逆 |
没有。那条线的地板口径挂在「待决定」栏里,一直到封盘都没改。 也就是说后面十几个小时的风控,跑的一直是那个会低报自己的口径。
写在这里不是为了自嘲,是因为这是判死档案该有的样子: 发现了缺陷、记录了缺陷、但没修 —— 这个事实本身也要留在页面上。
风控最危险的失败不是「没触发」,是「因为错的理由触发了」 —— 你会以为它在保护你。 而绝大多数这类失败,根源都是同一句:把「不知道」当成了一个数字。