English· Español· Deutsch· Nederlands· Français· 日本語· ქართული· 繁體中文· 简体中文· Português· Русский· العربية· हिन्दी· Italiano· 한국어· Polski· Svenska· Türkçe· Українська· Tiếng Việt· Bahasa Indonesia

un

访客
1 / ?
返回课程列表

早已存在的理论

每种 MOAD 缺陷在 2026 年系统性检测前数十年就已经存在已知的解决方案。这些缺陷之所以持续,并非因为无人知晓更好的方法,而是因为“知道”并不等同于“检测到”。

MOAD 持续恶化时间线:五种缺陷的理论已知时间与检测时间对比

MOAD-0001: O(N²) list.contains

Donald Knuth, 1973. 《计算机程序设计艺术》第 3 卷:排序与查找。 1973 年已完整定义并分析了用于 O(1) 查找的哈希表。O(N) 线性搜索与 O(1) 哈希查找之间的差异已被记录、形式化并广泛引用。Java 在 1.0 版(1996 年)就提供了 HashSet。Python 在 2.4 版(2004 年)将 set 作为一等类型提供。该修复方案在成为每个生态系统的默认习惯用法之前,已经存在了 30 年。

Richard Hamming, 1986. Bell Labs 讲座(后出版为 The Art of Doing Science and Engineering, 1997)。Hamming 明确讲授了算法复杂度、正确与高效的区别,以及构建“小规模可行、大规模失效”系统的风险。他称之为“只为今天可见的问题设计,而非为明天将面对的问题设计。”

MOAD-0002: 共享全局状态耦合

David Parnas, 1972. 《用于将系统分解为模块的准则》。CACM,1972 年 12 月。Parnas 主张模块应通过信息隐藏进行分解——每个模块拥有自己的状态,不存在可变的共享全局变量。这是 Intertangle 修复的直接理论前身。Parnas 明确指出:全局共享状态会产生测试无法发现的隐式耦合。

MOAD-0003: ThreadLocal 身份泄漏

Java 1.2, 1998. ThreadLocal 作为 Java 标准库类发布。当线程池与 ThreadLocal 共存时,泄漏机制即已存在。该缺陷是结构性的:载体生命周期绑定于线程,而非工作单元。Java EE 生命周期早期文档已对此发出警告。

MOAD-0004: 记录凭证

RFC 1945, 1996. HTTP/1.0 定义了 Authorization 头。自 Authorization 头出现之日起,凭证记录缺陷即已可能。OWASP 成立于 2001 年,并在首版指南中将凭证记录列为漏洞类别。模式为:Authorization 头 → 日志中间件 → 明文凭证落盘。自首个 HTTP 认证规范起即可预知。

MOAD-0005: 惊群 / 缓存雪崩

Unix 内核,1993 年。 “惊群问题”(thundering herd problem)——多个进程在共享事件发生时被同时唤醒——在 20 世纪 90 年代初的 Unix 内核开发讨论中已出现。Doug Schmidt 于 1994 年提出的 Reactor 模式和 1995 年提出的 Half-Sync/Half-Async 模式从基础设施层面解决了同步问题。缓存雪崩变体(N 个线程在缓存未命中时同时计算相同值)在 2001 年的分布式系统文献中已有记载。 [BLOCK_TYPE knowable/theory_exists]

--- [BLOCK_TYPE knowable/theory_exists]

理论:已完备。检测工具:缺失。“可知”与“已检测”之间的差距在 28 至 54 年之间,具体取决于缺陷类型。

可知性差距 [BLOCK_TYPE knowable/knowable_question]

时间线显示,每一类 MOAD 缺陷在被系统性检测前,至少已有 28 年的已知解决方案。最短差距(MOAD-0003)为 28 年,最长差距(MOAD-0002)为 54 年。 [BLOCK_TYPE knowable/knowable_question]

这不是关于无知的故事。Knuth、Parnas、Hamming——这些都是计算机科学领域被引用最多的作者。他们的著作曾在大学中被指定为教材。他们的术语(Big O、信息隐藏、算法复杂度)已成为标准课程内容。

为什么对这些缺陷类别的了解未能阻止缺陷持续存在?请选择一个 MOAD 缺陷,追溯其“可知解决方案”与实际检测之间的具体差距。 [BLOCK_TYPE knowable/knowable_question]

代码为何滋生:五种条件

缺陷并非偶然持续存在。当五种结构性条件同时出现时,便形成了滋生环境。移除其中任意一种,检测便成为可能。

缺陷滋生的五种条件:从理论到滋生的因果 DAG

条件 1:正确输出

列表和集合对成员资格问题的回答完全相同。list.contains(x)set.contains(x) 返回相同的布尔值。ThreadLocal 携带的过期身份仍然携带 一个 身份——只是它属于错误的请求。已记录的凭证被正确记录——凭证无误地到达日志文件。缺陷不是功能故障。它只在成本或安全后果上表现为故障。检查输出的测试通过。检查成本或安全后果的测试:大多未编写。 [BLOCK_TYPE five_conditions/conditions_diagram]

条件 2:CI 中没有复杂度测试
[BLOCK_TYPE five_conditions/conditions_diagram]

Dijkstra 说过:“测试只能显示缺陷的存在,不能证明缺陷的缺失。”Hamming 进一步指出:我们测试的缺陷就是我们能发现的缺陷。2026 年的 CI 流水线测试:正确性、类型安全、API 契约、功能行为。它们不测试:每次操作的算法复杂度、每次调用的内存增长、授权头清理、线程身份生命周期。 [BLOCK_TYPE five_conditions/conditions_diagram]

没有测试被运行。没有测试失败。流水线显示绿色。缺陷不可见。 [BLOCK_TYPE five_conditions/conditions_diagram]

条件 3:Small-N 起源
[BLOCK_TYPE five_conditions/conditions_diagram]

代码在开发环境中编写和评审。开发环境的图有 50 个节点。开发环境的请求负载有 10 个并发线程。开发环境的缓存未命中率很低(缓存已热,键很少)。在 N=50 时,O(N²) 成本是 2,500 次操作。不可见。在 N=50,000 时,成本是 2,500,000,000 次操作。构建时间从 1 秒变成 17 分钟。 [BLOCK_TYPE five_conditions/conditions_diagram]

编写代码的作者从未见过 N=50,000。批准代码的评审者从未见过 N=50,000。缺陷在编写时的规模下不可见。 [BLOCK_TYPE five_conditions/conditions_diagram]

条件 4:复制传播时丢失上下文

一个正确的算法具有指导意义。教程用正确的示例教学。文档展示可运行的代码。同样的 Tarjan SCC 骨架 —— visited = [],内部 if n not in visited —— 出现在 GHC、Maven、Python pip、Cargo、TypeScript 编译器、Kotlin 编译器、Scala 编译器和 javac 中。不同的团队、不同的语言、不同的年代。同样的化石。原始作者的 N=50 并未随代码传播。传播的是正确输出。留下的则是性能假设。 [BLOCK_TYPE SECTION/STEP]

条件 5:规模在冻结代码周围增长
[BLOCK_TYPE SECTION/STEP]

代码不会退化。基础设施会扩展。2003 年为 200 个包编写的依赖解析器,在 2024 年运行在 50,000 个包上。没有人重写它——它能工作。没有人分析它——CI 是绿的。让 O(N²) 代价变得灾难性的 N 逐渐地、无形地出现在生产规模上。那时原始作者早已离开。代码已成为依赖。没有人会去触碰能正常工作的依赖。

诊断:五种条件

这五种条件:正确输出、无复杂度测试、小 N 起源、复制时丢失上下文、规模在冻结代码周围增长。

所有五种条件在每一次 MOAD 中同时存在。这不是巧合——它是沉积型缺陷类的结构特征。

五种条件中,哪一种最难消除,为什么?要从真实软件组织的流水线中移除它,需要付出什么?

Hamming 所说

Richard Hamming 1986 年在贝尔实验室的讲座(1997 年出版为 The Art of Doing Science and Engineering)包含的警告,直接描述了 MOAD 缺陷模式。他当时并非在描述 MOAD,而是在描述工程系统的一种结构性倾向:围绕局部正确的决策逐渐固化,最终导致全局代价高昂。

Hamming 论复杂度:“计算的目的是洞见,而非数字。但你必须使用正确复杂度的算法,否则数字永远不会出来。一个在 N=100 上能跑的 O(N²) 算法,在 N=1,000,000 时你退休前都跑不完。”

Hamming 论复制:“优秀的工程师不会只是复制解决方案。他们理解为什么这个方案有效、在什么条件下成立,以及什么会破坏它。没有条件的复制方案是一颗定时炸弹。”

Hamming 论测试:“测试你所测量的内容,不等于测量真正重要的内容。我们为选定的属性构建了复杂的测试套件,却未测试那些未被选中的属性。未被测试的属性,正是生产环境中让我们吃惊的原因。”

Hamming on scale: 'The error you make in the first year of a project is the error you are still correcting in the tenth year. Early assumptions calcify. The project scales around them. Nobody rewrites the foundation.'

警告与操作化之间的差距

Hamming 的警告是正确的。它们被教授、被引用、被纳入课程。但警告不是检测器。Hamming 描述了缺陷的形态,却没有构建能在 CI 中运行并标记 O(N²) 热点路径、ThreadLocal 身份泄漏或凭证日志记录的工具。“可知”与“可检测”之间的差距,就是理论与其作为自动化基础设施的操作化之间的差距。

MOAD 之所以存在,是因为该领域建立了正确性基础设施,却未在同等水平上建立性能或安全基础设施。单元测试:自 1970 年代起已成为标准。基于属性的测试:自 1990 年代起已成为标准。CI 中的算法复杂度基准测试:到 2026 年仍处于实验阶段。

操作化警告

Hamming 警告过复杂度固化、未测试的属性、复制解决方案却忽略其适用条件,以及规模对早期假设的碾压。他给了我们词汇,却没有给我们检测器。

MOAD 流水线填补了这一空白:扫描 → 工单 → 补丁 → 单元测试 → 披露 → PR → 上游合并。这就是操作化的 Hamming:不仅是警告,而是自动化检测与修复流水线。

Hamming 曾说:“我们未测试的内容,就是生产环境中让我们吃惊的内容。”请举出一个软件行业系统性未测试的属性,并描述自动化检测会是什么样子。

The Fester Signature

MOAD 类缺陷具有可识别的签名。五个条件同时存在。所有五个条件都可在编写任何扫描前进行检查:

1. 输出正确吗? 运行标准测试套件。如果通过,则该缺陷属于性能或安全属性,而非正确性属性。这意味着标准 CI 不会捕获它。

2. 未进行复杂度测试? 检查 CI 配置。是否存在基准测试阶段?是否对比算法行为(而非仅对比运行时间)与前一次提交?若无:条件成立。

3. 小规模 N 起源? 检查 git blame 及原始提交。首次实现时数据集规模是多少?该规模是否比当前生产负载小 100 倍以上?若是:条件成立。

4. 复制传播? 在代码库及生态系统中搜索该模式。该结构模式是否出现在 ≥3 个无共同祖先的独立代码库中?若是:化石已传播。每个副本:一个新的溃烂点。

5. 规模增长? 检查生产指标。当前 N 值与首次部署时的 N 值相比如何?增长率是否持续?在何种 N 值下缺陷将变得运维关键?

若五项全部检查并确认:即为 MOAD 级缺陷。修复始终只需一行或一个方法的替换。发现是难点,修复是易事。

这就是 Hamming 所指:工程不在于修复。修复一旦看清便直截了当。工程在于构建能让你看清它的系统。

应用特征

MOAD 溃烂特征:正确输出 + 无复杂度测试 + 小规模 N 起源 + 复制传播 + 规模增长。

此签名不仅限于五种 MOAD 缺陷。它描述了一类缺陷,只要系统中仅将正确性测试作为唯一的自动化质量门禁,该类缺陷就会持续存在。

请举出一个不属于五种 MOAD 的缺陷类别,符合“fester”签名。说明其中存在的五项条件,并描述自动化检测器应具备的形态。