在大型语言模型(LLM)重塑开发范式的当下,这篇文章对于理解“开发者角色的本质”提供了极为珍贵的视角。
然而,当我研读原文时,确实遇到了不小的挑战。Naur 的文章充满了深刻的论述思辨,辅以结构复杂、层层嵌套的英文长难句。
这种高度抽象和严谨的学术表达,对于初次接触的读者(包括我自己)来说,理解门槛着实不低。
但正是文章价值与可读性之间的张力,促使我着手进行这次翻译和改写。
这篇译文是我在力求忠实于原意的基础上,再结合过去的编程经验对表达方式进行了较大幅度重构的成果,试图在原文的学术严谨性与中文读者的阅读习惯之间架起一座桥梁。
能力所限,疏漏难免。 面对如此深邃的思想,我的解读和转述必然有其局限。
欢迎各位读者指正译文中的任何错漏、不准确或可优化之处。
希望这篇《编程即理论构建》,能帮助大家更轻松地触及 Naur 的智慧,并激发在 AI 浪潮中对开发者核心价值与独特能力的深入思考。
Peter Naur 在 1985 年的经典文章《编程即理论构建》中提出:程序并非其源代码文字本身。
程序是一种存在于开发者脑海中的心理构建(他称之为“理论”)。
源代码只是程序的书面记录,这种记录本身有信息缺失,所以不可能单靠代码还原出真正的程序。
文章提出:编程应当是一种活动,程序员通过它来掌握对实际问题的深刻理解,即“理论”。
这种观点和主流看法不同:很多人认为编程就是产出程序代码及其他某些文本的过程。
本文观点的部分依据,来源于对程序和开发团队实际情况的观察,特别是当程序出现意外或错误时,以及需要修改程序时,他们的具体表现。
“编程即代码产出”的观点很难解释这些实际情况,这就证明它是有问题的。
本文的深层动机源于一个坚定信念:准确理解编程的本质至关重要。
如果理解不当,我们就会误判实践中遇到的困难,尝试解决时反而会引发冲突与挫败。(译注:南辕北辙)
随后将阐释一种编程本质的理论:“编程即理论构建”。
后续章节将深入探讨“编程即理论构建”的关键意义。
我会用“编程”这个词,指代设计并实现程序解决方案的完整过程。
我关注的是:如何把现实世界活动的关键部分,转化为计算机程序能够处理的符号操作。
由此,编程活动必须包含持续调整:当现实世界活动变化时,程序也必须随之修改。
我想要阐明的核心观点之一是:编程的本质必须是程序员的知识积累,这些知识必须是程序员直接掌握的,所有文档都只是辅助的次要产物。
为后面几节详细阐述该观点作铺垫,本节剩余内容将描述我处理大型程序的实际经历,随着对这些问题的深入思考,这些经历显得愈发重要。
这些经验要么来自我的亲身经历,要么来自直接参与相关活动的一线人员反馈。
该编译器由 A 团队为语言 L 开发,并在计算机 X 上运行良好。
另一个团队 B 接到任务,要为计算机 Y 开发新编译器,新语言是 L 的小幅扩展版 L + M。
B 团队认为 A 团队开发的 L 语言编译器是理想起点,于是与 A 团队签约获得全套支持:包括带注释的源码、大量设计文档及人工指导。
这项合作方案高效落实,最终 B 团队如愿研制出了他们想要的编译器。
在本案例中,最关键的事实是:当团队 B 为语言 L 添加 M 扩展功能时,来自团队 A 的真人指导起到了决定性作用。
在设计阶段,B 团队就扩展功能的实现方案提出建议,并提交给 A 团队审核。
结果发现:B 团队提出的方案多次忽略现有编译器的内置特性(这些特性不仅本身存在,文档也详细说明过),反而通过打补丁的方式破坏其核心结构与简洁性。
A 团队成员总能立刻识别出问题,并提出巧妙解决方案,这些方案完全利用了现有结构优势。
即便有完整的代码和文档,实力强劲的 B 团队也无法掌握原始编译器的设计精髓,而这些核心理论在 A 团队成员心中早已不言自明。
此后数年里,该编译器移交给了同一机构的其它程序员维护,且未再获得 A 团队的指导。
大约 10 年后,A 团队成员在后续修改中发现:原始编译器的强大架构核心虽然可见,却被各种混乱的代码堆砌破坏得面目全非,完全丧失了所有结构性优势。
由此可见,程序代码和文档根本承载不了最核心的设计理念。
案例 2 涉及一套用于工业生产监控的大型实时系统的安装部署和故障排查。
该系统的制造商负责销售,每次交付时都会根据具体使用环境单独定制传感器和显示设备。
这类系统的核心经验在于:安装和故障排查团队的角色与工作方式。
具体而言,这些程序员从系统设计阶段就全职深度参与,持续投入多年时间。
其次,当进行故障诊断时,这些程序员几乎完全依赖自身对系统的实战经验和带注释的源代码,根本无法想得到其他对他们有用的补充文档类型。
第三,负责具体系统部署的客户团队虽然拿到了厂商的完整文档和使用指南,却常因理解不足而卡壳,但厂商的安装调试程序员每次都能轻松解决问题。
核心结论是:对于某些大型程序而言,其持续调整、修改和纠错能力,本质上依赖于特定程序员团队的专有知识,这些程序员必须与程序保持长期密切接触。
既然编程的核心是构建程序员的知识,那么接下来就需要具体说明这种知识的本质。
本文研究认为,应当将程序员的知识视作一种理论,一种按照 Ryle [11] 定义的知识论意义上的理论。
简而言之,掌握这种理论的人不仅懂得如何操作,还能对自己的操作进行解释、论证并解答相关疑问。
值得注意:Ryle 的理论概念,在 K. Popper [10] 的理论框架中属于客观知识,是具有公认学术根基的哲学立场。
Ryle 在著作 [11] 中提出:理论是区分 Intellectual 和 Intelligent 的本质特征,并解释了前者如何超越后者。
(译注:这两个词在中文里都常与“聪明”、”智能“挂钩,但它们在英文中的含义和用法有显著区别,简单地说,Intellectual 代表复杂问题的抽象能力,Intelligent 代表问题解决能力)
在 Intelligent 中,人展现的不是具体的知识储备,而是某些能力,比如讲笑话接梗、语法正确的谈话,或是钓鱼这类技巧。
进一步说,Intelligent 的特点是:行动不仅符合特定标准,更能主动应用这些标准来发现偏差、纠正错误,并借鉴他人经验。
需要特别指出:Intelligent 的本质,并不基于“人类必须遵循特定规则、方法或指令”的观念。
相反,遵循规则这件事本身就有聪明多少之分,如果 Intelligent 真要靠遵守规则才能实现,那么就必须有关于如何遵循规则、如何遵循关于遵循规则的规则等等的规则,这将陷入无限循环,这显然是荒谬的。
Intellectual 区别于 Intelligent 的本质特征在于:人类不仅需要掌握智能操作的能力,更要构建一套理论体系,这套理论能让人解释行为逻辑、解答相关疑问、进行深度论证,从而超越单纯的行为层面。
此处提出的“理论”概念,既适用于专业领域的复杂构建,也适用于任何受过教育的人在特定情境下参与的活动。
即使是生活中最普通的琐事,比如规划家具摆放位置或研究乘车路线,也会引发人们的理论化思考。
此处所指的“理论”概念,既涵盖最抽象的认知框架,也包括具体的实践细节。
例如,这里理解牛顿力学的“理论”,光懂核心定律(比如力等于质量乘加速度)是远远不够的。
此外 Kuhn [4] 指出,真正掌握“理论”的人必须理解核心定律如何处理实际问题,才能将理论应用于新的相似情况。
因此,掌握牛顿力学理论的人必须理解钟摆运动、行星运行等现象背后的原理,并能识别现实中的类似现象,从而正确运用该理论的数学公式。
“理论”依赖于把握现实世界中情境与事件之间的相似性,这种知识无法通过规则来表述。
现实世界中的这类相似性本质上无法用标准定义,就像人脸、旋律或酒香的差异,既难以量化,更无法精确描述。
按照 Ryle 的理论概念,程序员必须构建一套核心认知:理解计算机程序将如何支撑和处理现实世界中的具体事务。
根据“编程即理论构建”的观点,程序员构建的理论比程序代码、用户文档或需求说明书等其他产物更重要。
要论证“编程即理论构建”这一观点,核心在于证明程序员通过理论掌握的知识必然超越、且本质上高于代码文档等有记录产物。
对于这一点,程序员的认知将在至少三个核心维度上超越书面内容:
掌握程序理论的人能解释清楚:程序解决方案如何关联并处理现实世界中的具体事务。
这种解释本质上需要揭示:现实世界的各种情况(无论整体框架或具体细节)究竟如何被映射进程序代码及文档中,并确保其可理解性。
程序员必须能阐明:现实世界中的哪些具体情况,是通过代码的每个部分及整体架构来实现的。
反之,对于现实世界的任何情况,程序员都能准确说明它被转化成程序代码的具体方式。
当然,现实世界中绝大多数活动将超出程序文本的范围,它们在当前上下文中可以忽视。
然而,只有理解全局的人才能判断现实世界的哪些事务需要程序处理。
这种全局理解能力必须由程序员来提供。
掌握程序理论的人能解释每一段代码的设计依据,换句话说,他们能用理论依据证明代码的合理性。
程序合理性的根本依据,始终是程序员的直接直觉认知或预判。
即便在运用设计规则、量化分析或方案对比时,程序合理性的根本依据仍是程序员的直觉判断。
因为选择哪些规则、判断规则是否适用,归根结底都依赖程序员的直接认知。
掌握程序理论的人能有效响应程序修改需求,使程序以新的方式处理现实事务。
设计程序修改方案的核心在于:准确识别新需求与程序既有功能之间的内在关联。
这种需要被识别的相似性,本质上是现实世界中各种实际情况之间的内在关联。
这种理解只对掌握领域知识的程序员有意义,且无法简化为特定标准或规则,原因和之前论证程序合理性时说的完全一样。
本节讨论虽为“编程即理论构建”提供了基本论证,但评估该观点时仍需考量:它能在多大程度上推进对编程及其问题的系统性理解。
提出“编程即理论构建”的根本动机是:建立对程序修改底层逻辑的深刻认知。
程序一旦投入运行,人们总会发现它只能解决实际问题的某个方面。
程序的实际运行过程本身也会激发新的需求灵感,促使人们思考程序应当提供哪些新增的实用功能。
当需要改变程序运行方式时,人们总认为修改现有代码通常比重写整个新程序更节省成本。
这种“低成本修改程序应该可行”的预期,需要深入分析。
首先应当指出,这种预期无法通过类比其他复杂人造结构的修改来支持。
例如在建筑领域,偶尔进行的改造项目其实成本高昂,事实上完全拆除重盖反而更划算。
其次,低成本修改程序的预期可能性源于程序文本存储在可编辑介质中的特性。
“编程即理论构建”不认同低成本程序修改普遍可行的预期。
在程序中加入灵活性,意味着提前构建某些当前不需要但未来可能用到的功能。
具备灵活性的程序无需修改即可应对外部环境的特定变化。
人们常说程序应当设计得足够灵活,这样才能快速适应变化的需求。
每一项灵活性功能的设计都需要确定:它需要覆盖哪些应用场景,以及通过何种参数进行控制。
这种成本是为实现一个其有用性完全取决于未来事件而产生的。
显然,内置程序灵活性并非应对世界变化环境下对程序适应性普遍需求的解决方案。
修改程序时,必须调整现有方案来应对现实世界活动的变化。
修改程序的第一件事,就是要评估现有方案与新修改需求是否匹配。
在此对比过程中,必须确定现有解决方案的能力与新需求之间的相似程度和类型。
这种对相似性的判定需求,恰好突显出“编程即理论构建”的核心价值。
事实上,任何编程观点若绕开“需要核心洞察力持有者直接参与”这一点,在判定相似性时就会暴露出根本缺陷。
关键在于:这种相似性的判定只有掌握程序理论的人才能做到,它完全超出规则可判定的范围,因为连判定标准本身都无从定义。
程序员通过洞察新需求与程序既有功能的相似性,就能精准设计出实现修改所需的代码改动。
实际上,掌握理论的人随时能应对那些导致程序修改的问题和需求。
程序修改的问题根源在于:人们错误地将编程等同于产出程序文本,而未能意识到编程本质上是构建理论的过程。
由此,那些未能正确掌握基础理论的程序员修改代码导致程序质量退化的现象,基于“编程即理论构建”就能得到合理解释了。
事实上,如果只关注程序代码和执行结果的变动,某个修改需求通常可以有多种实现方案,而且这些方案在技术上都能成立。
然而,若结合程序理论来审视,这些方案差异巨大:有些方案可能符合理论逻辑或自然扩展了理论,而另一些方案可能与理论完全冲突,表现为在程序主体上打零散的补丁。(译注:也就是堆屎山)
这种程序修改层面的本质差异,只有掌握程序理论的人才能真正理解。
同时,程序代码的修改质量直接关乎程序的长远生命周期。
为了保持程序的质量,每次修改都必须牢固建立在其理论基础之上。
程序质量的核心标准(如简洁性和结构性)只能通过程序理论来理解,因为这些标准本质上是将实际代码与程序员认知中那些能实现相同功能但未被编写的潜在方案进行对比的结果。
“编程即理论构建”的核心主张是:任何程序的理论本质上无法被完整表达,且必然与人类认知紧密绑定。
描述程序生命状态时,必须明确掌握其理论的人员对该程序的实际掌控程度。
为强调这一状况,可通过引入程序生命、死亡与复活的概念来扩展程序构建的内涵。
程序的构建过程,就是程序员团队在其内部构建相关理论的过程。
在程序生命期间,掌握其理论知识的程序员团队持续主导程序,特别是掌控所有修改权。
但当修改需求无法得到专业回应时,程序的实际死亡状态就显现出来了。
根据这些概念,程序的寿命取决于新一代程序员能否继承该程序的理论。
新程序员若想掌握既有程序理论,仅靠熟悉程序文本和相关文档是远远不够的。
新程序员必须有机会与掌握理论的原团队紧密协作,通过参与相关现实场景实践来理解程序的定位,并学习程序运作机制及程序理论框架下如何处理异常反应和修改需求。
新程序员学习既有程序理论的培养问题,本质上类似音乐创作或乐器演奏,核心在于实践经验的传递,而非理论知识灌输。
在编程领域,最关键的实践教学要包含两项内容:讨论程序与现实世界的具体关联,以及明确程序处理现实事务的边界。
“编程即理论构建”一个重要的结论是:仅凭文档绝对无法复活程序(即重建其理论)。
退一万步说,即便程序复活看似不可行,但现实中完全死亡的程序需要复活的情况也极少,毕竟很难想象会让对原始理论毫无了解的新团队执行复活任务。
即便如此,“编程即理论构建”仍然强烈主张:程序复活只能在极特殊情况下尝试,且必须事先有心理准备,即便成功也要付出高昂代价,甚至复活后的理论可能偏离原团队的认知,与程序代码遗留潜在冲突。
“编程即理论构建”主张:与其复活旧程序,不如放弃现有代码,让新团队从头解决原始问题。
这种做法比复活程序更可能产出可行程序,成本相当甚至更低。
关键在于:强行根据现有代码反推理论体系,是个极其痛苦、挫败且耗时的过程。
新程序员会陷入撕裂感:既要忠于可能晦涩脆弱的现有代码,又必须构建新理论体系。而无论好坏,新理论几乎必然偏离代码背后的原始设计理念。
即便在持续维护的程序中,随着程序员团队的人员更替和个体能力差异,最终也会出现类似问题,团队对程序理论的理解会不断偏离最初版本。
本文最后将探讨“编程即理论构建”与现有编程方法之间的关联。
首先,什么是编程方法?这一点往往并不明确,即使那些推荐具体编程方法的人,也未必能清晰阐述。
在此,编程方法将被定义为一套针对程序员的工作规则:规定他们该做什么、按什么顺序做、使用哪些符号或语言,以及在不同阶段要产出哪些文档。
关键点在于:现有编程方法与“编程即理论构建”的核心差异在于操作步骤及其顺序。
编程方法的核心理念是:程序开发应该按特定步骤序列执行,每个步骤会产出对应的交付物。
而在理论构建过程中不可能有固定操作序列,因为人类掌握的理论本身没有固有组成部分或顺序。
相反,真正掌握理论的人能根据不同问题或需求,基于理论产出多种形式的阐述。
对于特定符号或形式化的使用,这只能是次要问题。因为核心的理论本身无法被表达,因此也就不存在其表达形式的问题。
根据“编程即理论构建”,对于编程最核心的活动而言,不存在所谓唯一正确的方法。
这个结论看似与主流观点存在多处矛盾,甚至可能被当作反驳“编程即理论构建”的依据。
这里将讨论两个看似矛盾的观点:一是科学探究中方法的重要性,二是软件开发中实际采用的编程方法论的成效。
第一个观点认为:软件开发应遵循科学规范,采用类似科学方法的操作步骤。
这个观点的根本错误在于:它认为存在某种科学方法且这种方法对科学家有帮助。
近年来这个问题争议不断,像 Feyerabend [2](以物理学史为例)和 Medawar [5](从生物学角度论证)等学者得出结论:将科学方法视为科学家实践指南的观念是错误的。
这一结论与 Polya [8,9] 关于解题方法的研究成果并不矛盾。这项研究以数学领域的案例为基础,得出的见解对编程领域也高度相关。但绝不能宣称它提供了可遵循的方法。
相反,它更像是解题思路的催化剂:通过指出各种可任意组合的工作模式,激发解题者的思维活力。
第二个看似与“编程即理论构建”对编程方法的否定相矛盾的论点是:据报告,特定编程方法在实践应用中已取得成效。
但迄今为止,似乎从未有人真正做过严谨论证编程方法成效的研究。
这项研究必须采用成熟的对照实验技术(参见 Brooks, 1980 [1] 或 Moher & Schneider, 1982 [6])。
这种研究的缺失部分归因于高昂成本(若得出有效结论则成本必然巨大),部分在于难以在实际操作中准确定义程序开发领域所谓编程方法的核心理念。
现有的大多数相关编程方法报告,都仅停留在描述和推荐某些技巧及流程层面,而绝非通过系统方法实证其实际效用。
C. Floyd 与多位合作者 [3] 对五种编程方法展开深入研究,最终得出结论:认为存在某种规则体系能机械式导出优质解决方案的想法纯属幻想。
编程方法的真正价值体现在程序员的教育上。这一点与“编程即理论构建”完全相容。
实际上,程序员构建的理论质量主要取决于三点:对典型问题的模型解决方案的熟悉度、对描述与验证技术的掌握程度、以及对多部件复杂交互系统的构建原则的理解深度。
编程方法的诸多关注点其实都涉及理论构建的核心范畴。
“编程即理论构建”与编程方法的根本分歧在于:使用何种技术以及采用什么顺序。
根据“编程即理论构建”的观点,程序员必须完全根据实际要解决的问题来自行决定这些事项。
“编程即理论构建”与当前主流观点最惊人的差异在于:程序员对编程活动的个人贡献及其专业地位的认定。
在多数关于编程的讨论中,“编程即理论构建”与主流观点在程序员个人贡献层面的冲突显而易见。
以 Oskarsson [7] 对大型软件系统可修改性的研究为例:
这项研究详细记录了一个大型商业系统在单次版本更新中的大量修改情况。
该研究详细记录了每次修改的背景、内容和实现过程,尤其关注程序变更如何限定在特定模块内。
然而研究完全未涉及以下关键因素:500 名程序员的项目经验(例如参与时长)、技术决策权在团队中的具体分配机制。
研究仍通过“决策被错误地实施在错误的模块中”及“AXE 系统设计哲学”等表述,间接承认了底层理论的存在价值。
该研究的方法论缺陷导致这些关键发现只能作为割裂的理论痕迹呈现,无法系统化展现程序理论体系。
更普遍地说,当前许多编程讨论似乎默认编程类似工业生产,程序员被视为生产线上受规则约束的零件,可随意替换。
另一种相关观点认为:人类只有像机器般遵循规则才能发挥最佳效能,这导致过度强调形式化表达方式,因为只有形式化才能用操作规则来论证逻辑。
这种观点恰好契合计算机从业者中普遍存在的观念:人脑的运作机制与计算机相同。
在工业管理层面,这类观念支持将程序员视为责任较轻、仅需简短培训的普通工人。
在“编程即理论构建”视角下,编程活动的主要成果是程序员掌握的理论。
因为程序理论本质上是每个程序员独有的认知资产,所以必须摒弃将程序员视为程序生产中可替换零件的观念。
相反,程序员应被视为人机协作活动中负责的开发管理者。
为了担任这一角色,他或她必须获得一个永久职位,其地位应与其他专业人士(如工程师和律师)相当,这些专业人士为企业雇主的积极贡献基于其智力专业能力。
根据“编程即理论构建”的观点,要提升程序员地位,必须同步调整程序员教育方向。
虽然掌握符号系统、数据表示和流程处理等技能仍然重要,但教育的核心重点必须转向培养学生构建理论的理解力和能力。
这种能力是否真能教授,在多大程度上可以教授,仍是一个开放的问题。
最有希望的教学方法是让学生在积极创新的环境中,通过专业指导动手解决实际问题。
既然程序修改是编程的本质部分,那么编程的核心目标就是让程序员构建理论:理解程序如何支撑现实事务运行。
这种观点引申出程序生命的概念,即程序的生命周期完全依赖掌握该程序理论的程序员团队的持续支持。
此外,从这种观点来看:将编程方法理解为程序员必须遵循的操作规则,本质上是基于错误前提的,必须被彻底摒弃。
基于此观点的进一步思考,程序员必须被赋予责任主体地位:作为人机协作活动的核心开发者与管理者,其教育体系需在数据加工及符号系统训练之外,同步强化理论构建能力的培养。
Brooks, R. E. Studying programmer behaviour experimentally. Comm. ACM 23(4): 207–213, 1980.
Feyerabend, P. Against Method. London, Verso Editions, 1978; ISBN: 86091–700–2.
Floyd, C. Eine Untersuchung von Software–Entwicklungs–Methoden. Pp. 248–274 in Programmierumgebungen und Compiler, ed H. Morgenbrod and W. Sammer, Tagung I/1984 des German Chapter of the ACM, Stuttgart, Teubner Verlag, 1984; ISBN: 3–519–02437–3.
Kuhn, T.S. The Structure of Scientific Revolutions, Second Edition. Chicago, University of Chicago Press, 1970; ISBN: 0–226–45803–2.
Medawar, P. Pluto’s Republic. Oxford, University Press, 1982: ISBN: 0–19–217726–5.
Moher, T., and Schneider, G. M. Methodology and experimental research in software engineering, Int. J. Man–Mach. Stud. 16: 65–87, 1. Jan. 1982.
Oskarsson, ¨O Mechanisms of modifiability in large software systems Link¨oping Studies in Science and Technology, Dissertations, no. 77, Link¨oping, 1982; ISBN: 91–7372–527–7.
Polya, G. How To Solve It . New York, Doubleday Anchor Book, 1957.
Polya, G. Mathematics and Plausible Reasoning. New Jersey, Princeton University Press, 1954.
Popper, K. R., and Eccles, J. C. The Self and Its Brain. London, Routledge and Kegan Paul, 1977.
Ryle, G. The Concept of Mind. Harmondsworth, England, Penguin, 1963, first published 1949. Applying “Theory Building”
评论区
共 条评论热门最新