命名约定大全:camelCase、snake_case及使用场景

7 min2026年6月6日

命名约定为何重要:代码的可读性契约

代码是写给人看的——这句话在命名约定的语境下尤为贴切。一致的命名约定就像一种无声的沟通协议:当你看到 getUserName 就知道它是一个函数/方法,看到 MAX_RETRY_COUNT 就知道它是常量,看到 UserProfile 就知道它是一个类或类型。这种约定省去了阅读实现细节的必要,让代码的结构意图一目了然。

不一致的命名是代码可维护性的隐形杀手。当一个项目中 get_user_name、getUserName、GetUserName 混用时,开发者在调用函数前必须先查证"这个模块用的是哪种风格",认知负担倍增。更严重的是,不一致会掩盖 bug——你以为 userId 和 user_id 是两个不同变量,结果它们引用同一个概念但在不同模块中命名不同。

命名约定不仅仅是"好看"的问题,它承载了语义信息。在 JavaScript/TypeScript 中,大写开头(PascalCase)通常表示构造函数或类,小写开头(camelCase)表示普通变量和函数。打破这个约定等于向其他开发者传递了错误的信号——这可能导致 new ordinaryFunction() 或直接调用 ClassAsFunction() 这样的误用。

团队协作中,命名约定是最基础也最容易产生分歧的规范之一。"用 camelCase 还是 snake_case?"这个问题可以引发旷日持久的争论。正确的做法是:遵循语言社区的惯例(Python 用 snake_case,JavaScript 用 camelCase),在项目初期用 linter 规则锁定约定,然后不再浪费时间讨论。约定本身是什么不重要,一致性才重要。

camelCase 与 PascalCase:JavaScript/TypeScript 的世界

camelCase(小驼峰)以小写字母开头,后续每个单词首字母大写:firstName、getUserById、isActive。这是 JavaScript、TypeScript、Java、C#(方法和局部变量)的标准命名风格。它的名字来源于单词中间凸起的大写字母像骆驼的驼峰。camelCase 的优势是紧凑——不需要分隔符,在 URL、JSON key、变量名中都很自然。

PascalCase(大驼峰)与 camelCase 的唯一区别是首字母也大写:FirstName、UserProfile、HttpClient。在 JavaScript/TypeScript 生态中,PascalCase 专门用于类名、接口名、类型名、React 组件名和枚举名。C# 更广泛地使用 PascalCase——公共方法和属性都用它。这种区分让你看到名字就能判断它是"类型级别"还是"值级别"的东西。

TypeScript/React 的具体约定:组件用 PascalCase(UserCard)、hooks 用 camelCase 且以 use 开头(useAuth)、常量用 SCREAMING_SNAKE(MAX_RETRIES)或 camelCase(取决于团队)、类型/接口用 PascalCase(不需要 I 前缀——TypeScript 官方不推荐 IUserProfile 这种 C# 风格)、文件名用 kebab-case(user-profile.tsx)或 PascalCase(UserProfile.tsx,React 社区常见)。

JSON API 的命名约定则有分歧:JavaScript 前端偏好 camelCase(userId),但很多后端 API 返回 snake_case(user_id)——特别是 Python/Ruby 后端。这导致了前后端之间频繁的命名转换需求。解决方案包括:在 API 层统一用 camelCase(前端友好)、用 transform 层自动转换、或使用 GraphQL 的显式字段映射。

snake_case 与 SCREAMING_SNAKE_CASE:Python/Rust/数据库

snake_case 用下划线分隔单词且全部小写:user_name、get_active_users、is_valid。它是 Python(PEP 8 规范)、Ruby、Rust、Elixir、数据库表/列名的标准风格。snake_case 的优势是:下划线提供明确的视觉分隔(特别是在等宽字体中),避免了"连续大写字母"的歧义问题(如 XMLParser 在 camelCase 中的困惑)。

SCREAMING_SNAKE_CASE(全大写蛇形)用于常量和环境变量:MAX_CONNECTIONS、DATABASE_URL、PI_VALUE。几乎所有编程语言都遵循这个约定——它是一个"跨语言共识"。全大写的视觉冲击力传达了"这个值不应该被修改"的语义。在 JavaScript 中,const API_BASE_URL = "..." 比 const apiBaseUrl = "..." 更清晰地表达了"这是一个应用级常量"。

Python 的 PEP 8 规范是 snake_case 使用的典范:变量和函数用 snake_case、类用 PascalCase、常量用 SCREAMING_SNAKE_CASE、受保护成员用 _leading_underscore、名称修饰用 __double_leading(name mangling)。Rust 更严格——编译器会对不符合命名约定的标识符发出警告(snake_case 用于函数/变量,PascalCase 用于类型/trait,SCREAMING_SNAKE 用于常量/static)。

数据库领域几乎统一使用 snake_case:表名 user_profiles、列名 created_at、索引名 idx_users_email。原因包括:SQL 标准不区分大小写(所以 camelCase 的大小写信息会丢失)、下划线在 SQL 查询中视觉清晰、历史惯例(早期数据库系统的标识符限制)。如果你的 ORM 需要在 JavaScript 的 camelCase 和数据库的 snake_case 之间映射,大多数 ORM 都内置了自动转换功能。

kebab-case:URL、CSS 与文件命名

kebab-case 用连字符分隔单词且全部小写:user-profile、font-size、my-component。它得名于单词被"串"在一起像烤肉串(kebab)。kebab-case 是 CSS 属性名(background-color、flex-direction)、HTML 属性(data-user-id)、URL slug(/blog/my-first-post)和很多前端项目文件名的标准风格。

为什么 CSS 选择 kebab-case 而不是 camelCase?历史原因:CSS 诞生时参考了 HTML 属性的风格(本身就是连字符分隔的),且连字符在 CSS 选择器中不会引起歧义(不像下划线可能与某些选择器语法冲突)。这导致了一个有趣的摩擦:在 JavaScript 中操作 CSS 属性时需要转换——element.style.backgroundColor 对应 CSS 的 background-color。

URL 和文件名偏好 kebab-case 的原因更实际:URL 不区分大小写(或不同服务器处理不一致),所以 camelCase 和 PascalCase 在 URL 中会丢失信息或导致 404。连字符被搜索引擎视为单词分隔符(有利于 SEO),而下划线不是。因此 /random-number-generator 比 /random_number_generator 或 /randomNumberGenerator 更适合作为 URL slug。

需要注意的限制:kebab-case 不能直接用作 JavaScript 标识符(连字符会被解释为减号),所以对象属性如果用 kebab-case 必须用方括号访问:obj["my-key"]。这在 CSS-in-JS、JSON 配置文件中经常需要处理。Vue.js 的一个有趣设计是组件可以用 PascalCase 注册但在模板中用 kebab-case 引用——框架自动处理转换。

// === 各种命名约定示例 ===

// camelCase - JavaScript 变量和函数
const userName = 'Zhang San';
function getUserById(userId) { /* ... */ }
const isAuthenticated = true;

// PascalCase - 类、类型、React 组件
class UserProfile { /* ... */ }
type HttpResponse = { statusCode: number };
function UserCard({ name }) { return <div>{name}</div>; }

// snake_case - Python 风格(在 JS 中常见于 API 响应)
const api_response = { user_name: 'zhang_san', created_at: '2024-01-01' };

// SCREAMING_SNAKE_CASE - 常量
const MAX_RETRY_COUNT = 3;
const API_BASE_URL = 'https://api.example.com';
const DEFAULT_TIMEOUT_MS = 5000;

// kebab-case - URL slug、CSS、文件名
// /api/user-profiles
// background-color: red;
// user-profile-card.tsx

// === 命名约定转换函数 ===
function camelToSnake(str) {
  // 处理连续大写字母(如 "XMLParser" → "xml_parser")
  return str
    .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
    .replace(/([a-z\d])([A-Z])/g, '$1_$2')
    .toLowerCase();
}

function snakeToCamel(str) {
  return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
}

function camelToKebab(str) {
  return str
    .replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2')
    .replace(/([a-z\d])([A-Z])/g, '$1-$2')
    .toLowerCase();
}

// 测试转换
console.log(camelToSnake('getUserName'));    // "get_user_name"
console.log(camelToSnake('XMLHttpRequest')); // "xml_http_request"
console.log(snakeToCamel('user_name'));      // "userName"
console.log(camelToKebab('backgroundColor')); // "background-color"

各语言与框架的命名惯例对照

JavaScript/TypeScript:变量和函数 camelCase、类和类型 PascalCase、常量 SCREAMING_SNAKE 或 camelCase、私有属性 #privateField(ES2022)或 _conventionalPrivate、React 组件 PascalCase、事件处理器 handleClick / onSubmit、CSS-in-JS 属性 camelCase。ESLint 的 @typescript-eslint/naming-convention 规则可以强制执行这些约定。

Python:一切遵循 PEP 8——变量/函数/方法 snake_case、类 PascalCase(不推荐用 C_ 或 T_ 前缀)、常量 SCREAMING_SNAKE_CASE、模块名 snake_case(短小为佳)、包名全小写无下划线。特殊约定:_single_leading 表示"内部使用"、__double_leading 触发 name mangling、__dunder__ 是魔术方法。pylint 和 black 可以自动检查和格式化。

Go 语言用大小写控制可见性——这是命名约定具有语法意义的罕见例子。首字母大写表示 exported(公开):UserProfile、GetName();首字母小写表示 unexported(包内私有):userData、helper()。Go 使用 camelCase/PascalCase,不使用 snake_case(除了测试文件名中的 _test.go 后缀)。缩写词保持全大写:HTTPClient、XMLParser、userID(不是 userId)。

CSS/HTML:属性用 kebab-case(background-color、data-user-id)、BEM 方法论用 block__element--modifier 格式(card__title--highlighted)、CSS 自定义属性用 --kebab-case(--primary-color)、Tailwind 类名用 kebab-case(text-lg、bg-blue-500)。Sass/LESS 变量用 $kebab-case 或 @kebab-case。

自动转换陷阱:缩写词与特殊情况

命名约定转换看似简单——"遇到大写字母就插入分隔符"。但实际中有大量边界情况会让天真的转换算法翻车。最典型的问题是缩写词(acronyms):XMLHttpRequest 应该转成 xml_http_request 还是 x_m_l_http_request?HTMLElement 是 html_element 还是 h_t_m_l_element?不同语言和库对缩写词的处理方式不同。

JavaScript 生态通常保持缩写词的大写整体性:XMLParser、HTMLElement、JSONResponse。但当缩写词在 camelCase 开头时变为全小写:xmlParser、htmlElement。Go 语言坚持缩写词全大写:HTTPServer、XMLAPI(但这导致了 XMLAPI 还是 XmlApi 的争论——Go 官方选择前者)。Python 在 snake_case 中一律小写:xml_parser、html_element,反而没有歧义。

数字的处理是另一个陷阱:utf8Decode 应该变成 utf_8_decode 还是 utf8_decode?vector3D 变成 vector_3_d 还是 vector_3d?OAuth2Token 变成 o_auth_2_token 还是 oauth2_token?没有统一标准——不同的转换库行为不同。最安全的做法是:在项目中约定数字的处理规则,并在命名中避免歧义组合(如用 Utf8Decode 而非 UTF8Decode)。

还有一些特殊模式需要注意:以 is/has/can 开头的布尔值(isActive → is_active)、以 get/set 开头的访问器、以 on/handle 开头的事件处理器。这些前缀在转换时应当保留语义完整性。此外,API 边界的自动转换(如 axios 的 transformResponse 自动将 snake_case JSON 转为 camelCase)可能导致 debug 困难——打断点看到的是转换后的名称,而 network panel 中显示的是原始名称。保持对这层转换的意识很重要。

团队规范:如何制定和执行命名约定

最重要的原则:命名约定应该是机器强制执行的,而不是靠人记忆和 code review 发现。ESLint 的 naming-convention 规则、Rust 编译器的内置警告、Python 的 pylint/flake8、Go 的 golint——这些工具能在代码编写时就给出反馈,比事后在 PR review 中指出高效得多。将 linter 配置为 CI 的必通过检查,约定就自动执行了。

制定约定时优先遵循语言社区的主流风格。不要在 Python 项目中用 camelCase,也不要在 JavaScript 项目中用 snake_case——这会给新成员带来不必要的认知负担。社区约定的价值在于它的广泛共识性:任何有经验的 Python 开发者打开一个 PEP 8 风格的项目都能立刻感到熟悉。只有当存在强烈的技术理由时(如与特定 API/数据库的一致性需求)才偏离社区惯例。

跨语言项目需要明确的边界规则。前后端分离项目中,API 的 JSON 字段命名是关键决策点:统一用 camelCase(前端友好)还是 snake_case(后端友好)?最佳实践是选择一种并配合序列化层自动转换——比如后端 Python 内部用 snake_case 但 API 响应统一输出 camelCase,前端直接使用不需要转换。GraphQL schema 中推荐 camelCase。

文档化你的命名约定,但不要过度——一页 Markdown 足够。内容应包括:各类标识符的命名风格(变量、函数、类、常量、文件)、缩写词的处理方式、常见前缀/后缀的使用规则(如 is/has 前缀用于布尔值)、对应的 linter 配置文件路径。新成员入职时只需要"看 linter 配置"和"看现有代码"就能理解规范——不需要背诵文档。

// === ESLint 命名约定配置示例 ===
// .eslintrc.js 中的 @typescript-eslint/naming-convention 规则
module.exports = {
  rules: {
    '@typescript-eslint/naming-convention': [
      'error',
      // 默认:所有标识符用 camelCase
      { selector: 'default', format: ['camelCase'] },
      // 变量允许 camelCase 和 UPPER_CASE(常量)
      { selector: 'variable', format: ['camelCase', 'UPPER_CASE', 'PascalCase'] },
      // 类型和接口用 PascalCase
      { selector: 'typeLike', format: ['PascalCase'] },
      // 枚举成员用 PascalCase 或 UPPER_CASE
      { selector: 'enumMember', format: ['PascalCase', 'UPPER_CASE'] },
      // 布尔变量必须以 is/has/can/should 开头
      {
        selector: 'variable',
        types: ['boolean'],
        format: ['PascalCase'],
        prefix: ['is', 'has', 'can', 'should', 'will'],
      },
      // 私有成员以下划线开头(可选)
      {
        selector: 'memberLike',
        modifiers: ['private'],
        format: ['camelCase'],
        leadingUnderscore: 'require',
      },
      // 解构的变量不强制(因为可能来自外部 API)
      {
        selector: 'variable',
        modifiers: ['destructured'],
        format: null,
      },
    ],
  },
};

// === 实际项目中的命名示例 ===
// ✅ 一致且符合约定的代码
const MAX_RETRIES = 3;                    // 常量:SCREAMING_SNAKE
const isAuthenticated = false;            // 布尔值:is 前缀
class UserRepository { /* ... */ }        // 类:PascalCase
type ApiResponse<T> = { data: T };       // 类型:PascalCase
function fetchUserProfile() { /* ... */ } // 函数:camelCase

// ❌ 不一致的代码(容易引发困惑)
const max_retries = 3;       // JS 中不应使用 snake_case
const Authenticated = false; // 首字母大写暗示这是类/组件
class user_repo { /* ... */ } // 类不应该是 snake_case