<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>计算机科学 on Lingming 灵明</title><link>https://lingming.blog/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6/</link><description>Recent content in 计算机科学 on Lingming 灵明</description><generator>Hugo</generator><language>zh-CN</language><lastBuildDate>Tue, 21 Jul 2026 18:50:13 -0400</lastBuildDate><atom:link href="https://lingming.blog/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6/index.xml" rel="self" type="application/rss+xml"/><item><title>黑箱转向（四）：控制不可读之物，需要哪些证据</title><link>https://lingming.blog/posts/2026/controlling-the-unreadable/</link><pubDate>Tue, 03 Mar 2026 14:53:41 -0500</pubDate><author>Lingming</author><guid>https://lingming.blog/posts/2026/controlling-the-unreadable/</guid><description>控制复杂或生成式软件不能只靠逐行阅读，也不能只看测试是否通过。本文以转账系统为例，建立需求、契约、性质测试、实现分析、形式验证与运行监控组成的六层证据体系，说明每种方法各自能证明什么。</description><content:encoded>&lt;p>假设 AI 生成了一个转账函数，单元测试全部通过。我们能否据此放心上线？&lt;/p>
&lt;p>还不能。测试可能只覆盖余额充足的正常路径，没有检查重复请求、并发扣款、货币精度、审计记录或下游超时。即使每一行代码都由人写，问题也一样存在。阅读能够发现某些错误，却无法穷尽输入、状态和环境；测试能够提供行为证据，也无法证明需求本身完整。&lt;/p>
&lt;p>&lt;a href="https://lingming.blog/posts/2026/the-collapse-of-linearity/">上一篇&lt;/a>讨论团队理解与系统之间的认知债。本篇转向工程保证：当完整理解不可得时，怎样建立足够但不虚假的信心？&lt;/p>
&lt;h2 id="不是从实现控制切换到约束控制">不是从“实现控制”切换到“约束控制”&lt;/h2>
&lt;p>接口契约、不变量、测试先行和形式验证都早于生成式 AI。它们不是黑箱时代突然出现的新方法，也没有取代实现检查。AI 改变的是相对成本：候选实现更容易产生，明确性质和核验证据便显得更稀缺。&lt;/p>
&lt;p>因此，更准确的方向不是二选一，而是从单一证据转向&lt;strong>分层保证&lt;/strong>：&lt;/p>
&lt;blockquote>
&lt;p>说明我们主张系统满足什么，用相互独立的方法支持这项主张，并诚实记录仍未覆盖的条件。&lt;/p>&lt;/blockquote>
&lt;p>这与安全关键领域中的 assurance case 思路相近。控制不是“测试绿了”的感觉，而是一条可以被质疑的主张—证据链。&lt;/p>
&lt;h2 id="第一层先证明问题被定义">第一层：先证明问题被定义&lt;/h2>
&lt;p>转账功能的第一层不是代码，而是业务和风险要求：&lt;/p>
&lt;ul>
&lt;li>金额必须为正，并使用明确的货币与精度规则；&lt;/li>
&lt;li>同一幂等键不能造成重复扣款；&lt;/li>
&lt;li>未授权主体不能发起转账；&lt;/li>
&lt;li>余额与账本必须保持一致；&lt;/li>
&lt;li>下游超时不能留下无法判定的中间状态；&lt;/li>
&lt;li>每次变更必须留下可审计记录。&lt;/li>
&lt;/ul>
&lt;p>这些要求包含功能、禁止行为和失败处理。若团队只给模型一句“实现转账 API”，生成器会用训练数据中的常见模式填补空白。实现可以整洁，却不一定符合这个组织的真实制度。&lt;/p>
&lt;p>需求层的证据来自领域专家确认、威胁建模、历史事故、法规和用户场景。它的盲点是自然语言可能歧义，利益相关者也可能遗漏风险。&lt;/p>
&lt;h2 id="第二层把关键性质写成契约和不变量">第二层：把关键性质写成契约和不变量&lt;/h2>
&lt;p>契约把部分要求变成更精确的边界：&lt;/p>
&lt;ul>
&lt;li>前置条件：调用者已授权，金额与货币合法；&lt;/li>
&lt;li>后置条件：成功时借贷双方账本变化守恒；&lt;/li>
&lt;li>不变量：任何时刻总账不能因内部转账凭空增加或减少；&lt;/li>
&lt;li>接口保证：重复幂等键返回同一业务结果。&lt;/li>
&lt;/ul>
&lt;p>类型系统、数据库约束、状态机和权限策略都可以承担这种工作。它们让一部分错误变得不可表达，或者在边界处立即失败。&lt;/p>
&lt;p>但契约不会自动正确。若把“每天限额”写错，机器可以完美验证一个错误规则；若性质没有包含隐私泄漏，形式上的资金守恒也不能证明系统安全。约束减少行为空间，不替我们决定哪一个空间值得允许。&lt;/p>
&lt;h2 id="第三层用多种测试探索性质">第三层：用多种测试探索性质&lt;/h2>
&lt;p>示例测试适合固定已知场景；性质测试从更广输入中寻找反例；状态机或模型测试适合检查操作序列；模糊测试可以探索解析器和协议边界；故障注入则检查依赖失败时的系统反应。&lt;/p>
&lt;p>对于转账系统，可以让生成器产生不同金额、账户状态、重复请求和并发顺序，持续检验余额守恒与幂等性。QuickCheck 在 2000 年已经展示了用随机数据检验程序性质的方法，这并不是 AI 时代的新发明。&lt;/p>
&lt;p>测试的力量在于找到具体反例，而不是证明“所有可能情况都没问题”。测试数据分布、模拟环境和断言质量都会限制结论。AI 可以帮助提出边界案例，也可能生成与实现共享同一误解的测试，因此高风险性质需要独立来源。&lt;/p>
&lt;h2 id="第四层检查实现而不是宣布实现无关">第四层：检查实现，而不是宣布实现无关&lt;/h2>
&lt;p>代码审查、静态分析、依赖扫描、动态分析和渗透测试仍然重要。行为测试可能发现“发生了错误”，实现分析更容易解释错误怎样发生，以及是否存在同类模式。&lt;/p>
&lt;p>代码覆盖率在这里仍有用途：它能指出测试从未执行的语句或分支。它不能证明需求已经覆盖，也不能衡量断言质量。所谓“行为覆盖”如果不定义行为模型、输入分区和性质集合，就只是一个好听的新标签。&lt;/p>
&lt;p>更合理的关系是：&lt;/p>
&lt;ul>
&lt;li>需求覆盖问：重要主张是否有证据；&lt;/li>
&lt;li>代码覆盖问：实现结构哪些区域从未执行；&lt;/li>
&lt;li>性质测试问：给定生成分布是否找到反例；&lt;/li>
&lt;li>变异测试问：测试是否能识别人为注入的错误；&lt;/li>
&lt;li>静态分析问：是否存在某类结构性缺陷。&lt;/li>
&lt;/ul>
&lt;p>这些维度互补，没有一个可以单独取代其余部分。&lt;/p>
&lt;h2 id="第五层用形式验证承担适合它的高风险主张">第五层：用形式验证承担适合它的高风险主张&lt;/h2>
&lt;p>形式验证可以证明实现相对于形式化规格满足某些性质。seL4 微内核展示了机器检查的功能正确性证明能够达到很深层级，同时也明确列出编译器、硬件和规格等可信假设。&lt;/p>
&lt;p>这正说明形式证明的边界：它证明的是模型中写出的性质，不是现实世界的一切正确性。完整验证成本高，也不必施加在每段低风险业务代码上。&lt;/p>
&lt;p>更现实的做法是风险分配：&lt;/p>
&lt;ul>
&lt;li>用类型和约束处理大量局部错误；&lt;/li>
&lt;li>用模型检测检查有限状态协议；&lt;/li>
&lt;li>用求解器验证关键权限与资源性质；&lt;/li>
&lt;li>对安全内核、加密协议或高后果控制器投入更强证明；&lt;/li>
&lt;li>对其余系统保留测试、审查和运行防护。&lt;/li>
&lt;/ul>
&lt;p>AI 可能帮助生成规格或证明，但规格与证明脚本仍需独立检查。由同一个模型同时提出实现、测试和“证明”，容易产生相关性失败。&lt;/p>
&lt;h2 id="第六层上线以后仍需验证">第六层：上线以后仍需验证&lt;/h2>
&lt;p>前五层都无法完整复制生产环境。真实流量、数据分布、依赖故障和运维操作会暴露新的条件，因此保证必须延伸到运行期：&lt;/p>
&lt;ul>
&lt;li>灰度或金丝雀发布限制初始影响；&lt;/li>
&lt;li>指标、日志和追踪帮助识别越界；&lt;/li>
&lt;li>对账和审计任务检查业务不变量；&lt;/li>
&lt;li>限流、熔断与权限边界限制传播；&lt;/li>
&lt;li>降级、前滚修复或回滚缩短影响时间；&lt;/li>
&lt;li>事故复盘把新事实反馈到需求和测试。&lt;/li>
&lt;/ul>
&lt;p>运行监控不是承认前面的工程失败，而是承认开放系统总会遇到模型外事实。&lt;/p>
&lt;h2 id="把证据放进同一张表">把证据放进同一张表&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>层级&lt;/th>
 &lt;th>转账案例中的主张&lt;/th>
 &lt;th>主要证据&lt;/th>
 &lt;th>不能单独证明&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>需求&lt;/td>
 &lt;td>这是正确的业务与风险目标&lt;/td>
 &lt;td>领域评审、威胁模型&lt;/td>
 &lt;td>要求无遗漏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>契约&lt;/td>
 &lt;td>关键状态必须守恒&lt;/td>
 &lt;td>类型、约束、不变量&lt;/td>
 &lt;td>契约符合现实意图&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>测试&lt;/td>
 &lt;td>代表性场景满足性质&lt;/td>
 &lt;td>示例、性质、故障注入&lt;/td>
 &lt;td>输入空间已经穷尽&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>实现&lt;/td>
 &lt;td>没有已知结构性缺陷&lt;/td>
 &lt;td>审查、静态与动态分析&lt;/td>
 &lt;td>所有行为正确&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>形式验证&lt;/td>
 &lt;td>指定模型满足指定性质&lt;/td>
 &lt;td>机器检查证明&lt;/td>
 &lt;td>模型外环境安全&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>运行验证&lt;/td>
 &lt;td>真实系统保持在边界内&lt;/td>
 &lt;td>监控、对账、灰度&lt;/td>
 &lt;td>未来不会出现新风险&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>这张表的价值不在于让流程变长，而在于防止证据越权。测试通过只能支持它实际检查的主张，代码可读也只能提高发现某些错误的机会。&lt;/p>
&lt;h2 id="结语控制是可审查的信心">结语：控制是可审查的信心&lt;/h2>
&lt;p>黑箱系统最危险的不是我们没有读过全部实现，而是我们无法说清自己凭什么相信它。&lt;/p>
&lt;p>分层保证保留了理解的价值，也承认理解的边界。工程师可以阅读关键实现，同时用契约限制状态、用测试搜索反例、用形式方法证明高风险性质，并在运行期准备发现和恢复。&lt;/p>
&lt;p>控制不可读之物，不是把内部过程忘掉，而是让每项重要主张拥有与其风险相称的证据。&lt;/p>
&lt;h2 id="延伸阅读与参考资料">延伸阅读与参考资料&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://doi.org/10.1109/2.161279">Bertrand Meyer：Applying “Design by Contract”&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://doi.org/10.1145/351240.351266">Koen Claessen、John Hughes：QuickCheck&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://doi.org/10.1145/1743546.1743574">Gerwin Klein 等：seL4 — Formal Verification of an Operating-System Kernel&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.nist.gov/itl/executive-order-14028-improving-nations-cybersecurity/software-supply-chain-security-guidance-0">NIST：Recommended Minimum Standards for Vendor or Developer Verification of Software&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://csrc.nist.gov/pubs/sp/800/218/final">NIST：Secure Software Development Framework&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>&lt;em>上一篇：&lt;a href="https://lingming.blog/posts/2026/the-collapse-of-linearity/">《黑箱转向（三）：认知债——团队知道得越来越少吗》&lt;/a>；下一篇：&lt;a href="https://lingming.blog/posts/2026/architecture-as-risk-geometry/">《黑箱转向（五）：架构即风险几何》&lt;/a>。&lt;/em>&lt;/p></content:encoded></item><item><title>黑箱转向（一）：软件工程从来不是完全的白箱</title><link>https://lingming.blog/posts/2026/the-white-box-assumption/</link><pubDate>Mon, 02 Mar 2026 11:26:06 -0500</pubDate><author>Lingming</author><guid>https://lingming.blog/posts/2026/the-white-box-assumption/</guid><description>传统软件工程追求可理解性，却从未要求一个人理解全部系统。本文从结构化编程、信息隐藏、版本控制和大型系统协作出发，区分源码可见、局部可解释、全局可预测与系统可控制，重新界定所谓“白箱假设”。</description><content:encoded>&lt;p>如果把一个现代浏览器的全部源码打印出来，没有任何一页被隐藏，它就是白箱吗？&lt;/p>
&lt;p>从访问权限看，是。从个体理解看，不是。从团队能否定位一次故障、判断一次改动或恢复一个版本看，答案又取决于文档、测试、观测和组织分工。把这些问题都压进“透明”一词，容易虚构出一个从完整白箱突然坠入 AI 黑箱的历史。&lt;/p>
&lt;p>&lt;a href="https://lingming.blog/posts/2026/the-manifesto-for-software-engineering-after-human-comprehension/">总序&lt;/a>提出，AI 扩大的是生成—验证落差。本篇向后退一步：传统软件工程究竟追求过哪一种可理解性？&lt;/p>
&lt;h2 id="结构化编程限制了理解负担">结构化编程限制了理解负担&lt;/h2>
&lt;p>Dijkstra 在 1968 年反对不受约束的跳转，重点并不是让程序像散文一样“好看”，而是让程序运行过程与人的概念结构更容易对应。顺序、选择和循环等受控结构，缩小了工程师必须同时追踪的程序状态。&lt;/p>
&lt;p>这揭示了软件工程一个持续至今的目标：不是把全部复杂性消灭，而是让局部推理有边界。一个函数内部的控制流更清楚，工程师就能在有限范围内判断前置条件、后置结果和可能副作用。&lt;/p>
&lt;p>但局部可推理并不等于全局可穷尽。即使每个函数都写得清晰，并发、配置、数据和组件组合仍可能产生难以预料的系统行为。结构化编程提高了可理解性，没有建立“一个心智可以完整覆盖整个系统”的保证。&lt;/p>
&lt;h2 id="信息隐藏主动制造了可用的黑箱">信息隐藏主动制造了可用的黑箱&lt;/h2>
&lt;p>Parnas 在 1972 年讨论模块分解时，把信息隐藏作为重要标准：模块应隐藏可能变化的设计决定，通过稳定接口降低其他模块对内部知识的依赖。&lt;/p>
&lt;p>这意味着成熟的软件工程从一开始就在主动制造局部黑箱。调用数据库接口的业务工程师不必理解查询优化器的全部实现，应用开发者也不必掌握操作系统调度器的每条路径。只要契约足够稳定、失败模式得到说明，隐藏细节反而提高了整个组织的可操作性。&lt;/p>
&lt;p>所以，抽象不是对透明性的背叛，而是对注意力的分配。它把“所有人都要理解一切”改成：&lt;/p>
&lt;ul>
&lt;li>模块维护者理解内部关键机制；&lt;/li>
&lt;li>使用者理解接口、保证和失败方式；&lt;/li>
&lt;li>组织保存跨模块决策与运行证据；&lt;/li>
&lt;li>高风险部分接受更深的独立检查。&lt;/li>
&lt;/ul>
&lt;p>这种分层理解比“某位架构师完整掌握系统”更接近大型软件的真实工作方式。&lt;/p>
&lt;h2 id="五个经常被混用的问题">五个经常被混用的问题&lt;/h2>
&lt;p>讨论白箱与黑箱时，至少应区分五个维度。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>维度&lt;/th>
 &lt;th>核心问题&lt;/th>
 &lt;th>反例&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>可见性&lt;/td>
 &lt;td>能否取得源码、配置和模型信息？&lt;/td>
 &lt;td>开源代码全部可见，但规模巨大&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>可理解性&lt;/td>
 &lt;td>目标读者能否建立足够心智模型？&lt;/td>
 &lt;td>代码可读，却缺少领域背景&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>可追溯性&lt;/td>
 &lt;td>变更、依赖与决策能否回溯？&lt;/td>
 &lt;td>Git 有提交，但需求理由未记录&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>可预测性&lt;/td>
 &lt;td>给定条件下能否预期行为？&lt;/td>
 &lt;td>并发竞态使结果依赖时序&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>可控制性&lt;/td>
 &lt;td>能否限制、发现并纠正越界行为？&lt;/td>
 &lt;td>系统易懂，却没有权限隔离和恢复机制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>源码可见并不保证理解，理解也不保证预测。反过来，一个内部实现不公开的组件，也可能通过严格契约、充分测试和可替换设计而保持可控制。白箱与黑箱不是单一开关，而是一组不同性质。&lt;/p>
&lt;h2 id="代码审查和版本控制真正提供什么">代码审查和版本控制真正提供什么&lt;/h2>
&lt;p>代码审查并不要求审查者重新构造整个系统。它更现实的作用是检查一组有限变更：意图是否合理，局部实现是否符合约定，风险是否被识别，测试是否与主张匹配。审查质量取决于变更大小、上下文、专业知识和工具支持，而不是代码出自人还是模型。&lt;/p>
&lt;p>版本控制同样不保证变更“可以被解释”。它可靠地记录文本差异、作者身份和时间，却未必保存为什么选择这种设计、放弃了哪些方案，或者上线后出现了什么新事实。Diff 是证据的一部分，不是完整的因果叙事。&lt;/p>
&lt;p>AI 生成代码的特殊问题，也因此不只是“作者不再亲手敲字”。如果生成内容保持小批量，有明确需求，经过有效审查并附带测试，传统机制仍然工作。如果一次生成跨越大量模块，意图只存在于短暂对话中，审查者又主要确认“能运行”，那么可见的源码仍会伴随严重的理解缺口。&lt;/p>
&lt;h2 id="大型软件早已依靠不完全理解运行">大型软件早已依靠不完全理解运行&lt;/h2>
&lt;p>Brooks 在《没有银弹》中把复杂性、顺应性、可变性和不可见性视为软件的固有困难。这个判断早于生成式 AI 数十年。今天的依赖网络、云服务和持续部署放大了这些困难，但没有创造它们。&lt;/p>
&lt;p>因此，传统软件工程更准确的历史不是：&lt;/p>
&lt;blockquote>
&lt;p>人类曾理解完整系统，后来 AI 夺走了透明性。&lt;/p>&lt;/blockquote>
&lt;p>而是：&lt;/p>
&lt;blockquote>
&lt;p>人类长期通过抽象、专业分工和多种证据，在无法完整理解的系统中获得局部可信度；AI 让实现增长得更快，也可能打乱原本维持这种可信度的节奏。&lt;/p>&lt;/blockquote>
&lt;p>这个改写很重要。若误以为过去依靠“逐行理解一切”，我们就会把既有工程实践低估为过时方法；若误以为未来只需看测试结果，又会丢掉解释、审查和调试仍然提供的独特价值。&lt;/p>
&lt;h2 id="白箱假设应当怎样保留">“白箱假设”应当怎样保留&lt;/h2>
&lt;p>白箱假设仍可以作为一个有限的规范理想：&lt;/p>
&lt;ol>
&lt;li>关键行为不应故意逃避检查。&lt;/li>
&lt;li>重要变更应留下可追溯的意图与证据。&lt;/li>
&lt;li>负责维护的人应能解释与其职责相称的系统范围。&lt;/li>
&lt;li>在安全、财务、身份和权限等高风险区域，应提高透明性和独立验证要求。&lt;/li>
&lt;/ol>
&lt;p>它不要求每个人理解每一行，也不要求所有组件公开全部内部机制。真正需要防止的不是抽象，而是“没有人知道、没有证据支持、也没有恢复路径”的三重空白。&lt;/p>
&lt;p>AI 时代不是从白箱走向黑箱，而是迫使我们承认：软件一直由不同透明度的区域组成。问题不在于是否允许黑箱，而在于谁可以把什么当作黑箱，以及他必须用什么证据来换取这种信任。&lt;/p>
&lt;h2 id="延伸阅读与参考资料">延伸阅读与参考资料&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://ir.cwi.nl/pub/28228">E. W. Dijkstra：Go To Statement Considered Harmful&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://doi.org/10.1145/361598.361623">D. L. Parnas：On the Criteria To Be Used in Decomposing Systems into Modules&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.cs.unc.edu/techreports/86-020.pdf">Frederick P. Brooks Jr.：No Silver Bullet—Essence and Accidents of Software Engineering&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>&lt;em>上一篇：&lt;a href="https://lingming.blog/posts/2026/the-manifesto-for-software-engineering-after-human-comprehension/">《黑箱转向（总序）：当生成速度超过验证速度》&lt;/a>；下一篇：&lt;a href="https://lingming.blog/posts/2026/the-generative-inflection-point/">《黑箱转向（二）：生成—验证落差，真正的拐点》&lt;/a>。&lt;/em>&lt;/p></content:encoded></item></channel></rss>