kebab-case、snake_case、camelCase、PascalCase 和 CONSTANT_CASE 解决的是同一个问题:怎样在没有空格的情况下写出多个单词。但每种写法都有自己的「地盘」:网址和 CSS 用连字符,Python 用下划线,JavaScript 用驼峰。用错了地方,轻则代码评审里满是改名意见,重则出现 bug 或失效链接。本指南把各种命名风格并排对比,解释网址为什么用连字符,依据 MDN 和 PEP 8 列出各语言、各文件格式习惯用哪种风格,并讲清最容易出错的缩写词与数字。文中所有转换结果都由我们的 Kebab Case 转换器 实际跑出,两段示例代码也在测试里与转换器逐条比对过。

提示

简短回答

  • 网址用 kebab-case。 Google 建议网址中用连字符而不是下划线分隔单词;全部小写还能避免重复网址。
  • CSS 和 HTML 用 kebab-case。 background-color 这样的属性和 aria-label 这样的特性名都用连字符。
  • Python 和 Rust 的函数、变量用 snake_case; Python 的类用 PascalCase(PEP 8 称为 CapWords)。
  • JavaScript 和 Java 的变量、函数用 camelCase, 类用 PascalCase。
  • 常量和环境变量用 CONSTANT_CASE。
  • 身处哪个生态就遵循哪个生态的约定, 在边界处做转换,比如 JSON 接口与代码之间。

命名风格一览

以短语「Summer Clothing Sale」(夏季服装促销)为例:

风格结果典型用途
kebab-casesummer-clothing-sale网址、CSS 类名、文件名、命令行参数
snake_casesummer_clothing_salePython 和 Rust 的函数与变量、数据库字段
camelCasesummerClothingSaleJavaScript 和 Java 的变量与函数、许多 JSON 接口
PascalCaseSummerClothingSale类和类型、React 组件
CONSTANT_CASESUMMER_CLOTHING_SALE常量、环境变量
dot.casesummer.clothing.sale配置键、翻译键、部分生态的包名

MDN 对 kebab case 的描述是:用连字符连接单词,通常全部小写;并指出在许多语言里连字符表示减号,所以 kebab case 不能用作变量名。在 JavaScript 里,summer-clothing-sale 的意思是 summer 减 clothing 再减 sale。snake case 从不包含大写字母;像 Number.MAX_SAFE_INTEGER 这样全部大写的写法,MDN 称之为「screaming snake case」。camel case 从第二个单词起首字母大写;如果第一个字母也大写,就叫大驼峰(upper camel case)或 Pascal case。

网址为什么用连字符

Google 的网址结构指南写得很明确:网址中用连字符而不是下划线分隔单词,这样有助于用户和搜索引擎识别网址中的概念。不推荐下划线是出于历史原因:下划线常用来表示应当连在一起的概念,比如各种编程语言里的函数名 format_date;而连字符清楚地表示单词边界。指南列出了同一网址的三种写法:

写法示例Google 的态度
连字符https://example.com/summer-clothing/filter?color-profile=dark-grey推荐
下划线https://example.com/summer_clothing/filter?color_profile=dark_grey不推荐
单词连写https://example.com/greendress不推荐

还有两个理由支持网址用 kebab-case:

  • 网址区分大小写。 Google 把 /APPLE 和 /apple 视为不同网址,所以驼峰式路径很容易产生 /SummerSale 与 /summersale 这样的重复页面。全小写的 kebab-case 没有这个问题。
  • 下划线在链接里会「消失」。 很多应用会给链接加下划线,_ 被遮住后,summer_sale 看起来就像 summer sale。

这也是我们的 URL Slug 生成器 只输出小写、连字符分隔的 slug 的原因。好 slug 的其他要点见 URL Slug SEO 指南。

各种风格在代码里的位置

Python(PEP 8)。 函数名和变量名小写,必要时用下划线分隔单词以提高可读性;类名通常用 CapWords;常量全部大写并用下划线分隔,例如 MAX_OVERFLOW;模块名简短、全小写,可以用下划线,但包名不鼓励用下划线。mixedCase(即 camelCase)只允许在已经是主流风格的地方使用,以保持向后兼容。

JavaScript 和 Java。 MDN 说 camelCase 是 JavaScript、Java 等许多语言里最流行的约定,内置名称就是例子:encodeURIComponent、ArrayBuffer、HTMLElement。类和 React 组件用 PascalCase。

Rust。 MDN 把 snake case 列为 Python、Rust 等语言里最流行的约定。

CSS 和 HTML。 background-color、font-family 等 CSS 属性是 kebab-case,aria-label 和 data-* 等 HTML 特性也是。在 JavaScript 里,element.dataset 会把 data-user-id 变成 userId,element.style 用的是 backgroundColor,同一个名字在边界处从 kebab-case 变成了 camelCase。

文件和命令行。 summer-sale.md 这样的 kebab-case 文件名在任何操作系统上都安全,在静态网站上也会直接变成干净的网址。命令行参数按惯例用连字符,例如 --dry-run。

数据。 JSON 接口各不相同:面向 JavaScript 的接口多用 camelCase,以 Python 或数据库为后端的接口多用 snake_case。每个接口选定一种,并在一个地方统一转换,最好放在序列化层或接口客户端里,这样业务代码只会看到一种风格。

缩写词与数字

缩写词是各种风格分歧最大的地方。MDN 指出 camel case 对缩写词的处理并不统一:有人保持缩写词全大写(encodeURIComponent),有人只大写首字母(XmlHttpRequest),而真实存在的 XMLHttpRequest 两种都用了。PEP 8 对 Python 类名表了态:「HTTPServerError 比 HttpServerError 好」。

转换器必须先把这类名字拆成单词,才能重新拼接。我们的转换器把一串连续大写字母当作一个单词,把「大写字母 + 小写字母」当作新单词的开头:

输入kebab-casesnake_casecamelCasePascalCase
XMLHttpRequestxml-http-requestxml_http_requestxmlHttpRequestXmlHttpRequest
getUserIDget-user-idget_user_idgetUserIdGetUserId
HTTPServerErrorhttp-server-errorhttp_server_errorhttpServerErrorHttpServerError
parseHTML5Docparse-html5-docparse_html5_docparseHtml5DocParseHtml5Doc
user_profile_v2user-profile-v2user_profile_v2userProfileV2UserProfileV2

注意 PascalCase 一列:转换器会把缩写词规范成 HttpServerError。如果你的 Python 类名遵循 PEP 8,就手动改回 HTTPServerError。数字会跟在前一个单词后面,所以 v2 和 html5 都作为一个单词保留。

这也是 slug 生成器和大小写转换器是两种工具的原因。slug 生成器把输入当作标题,XMLHttpRequest 只会变成一个单词 xmlhttprequest;大小写转换器把输入当作标识符,会找出其中的单词边界,得到 xml-http-request。页面标题用 slug 生成器,代码里的名字用 Kebab Case 转换器。

在代码里转换命名风格

下面两个函数对 ASCII 名字的拆分方式与我们的转换器相同,输出 kebab-case;我们在测试中用本指南的每个例子与转换器做了比对:

function toKebab(name) {
  return name
    .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
    .replace(/([A-Z])([A-Z][a-z])/g, '$1-$2')
    .split(/[^A-Za-z0-9]+/)
    .filter(Boolean)
    .map((w) => w.toLowerCase())
    .join('-');
}
toKebab('XMLHttpRequest'); // 'xml-http-request'
import re

def to_kebab(name):
    name = re.sub(r"([a-z0-9])([A-Z])", r"\1-\2", name)
    name = re.sub(r"([A-Z])([A-Z][a-z])", r"\1-\2", name)
    return "-".join(w.lower() for w in re.split(r"[^A-Za-z0-9]+", name) if w)
to_kebab("getUserID")  # 'get-user-id'

两个正则各管一种边界:第一步在「小写字母或数字 + 大写字母」之间插入连字符,处理 getUser 这样的驼峰;第二步在「一串大写字母」与「大写字母 + 小写字母」之间插入连字符,把 XMLHttp 拆成 XML 和 Http。之后再按非字母数字字符切分、转小写、用连字符拼接。两步的顺序要与转换器保持一致,结果才能逐字相同。

想要 snake_case,把 - 换成 _ 拼接即可。名字含非 ASCII 字符、或者用于生产代码时,请使用经过测试的库或直接用转换器,不要依赖自己手写的正则。要把页面标题变成网址 slug,请用我们 代码片段指南 里的 slugify 代码,它还能处理重音字母和 &。

注意

常见错误

  • 网址里用下划线或驼峰。 用小写 kebab-case,Google 推荐连字符。
  • 变量名用 kebab-case。 在大多数语言里 user-id 是一个减法。
  • 同一接口混用风格。 userId 和 created_at 放在一起,每个调用方都得猜。
  • 忘了在边界处转换。 data-user-id 在 JavaScript 里是 dataset.userId。
  • 缩写词完全交给转换器。 按你的风格指南核对 HTTPServerError 与 HttpServerError。
  • 为了「统一风格」改网址却不做跳转。 把 /Summer_Sale 改成 /summer-sale 需要 301 跳转。

可复制的命名风格检查清单

[ ] 网址和 slug:小写 kebab-case
[ ] CSS 类名、文件名、命令行参数:kebab-case
[ ] Python 和 Rust 的函数/变量:snake_case
[ ] JavaScript 和 Java 的变量/函数:camelCase
[ ] 类、类型和组件:PascalCase
[ ] 常量和环境变量:CONSTANT_CASE
[ ] 每个接口只用一种风格,并在一处统一转换
[ ] 缩写词已按风格指南核对
[ ] 改过的网址已配好 301 跳转

用工具转换命名

把名字粘贴到 Kebab Case 转换器,每行一个,即可同时看到 kebab-case、snake_case、camelCase、PascalCase、CONSTANT_CASE 等多种写法。页面标题请用 URL Slug 生成器;修正旧的下划线或大写网址时,用 301 重定向生成器 生成跳转规则。

参考资料

FAQ

常见问题

网址用 kebab-case 还是 snake_case 更好?

kebab-case。Google 建议网址中用连字符而不是下划线分隔单词,因为连字符清楚地标出单词边界,而下划线常用来把概念连在一起(比如函数名)。另外网址区分大小写,记得保持全部小写。

为什么变量名不能用 kebab-case?

在大多数编程语言里连字符就是减号,user-id 的意思是 user 减 id。变量名请根据语言用 snake_case 或 camelCase,kebab-case 留给网址、CSS 和文件名。

Python 用哪种命名风格?

PEP 8 规定函数、变量和模块用 snake_case,类用 CapWords(即 PascalCase),常量用 CONSTANT_CASE。camelCase 只允许在它已经是主流风格的地方使用。

camelCase 里的缩写词该怎么写?

没有统一做法。JavaScript 自带的名字就混用了两种风格(XMLHttpRequest、encodeURIComponent),PEP 8 则推荐 Python 类名写成 HTTPServerError。许多风格指南把缩写词当普通单词处理,写成 xmlHttpRequest。为你的代码库定一条规则并全部遵守即可。