第 10 章

报错是一种修辞

视觉秩序留下的最持久遗产是先于语义的信任与怀疑——人们在理解代码之前已先觉得它属于或不属于这里,这种判断没有写进章程,却在每次打开源文件时被重新激活。后来者要理解社区运行逻辑,首先面对的或许不是设计决策或著名争议,而是眼睛落在屏幕那一瞬间所感受到的东西。

但代码终究要运行。当屏幕上的括号和缩进被送进编译器,当REPL里的表达式被求值,另一种界面就会打开。代码排版是静态的视觉,运行时爆出的红字则是语言与开发者之间流动的界面,它不等人准备好,也不给人留出品味的时间。它直接打断工作流,把失败摊在眼前。

大多数程序员对错误信息的第一反应是烦躁。这很自然。你花了一小时构建一个心智模型,以为自己在建造一座宫殿,结果编译器告诉你连地基都没打牢。Clojure的早期使用者面对的是双重挫败:他们不仅要面对自己代码中的错误,还要面对那些错误被呈现出来的方式。

2008年的Clojure还栖身在Java的报错体系里。堆栈跟踪是Java抛出的,异常消息是Java写的,只有最顶层那一两行露出Clojure函数的名字。使用者在邮件列表里贴出的堆栈跟踪动辄几十行,从Java的集合框架穿过Clojure的核心库,最后落在一个用户定义的函数名上。贴出这些信息的人通常已经盯了它十分钟,仍然不知道问题出在哪里。里奇·希基在公开发布Clojure之前,花了大约两年半的时间开发,没有外部资金,大部分时间都专门投入在Clojure上。开发快要完成时,他向Common Lisp社区里的一些朋友发送电子邮件,宣布他完成开发了Clojure。

邮件列表里重复出现一种场景。有人贴出截去开头的堆栈跟踪,大概觉得前半截是Java内部的东西,与自己的问题无关。回答者让他把完整堆栈贴出来,然后指出问题所在:传进去的集合类型不对,函数期望一个向量,他给了列表。线索在堆栈跟踪的中间位置,一个Java的类转换异常,但那个异常的名字并不能直接说明类型错误。回答者之所以能看出问题,不是因为他读懂了那条异常消息,而是因为他熟悉Clojure核心函数的实现方式,知道哪个Java类型转换会在什么情况下被触发。

这种解读工作在社区形成的头两三年里反复发生。它不是正式教学,也不在任何文档里,但它逐渐塑造出一种集体能力:把Java的异常消息翻译成Clojure语境下的诊断。这翻译不是一个人完成的,而是通过邮件列表里的问答积累起来的。一个人贴出问题,另一个人指出原因,第三个人补充说最新版本里消息已经变了,第四个人解释为什么那个类转换异常会在那个位置出现。这些讨论串被搜索引擎收录,后来者遇到类似错误时可能直接搜到对应的邮件存档,不用再问一遍。但存档里的信息并不总是有效,因为Clojure版本在更新,异常消息的措辞在变,触发条件也在变。社区面对的是一个持续移动的目标。

异常消息的第一重困境来自它的出身。Clojure运行在Java虚拟机上,这意味着很多运行时错误不是由Clojure自己产生的,而是由底层平台抛出的。当用户调用一个函数传入了错误类型的参数,Clojure代码可能根本没有机会检查参数类型,错误会被Java的类型系统直接捕获,抛出一个Java的异常。这个异常带着Java的命名习惯和Java的上下文,对不熟悉Java的Clojure开发者来说,它几乎是一门外语。

2010年前后,社区里开始出现一些专门讲解如何读懂Clojure堆栈跟踪的博客文章和wiki页面。这些文章的作者不是语言设计者,而是社区里那些踩过坑的人。他们画出了堆栈跟踪的解剖图:这一层是Java框架,这一层是Clojure核心,这一层是你的代码,错误通常发生在层次之间的边界上。这些解剖图后来被收入社区维护的文档,成为新来者必读的材料。

但解剖图解决不了根本问题。根本问题在于,错误信息本身没有说人话。它告诉你一个类转换失败了,却不告诉你为什么这个转换会发生,以及你应该怎么修改代码。这在静态类型语言里或许可以接受,因为类型系统本身提供了排查的框架。但在动态类型的Clojure里,类型错误可能源自一个相距很远的函数调用,错误信息的上下文往往不足以定位根源。

2012年,社区里出现了一些库,试图在Clojure层面包装异常,提供更友好的错误消息。其中最有名的是clojure.stacktrace,它能把Java堆栈跟踪重新格式化为更可读的形式,过滤掉与用户代码无关的框架层,并把Clojure函数名以更清晰的方式呈现出来。这个库后来被整合进Clojure的标准库,成为clojure.stacktrace命名空间。它没有改变错误消息的内容,但改变了错误消息的外观——它让堆栈跟踪看起来更像Clojure的东西,而不是Java的入侵者。

外观的改变在社区中引发了意想不到的讨论。有人问,为什么我们不更进一步,让错误消息本身变得更有用?这个问题的提出方式本身就揭示了社区对错误信息的理解框架:人们不把错误消息看作一个固定的技术事实,而把它看作一个可以被设计、被改进的界面。

2013年,Clojure核心团队开始讨论异常消息的措辞。讨论的起点是一个具体的例子:当用户试图对一个非数字值调用inc函数时,Clojure抛出的异常消息是一串Java类型名称。这条消息对于知道PersistentList和Number是什么的人来说,意思是清楚的。但对于一个刚开始学Clojure、可能不知道Java类型体系的人来说,它几乎毫无意义。核心团队的讨论围绕一个问题展开:Clojure是否应该在函数入口处进行类型检查,以便在Java抛出异常之前给出更友好的错误消息?里奇·希基作为Clojure的创造者,以终身仁慈独裁者的身份监督着开发过程,但JIRA错误报告由一组筛选者处理,最终由他批准更改。

这个问题的答案并不简单。在函数入口处进行类型检查意味着每个函数调用都会增加一层运行时开销,而Clojure的设计哲学一向倾向于让函数保持轻量,让调用者承担正确使用的责任。如果每个参数都做类型检查,核心函数的性能会显著下降。如果只检查部分参数,那么检查标准的不一致本身又会成为新的困惑来源。

讨论持续了几个月,最终形成的方案是一种折中:在关键位置增加断言,但断言只在开发模式下生效,在生产环境中可以被编译掉。这意味着同一个函数在不同模式下会有不同的错误表现——开发模式下的错误消息更友好,生产模式下则更简洁但更隐晦。这个方案没有让所有人满意,但它体现了一种社区共识:错误信息是一种有成本的修辞,它需要在不牺牲性能的前提下提供尽可能多的指导。

堆栈跟踪的讨论在2014年出现了另一个分支。那一年,Clojure社区经历了一次显著的增长,大量新开发者涌入,他们中的很多人没有Java背景,是直接从Python、Ruby或JavaScript转过来的。对于这些开发者来说,Java堆栈跟踪不仅难以理解,也是一种文化冲击。在Python社区,错误消息通常只包含与用户代码直接相关的信息,堆栈跟踪默认就是简洁的。在Ruby社区,错误消息的措辞被精心设计过,常常带有一种近乎口语化的亲切感。但这些新来的Clojure开发者面对的是数十行Java类名,每个类名都带着java.或javax.前缀,仿佛在提醒他们:你正在一片陌生的土地上作业。

一位当时加入社区的开发者后来在博客里回忆,他最初被Clojure吸引是因为它的语法简洁、理念优雅,但第一次运行时错误让他差点放弃。他的感受是,自己不是在调试Clojure代码,而是在调试Java代码,只不过那些代码碰巧被塞在一个Clojure文件里。他在邮件列表里提问,得到了耐心的解答,解答者一步步教他如何从堆栈跟踪里找到Clojure函数名,如何忽略Java框架层,如何定位真正的错误源。他学会了,但整个过程让他觉得,Clojure社区在错误信息的呈现上还有很长的路要走。

这篇博客在社区里引起了共鸣。有人回应说,问题不在于堆栈跟踪本身,而在于初学者缺乏足够的教育资源来理解它。也有人回应说,教育资源再丰富,也改变不了堆栈跟踪本身是Java产物的现实。这个分歧指向了一个更深层的张力:Clojure作为一种寄生在Java平台上的语言,它的运行时体验到底应该在多大程度上忠于Java,又应该在多大程度上建立自己的独立界面?这个张力在错误信息的问题上表现得尤为尖锐,因为错误信息是语言与开发者之间最频繁的接触点之一。每一次运行时错误都是一次小型危机,而危机中呈现出来的界面面貌,会深刻地影响开发者对语言的整体感受。

2015年,clojure.spec库的早期设计开始浮出水面,社区里出现了关于错误消息的新一轮讨论。clojure.spec的目标之一是为数据提供运行时验证,并对不符合规格的数据生成可读的错误报告。这个目标与错误信息修辞直接相关:它试图把什么出错了从Java层面提升到Clojure层面,用Clojure自己的术语来描述问题。但clojure.spec在初期设计阶段产生了一个意外后果:它生成的错误报告非常详细,详细到了令人不知所措的程度。

一份规格验证失败的报告可能包含数百行输出,每一个字段的名称、期望类型、实际值都被列出来,附带嵌套的上下文信息。对于复杂数据结构的调试来说,这些信息是无价的;但对于日常使用来说,它过于冗长,读起来像是在看一份数据尸检报告。

这种冗长在社区中引发了分歧。一部分人认为,详细是好事情,信息越多越容易定位问题。另一部分人认为,过度的详细是一种新的不友好,它把筛选信息的负担从工具转移到了人的身上。有一位开发者说,他第一次看到clojure.spec生成的错误报告时,想起的是医学影像报告——信息是全面的,但需要专业知识才能解读。他花了半小时才学会如何从报告中提取关键信息,而那半小时里他本可以用来修复实际的问题。这个反馈被clojure.spec的设计者注意到了,后续版本增加了错误报告的摘要功能和可定制格式,让用户可以选择是看详细报告还是只看关键错误。

但clojure.spec的讨论揭示了一个更大的问题:错误信息的设计不是纯粹的技术问题,而是一种修辞选择。什么样的错误信息是好的?这个问题没有绝对答案,它取决于受众。对于有经验的Clojure开发者来说,他们可能更倾向于简洁的错误信息,因为简洁意味着他们可以快速定位问题,不被冗余信息干扰。对于初学者来说,他们可能更需要解释性的错误信息,因为解释性意味着他们可以理解问题发生的原因,而不是仅仅知道问题发生了。

社区在邮件列表里反复讨论这个平衡,但始终没有形成一个明确的结论。不同的库采用了不同的错误报告风格,clojure.spec的详细报告只是其中一种。有些库选择在错误消息中嵌入修复建议,有些库选择提供指向在线文档的链接,有些库则保持极简,只输出错误类型和位置。这些不同风格的共存本身就是一种社区态度:错误信息的修辞没有被统一规定,而是被允许演化。不同项目的维护者根据自己的判断和自己的用户群体,选择不同的错误呈现方式。

这种多元化在2016年前后变得明显,当时Clojure的库生态已经相当丰富,开发者在使用不同库时会遇到不同的错误风格。有人抱怨这种不一致带来了学习成本,但更多的人接受它,视为社区生态的自然特征。一位开发者在邮件列表里写道,Clojure社区不试图把一切都标准化,错误消息的风格就像代码排版一样,有共识但没有强制,读得多了自然就知道哪种风格对应哪种库。这句话里藏着一种已经内化的社群习惯:把错误信息当作一种可以品味、可以比较的东西,而不是当作中性的技术信号。

这种习惯是逐步养成的。早期的邮件列表里,人们讨论错误信息时用的是好、坏、有用、没用这样的词。但到了2016年,讨论的词汇丰富了很多:人们开始用清晰、友善、啰嗦、生硬、温和、直接这样的词来描述错误消息。这些词本来是用来描述人的沟通风格的,现在被用来描述程序与开发者之间的沟通风格。这个转义不是偶然的。它意味着社区成员已经不自觉地接受了这样一个前提:错误信息是语言对开发者说的话,它体现了一种态度,一种对开发者处境的关切程度。

2017年,一个关于异常处理的讨论把这个前提推到了更远的地方。讨论的起点是一个技术问题:在Clojure中,应该如何处理库代码中抛出的异常?是应该在库内部捕获一切异常,保证不中断调用者的流程,还是应该让异常向上传播,让调用者自行决定如何处理?这个问题的技术层面并不新鲜,几乎所有编程语言社区都讨论过类似的问题。

但在Clojure社区的讨论中,一个独特的视角浮现出来:异常处理不只是程序健壮性的问题,也是一种表达。一个库选择抛出异常而不是吞掉它,是在对调用者说,这里出了一个问题,你需要知道。一个库选择捕获异常并返回一个错误值,是在对调用者说,我知道这里可能出问题,我已经处理过了,你不用担心。

这两种表达方式对应着两种不同的开发者关系。抛出异常的方式把决策权交给调用者,但也把处理错误的责任推给了调用者。返回错误值的方式则减轻了调用者的负担,但也可能隐藏了调用者需要知道的信息。Clojure社区在这个问题上没有达成统一意见,但讨论本身产生了影响:它让异常处理从怎么让程序不崩溃延伸到了怎么让失败可表达。

“让失败可表达”,这个说法后来在社区的讨论中反复出现。它意味着失败不是程序的羞耻,而是程序与开发者之间的一次诚实对话。程序失败了,它应该清楚地告诉开发者它为什么失败,在哪个层次上失败,以及可能的方向是什么。如果程序吞掉了异常,假装一切正常,那它就是在对开发者说谎。

这个理念与Clojure语言本身的设计哲学有内在的呼应。Clojure强调不可变性和显式的时间进展构造,它的设计倾向于让状态变化可见、可追踪。在错误处理上,让失败可表达意味着让错误可见、可追溯。异常的堆栈跟踪本身就是一种时间进展构造:它记录了程序从正常运行到失败所经过的路径。

在Java的原始堆栈跟踪中,这条路径上有很多噪音——框架层的调用、中间件的调用、与用户代码无关的基础设施调用。Clojure社区对堆栈跟踪的改造,本质上是在这条路径上做减法,过滤掉噪音,让失败的路径变得更容易追踪。clojure.stacktrace做的格式化工作、clojure.spec做的错误报告工作,都是在做同样的减法,只不过减法的层次不同。

2018年,一个关于错误信息措辞的微小改动在邮件列表里引发了一场不小的讨论。Clojure的某个核心函数在参数类型错误时抛出的异常消息从原来的技术表述改为更口语化的表述,例如从涉及Iseq和Long的技术措辞改为更直白的说明——不能从一个Long创建序列,因为它不是集合类型。

这个改动看起来很小,但社区里的反应分歧很大。支持者认为新消息更友好,能让初学者更快理解问题。反对者认为旧消息更精确,Iseq是Clojure序列抽象的核心概念,用这个词能帮助初学者建立正确的概念模型,而不是用模糊的collection一词替代。

讨论持续了数周,最终核心团队保留了新消息,但在文档中补充了对Iseq概念的说明,试图在友好性和精确性之间找到平衡。Clojure的开发过程在Clojure JIRA项目网页由社区驱动并管理,任何人都可以提交错误报告和想法,而贡献补丁前则需要先签署Clojure贡献者协议。

这场讨论最值得注意的不是它的结果,而是它的发生本身。一条错误消息的措辞改动,能引发社区数周的讨论,说明这个社区对错误信息的重视程度已经超出了功利层面。人们不是在讨论怎样写错误消息能减少重复提问,而是在讨论怎样写错误消息能更好地传递Clojure的思维方式。错误消息被当作一种教学工具,一种价值观的载体。这个转向在技术社区中并不常见。大多数编程语言社区把错误消息视为一种不得不提供的东西,一种在功能完成之后才需要考虑的附加品。Clojure社区则把它当作语言设计的一部分,与语法、标准库、工具链同等重要。

这种重视的来源可以追溯到社区早期的邮件列表实践。在Clojure诞生之初,邮件列表是唯一的求助渠道。当一位开发者遇到错误时,他不能把错误消息复制粘贴到Stack Overflow上等待回答,因为Stack Overflow上的Clojure板块还没有形成。他只能把错误消息发到邮件列表里,然后等社区里的资深成员帮忙解读。

那些资深成员在解读错误消息的过程中,逐渐形成了一种习惯:他们不只是指出错误的原因,还会解释错误消息本身的含义,以及如何从错误消息中推断出问题的根源。这种元层次的解释——解释如何解释错误消息——在社区的知识传递中占据了一个奇异的位置。它不是正式文档的内容,也不是语言教程的内容,但它是每一个新来者必经的入门仪式。你学会了读错误消息,你就获得了一种社区成员才有的解码能力。

这种解码能力后来被一些库的作者内化到库的设计中。2019年,几个流行的Clojure库开始在错误消息中嵌入指向在线文档的URL。点击那个URL,你会被带到一个页面,页面里详细解释了这种错误通常在什么情况下发生,以及常见的修复方法。这种做法把邮件列表里的问答循环压缩成了一个链接,但它保留了社区知识传递的核心特征:错误消息不是终点,而是入口。它引导你去一个更大的语境,让你理解错误发生的原因和解决的方向。这种设计背后的假设是,开发者不是只需要知道出错了,他们还需要知道为什么出错,以及从这个错误中能学到什么。

这个假设与Clojure社区的整体气质是一致的。在Clojure社区中,失败不被视为一种羞耻,而被视为学习过程的一部分。REPL文化鼓励试验,鼓励试错,鼓励在运行时观察程序的行为。错误是这个过程中的自然产物,它不是对开发者能力的否定,而是程序在告诉开发者:你尝试的这条路走不通,试试别的。当错误信息被赋予这种教育功能时,它的语调就变得重要了。一条生硬的错误消息——比如Java的ClassCastException——听起来像是判决:你错了。一条温和的错误消息——比如“不能从一个Long创建序列”——听起来更像是提示:你这里可能想要另一个类型。判决和提示之间的差别,就是社区对开发者的态度。

这种态度在2020年前后变得更加明确。那一年,Clojure的错误信息经历了又一次系统性的改进,核心团队在多个函数中统稿了错误消息的措辞,使之更加一致和可读。改进后的消息遵循一种隐含的模板:先说明出了什么问题,再指出可能的原因,最后提示可能的修复方向。这个模板不是被正式宣布的,而是从邮件列表的讨论中自然浮现的。那些在邮件列表里帮人解读错误信息的资深成员,多年来一直在使用这种三段式结构回答问题。现在,这种结构被写进了语言本身,变成了错误消息的标准格式。这是社区习惯反向塑造语言设计的又一个例子。

但改进也留下了未解决的问题。堆栈跟踪的长度问题始终没有彻底解决。即使clojure.stacktrace做了过滤,生产环境中的堆栈跟踪仍然可能包含数十行与用户代码无关的框架层信息。2021年,一个社区项目尝试提供一种全新的堆栈跟踪视图,把调用链以树形结构展示,让用户可以折叠和展开不同的层次。这个项目没有进入Clojure核心,但它在社区中获得了不少关注,因为它代表了一种持续的努力:让错误信息不仅是文本,也是一种可交互的界面。这种努力背后的驱动力不是技术上的必要性,而是审美上的不满足。社区成员对现有错误呈现方式的不满足,推动着他们不断试验新的表达形式。

这种审美上的不满足也与上一章讨论的代码视觉秩序有隐秘的联系。代码排版关乎眼睛在屏幕上的静态感受,错误信息关乎程序在失败时对开发者说的话。两者都是界面,都是语言与开发者之间的接触面。在代码排版上,社区形成了一套松而不散的视觉共识,这套共识让成员在阅读代码时产生归属感。在错误信息上,社区则形成了一套关于如何表达失败的修辞共识,这套共识让成员在遭遇错误时感到被尊重。

被尊重的感受来源于什么?来源于错误信息不是冷冰冰地甩出一个技术术语,而是试图解释、试图引导、试图把失败变成一次学习。这种尊重不是空泛的姿态,它体现在措辞的选择、信息量的控制、语气的高低上,它的背后是对开发者认知负荷的关切。

这种关切也有它的边界。并不是所有Clojure的错误信息都是温和的。有些错误信息仍然生硬,有些堆栈跟踪仍然令人畏惧。社区的改进是一步一步的,每一次改进都针对特定的痛点,而不是试图一次性解决所有问题。

这种渐进式改进与社区对稳定性的偏好是一致的。Clojure社区对语言核心的改动非常谨慎,错误信息的改进也不例外。每一条错误消息的措辞改动,都需要经过核心团队的讨论和社区的反馈,因为改动可能影响依赖错误消息进行自动化处理的工具。有些工具通过匹配错误消息的文本来判断错误类型,措辞的改动可能导致这些工具失效。这种对兼容性的顾虑,使得错误信息的修辞改进始终必须在友好性和稳定性之间取得平衡。

平衡的结果是,Clojure的错误信息处在一个中间状态:它比Java原生的异常消息友好得多,但比一些现代语言尚显生硬。这个中间状态不是缺陷,而是选择。它反映了社区对“什么是足够好”的判断,也反映了社区愿意把多少资源投入到错误信息的改进上。在邮件列表的讨论中,经常可以看到这样的论调:与其花时间打磨错误消息的措辞,不如花时间改进文档,让开发者少犯错误。这种论调隐含着一层意思:错误消息是最后一道防线,它不应该承担过多的教学责任。教学责任应该由文档、教程和社区互动来承担,错误消息只需要做到清晰和准确,不需要做到温柔和体贴。

但另一些人认为,这种分工是理想化的。在实际的使用场景中,开发者遇到错误时,文档可能不在手边,教程可能已经过时,社区互动需要等待。错误消息是他们唯一能即时得到的东西。因此,错误消息的质量直接决定了他们在那一刻的体验。

这种观点在2022年的一次社区调查中得到了表达,调查中很多受访者将错误消息难以理解列为他们学习Clojure时遇到的最大障碍之一。调查结果在邮件列表里引发了讨论,讨论中没有出现对立阵营,而是出现了更多关于具体改进的建议。有人建议在错误消息中增加到官方文档的链接,有人建议在REPL中提供更丰富的错误上下文,有人建议提供一种初学者模式,在初学模式下显示更详细的错误解释。这些建议中的一部分后来被实现,另一些还在讨论中。

但重要的是,讨论本身已经成了一种社区仪式。就像邮件列表里的问答循环塑造了社区解读错误消息的能力,关于错误消息的持续讨论塑造了社区对错误信息的期待。新来者进入Clojure社区时,他们面对的不只是一套固定的错误信息,而是一个对错误信息持续反思的集体。这种反思构成了Clojure隐性宪法的一部分。

隐性宪法在错误信息领域的执行方式很具体:它不要求所有错误消息都遵守某种格式,但它在社区中培养了一种期待,期待错误消息应该解释而非仅仅通告,应该引导而非仅仅判决。这种期待没有写成正式规范,但一个库如果提供了质量低下的错误消息,很可能在社区中受到批评。

批评的方式也值得注意。在Clojure社区,批评错误消息质量时很少使用攻击性语言。人们通常会说“这个错误消息可以更清晰一些”,而不是直接否定维护者的劳动。这种温和的措辞风格本身就是社区交流习惯的体现。

它意味着批评者理解错误消息的背后是库维护者的劳动,他们尊重这种劳动,只是希望它变得更好。这种沟通习惯不是天然形成的,而是社区十几年交流历史沉淀下来的。早期邮件列表里的争吵、辩论和和解,为后来的交流风格奠定了基础。当老成员用温和的方式指出问题时,新成员会模仿这种风格,风格就这样一代代传递下去。Clojure的开发过程目前由社区驱动,其作者里奇·希基则以终身仁慈独裁者的身份监督。

回到错误消息本身。Clojure的错误消息修辞在2023年前后达到了一个相对成熟的阶段。核心函数的错误消息经过了多轮改进,措辞基本统一,信息量适中。clojure.spec的错误报告功能已经稳定,提供了从详细到摘要的多种输出格式。社区库里流行的做法是在错误消息中嵌入文档链接。堆栈跟踪虽然仍然带着Java的痕迹,但clojure.stacktrace的格式化功能已经成为标准工具,REPL环境通常默认启用它。一个开发者在2023年遇到Clojure错误时,体验已经比2009年好了很多。这不是某个英雄人物一蹴而就的成果,而是十几年间无数人持续打磨的结果。

但打磨还没有结束。错误的类型在增加,新的库带来新的错误模式,新的开发场景带来新的错误呈现需求。Clojure在2024年面对着与早期不同的挑战:它不再是一个小众语言,用户群体包含了从初学者到资深工程师的广泛光谱,他们对错误信息的期望各不相同。如何在一条错误消息中同时满足初学者和资深开发者的需求,这个问题的难度不亚于设计一个宏系统。社区的选择是让错误信息分层:基础层提供简洁的错误描述,扩展层提供详细的解释和修复建议,用户可以根据自己的需要在不同层次之间切换。这个分层方案与Clojure语言本身的设计思路一致:提供简单的核心,把复杂性放在可扩展的层次上。

报错是一种修辞。这个论断在Clojure社区的实践中得到了最充分的体现。修辞不是文饰,不是把生硬的东西包装漂亮,而是根据受众选择表达方式,在传递信息的同时传递态度。Clojure的错误信息修辞传递的是一种双重态度:对技术问题的诚实和对开发者处境的关切。诚实意味着不隐藏错误,不吞掉异常,让失败可见。关切意味着不把错误当作开发者的失败,而把它当作程序与开发者之间的一次沟通,需要清晰的表达和耐心的解释。这两种态度合在一起,构成了Clojure社区对失败的独特理解:失败不是终点,而是反馈;错误信息不是惩罚,而是路标。

这种理解不会出现在语言的设计文档里,但它存在于每一天的开发实践中。当开发者敢于让自己的程序失败,当他们在失败时不感到羞耻,当他们在错误信息中寻找线索而不是逃避它,社区就有机会在错误之上建立共识。这个共识不是关于什么是对的什么是错的,而是关于怎么面对错误、怎么谈论错误、怎么从错误中学习。它是隐性宪法中最日常、最频繁被执行的条款。每一次REPL求值,每一次编译运行,每一次测试失败,都在激活这条条款。开发者可能不会意识到它在起作用,但它的存在让社区的运行方式区别于那些把错误视为个人失败的文化。

程序在失败时说了什么,这是语言与开发者之间流动的界面。它流动在每一次交互中,每一次修订中,每一次社区讨论中。它不像代码排版那样固定在屏幕上,却又比排版更深刻地触及开发者与语言的关系。排版影响的是眼睛,错误信息影响的是心。

排版让代码看起来像属于这里,错误信息则让开发者感到自己属于这里。当一个新来者第一次遇到错误、第一次读懂错误消息、第一次根据错误消息修复了问题,那一瞬间,他不再是旁观者,而是参与者。他从错误信息中读到的不只是技术事实,还有一种邀请:这里出错了,但你可以修复它,你属于这里。这种邀请没有写进任何章程,却在每一次运行时错误中被重新激活,成为社区成员身份认同中最隐秘的根基。