在软件系统的演进过程中,命名往往被视为一种事后修饰 —— 先实现功能,再取个合适的名字。然而,这种观念忽视了命名本质上是契约的声明。一个函数、类或模块的名称不仅标识了它的存在,更向使用者承诺了特定的语义边界和行为预期。当命名模糊时,契约便失去约束力,系统的可组合性随之崩塌。
命名的契约本质
Eric Normand 在探讨可组合抽象时提出了一个关键洞察:优秀的抽象系统(如牛顿力学)与混乱的概念堆砌(如亚里士多德物理学)之间的根本区别在于概念的精确性与组合性。亚里士多德为每一个 "用户故事" 都发明一个新概念 ——"理想速度" 解释大小物体的运动差异,"自然位置" 解释物体为何停留在某处 —— 这些概念彼此孤立,无法组合。而牛顿仅用质量、速度、加速度三个基本概念,通过数学关系构建出可推导、可组合的理论体系。
命名在代码中扮演着类似的角色。当开发者面对一个名为 processData 的函数时,他们无法从中提取任何关于输入约束、副作用范围或返回语义的承诺。这种模糊命名迫使使用者深入实现细节才能理解 "契约" 内容,破坏了抽象存在的意义。
从物理隐喻到数学语义
构建可组合抽象的可靠路径遵循三步过程:物理隐喻 → 数学语义 → 实现。
物理隐喻是第一步的锚点。Normand 建议从现实世界的共享经验中寻找灵感 —— 剪纸、绘画、建筑等物理活动都蕴含着可直观理解的组合规则。例如,将图形系统建模为 "彩色剪纸" 的隐喻,自然地引出了叠加顺序、旋转独立性等可讨论的特性。物理隐喻的价值在于它提供了 "常识性" 的验证标准:当代码行为与物理直觉冲突时,问题往往出在抽象设计而非实现细节。
第二步是将物理直觉转化为精确的数学语言。这一步骤要求定义类型、函数签名以及组合规则。关键在于先定义组合方式,再定义事物本身。以图形变换为例,应该首先明确 "平移后旋转" 与 "旋转后平移" 是否等价,并据此约束系统行为,而不是先实现变换再考虑组合问题。
第三步才是实现。此时,契约已经明确,实现只是对语义的忠实表达。这种 "契约先行" 的方法使得重构成为安全的操作 —— 只要不改变外部行为(即契约),内部实现可以任意调整。
可组合接口的命名策略
接口与契约是两个不同层次的概念。接口定义编译时的结构边界 —— 方法签名、参数类型、返回类型;契约则定义运行时的保证 —— 幂等性、错误语义、性能预期、版本兼容性。命名策略需要同时反映这两个层次的信息。
有效的命名应该回答三个问题:
1. 这是什么抽象层次?
使用命名空间前缀或后缀区分抽象层次。例如,PaymentService 暗示这是一个服务层抽象,而 PaymentProcessor 可能指向更底层的处理逻辑。I 前缀(如 IPaymentGateway)明确标识接口类型,避免与实现类混淆。
2. 它承诺了什么契约?
动词选择至关重要。fetch 暗示可能涉及网络调用和延迟,get 暗示本地快速访问,calculate 暗示纯计算无副作用。validate 与 check 的区别在于前者通常会在失败时抛出异常,后者则返回布尔值。
3. 它如何与其他组件组合?
可组合性要求组件之间保持正交。命名应该避免隐含的上下文依赖。例如,processUserOrder 隐含了用户上下文,而 processOrder(userId, orderData) 则明确了依赖关系,使得函数可以在更广泛的场景中复用。
边界情况与命名的关系
边界情况(corner cases)是组合性的敌人。每一个边界情况都会在组合时产生分支,两个带有边界情况的组件组合会产生四个分支,三个则产生八个。命名模糊往往掩盖了边界情况的存在。
当 overlay 操作接收两个图形参数时,"叠加后的颜色是什么" 成为一个边界问题。与其在实现中处理这个特殊情况,不如在命名和类型设计阶段就消除它 —— 将 overlay 的参数从 "两个图形" 改为 "一个拼贴画和一个图形",拼贴画天然地表达了叠加顺序,消除了颜色歧义。
这种 "在命名中消除边界" 的策略要求开发者对领域概念有深入理解,并愿意为了组合性而调整抽象边界。
落地检查清单
在代码审查中,可以通过以下问题检验命名的契约清晰度:
-
调用者能否仅凭名称和签名推断出前置条件? 例如,
sendEmail(to, subject, body)的调用者应该明确to是否需要预先验证格式。 -
名称是否暗示了副作用范围?
updateCache与invalidateCache的区别在于前者可能涉及网络调用,后者通常只是本地状态标记。 -
返回值语义是否明确?
findUser返回null与抛出异常的区别应该在命名中有所体现,或者通过返回类型(如Optional<User>)明确。 -
组合时是否会产生意外行为? 检查相同抽象层次的组件组合时,命名是否暗示了交换律、结合律等代数性质。
-
是否存在亚里士多德式的概念膨胀? 警惕为每个业务场景都创造新名词的倾向,检查是否可以用更基本的概念组合表达相同语义。
结语
命名作为契约,是软件设计中最具杠杆效应的决策之一。一个精确的命名可以在团队沟通、代码维护和系统演进中持续产生价值;而模糊的命名则会在每个调用点制造摩擦,累积为技术债务。
从物理隐喻出发,经由数学语义的精确化,最终落实到可验证的实现 —— 这一路径不仅适用于图形系统或物理模拟,也适用于业务逻辑、数据流和分布式架构。当命名真正成为契约的载体时,系统的可组合性便有了坚实的基础。
参考来源
- Eric Normand, "Building composable abstractions", 2016
- Enrico Piovesan, "Why Interfaces and Contracts are not the same and why that matters", 2025
内容声明:本文无广告投放、无付费植入。
如有事实性问题,欢迎发送勘误至 i@hotdrydog.com。