代码命名
核心发现:命名不是语法问题而是设计问题——它要求开发者在命名时完成对事物本质的理解,这是”命名是编程中最难的事”的根本原因。 信息源:Robert C. Martin《Clean Code》第2章、Peter Hilton 博客、Martin Fowler “Two Hard Things”、freeCodeCamp
一、“命名是编程中最难的事”——为什么?
Phil Karlton 的名言”计算机科学只有两件难事:缓存失效和命名”之所以广为流传,是因为它精准地颠覆了预期——命名看似是最简单的事,实际上却是最难的事。
命名难在哪里:
-
本质理解的要求:一个好的命名要求开发者理解这个事物的本质——它为什么存在、做什么、怎么被使用。如果你无法给一个变量取个好名字,往往意味着你对它所代表的概念理解还不够清晰。命名困难本质上是设计困难的表征。
-
持续的创意压力:编程需要日常性、高频率地创造名字。每天需要为变量、函数、类、模块、文件取几十上百个名字,这是对创造力的不间断要求。
-
心理承诺问题:取名意味着做出一个不是所有人都喜欢的承诺。代码中的名称就像学校里的昵称一样”粘性强”——一旦建立就很难改变。开发者重构时很少去重命名。
-
缺乏共识:对什么是好名字、什么是坏名字,整个行业缺乏统一标准。
Robert C. Martin 的总结:“一个聪明的程序员和一个专业的程序员之间的区别在于,专业的程序员理解清晰性是王道。专业程序员用他的能力做好事——写出其他人能看懂的代码。“
二、Clean Code 命名原则
Robert C. Martin 在《Clean Code》第 2 章提出的核心命名规则:
1. 意图揭示(Intention-Revealing)
名字应该回答三个大问题:为什么存在?做什么?怎么用?
// ❌ 坏名字——需要注释才能理解
int d; // elapsed time in days
// ✅ 好名字——名字本身就是解释
int elapsedTimeInDays;
int daysSinceCreation;
int fileAgeInDays;
检验标准:如果你的变量需要注释来解释它是什么,那就不是一个好名字。
2. 有意义的区分(Meaningful Distinctions)
// ❌ 数字后缀——完全不传达信息
String user1, user2;
void copyChars(char a1[], char a2[])
// ✅ 有意义的区分
void copyChars(char source[], char destination[])
3. 可发音(Pronounceable)
// ❌ 无法发音
Date genymdhms; // generation year/month/day/hour/minute/second
// ✅ 可以说出来
Date generationTimestamp;
4. 可搜索(Searchable)
单字母变量和魔法数字在代码库中不可搜索。循环计数器 i/j/k 是唯一可接受的单字母变量。
// ❌ 魔法数字
if (status == 5) { ... }
// ✅ 命名常量
const int TASK_COMPLETE = 5;
if (status == TASK_COMPLETE) { ... }
5. 避免编码(Avoid Encodings)
不要在名字中添加前缀、类型信息或其他编码:
// ❌ 匈牙利命名法 / 接口前缀
IUser, m_user, s_name, strName, bIsActive
// ✅ 干净的名字
User, name, isActive
6. 类名用名词,方法名用动词
- 类名:名词或名词短语(
Customer,Account,OrderParser),不用动词 - 方法名:动词或动词短语(
save(),deletePage(),calculateTotal()) - 访问器/修改器/谓词:遵循 JavaBean 标准——
getXxx(),setXxx(),isXxx()
三、不同语言的命名惯例
命名格式对照表
| 格式 | 样式 | 主流使用语言 | 适用场景 |
|---|---|---|---|
| camelCase | myVariableName | Java, JavaScript, TypeScript, Swift | 变量名、函数名、方法名 |
| PascalCase | MyClassName | C#, Java, TypeScript, Rust | 类名、接口名、类型名 |
| snake_case | my_variable_name | Python, Ruby, Rust, C | 变量名、函数名、文件名 |
| SCREAMING_SNAKE_CASE | MAX_RETRY_COUNT | 几乎所有语言 | 常量 |
| kebab-case | my-component-name | HTML, CSS, URL, YAML, CLI | HTML属性、CSS类名、URL路径 |
历史溯源
- snake_case 最早可追溯至 1960 年代末,在 1978 年的《The C Programming Language》中被广泛采用
- camelCase 随 Java(1995)和 JavaScript(1995)的流行而成为主流
- kebab-case 至少从 2012 年开始流行,随 Web 开发的兴起而普及
可读性研究
一项研究发现 snake_case 比 camelCase 更容易辨认——下划线使得单词边界更加清晰。但 camelCase 更紧凑,在长名字中占用更少空间。
各语言典型规范
| 语言 | 变量/函数 | 类/类型 | 常量 | 文件名 |
|---|---|---|---|---|
| Python (PEP 8) | snake_case | PascalCase | SCREAMING_SNAKE | snake_case.py |
| Java | camelCase | PascalCase | SCREAMING_SNAKE | PascalCase.java |
| JavaScript/TS | camelCase | PascalCase | SCREAMING_SNAKE | kebab-case.ts |
| Rust | snake_case | PascalCase | SCREAMING_SNAKE | snake_case.rs |
| Go | camelCase | PascalCase | camelCase | snake_case.go |
| Ruby | snake_case | PascalCase | SCREAMING_SNAKE | snake_case.rb |
| C# | camelCase (私有), PascalCase (公开) | PascalCase | PascalCase | PascalCase.cs |
四、好的命名 vs 坏的命名案例集
变量命名
| 坏名字 | 问题 | 好名字 |
|---|---|---|
d | 不传达任何信息 | elapsedTimeInDays |
list | 过于泛化 | activeUsers |
data | 意义虚无 | orderHistory |
temp | 逃避命名 | swapBuffer |
flag | 不知道标记什么 | isUserVerified |
user1, user2 | 数字区分无意义 | sender, recipient |
函数命名
| 坏名字 | 问题 | 好名字 |
|---|---|---|
process() | 做什么?不知道 | validatePayment() |
handle() | 处理什么? | handleLoginFailure() |
doStuff() | 完全无信息量 | syncInventoryData() |
calc() | 缩写损失含义 | calculateShippingCost() |
getData() | 太宽泛 | fetchUserProfile() |
布尔变量命名
布尔变量是最容易出好名字或坏名字的地方:
# ❌ 坏名字——读代码时无法形成自然语句
if status: # status 是什么?
if login: # 是名词还是动词?
# ✅ 好名字——读起来像英语句子
if is_active: # "如果是活跃的"
if has_permission: # "如果有权限"
if should_retry: # "如果应该重试"
if can_edit: # "如果能编辑"
信息源
核心参考
- Robert C. Martin,《Clean Code》第 2 章 Meaningful Names
- Peter Hilton — Why naming things is hard
- Martin Fowler — Two Hard Things
- Clean Code Summary (GitHub Gist)