尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

编程作为理论构建:从Naur与Ryle的分歧看软件维护的核心难题

编程作为理论构建:从Naur与Ryle的分歧看软件维护的核心难题 接手过一个别人留下的“好工程”代码分层清晰单测覆盖率很高注释写得到位技术文档也整理得干干净净。按理说你只需要按文档走查一遍就能放心大胆地修改。实际情况是你对着一个看起来非常合理的函数犹豫了半小时最后还是不敢动那一行。你缺的不是文档不是代码也不是注释。你缺的是写代码那个人脑子里那套“它为什么会变成这样”的理解。2005 年图灵奖得主 Peter Naur 很早就看穿了这件事他在著名论文《Programming as Theory Building》里给出判断编程的核心不是产出代码和文档而是构建一种关于问题域和程序的“理论”。代码只是理论的投影理论本身存在于人的头脑中。这个判断很锐利但有个容易被忽略的坑Naur 试图用哲学家 Gilbert Ryle 在《心的概念》里的观点来支撑自己结果在思想迁移中出现了一个很有意思的偏差。Ryle 用尽力气要消灭“脑子里的神秘实体”Naur 却把“理论”重新放回了程序员的头脑里。可以说他借了一位专门打击神秘实体的哲学家的武器最后却造出了一个新的“机器中的幽灵”。这篇文章会把 Naur 和 Ryle 的核心观点拆开讲清楚指出 Naur 在哪些地方用对了、在哪些地方跑偏了最后落到今天的软件工程实践既然“理论”不能只活在个人脑中我们该用哪些手段让它变得可共享、可检验、可演进。话题听起来偏哲学但它直接影响你接手旧系统、做架构决策、带新人这三类日常工作的效率。1. 为什么一篇 1972 年的论文今天还在被人反复讨论Peter Naur 不是一位普通的软件理论家。他是 ALGOL 60 语言的主要设计者之一常见的语法描述范式 BNF全称 Backus-Naur Form其中 Naur 指的就是他。2005 年ACM 把图灵奖授予他表彰他在编程语言定义与软件工程实践方面的贡献。一个能把语言形式化做到如此精确的人却写出了一篇强调“不可完全形式化”的论文这种反差本身就很值得琢磨。在 Naur 提出“编程作为理论构建”之前主流软件工程管理思路非常像制造业需求是输入代码是产品文档是交付物测试是质检。只要你把文档写全、代码写完项目就算“生产”完成。Naur 在这篇论文里明确反对这种视角。他认为程序并不是“写在纸上的代码”也不只是“跑在机器上的行为”。一个程序的存在首先依赖一系列人脑中的理论。这个理论包括对问题域的理解、对用户需求背后原因的判断、对设计方案取舍的把握以及对未来变化的预期。代码、文档、测试只是这个理论的外化形式而且是不完全的外化。Naur 由此推出一个著名的结论软件维护的困难主要不是修改代码的物理动作而是维护者必须先重建当初那个“理论”。丢失理论比丢失代码更可怕。这也是为什么很多时候文档健在、代码可读但团队仍然痛苦不堪——理论已经死亡剩下的只是一具可以运行的躯壳。这篇论文之所以几十年后仍被反复引用是因为它解释了一个真实的工程现象一个优秀的项目并不是文档最全的项目而是团队内“理论”最统一、最可再生的项目。今天很多话题比如架构决策记录、结对编程、领域驱动设计里的通用语言、代码评审其实都可以被理解为一种“理论治理”手段。理解了 Naur 的观点再看这些实践会有完全不同的层次感。2. Ryle 的《心的概念》到底在反对什么要想搞清楚 Naur 哪里跑偏了得先认识 Gilbert Ryle 和他 1949 年出版的《心的概念》。这本书是 20 世纪英国分析哲学的重要作品它面对的主要靶子是笛卡尔式的心身二元论。笛卡尔式的图景普通人很熟悉身体是一台精密机器心灵是藏在机器里面的操作员这台操作员在幕后“看着”外部输入做出判断然后指挥身体行动。Ryle 给这个图景起了一个著名的外号叫做“机器中的幽灵”。他认为这种图景看起来有道理实际上是一种范畴错误心智现象并不属于某种“内部实体”的范畴却被错误地归类到了实体范畴里。他举过很多生动的例子。比如一个懂礼貌的人你观察他的行为会发现他举止得体、说话得体、做事有分寸。他不会在脑子里先调用一套“懂礼貌理论”再逐条演算然后才行动。所谓礼貌更像是一种行为倾向在各类场合下自然做出恰当反应。Ryle 把这个思路扩展到所有智能活动上。一个优秀的棋手能够在一秒内做出好棋这并不等于他在脑中完成了一次大型命题推理。会下棋是“知道如何做”的能力而不是“知道一堆棋理命题”的知识。这里就引出了 Ryle 最著名的一组区分knowing-that 和 knowing-how。前者是命题性知识比如“北京是中国的首都”“冒泡排序最坏时间复杂度是 O(n²)”。后者是能力性知识比如“会骑自行车”“会调试并发问题”。Ryle 的核心主张是knowing-how 不能化约为 knowing-that。一个人可以知道所有关于骑自行车的物理公式仍然不会骑车反过来一个人骑车技术高超却不一定能讲清楚任何一条公式。更重要的是Ryle 坚持认为像智力、技能、倾向这类概念应该用公共可见的行为和倾向来解释而不是靠假设一个私密的内部剧场。剧场里的戏外人看不见无法检验也无法解释任何行为。Ryle 的分析把“心智”从内部剧场拉回到公共生活的行为习惯中这是理解《心的概念》最关键的线索。3. Naur 从《心的概念》里借了什么Naur 在构建“编程作为理论构建”的论点时最需要解释的问题是为什么软件维护不能靠文档传递如果软件的全部知识都被记录在文档和代码里那么换一个人维护应该不成问题。但实际经验告诉我们不是这样。Naur 从 Ryle 的区分里找到了支持。他认为程序员在开发过程中形成的理解主要是关于如何在各种情境中妥善行动的“知道如何”而不只是能够写下来的“知道什么”。一个资深维护者接到新需求他能判断该去改哪一层、哪段代码容易埋雷、哪项改动会牵动哪些隐藏假设。这些判断很难全部转化成文档。Ryle 说得好knowing-how 不等于 knowing-that。那么软件维护中大量需要的“手感”自然也不能靠交付一摞文档来传递。顺着这个逻辑Naur 把“程序”重新定义了程序的核心是构建者脑中的理论构建者对程序行为、问题域、设计约束的整体理解。这个理论部分可以表达但总有剩余部分很难表达。所以当一个项目更换维护者时必须有人通过讨论、阅读代码、修改实验来“重新构建”这个理论。代码还在理论可能已经变了。从表面看这套说法借用了 Ryle 对命题知识和能力知识的区分用来反叛“软件工程 文档管理”的机械化视角方向完全正确也确实比很多项目管理理论更贴近真实开发。这也是为什么后来研究软件工程思想的学者普遍把 Naur 当作一位有哲学敏感度的先驱。但在哲学层面这个借用存在一个隐含问题Ryle 用 knowing-how 是为了取消“头脑中的内部实体”而 Naur 却把“理论”描述成一种存在于程序员头脑中的、相对稳定的知识结构。哪怕 Naur 一再强调理论不只是命题集合它仍然获得了类似“内部拥有物”的地位。这等于把 Ryle 刚刚赶走的幽灵又从后门请了回来。4. Naur 到底在哪里跑偏了如果说 Naur 只是借用一个哲学概念来表达“编程有隐性知识”那完全可以接受。但他把“理论”这个词用得太重了以至于在认识论层面留下了四个明显的裂缝。第一从“能力”滑向了“实体”。Ryle 认为智力行动并不需要一个隐藏的内部理论来驱动。一个人能骑车不是因为他脑中有一套完善的骑行理论而是因为他获得了某种行为倾向。Naur 却在论文里把“理论”当成程序员头脑中承载的东西认为项目能否存续取决于是否有人持续保有这个理论。从 Ryle 的立场看这是把一种公开可观察的能力重新解释成了一种私密的占有物。你以为你在解释开发者的能力实际上你在假设其中一个“幽灵理论”。第二knowing-how 和 knowing-that 被含混地揉在一起。Naur 的“理论”有时听起来像命题知识对系统架构的解释、对需求背景的陈述、对设计决策的论证。有时又像能力知识会定位 bug、会权衡取舍、会修复故障。这两种知识在 Ryle 那里是严格区分的而在 Naur 的论述中边界模糊。模糊的好处是能让“理论”看起来既包含显性知识又包含隐性知识坏处是你根本无法判断当一个开发者离开时团队失去的到底是命题性知识还是能力性知识这两种损失对应的补救手段完全不同。第三重新落入了私人知识的困境。Ryle 批判笛卡尔式的“内部剧场”正是因为内部状态无法公开检验。Naur 说理论主要存在于个人脑中有一部分无法完整表达。这实际上制造了一个不可比较、不可验证的认知黑箱。两个开发者都声称自己“理解了系统理论”如何证明如果理论不能被外部检验它就和幽灵一样只能靠信仰。这里的反讽很强烈一个用来批评“软件工程管理像制造业”的理论自己也带上了浓厚的不可知论色彩。第四理论的社会性被低估了。Ryle 的分析里心智概念的意义来自公共语言和行为实践。一个人说明白了一个问题不是“他脑子里跑完了完整推理”而是他能通过语言、行动、回应质疑在公共场合证明自己理解。Naur 虽然讨论过团队中的理论建设但他最核心的图景仍然是“程序员个体头脑中的理论”。当他描述项目危机时典型表述是“那个拥有理论的人走了”。这会导致一个错误的工程暗示只要留住这个人理论就在。实际上理论完全可以存在于团队讨论的记录、测试断言的语义、代码评审中的共识、以及反复协作形成的行为规范里。过度个人化是 Naur 对 Ryle 最重要的误读。5. 重新审视“程序即理论”从个人脑内搬到公共空间如果接受上面四个批评是不是意味着 Naur 的整篇论文不可信恰恰相反他的核心洞察仍然成立软件的本质不是静态产物而是持续演进的理解。真正需要纠正的是“理论只能存在于个人脑中”这个隐含假设。更稳妥的判断是理论可以、也应该被外部化到团队可共享、可检验的载体中。外部化不等于文档化。文档只是外部化的一种粗糙形式。真正有效的理论外部化是把隐性的理解和判断转化成团队能够反复讨论、验证和修改的“公共物件”。下面这些现代工程实践都可以看作是这一思路的体现。测试是最常见的一种理论外部化。每一条单元测试本质上都是对系统行为的一项断言。它把你对“系统应该怎样”的理解转成了机器可执行的检查。当你写测试时你不只是在防回归你是在把理论碎片固化下来。这也是为什么测试描述性的命名、针对业务规则而非实现细节去写断言会比单纯堆覆盖率重要得多。架构决策记录ADR是另一种外部化手段。每当团队做出一个关键设计选择就把当时的背景、选择、理由和后果记录下来。它不是给管理者看的报告而是给未来维护者的“理论口述”。后续有人想改架构时可以沿着 ADR 看到当初的权衡而不是对着代码猜测。代码评审和结对编程则提供了理论传播的实时通道。两个人同时面对一段代码一人能指出“这里如果改成缓存机制会引发一致性问题”这正是在传递无法写入文档的 know-how。这种能力不是说出来的是在对话和挑战中生成的。领域驱动设计中的通用语言也很有价值。团队统一命名核心业务概念让代码、文档、白板讨论用的是同一套词汇它能把分散在不同人脑中的局部理论逐渐收敛成一个共享的语义基础。与之相对最应该警惕的是那种“某个老程序员是系统唯一理论容器”的状态。一旦组织把理论寄托在个人身上个人就会成为系统的单点故障而且是比服务器单点更隐蔽的故障。服务器挂了能重启人走了理论很难倒回来。6. 落地实践把理论变成团队可以维护的东西理解了前面的道理你会明白一个基本结论与其强调“理论不可说”不如设计流程和工具把理论尽可能多地“钓”出来。这里给出三组可直接使用的落地手段。6.1 用架构决策记录沉淀关键理论ADR 是一个把隐性决策显性化的低成本工具。它不需要复杂的平台一个 Markdown 目录就够。建议在仓库中维护docs/adr/目录每个文件记录一个决策。模板可以这样开始# 0002. 订单模块使用事件驱动方式处理超时关闭 - 日期2025-01-15 - 状态已接受 ## 背景 订单创建后需要在一定时间内完成支付否则自动关闭。 最初实现方式是定时任务扫描订单表但订单量增长后 扫描延迟和数据库压力开始成为问题。 ## 决策 订单创建时发送一条延迟消息消费者在到期时间后收到 消息并执行关闭操作。 ## 后果 - 正面关闭时延可控不再依赖全表扫描 - 负面需要处理消息丢失、重复消费 - 权衡配合消息幂等与对账任务保障最终一致性 - 关联ADR-0001 中关于消息中间件选型的决策这个文件的价值不在于格式而在于它强迫团队回答三个问题当时面对什么背景为什么这样选代价是什么下次有人提出要改回定时任务可以先看这份记录而不是重新吵一遍。6.2 用交接清单完成“理论对话”新人接手核心模块时最有效的学习不是看文档而是和有经验的同事进行一轮“理论对话”。把这种对话固定成可执行的流程能够避免交接变成“你自己看代码”。下面这个 YAML 清单可以作为团队工作坊的参考# docs/onboarding/theory-handover.yaml handover_session: duration_minutes: 90 roles: - 现负责人讲解 - 新接手人提问 - 记录员沉淀结论 agenda: - step: 画核心模型墙 time_minutes: 30 action: 新接手人画出系统最核心的 5 个概念及关系 check: 能解释每个概念在代码中的入口 - step: 走查关键链路 time_minutes: 30 action: 从请求入口到数据落库完整走一遍 check: 标记出“你敢改”和“你不敢改”的位置 - step: 反向追问 time_minutes: 30 action: 现负责人回答“哪些改动会牵一发动全身” check: 记录至少 5 条未写入文档的约束 output: - 更新一份设计说明 - 补充或修订 ADR - 新增 TODO 与风险列表这个清单并不神秘它只是在对抗一种常见情况交接双方各自抱着自己的理论谁也不确定对方的理论是否匹配。6.3 用 Git 历史重建“理论演进链”接手旧模块时与其只看当前代码不如沿着提交历史理解它今天的形态。这不只是看git log的提交信息更要把文件、函数、ADR 放在时间轴上对比。常用命令如下# 查看某个目录的演进脉络 git log --oneline --follow -- docs/adr/ # 查看某个关键函数的历次修改 git blame -L 10,40 src/main/java/com/example/order/OrderService.java第一行命令让你知道架构决策是何时、因为什么原因出现的。第二行命令让你看到当前这段代码是哪些提交一层层叠出来的。看历史不是考古而是帮你恢复代码产生时的约束条件当时为什么这样做、后来为什么变成那样。这些信息很难出现在最终代码里却是理解系统理论的最快路径。7. 常见误区与排查思路很多人第一次接触“编程即理论构建”时容易走向两个极端要么觉得这是玄学要么觉得这是“文档无用论”。这里把最常见的六个误区整理成表格方便对照。误区表面看起来实际风险建议文档齐全 理论可交接看文档就能改代码文档表达不出所有 know-how交接仍失败文档之外增加对话、结对、走查注释越详细越好每行都解释了意图注释与代码难以同步反而制造噪音注释写“为什么”不写“是什么”测试全绿 理解正确断言通过说明行为正确可能测试与代码共享同一错误假设让新人解释每条测试对应的业务规则资深程序员独立承载理论系统里有“什么都知道的人”个人成为隐性单点离开即灾难通过 ADR、评审、结对把理论分散到团队架构评审只是看 PPT会议有汇报有结论真正的约束和权衡不会出现在 PPT 上评审前先看代码路径评审中直接对设计决策提问代码自动生成 告别理论模型能写代码就不需要人理解理论谬误会稳定复制到生成结果中生成结果要有人负责验证、审查、解释排查理论问题时建议按这个顺序走先看你手头有没有 ADR 或类似历史记录没有就查 Git 历史再找团队成员做一次 30 分钟的“理论对谈”重点问三件事这个模块为什么存在它不这么做会怎样哪些改动是危险的最后再回头读代码你会发现很多费解之处都有了答案。8. 最佳实践与工程建议如果要把这一整套思想落到团队日常下面几条建议值得优先执行。第一把“核心理论”视为一等公民资产。项目例会不要只排需求和进度也要排“理论维护”这个迭代里有哪些关键决策需要写 ADR哪些模块换了维护者需要安排交接对话哪些代码区域代码与实现已经明显偏离理论不及时维护系统会慢慢变成谁都不敢动的雷区。第二用测试和契约作为理论的执行层。一条能跑通的测试比十页 Excel 文档更能维护理论。特别是团队用事件驱动、微服务架构时把跨模块的交互规则写成契约测试可以让“系统应该怎样协作”的理论从人脑中释放到 CI 里。第三代码可读性优先于“炫技”。可读代码让人更容易通过阅读代码重建理论。这里要强调可读不是变量名长短的问题而是逻辑组织是否符合领域模型。一个函数读起来像一段有逻辑的叙述维护者就能快速在脑中重建原始理论反之代码全是花哨技巧理论很快丢失。第四安全边界与权限意识同样适用于知识文档。ADR、交接清单、架构文档中可能包含系统内部细节和业务流程描述。在开源的仓库或对外分享的平台上放这类文档之前要检查是否包含敏感信息、内部地址、密钥占位符。对生产环境相关的行为坚持最小权限和先测试后变更的原则这和理论构建并不矛盾理论越清晰越能在限制条件下安全演进。第五主动做“理论备份”。每半年可以问自己一个问题如果我们团队的“核心理论持有者”下个月无法参与项目大家从当前文档、代码、测试和协作记录里能恢复出多少理解这个问题的答案基本就是团队知识沉淀的真实水平。答案不理想说明该开始写 ADR、做交接对话、开展结对编程了。9. 总结与后续学习方向回到标题本身。Naur 在《编程作为理论构建》里借用了 Ryle 的《心的概念》准确的用法是用 knowing-how 与 knowing-that 的区分说明软件维护无法靠文档完全传递。但当他进一步把“理论”当作存在于程序员头脑中的知识结构时他就在认识论层面偏离了 Ryle——Ryle 的矛头正是指向这种“内部幽灵”式的解释。所以“Naur 错的地方”不是“编程即理论”这个结论而是他为结论配置的哲学底座出现了裂痕。对我们今天做技术的启示其实更实用理论不该被锁在任何一个人的脑子里它应该通过测试、ADR、代码评审、结对编程、交接对话这些手段逐步变成团队可以共同维护的公共知识。代码会腐烂文档会过时真正有韧性的项目是理论能在团队中快速重现、持续演进的项目。如果你对这个方向感兴趣下一步可以先读 Naur 的《Programming as Theory Building》原文再回到 Ryle 的《心的概念》重点看开篇关于“机器中的幽灵”和“知道如何与知道什么”的章节。想继续延伸的话Michael Polanyi 的默会知识理论、软件工程里的团队认知研究、甚至知识管理领域关于“实践社群”的讨论都是很好的衔接点。读懂它们之后你可能再也不会用“写代码的人走了就完了”这种话概括一个系统的命运。
返回列表