第 3 章
库的命名术
在那张半圆形座位的照片被上传到会议网站,作为首届聚会的视觉记录留存下来的同一段岁月里,这份文档最初的创建日期是2010年3月17日。它被保存在一个名为“naming”的文件夹里,后来被修改过很多次,最后一次更新是在2012年11月4日。它的创建者给它起过好几个名字——“styles”、“naming”、“clojure-naming-guide”——每一个名字都比上一个更正式一点,但始终没有越过某条线。它从未被标注为“官方”,从未被任何核心团队成员签署,从未在Clojure的官方网站上获得一个固定位置。但它存在过,在那些年里被引用过,被新加入的开发者下载过,被邮件列表里的回答者链接到过。它是一份没有权威的指南,却行使着某种裁定功能:裁定哪些名字看起来属于这个社区,哪些名字看起来不是。
要理解这份文档为什么会出现,需要回到2010年初的Clojars仓库。那时Clojure的第一个稳定版本已经发布了两年,库的数量开始从几十个向几百个爬升。每天都有新的项目被上传,每个项目都需要一个名字。
名字的选取在最初是完全自由的——Clojars不做审核,不设命名规则,不要求与任何既有规范保持一致。但自由并不等于混乱。在那些最早被上传的库中,已经出现了一种可辨识的命名模式:Ring、Enlive、Compojure、Leiningen。这些名字的共同特征很明显:它们都是英语中的实词,或者基于英语词根构造的、听起来像真实单词的组合;它们不使用技术缩写,不包含包名结构,不用大写驼峰式分隔单词;它们短小,好记,不暴露实现细节。
这种模式最初只是几个早期库作者的审美偏好。但偏好一旦被展示,就变成了可供模仿的样本。在2010年,当Clojars上的库数量还只有几百个时,新上传的库名已经开始向这种模式收敛。那些名字很长的、包含技术缩写或Java风格包名结构的库,并没有被禁止发布,但它们在邮件列表里的讨论量明显更少,在依赖关系中被引用的频率也更低。它们没有被宣告为“不合格”,只是被绕开了。
这种绕开是无声的,不留下任何决策记录,但它构成了一个清晰的信号:名字不是无关紧要的装饰,它决定了一个库在生态中的可见性,而可见性决定了它的生存机会。正是在这种背景下,那份文档的创建者开始整理命名惯例。他不是核心团队成员,不是语言的贡献者,只是一个在邮件列表里活跃的社区成员。他在2010年3月发了一条消息,说他正在收集社区里常见的命名实践,希望能帮助新手更快地理解“什么样的名字在这个社区里被认为是合适的”。他用的词是“被认为是合适的”,而不是“被要求的”。
这个措辞上的谨慎贯穿了整份文档的各个版本。在最初的版本里,只有寥寥几条观察:库名通常使用短小的英语常用词;避免Java类名中常见的大写驼峰式;如果库实现了一个广为接受的抽象概念,优先使用那个概念本身的名字,而不是加上技术前缀或后缀。这些观察不是从某份官方规范中推导出来的,而是从2009年到2010年初的邮件列表讨论中提取出来的。
它们散落在里奇·希基回复某个库作者的邮件里,在某个代码审查线程中反复出现的措辞里,在那些被社区成员链接到新手提问下的示例代码中。它们不是被宣布的,而是被重复的。每一次重复都在加固它们的存在,直到它们从“有人这么说过”变成一种默认的预期。
这份文档的创建者试图把这些预期固定下来。但他面临一个难题:惯例一旦被写下,就获得了一种此前不曾拥有的权威性。它不再是那些可以被争论、被修订、被重新解释的日常实践,而变成了一套可以被援引、被执行的规则。这在当时是一个敏感的边界。社区还没有准备好接受一份“官方命名规范”。里奇·希基本人从未发布过这样的文件。
他的做法是就具体案例给出意见,每次讨论一个名字,每次解释一次原则,但从不把原则汇编成章。这种审慎不是偶然的。它保留了共识机制的弹性:每一次命名争议都可以重新回到原则的讨论,而不必被一份已经写好的文件框定。但文档还是被创建了。在发布那条消息之后,创建者收到了十几封回复。
没有人反对他整理,但所有参与讨论的人都提醒他——这个提醒的措辞各不相同,但核心意思一致——这份文档不应该被视为“官方”的。它只是一份观察报告,是对已经存在的实践的描述,而不是对未来的规定。这个区分在当时的邮件列表里被小心翼翼地维护着。它揭示了那个后来被反复验证的道理:共识可以被描述,但很难被制定。当它被描述时,它仍然保持开放,仍然可以被新的实践所修订;当它被制定时,它就关闭了,变成了一个需要被遵守而非被讨论的东西。
这份文档的命运恰好印证了这一点。在创建后的两年里,它被反复修改,每一次修改都对应着一次新的命名争议。它的内容在增长,但它的权威性并没有随之增长。它始终停留在“非正式”的边界上,像一个忠实的记录员,记录着社区在命名问题上做出的选择,却从不试图替社区做出选择。到2012年末,当它的最后一个版本被保存时,它所记录的惯例已经与最初的版本有了显著的不同。有些原则被强化了,有些被弱化了,有些新的原则被添加进来。
但它的创建者从未在任何一个版本中写下“必须”这个词。他用的是“通常”、“倾向于”、“建议”。这些词语的谨慎选择本身就是一种共识仪式的实践:他在记录规则,但他拒绝成为规则的制定者。这份拒绝不是软弱,而是对共识机制运作逻辑的深刻理解——规则一旦被某个个人制定,就失去了它作为共识的合法性。
要理解这种谨慎,需要回到那些具体的选择时刻。在这份文档最初的几条建议中,有一条后来被证明是最具约束力的:库名应该使用短小的英语常用词。这条建议的来源可以追溯到2009年,当时Clojure的库生态还处于萌芽期。最早的一批库的名字都在遵循这个模式。它们不是缩写,不是技术术语的拼接,而是英语中已经存在的词,或者基于英语词根构造的、听起来像真实单词的新词。这种命名模式与Java生态中常见的命名习惯形成了鲜明的对比。
在Java的世界里,库名通常采用包名结构——com.company.product.module——或者使用描述性的技术术语——SpringFramework、Hibernate、Log4j。Clojure的早期库作者们有意无意地避开了这些模式。他们选择的名字短小、好记、不暴露实现细节,更像是一个品牌名而不是一个技术标签。这种选择最初可能只是审美偏好。
但在接下来的两年里,它逐渐演变成一种社区规范。新发布的库如果名字太长、太技术化、或者包含Java风格的包名结构,就会在邮件列表里收到修改建议。这些建议通常很温和,但也很坚持。提出建议的人不会说“你这样命名是错的”,而是会说“你考虑过用一个更短的名字吗”。这种措辞的温和并不意味着规范的软弱。恰恰相反,温和的措辞使得规范更难被拒绝——你无法对一个建议发火,你只能选择接受或者忽视。而如果你选择忽视,你的库可能不会受到公开的批评,但它也可能会被默默地跳过。
在Clojars的仓库里,那些名字不太符合惯例的库并没有被删除,它们只是很少被下载,很少被引用,很少在讨论中被提及。它们被社区冷落了,而这种冷落没有留下任何可供追溯的痕迹。这就是共识机制的日常运作方式。它不依赖投票,不依赖命令,甚至不依赖明确的规则。它依赖的是示范和模仿。成功的库示范了一种命名风格,新的库作者模仿这种风格来获得可见性。那些不模仿的,不会被惩罚,但也不会被看见。
这种机制在2010年到2012年间的Clojars仓库里清晰可见。Clojars本身并不是一个中央包管理器,它只是一个松散的发布平台。任何人都可以上传自己的库,不需要审核,不需要批准,甚至不需要注册一个账户——至少在最初的一段时间里是这样。这种去中心化的设计意味着没有守门人可以决定什么名字能进、什么名字不能进。但它也让命名规范的形成变得更加依赖于自发秩序。开发者们通过观察哪些库被广泛使用,来判断什么样的名字是“Clojure的”。
他们模仿那些成功的名字,不是因为被要求,而是因为想要被识别。这种模仿行为在Clojars的库名列表里留下了一条清晰的轨迹。在2010年,当Clojars上的库数量还只有几百个时,命名风格已经开始收敛。短小、具象、英语常用词的比例在上升,而长名、缩写、技术术语的比例在下降。这种趋势在2011年加速,到2012年已经基本稳定。那些后来被广泛使用的库——它们的名字共享着一种可辨识的家族相似性,但这种相似性从未被明文规定。它是通过无数次微观的模仿累积而成的。每一次模仿都在强化这个模式,每一次强化都让后来者更容易辨认和遵循。但模仿只能解释收敛,不能解释那些分歧。
在命名惯例的形成过程中,争议同样扮演了关键角色。那些争议之所以重要,不是因为它们改变了规则,而是因为它们在改变规则的过程中暴露了规则的存在。当一个名字被平静地接受时,规范是隐形的;当一个名字被激烈地争论时,规范才浮出水面,变得可以被审视、被讨论、被修订。
core.async的命名争议就是这样一个时刻。那是2012年6月。里奇·希基在邮件列表里宣布了一个新的库,用于支持异步编程。他给出的名字是core.async。
这个名字的结构包含两个部分:前缀“core”表明这个库是Clojure核心生态的一部分,基本名“async”则直接指向它所处理的领域。在宣布的邮件发出后不久,邮件列表里出现了不同的声音。有人提出,为什么不用更短的“async”?省去前缀可以让名字更简洁,也更符合Clojure库名短小精悍的一贯风格。还有人提出了更激进的建议:如果这个库是核心库,为什么不让它直接成为语言的一部分,而要以库的形式发布?这个问题触及了一个更深层的议题——什么算作“核心”,什么算作“扩展”——但那是另一个故事了。在命名这个具体的争议点上,两方的立场都很清晰。支持“async”的一方援引的是社区已经形成的命名惯例。
他们说,Ring、Compojure、Enlive——这些名字都是单独的词,没有前缀。如果一个库足够重要,它不需要前缀来证明自己的身份。如果它在生态中占据了一个关键位置,它的名字自然会成为那个位置的代名词。Ring就是最好的例子。Ring最初是由马克·麦格拉纳汉在2011年创建的。
在它之前,Clojure的Web开发领域没有一个统一的抽象层。不同的框架使用不同的方式处理HTTP请求和响应,它们之间的互操作性很差。Ring的贡献在于定义了一个简单的接口:一个HTTP请求是一个Clojure映射,一个HTTP响应也是一个Clojure映射,而处理请求的函数就是从一个映射到另一个映射的转换。这个抽象本身并不复杂,但它的命名选择让它在生态中占据了一个独特的位置。“Ring”这个词被选中,是因为它暗示着“环”——一个请求进来,经过处理,变成一个响应出去,形成一个环形的流程。但这个名字的真正力量不在于它的隐喻,而在于它的简洁和独占性。
在Clojure的命名空间里,一个叫“ring”的库占据了一个顶尖的语义位置。它没有说自己是“web-server”或“http-kit”或“request-handler”,而是用一个更抽象的词来命名一个更抽象的概念。这种命名策略使得Ring能够容纳不同的实现——你可以用Jetty作为底层服务器,也可以用Netty,或者用任何其他适配器——而不会让库名变得过时。命名在这里不是描述功能,而是定义位置。位置一旦确定,实现可以变化,但名字保持不变。
Ring的成功命名产生了示范效应。在它之后,Clojure社区中出现了一批以抽象概念而非具体实现命名的库。这些名字通常是一个英语单词,词义与它所服务的领域相关,但又不直接等同于任何技术术语。它们像是一个个占位符,在生态系统中占据了一小块语义空间。一旦一个名字占据了一个位置,其他库就很难再进入同一语义空间。这不是因为有什么规则禁止重复,而是因为重复会造成混淆,而混淆会让使用者却步。
命名在这里成为一种稀缺资源的竞争。好的名字——短小、好记、语义准确——是有限的,而先到者占据了最好的那些。回到core.async的争议。
支持“core.async”的一方则援引了另一种逻辑。他们指出,async这个词本身太泛了。在Java生态中,有大量以async命名的库和工具。如果只用“async”,这个库可能会在依赖列表中与其他语言的同名库混淆,也可能在搜索时被淹没。
更重要的是,前缀“core”在这里承担着一个明确的信号功能:它告诉使用者,这个库是由Clojure核心团队维护的,它的设计理念和语言本身保持一致,它的稳定性有某种隐性的承诺。在Clojure的命名惯例中,“core”前缀是一个特殊的标记。它最初出现在Clojure自身的核心命名空间中——clojure.core——那是语言内置函数的所在。当这个前缀被用在库名上时,它暗示着一种靠近中心的距离感。
不是所有库都能叫core.xxx,只有那些与语言设计理念紧密相关、由核心贡献者维护、或者实现了语言本身未能内置的功能的库,才有资格获得这个前缀。这场争议持续了大约两周。在邮件列表的线程里,双方都提出了自己的理由,都引用了社区已有的实践,都试图证明自己的立场更符合Clojure的精神。没有投票,没有最终裁决。里奇·希基在几轮讨论后重申了他的选择,解释了他为什么认为core.async是合适的名字。他没有宣告“这是最终决定”,但他继续使用这个名字,在后续的文档和演讲中只用这个名字。社区的讨论逐渐平息。core.async保留了下来,前缀“core”从此成为一个更明确的信号,被后续的库——core.cache、core.logic、core.match——所沿用。那些曾经主张“async”的人没有继续抗议。
他们中的大多数后来在各自的代码里使用了core.async这个名字,就像他们使用Ring、Compojure、Leiningen一样。共识不是通过胜利达成的,而是通过时间的流逝和持续的使用累积起来的。
但这场争议的真正意义不在于谁赢了。它在于争议本身让命名惯例变得可见。在争论的过程中,双方都必须说清楚他们的假设——库名应该短小,核心库应该被标记——这些假设在争议之前是不言自明的,是社区成员在模仿和实践中内化的。争议把它们从背景中拉出来,迫使社区正视它们的存在,并做出一个有意识的选择。
这个选择一旦做出,就不再是隐形的惯例,而是一个被讨论过、被挑战过、最终被保留的共识。它比那些从未被争议过的惯例更坚固,因为它已经承受过压力。那些没有承受过压力的惯例,可能会在未来的某个时刻突然断裂。而那些承受过压力的,会被社区记住。
core.async的命名争议被记住的方式,不是通过一份会议纪要或一份正式决议,而是通过那份“非正式命名指南”中的一个条目。在2012年11月的那次修改中,创建者添加了一条关于“core”前缀的说明:如果一个库被核心团队维护且与语言设计理念紧密相关,前缀“core”是合适的;否则,应该避免使用这个前缀以免造成混淆。这条说明没有援引那场争议,但它就是那场争议的产物。争议被压缩成一条建议,安静地躺在文档里,等待着下一次被引用。
那些因命名“不够Clojure”而被冷落的库,则没有留下这样的记录。它们的故事只能从Clojars的仓库日志和邮件列表的搜索中拼凑出来。
一个典型的案例是某个在2011年发布的Web工具库。它的创建者给它起了一个包含Java风格包名结构的名字,类似于“com.example.webtool”这样的格式。这个库在Clojars上发布了,但几乎没有被讨论过。
在邮件列表里搜索它的名字,只能找到寥寥几条提及,其中一条是一位社区成员在回答新手提问时写下的回复。那个回复没有评价那个库的好坏,没有说“不要用它”,只是说“你可以看看Ring”。没有否定,只有指引。指引的方向是那个已经占据标准位置的库,而那个偏离了命名惯例的库,就这样被留在视野之外。
这种“无声的排斥”是共识机制最微妙也最有效的运作方式。它不制造冲突,不留下痕迹,不提供申诉的机会。它只是让某些东西消失在视野之外。
在Clojars的仓库里,那些被冷落的库并没有被删除,它们继续存在,继续可以被下载,继续在技术意义上是一个合法的Clojure库。但在社区的意义上,它们不是。它们缺少那个让一个库成为“Clojure库”的标记——那个由命名惯例所赋予的识别符号。
这个识别符号不是官方授予的,不是由某个权威机构认证的,而是由社区成员在日常使用中反复确认的。每一次引用、每一次推荐、每一次在文档中列出依赖项,都是一次确认。
那些没有获得确认的库,不是被否定了,而是被忽略了。而忽略,在生态系统中,比否定更致命。
这种忽略机制的存在,解释了为什么命名惯例在Clojure社区中如此重要,尽管它们从未被正式化。在缺乏中央权威的情况下,命名是少数几个能够自发形成秩序的工具之一。它不依赖于任何人的许可,但它的效果却具有很强的约束力。一个库的名字决定了它在生态中的可见性,而可见性决定了它的使用率,使用率决定了它的贡献者数量,贡献者数量决定了它的长期存活力。名字看上去是表面的事,但它触及的是一个库的生存机会。
Clojars的松散治理结构在这一点上扮演了关键角色。如果有一个中央包管理器在审核库名,命名规范可能会通过行政手段强制执行。但Clojars没有这样做。它允许任何名字被发布,允许重复,允许混乱。这种放任在短期看来是混乱的,但在长期中却催生了自发的秩序。因为任何人都可以发布任何名字,所以好名字很快就会被占满。
那些后来者如果想要获得可见性,就必须在命名上做出更聪明的选择。他们不能依赖官方推荐,只能通过模仿成功案例来学习。
这种学习过程本身就是共识机制的一部分:规范不是被教会的,而是被观察到的。观察需要样本。在2010年到2012年之间,样本的数量在快速增长。Clojars上的库从几百个增加到几千个,邮件列表里的讨论从每天几封增加到几十封。
新成员在进入这个生态系统时,面对的是一个已经形成了初步秩序的名字空间。他们看到Ring、Compojure、Leiningen、Enlive这些名字,看到它们是如何被使用的,看到它们在文档中被引用的方式,看到那些模仿它们的名字获得关注,而那些偏离它们的名字被忽略。他们不需要被说服,他们只需要被展示。展示本身就是教育。这种教育的效果在2012年末已经很明显。新发布的库的名字呈现出更强的收敛性。那些可能引起混淆的名字——与Java类名冲突的、使用大写驼峰式的、包含技术缩写而不加解释的——越来越少。
取而代之的是一批风格统一的新名字:短小、具象、英语常用词,或者基于英语词根构造的、听起来自然的新词。这些名字不需要解释,因为它们遵循着一个已经被默认接受的模式。
这个模式的形成过程,就是那份“非正式命名指南”所试图记录的东西。但当指南在2012年11月被最后一次修改时,它所记录的模式已经不需要指南来维护了。它已经内化在社区成员的命名选择中,变成了他们判断一个新库是否“看起来像Clojure库”的直觉。
这种直觉的力量,在随后的岁月里会被反复验证。当新的语言特性被提出,新的库被创建,新的命名争议浮现时,社区成员会援引这种直觉来论证自己的立场。他们会说“这听起来不像Clojure的名字”,或者“这个名字符合我们一贯的风格”。他们不会引用那份指南,因为他们中的大多数人从未读过它。他们引用的是他们在使用Clojure的过程中积累起来的审美判断。这种审美判断是共识机制最深层的产物:它不是被教授的,而是被熏陶的;不是被规定的,而是被内化的。
但内化也有它的代价。当规范变得过于内化,它就开始排斥那些不熟悉它的人。
在2012年之后,Clojure社区的新成员增长速度在各个阶段有所不同。那些没有经历过早期命名争议、没有参与过邮件列表讨论、没有在线下会议的圆环上坐过的新成员,他们在面对这个已经高度收敛的命名生态时,会感到一种无形的压力。他们不知道什么样的名字是“Clojure的”,他们只能通过观察来学习。但如果观察的样本太多,他们可能会迷失;如果样本太少,他们可能会模仿错误的对象。
这个压力在2012年还只是一个微小的信号,但它的存在预示着未来那些更棘手的挑战:当社区规模继续扩大,当新成员的比例超过某个阈值,那些内化的规范是否还能通过潜移默化的方式传递下去?在2012年11月4日,那份文档的最后一次修改中,创建者添加了一条新的建议:“如果你不确定如何命名你的库,看看那些被广泛使用的库的名字,感受一下它们的风格。”他没有说“遵守这些规则”,也没有说“遵循这些示例”。
他说的是“感受一下”。这个词的选择是精准的。它承认了命名规范无法被穷尽地列出,只能被感受、被内化、被直觉地把握。
但“感受”是一个需要时间、需要接触、需要沉浸在社区中才能获得的能力。对于那些刚刚加入社区的人来说,“感受一下”是一句过于模糊的指引。它留出了空间,但那个空间在未来的日子里,会成为焦虑和争议的源头。
这份文档被保存在一个文件夹里,文件夹的名字是“naming”。它记录的不是命名的终局,而是一个阶段。在这个阶段,Clojure社区的命名惯例从邮件列表的日常讨论中浮现出来,通过示范和模仿传播,在几次争议中被明确化,最终沉淀为一份非正式的记录。这个过程的每一个环节——讨论、示范、模仿、争议、记录——都是共识机制的组成部分。它不依赖权威,不依赖投票,不依赖成文的规则。它依赖的是社区成员在命名这个最基本的日常决策中,一次又一次地做出相似的选择,直到那些选择成为一种不言自明的共识。
那些选择被记录在那份文档里,但文档本身也是选择的一部分。它被创建,被修改,被保留,但从未被赋予“官方”的地位。它的存在是一个标记,标记着社区在2010年到2012年之间走过的那段路。在它的最后版本里,那些建议安静地排列着,每一行都对应着一次讨论、一次争议、一次选择。它们加起来,构成了Clojure库生态中命名惯例的雏形。而在更深处,它们构成了一个更基本的图景:一个社区如何在没有任何人有权命令的情况下,竟然在命名这件事上达成了一致。
一致不是终点。在2012年11月之后,新的库会继续出现,新的命名争议会继续发生,新的惯例会继续在旧的基础上生长。
但那些后来的争议,都将在一个已经被命名惯例所塑造的生态中展开。它们面对的不再是一张白纸,而是一个已经布满了名字的版图。名字与名字之间有着微妙的关系,有的相互支撑,有的相互排斥,有的留出了空间,有的占满了位置。这个版图是社区共同绘制的,但没有人拥有它的所有权。
它是一份隐形的宪法,在Clojars的仓库里、在邮件列表的线程里、在每一个新建项目的第一个提交里,默默地被执行着。而那份最初的指南,在完成它的最后一次修改后,被留在文件夹里,再没有被更新过。它的创建者后来转向了其他的项目,社区中也没有人接过维护它的任务。它过时了,但它的意义不在于持续更新,而在于它曾经存在过。
它是那个时期的见证,见证了命名如何从个人的审美偏好,变成社区的共享惯例,再从惯例变成一种可以被记录、被讨论、被传给后来者的东西。这个东西没有名字,没有权威,没有强制力,但它构成了Clojure生态中一个不可见的骨架。库的名字来来去去,但命名的方式留下来,像一个沉默的法官,裁定着每一个新名字是否属于这个社区。在2012年11月4日之后,这份文档的最后一个版本安静地躺在文件夹里,文件名为“clojure-naming-guide”。它的创建者再也没有打开过它。
社区中偶尔有人会在邮件列表里问起这份指南的下落,得到的回答通常是“它已经过时了”或者“你看看那些流行的库就知道了”。那些回答没有错,但它们也回避了一个问题:如果指南过时了,那替代它的是什么?是那些流行的库本身吗?还是那些库所承载的、已经被内化到不需要被写下来的审美判断?这个问题在2012年末还没有被明确地提出,但它已经潜伏在那些回答的缝隙里,等待着在未来的某个时刻重新浮现——就像圆环上那个始终敞开的缺口一样。