1553 字
8 分钟
grep 不到只能证明你搜过的文件里没有

我 grep 了整个仓库找一个权限标识,只命中前端一处废弃注释。于是我写下结论:这个权限从没发放过,所有人都进不去。这条结论是错的,我后来撤回了它。我的判断是:grep 不到只能证明你搜过的那些文件里没有,它只管代码仓库里的文件,而权限、配置、运行时注入的值都不在那里。

否定性证据的四档强度,以及 grep 够不到的四类东西

同一个 401,我给过三个结论#

报障是一个接口每次都返回 401。

v1 就是开头那条:全仓 grep 那两个权限标识,命中的只有前端一处废弃注释。用户一句话否掉了它——「页面上测试是没问题的」。

这句话的价值超过我再读十个文件。它说明有一条路已经能走通,问题就从「找原因」变成「找两条路的差异」,要比的只剩参数。

v2:我改判是服务间调用没转发凭证,所以下游每次都 401。拿到集群上实测,又被否掉。

v3 才是真相。调用方填的成员 ID 和它所属的组织 ID 不匹配,换成那条能通的路上用的数字,立刻 200。

实测顺带否掉了 v1 的事实基础:那个身份实际有 114 条权限,我 grep 的那两个全都在。它们不在代码里,在身份服务的数据库和角色配置里。

两次我都写明「上一轮的结论错了,撤回」,没有悄悄改口。悄悄改口的代价是别人不知道哪些基于旧结论的工作要跟着作废。

grep 够不到的四类东西#

v1 的错不在手法,在于我把搜不到当成了不存在。这四类我都踩过。

运行时数据。权限、角色、成员关系、配额、特性开关——代码里只写判断逻辑,数据另存一份。

配置没注入。排查一个下游服务调不通时发现,配置文件把地址的默认值硬写成了一个具体的 NodePort,而部署用的 configmap 根本没注入这个环境变量。服务就用了镜像里带的那个默认值,它指向另一个命名空间里 41 天前的旧服务。grep 代码只能看到那行默认值,看不到它正在生效。

版本错位。部署的镜像可能比分支旧一两天,读的代码和跑的代码不是一份。更糟的一次是我派去取证的子代理在本地工作区 grep,而那个工作区落后远端六十多个提交,关键代码根本不在上面,整条结论作废。

跑不跑得到。搜到一个分支不代表它跑得到,可能有开关让那条路进不去。反过来也成立:没搜到一段清理逻辑,不代表没有兜底——可能有个定时任务会把卡住的资源扫出来清掉,没找到那段代码就会误判成「永久卡死」。

否定性证据有强弱,最弱的那档信息量是零#

同一段时间我还犯过一次同类的错。定位一次容器反复重启,我向上汇报的理由是「日志里没有 OOM 记录,所以不是内存问题」。

这条被当场否掉。内存超限是内核直接杀进程,进程来不及写日志。「查不到 OOM 日志」在这个场景下什么都证明不了。

换成四条站得住的:

  • 卡住期间服务一直在干活,打分一结束,积压的健康检查几毫秒内全部返回;
  • 三次重启的时刻,和「连续 6 次探针失败 + 宽限期」算出来的时间,误差不到 1 秒;
  • 有两次重启前走了完整的优雅关停流程,而内存超限是直接杀,不会走关停;
  • 同配置的另外两个副本当天零重启,只有跑评测那台出事。

否定性证据有多强,取决于它背后那个前提可不可靠。分四档:

形式成立的前提强度
日志里没有 XX 发生时一定会留下日志最弱,这个前提常常不成立
grep 不到 XX 一定以字面形式出现在我搜的文件里弱,仓库之外的东西搜不到
对照组也没有 X,但对照组是好的对照组确实只差这一个变量中
行为特征只符合其中一种解释两种解释的可观测行为确实不同最强

最后一档已经不算否定性证据,它是正面指认一种机制。要证明「不是 X」,最后多半得靠正面证明「是 Y」。

两条做法,和它们的代价#

一是子代理读代码一律指定版本:git show origin/<分支>:<路径>,不读工作区。需要跑验证的,用 git worktree add --detach 开一份临时工作区,验完删掉,不动原仓库的状态。

二是「没找到」必须附搜索过程。审计类任务我只允许三种结论:已实现(附 file 和关键代码)、未实现(必须说明在哪些地方找过、用过哪些关键词)、部分实现(明确哪部分有哪部分没有)。配套一条:确认 / 推断 / 未确认分开标注,拿不出证据的一律写未确认。

代价有两条,都在第二条上。

调研变慢,而且大半结论会降级成「未确认」。对要做决定的人来说,一份「12 条里 7 条未确认」的报告不如一句明确判断好用。这两头没法都要,只能按判错的代价定要不要这么严:改一行文案不值得,决定要不要删掉一个服务值得。

另一条是,「我在哪找过」这个要求本身可以被敷衍。列一串关键词不难,难的是关键词选得对。它堵住的是「没找到所以没有」这句话直接当结论用,堵不住搜得不认真。