在分布式系统的架构设计里,API 接口不仅仅是系统之间传递数据的通道——它更像是连接不同技术栈、组织文化乃至开发时代的契约。而在 RESTful API 的众多设计细节中,有一个看似微不足道、却能引发无休止争论的话题:
JSON 字段名,究竟该用驼峰式(camelCase)还是下划线式(snake_case)?
你可能会觉得这不过是审美偏好,但说穿了,它触及了后端持久层与前端表现层之间的"阻抗失配",更牵涉到序列化性能、网络传输效率、开发者体验(DX)和认知心理学。这个话题的复杂程度,远超一个简单的命名选择。
接下来,我们会从编程语言的演进史、底层技术实现机制入手,再参考 Google、Stripe 等行业巨头的架构决策,帮你理清这场争论背后的逻辑。
历史溯源:符号学的选择
要理解这场争论,得先往回看几十年。命名规范从来不是凭空产生的,而是特定时代硬件限制与社区文化共同塑造的结果。
snake_case 的起源:C 语言与 Unix 哲学
snake_case(比如 user_id)的流行可以追到 70 年代的 C 语言和 Unix 时代。那时的键盘设备(比如 Teletype Model 33)虽然有大写键,但很多早期编译器对大小写并不敏感。为了在低分辨率显示器上清晰地拆分单词,程序员们引入了下划线来模拟自然语言里的空格。这种做法后来深深植根于 SQL 数据库标准中——直到今天,PostgreSQL 和 MySQL 的默认列名风格依然是下划线,这也为后来 API 与数据库之间的字段映射埋下了伏笔。
camelCase 的崛起:Ja va 与 Ja vaScript 的霸权
camelCase(比如 userId)则随着面向对象编程的兴起而流行起来,尤其是 Smalltalk、C++ 和 Ja va。Ja va 确立了"类名大驼峰,方法/变量名小驼峰"的工业级标准。
决定性的转折点在于 Ja vaScript 的诞生。JSON 本身就是从 JS 对象字面量衍生出来的,JS 标准库——比如 getElementById——全面采用驼峰命名。随着 AJAX 和 JSON 逐步取代 XML 成为数据交换的霸主,camelCase 在 Web 领域也就获得了"原生"的地位。
核心冲突:技术栈的阻抗失配
当数据在不同语言之间流转时,阻抗失配几乎是无法避免的。
后端视角的惯性(Python/Ruby/SQL)
- Python/Django/FastAPI:Pydantic 模型通常直接对应数据库表结构。
class UserProfile(BaseModel):
first_name: str # 符合 Python 习惯
last_name: str
如果 API 强制要求返回 camelCase,那么序列化层就不得不配置别名或者转换器,引入额外的映射逻辑。
- SQL 数据库:绝大多数数据库列名使用的是 snake_case。API 如果采用 camelCase,就意味着 ORM 层必须始终承担转换工作。
- Stripe 的选择:它的早期架构构建在 Ruby 栈上,直接暴露内部模型可以保持后端的一致性,因此一直坚持使用 snake_case。
前端视角的统治(Ja vaScript/TypeScript)
- 代码风格割裂:如果 API 返回 snake_case,就会破坏前端代码风格。
const user = await fetchUser();
console.log(user.first_name); // 违反 ESLint camelcase 规则
render(user.email_address);
- 解构赋值的痛点:字段是 snake_case 时,需要进行繁琐的重命名操作。
const { first_name: firstName, last_name: lastName } = response.data;
这不仅仅是增加了样板代码,还显著提高了出错概率。
性能迷思:序列化与网络传输
关于性能,先澄清两个常见误区——
误区一:运行时转换开销
- 动态语言:Python 或 Ruby 里,频繁的正则转换确实会消耗 CPU,但现代框架(比如基于 Rust 的 Pydantic v2)通过预计算 Schema 映射,已经把开销降到了极低。
- 静态语言(Go/Ja va/Rust):转换几乎零成本,运行时不过是简单的字节拷贝。
要特别注意:前端浏览器主线程上不要做全局递归转换,否则很容易导致页面掉帧和内存抖动。
误区二:传输体积差异
从字面看,first_name 确实比 firstName 多一个字符。但开启 Gzip 或 Brotli 压缩后,这个差异几乎可以忽略。
- 压缩算法会利用重复的字符串引用——JSON 数组里的键是高度重复的,后续只需要用短指针代替。
- 实测表明:在 Gzip 级别 6 下,两种风格的 JSON 体积差异通常只有 0.5%–1%。
所以结论很清楚:传输体积不是问题,转换的工作应该由后端来承担。
开发者体验与认知心理学
- snake_case 的可读性优势:下划线提供了清晰的视觉分隔,处理连续缩写词时更容易解析。比如
parse_db_xml就比parseDbXml更容易被快速理解。 - camelCase 的一致性优势:对于全栈 JS/TS 团队来说,统一使用 camelCase 可以直接复用类型定义(比如 Interface 或 Zod Schema),消除"脑内翻译"的认知负荷。
行业巨头的标准与选择逻辑
| 公司 | 推荐风格 | 背景与逻辑 |
|---|---|---|
| camelCase | API 指南(AIP-140)强制 JSON 使用小驼峰,即使内部 Protobuf 是 snake_case,也会自动转换。 | |
| Microsoft | camelCase | 随着 .NET Core 和 TypeScript 的发展,全面转向 Web 标准。 |
| Stripe | snake_case | Ruby 栈公司,通过完善的 SDK 屏蔽了差异——Node SDK 传输时仍用下划线,但方法签名符合 JS 习惯。 |
| JSON:API | camelCase | 社区规范推荐,也是 Web 社区的共识。 |
深度架构建议:解耦与 DTO
需避免的反模式是:直接透传数据库实体——这样会导致 API 与数据库强耦合,违反信息隐藏原则。
最佳实践:引入 DTO(Data Transfer Object)层。
- 无论数据库的命名方式如何,都定义独立的 API 契约(DTO)。
- DTO 可以统一使用 camelCase,现代映射工具(比如 MapStruct、AutoMapper、Pydantic)可以轻松处理转换。
说白了,这才是真正的解耦思路——把内部实现细节与外部契约剥离干净。
面向未来:GraphQL 与 gRPC
- GraphQL:几乎 100% 使用 camelCase。如果 REST API 也采用 camelCase,就可以实现前瞻性的兼容。
- gRPC:Protobuf 文件的字段定义使用的是 snake_case,但 JSON 映射时必须转为 camelCase——这是 Google 在多语言环境下的标准做法。
总结与决策矩阵
推荐方案
- 默认使用 camelCase:面向 Web 或移动端 App 的新建 RESTful API,顺应 JSON/JS/TS 的统治地位,获得 IDE 和代码生成器的最佳支持。
何时使用 snake_case?
- API 的主要使用者是 Python 数据科学家或系统运维人员(Curl/Bash)。
- 遗留系统已经使用 snake_case,一致性优先。
- 极度追求后端开发速度,且没有资源维护 DTO 层。
决策一览表
| 维度 | 推荐风格 | 说明 |
|---|---|---|
| Web 前端 / 移动端 App | camelCase | 零阻抗,类型安全。 |
| 数据分析 / 科学计算 | snake_case | 符合 Python/R 的习惯。 |
| Node.js / Go / Ja va 后端 | camelCase | 原生支持或库支持完美。 |
| Python / Ruby 后端 | camelCase(建议配置转换器)或 snake_case(内部工具) | 灵活选择。 |
| 团队全栈化程度越高 | 越推荐 camelCase | 前后端统一,减少认知负荷。 |
结语:API 设计的本质是同理心。在 Web API 的语境下,把复杂性封装在后端,把便捷性留给使用者——这才是一个专业且务实的选择。
