简介:分析哲学百年演进,始终未能跳出“用语言解释语言”的循环。本文将这一洞见映射到软件工程:规约、文档、测试与大模型评估同样面临自指困境,并结合可执行规约、可观测性与制度化证伪等实践,系统给出以“执行与真实反馈”为外部锚点的破局路径与自检清单。
二十世纪初,哲学家们发起过一场雄心勃勃的“基础设施工程”:为全部知识寻找坚实基底。逻辑主义者试图把数学还原为逻辑公理,让知识像大厦一样从地基逐层垒起;早期维特根斯坦提出“语言图像论”,认为命题是世界事实的镜像,只要语言结构足够清晰,思想便无歧义;后期他亲手推翻这一图景,承认词句的意义取决于具体“语言游戏”中的用法;波普尔则以“可证伪性”重划科学边界;蒯因更进一步,指出任何陈述都无法被孤立地检验,知识是作为一个整体面对经验之流的。
这一百年的摸索留下一个耐人寻味的结论:无论工具是逻辑、图像还是游戏,解释符号的始终是符号,校验命题的始终是命题。牢笼并非某位思想家的失误,而是方法论的结构性缺陷——缺少一个语言之外的外部锚点。
软件工程师对此应当似曾相识。我们大概是人类历史上最依赖“语言”的工程群体:编程语言、建模语言、查询语言、配置语言、自然语言文档,以及如今的大模型提示词。同样,我们也大量生产“元语言制品”:用文档解释代码、用测试验证代码、用评分标准衡量模型输出。如果这些制品只在语言层面互相印证,而不接入任何外部锚点,我们就会重演那段哲学弯路——内部自洽,外部失真。
形式化方法承诺把需求翻译成无歧义的数学对象,例如用时序性质表达“每次请求最终都会得到响应”。这确实消除了自然语言的模糊,但请注意:规约本身仍是符号,并没有因此自动获得与真实世界的对应关系。业务方头脑中的期望、合规约束、用户习惯,都不在形式系统内部。规约只是把“翻译误差”从一种语言搬进了另一种语言。当实现与规约一致、而规约与意图不一致时,形式化验证反而会制造虚假信心——这正是“用语言解释语言”的工程版本。
注释与代码天然存在漂移风险:代码在演进,注释停在昨天。文档腐化(doc rot)是每个维护过长生命周期系统的人的切肤之痛。“文档即代码”等实践通过把文档与代码纳入同一变更流程来缓解漂移,但同步机制本身仍由人书写、由语言描述。漂移只能被压缩,无法被根除——因为约束双方都在同一侧符号世界里。结论是:文档是“声明”而非“证据”,它需要被行为持续校验。
测试也是程序。它检查代码,但它的期望值由人写入。如果同一种业务误解同时被编码进了实现与测试,测试套件会全绿通过,而系统在做错误的事——典型的如边界条件理解偏差被原样复刻到用例中。变异测试可以检验“测试对代码变更是否敏感”,性质测试可以把期望提升为不变式,但它们依旧运行在同一个形式宇宙中:不变式仍是一条符号化假设。测试提升的是内部一致性,而一致性不等于与事实相符。
大模型以语言为产出物,而常见评估手段同样是语言性的:评分细则、参考答案、模型互评。模型给模型的语言打分,构成一个封闭的语言回路。风险随之而来:评估基准与训练数据相互渗透,指标一旦成为目标便开始失真(古德哈特定律的又一次应验),自查自评还会放大系统性偏差。这与分析哲学的处境同构,只是规模更大、速度更快。
其一,基础主义的教训。 不存在完全自证的基座。工程上,类型系统、静态检查与形式验证可以把“可信基座”压缩到很小的核心(编译器、运行时、公理集),但永远不可能压缩为零。成熟的做法不是追逐绝对确定性,而是显式管理残余信任:明确哪些组件被信任、凭什么被信任、以及一旦它失效,系统行为的边界在哪里。
其二,意义即使用的教训。 后期维特根斯坦指出词义在于使用。一个接口的语义不取决于注释写得多漂亮,而取决于它在真实调用语境中的行为。当文档与实际调用行为冲突时,优先修文档与契约,而不是指责使用者“用错了”。
其三,可证伪的教训。 不可证伪的质量声明没有工程意义。“系统很稳定”是修辞;“P99 延迟在任意五分钟窗口内低于三百毫秒”才是断言——因为它可以被遥测数据反驳。全绿仪表盘只说明“尚未发现反例”,不构成正确性证明。
其四,整体论的教训。 单元正确性不能自动组合成系统正确性。模块交互处——序列化边界、时序、并发、容量——才是缺陷高发区。应把投入重心从单元覆盖率数字,转向结构化的集成验证、真实流量回放与全链路压测。
此外,计算理论给出了更硬的边界:停机问题意味着不存在能对所有程序(包括检验者自身)做完全判定的通用程序,对角线式论证同样约束着“完全自校验系统”的想象。这不是工程能力问题,而是逻辑边界,因此任何验证体系都必须引入外部视角或外部假设。
把关键假设写成不变式,然后交给随机化的执行去反复“反驳”它(示意伪代码):
# 不变式(语言假设):购物车总价 == 各商品单价×数量之和# 执行(外部检验):随机生成输入,批量尝试反驳for trial in range(10000):cart = random_cart()assert checkout.total(cart) == sum(i.price * i.qty for i in cart)
不变式仍是语言,但执行让它暴露于海量具体情形之下——反例会以崩溃的形式“从世界那一头传来”。
指标、日志、链路追踪不是“生产环境的文档”,而是系统自身的证词。示意配置如下:
# 质量声明(可证伪)slo:indicator: checkout_latency_p99_msbudget: 300window: 5mon_breach: page_oncall # 反例出现时,声明被证伪,责任人被唤醒
阈值应来自用户可感知的体验,而非团队内部便利;告警则是制度化的“证伪触发器”。
灰度发布是最小化爆炸半径的证伪实验——小流量先行,假设被反驳即可快速回滚。混沌工程主动注入故障,用以反驳“冗余有效、降级可用”这类平日无法检验的假设。无责复盘则把事故这个最珍贵的反例,回流为规约、架构与心智模型的修订。
文档应从工单与使用遥测中定期回修;产品判断应以受控实验与真实用户行为为准。对生成式应用,评估应锚定在真实任务分布上,保留从未参与调优的留出集,用抽样人工校验为模型互评分级提供外部基准,并持续监控线上产出质量。
分析哲学缺的是一台解释器:语言没有运行时,命题无法执行,于是校验命题的只能是另一批命题。软件工程幸运得多——编译器与运行时把符号连接到机器与真实用户,形成一条从语言通往世界的因果链。越狱的机会一直在那里,条件只有一个:拒绝让语言制品互相盖章,坚持让执行、遥测与真实用户反馈拥有最终裁决权。下次写下一行注释、一条断言或一份评分标准时,不妨自问:这句话的锚点在哪里?如果答案是“另一句话”,那就还差一步。