2.1 命名为什么是英语难题,不是设计难题
大多数中国程序员都听过 "命名是计算机科学两大难题之一" 这句话(另一个是 cache 失效)。但这个 "难",对中文母语程序员来说有双重困难——它既是抽象建模的难(这个变量到底代表什么),又是英语词汇与语法的难(用什么词、什么时态、单复数、缩写、词序)。后者对我们来说可能比前者还难。
看一组真实代码片段:
// Bad — 中式英语 / 拼音 / 时态错误 / 缩写不规范
let userInfomation; // info 拼写错误
let users_list = []; // 蛇形 + 冗余复数
let isExist = checkIfExist(); // 应为 exists(第三人称单数)
let getuserInfo; // 大小写不一致 + 拼凑
let dataLst; // L 开头大写但 t 没有,缩写不规范
let lefttime; // 应为 timeLeft 或 remainingTime
// Good — 工程惯例
let userProfile;
let users = [];
let exists = checkExistence();
let getUserProfile;
let dataList;
let remainingTime;
这些都是英语层面的小问题,但累积起来会让你的代码"看起来不专业"。Reviewer 不会逐条指出,而是会形成一个整体印象:"这个人英语不太行"——后果是你后续的 PR 被审得更挑剔。
2.2 变量、函数、类的命名规范
词性规则
命名的词性是有强烈惯例的,违反惯例本身就是 "code smell":
| 对象 | 词性 | 例子 | 反例 |
|---|---|---|---|
| 变量 / 字段 | 名词或名词短语 | userName, orderTotal | getUser(动词不当变量名) |
| 布尔值 | 形容词或 is/has/can 开头的谓语 | isReady, hasPermission, canEdit | flag, status(含义不清) |
| 函数 / 方法 | 动词开头的祈使句 | fetchUser(), validateEmail() | userFetch()(语序错) |
| 返回布尔的函数 | is/has/can/should + ... | isValid(), hasChildren() | checkValid()(动作而非问题) |
| 类 / 接口 | 名词,PascalCase | UserRepository, OrderService | RunTask(动词命名类) |
| 常量 | 全大写名词 | MAX_RETRIES, DEFAULT_TIMEOUT | RETRY(含义不清) |
| 事件 handler | on + 名词 + 动词过去分词 | onUserCreated, onPaymentFailed | userCreate |
单复数
这是中国程序员最容易翻车的英语点,因为中文没有名词复数变化。
// Good
const users = await fetchUsers(); // 集合用复数
const user = users[0]; // 单个元素用单数
function getUserById(id) {} // 单数(按 id 取一个)
function findUsersByRole(role) {} // 复数(可能多个)
const emails = ['a@x.com']; // 数组永远复数
// Bad
const user = await fetchUser(); // 但实际返回数组
const userList = []; // 已经复数还加 List 是冗余
const datas = []; // data 本身就是复数(拉丁语)
data 在严格英语里是 datum 的复数,永远不要写 datas。日常工程中 data 已被作为不可数名词使用,复数语义靠上下文判断。同理:information, feedback, equipment, software 都不可数。
语义梯度:get / fetch / load / retrieve / find / read
这六个动词都能翻译成中文 "获取",但在工程语境里语义有明显梯度,乱用会让 reviewer 困惑:
| 动词 | 语义 | 典型用法 | 暗示成本 |
|---|---|---|---|
get | 从内存 / 简单结构里取 | map.get(key), obj.getName() | O(1),同步,无副作用 |
fetch | 跨网络请求 | fetchUser(id) via HTTP | 异步,可能失败 |
load | 从磁盘 / 资源载入 | loadConfig(), loadModel() | I/O,一次性 |
retrieve | 正式语,强调"取回" | retrieveSession() | 偏正式,常用于 API doc |
find | 查找,可能找不到 | findUserByEmail() | 可能返回 null / None |
read | 读取(流 / 文件 / 数据库) | readFile(), readRow() | 顺序、流式 |
所以 fetchUserFromMap() 是错的(map 是内存结构,应该用 get),getUserFromAPI() 也是错的(应该用 fetch)。这种语义错配是英语水平的隐藏体现。
命名长度 vs 信息量
普遍误区:以为名字越长越清楚。事实是名字应当随作用域大小调整:
// 局部短作用域 — 短名字反而清晰
for (const u of users) { print(u.name); }
const r = res.json();
// 类成员 / 模块级 — 中等长度
class UserService { fetchActive() {} }
const cachedUsers = new Map();
// 公开 API / 跨模块 — 完整描述
export function calculateMonthlyRevenueByRegion(region: string): number;
Linus Torvalds 在 Linux kernel coding style 里写的原则:"LOCAL variable names should be short, and to the point. If you have some random integer loop counter, it should probably be called i." 短作用域就该短名字,否则反而是噪声。
2.3 缩写规范:哪些可以缩,哪些不能
大小写规则
英语里的首字母缩写词(acronym)在代码里有两套写法,必须保持一致:
// 风格 A:全大写(Java / Go 标准库经验)
HTTPServer, URLParser, IOError, JSONDecoder, XMLHttpRequest
// 风格 B:只首字母大写(Microsoft / Apple 风格)
HttpServer, UrlParser, IoError, JsonDecoder
// Go 官方坚持风格 A:URL 永远 URL,不写 Url
type URL struct {}
func (u *URL) String() string {}
// JavaScript / TypeScript 历史上混用,但 lint 通常推荐风格 B
class HttpClient {}
const apiUrl = '...';
选一种风格在整个项目里贯彻——不要 HTTPServer 和 HttpClient 在同一份代码里出现。语言生态的默认惯例是:Go/Python 全大写;TS/Java/C# 首字母。
id 还是 ID
这是最常被问的一个点。规则:ID 是 identifier 的缩写词,按缩写词规则处理。
// Go
type UserID int64 // ✓
type UserId int64 // ✗ Go lint 会报 stylecheck
// TypeScript
interface User { id: string } // 字段名小写 id(一个常用单词,已经"普通化")
class UserID extends Branded<...> // 类型用 ID(强调缩写身份)
// 但 SaaS 项目里也常见全部 id(避免争议)
interface User { id: string }
function getUserById(id: string) {} // 函数名里 By 后面跟 Id(驼峰中段)
哪些缩写在代码里是公认的
这些缩写已经"国际化",不会引起歧义,可以放心用:
id, idx, i / j / k, n / m, // 索引、循环
ctx, req, res / resp, err, // Web/Go 惯例
db, conn, ch, mu / mtx, // 资源
fn, cb, args, kwargs, // 函数相关
cfg / config, env, opts, // 配置
src, dst / dest, tmp, buf, // I/O
ok, eof, // 状态
e.g., i.e., etc., vs. // 注释里
pid, uid, gid, fd, addr, ptr // 系统
这些不要缩写:
// Bad — 自己造的缩写
usrInf, ordItm, prodCat,
cstmrSrvc, mngrApprv,
calcAvgSal, getDscnt
规则:"如果不是百度都能搜到的标准缩写,就不要缩"。
2.4 注释的语态:祈使句还是第三人称
英语里有三种主要的注释语态,对应不同场景:
语态 A:祈使句(imperative) — 内联注释、TODO、commit
// Cache the result to avoid recomputation
// Skip empty rows
// TODO: handle UTF-16 encoded files
// FIXME: race condition when N goroutines exceed pool size
祈使句简短、命令式、动作导向。它把读者当成 "另一个写代码的人",告诉对方代码在做什么。
语态 B:现在时第三人称 — 函数 / 方法的 doc comment
/**
* Returns the user with the given ID. Throws NotFoundError if not found.
* @param id - The user ID to look up
* @returns The user object
*/
function getUser(id: string): User {}
// Python — 注意是动词第三人称单数 "Returns",不是 "Return"
def get_user(id: str) -> User:
"""Returns the user with the given ID.
Args:
id: The user identifier.
Returns:
The user object.
Raises:
NotFoundError: If no user matches the ID.
"""
这是 PEP 257、JSDoc、JavaDoc 等多个规范的共同要求:用第三人称现在时陈述("Returns the X" 而不是 "Return the X" 或 "Returning the X")。
很多中国程序员写成 "Get the user by ID"(祈使句)放在 docstring 里——这在 PEP 257 和 numpydoc 风格里是错的。Python 官方的 PEP 257 明确说:"The docstring is a phrase ending in a period. It prescribes the function or method's effect as a command, not as a description; e.g. don't write 'Returns the pathname...'." ——但这条 "command 写法" 仅适用于一行 docstring 的动词形式,多行格式的实际惯例反而是 "Returns..."。结论:跟你项目的现有风格走,最忌 docstring 里大小风格混杂。
语态 C:解释性段落 — 复杂逻辑前的块注释
// We use a sliding window of 10 to balance recency and stability.
// A smaller window makes the system reactive but noisy; a larger
// window smooths out spikes but lags behind real changes.
// Empirically 10 hits the sweet spot for our traffic pattern.
const WINDOW_SIZE = 10;
这种语态用 we(团队 / 系统)作为主语,解释 "为什么"。它最有价值,但也最容易写成中式英语。
2.5 TODO / FIXME / HACK / NOTE / XXX 的语义
这些是行业公认标签,每个有自己的语义梯度,乱用会被 reviewer 标红:
| 标签 | 语义 | 紧迫度 | 典型用法 |
|---|---|---|---|
TODO | 计划要做但还没做 | 低 | // TODO: support i18n in v2 |
FIXME | 已知 bug 但暂时没修 | 高 | // FIXME: leaks memory under high concurrency |
HACK | 能跑但实现很丑,需要重构 | 中 | // HACK: bypass framework cache by toggling private flag |
XXX | 警告:危险或可疑代码 | 极高 | // XXX: relies on undocumented behavior of v1.2.3 |
NOTE | 读者需要注意但不一定要改 | 纯信息 | // NOTE: keep in sync with Java client v0.4 |
OPTIMIZE | 能跑但慢,可优化 | 低 | // OPTIMIZE: O(n²); fine for now since n < 100 |
DEPRECATED | 已废弃,将移除 | 提示 | // DEPRECATED: use newApi() instead |
规范的写法是 TAG(owner): description 或 TAG: description:
// TODO(alice): implement retry with exponential backoff
// FIXME(2024-Q3): N+1 query in this loop
// HACK: workaround for upstream bug aws/aws-sdk-go#3142
// XXX: do not call before App.init()
2.6 Conventional Comments:Code Review 的标签语义
Conventional Comments 是一份很重要但被严重低估的规范。它给 code review 评论加了一个标签前缀,让收件人 1 秒就知道你这条评论的分量:
# 格式
<label> [decorations]: <subject>
# 例子
praise: This is a really clean abstraction!
nitpick: Consider renaming `usr` to `user` for consistency.
suggestion: Could we extract this into a helper function?
issue: This will throw if `items` is empty.
todo: Add a test case for the timeout path.
question: Why do we cache here instead of relying on the HTTP layer?
thought: This pattern reminds me of the Builder we use elsewhere.
chore: Don't forget to update the changelog.
note (non-blocking): The PR description mentions feature X but I don't see it.
# 装饰器
nitpick (non-blocking): ... // 不阻塞合并
issue (security): ... // 标注安全问题
suggestion (test): ... // 测试相关
这套标签的妙处是:
praise让你能写"夸奖"——对 PR 作者有重要心理价值。nitpick明确告诉对方"这是吹毛求疵,不必非改"。issue是"必须改"的标准词。question让你不用为"我提的是问题还是异议"纠结。
很多大厂内部 lint 已经把 Conventional Comments 写进 PR template。即使你的团队没有正式采用,自己用也能让 review 沟通效率提升一截。
2.7 注释中的常见英语陷阱
陷阱 1:把 this 当主语
// Bad — this 指代不清
// This is for caching the user.
// Good — 直接说 "what"
// Caches the user object for 5 minutes.
// 或:The user cache. Keys expire after 5 minutes.
陷阱 2:误用现在分词
// Bad — 现在分词造成意思混乱
// Returning the user after validating email.
// (表示 "正在返回"?还是 "返回的对象是..."?)
// Good — 用第三人称单数动词
// Returns the user after validating the email.
陷阱 3:滥用被动
// Bad — 被动语态隐藏了主语
// The cache will be invalidated when user is updated.
// Good — 主动语态更清晰
// Invalidates the cache when the user is updated.
// 或:On user update, this clears the cache.
陷阱 4:复数 / 单数错配
// Bad
// Returns a list of user that match the criteria.
// Good
// Returns a list of users that match the criteria.
// 或:Returns users matching the criteria.
陷阱 5:拼音命名
// Bad — 拼音
function huoQuYongHuXinXi() {}
const dingDanZongJia = 0;
// Good — 哪怕不地道也是英文
function getUserInfo() {}
const orderTotal = 0;
陷阱 6:中文标点
// Bad — 注释里出现中文逗号、句号
// 缓存用户信息,避免重复查询数据库。
// Good — 全英文标点
// Caches user info to avoid duplicate DB queries.
// 或保留中文:缓存用户信息以避免重复查询数据库。
// (但不要中英文混合标点)
2.8 50 个 "中式 vs 地道" 对比表
| 中式 | 地道 | 说明 |
|---|---|---|
userInfomation | userProfile / userInfo | info 拼写 + 概念词 |
checkIfUserExist | userExists | 问句变陈述布尔 |
doSomething() | process() / handle() | do something 是占位词 |
tempVar | buffer / scratch | temp 含义太泛 |
handleClick | onClick | 事件 handler 用 on |
userList | users | 复数本身就是 list |
boolFlag | isReady / hasError | 具体语义 |
get the user info | fetch the user profile | 跨网络用 fetch |
delete user | removeUser / deleteUser | removeXxx 不破坏数据,deleteXxx 真删 |
cancelRequest | abortRequest | fetch API 用 abort |
err msg | errMsg / errorMessage | 缩写不要带空格 |
fileNum | fileCount | num 多用于编号,count 用于数量 |
logErr | logError | err 是变量缩写,函数名一般展开 |
doneFlag | isDone / completed | done 本身已是 ed |
old user | previousUser / existingUser | old 含价值判断 |
new user | createdUser / incomingUser | new 在 OOP 里有特殊含义 |
chineseName | localizedName / displayName | 避免文化绑定 |
realPath | absolutePath / resolvedPath | real 含义模糊 |
backendData | serverData / apiResponse | backend 是技术分层词,不该出现在数据名里 |
info 名词单用 | profile / details | info 太空泛 |
剩余 30 条建议在你写代码时随时查 https://github.com/kettanaito/naming-cheatsheet 这个仓库——它是工程界比较公认的命名速查表。
2.9 本章小结
- 命名是英语 + 建模双重难题。前者比想象中重要,因为 reviewer 会直接据此判断你的"工程素养"。
- 词性规则:变量名词、布尔形容词或 is/has 谓语、函数动词祈使句、类名词。
- 语义梯度:get/fetch/load/retrieve/find/read 不能互换。
- 缩写规范:跟语言生态走,
HTTPvsHttp全项目统一;不是公认缩写就不要缩。 - 注释三种语态:祈使句(内联)/ 第三人称(doc)/ 解释段(why)。
- TODO / FIXME / HACK / XXX / NOTE 各有语义;Conventional Comments 给 review 评论加分量标签。
- 陷阱:this 主语、被动滥用、单复数错配、拼音、中文标点。
下一章我们进入工程协作的真正第一课——commit message。