Chapter 02

代码命名与注释里的英语

每天都在写、每天都在读,却很少被严肃训练——命名和注释是程序员英语的"地基"。

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, orderTotalgetUser(动词不当变量名)
布尔值形容词或 is/has/can 开头的谓语isReady, hasPermission, canEditflag, status(含义不清)
函数 / 方法动词开头的祈使句fetchUser(), validateEmail()userFetch()(语序错)
返回布尔的函数is/has/can/should + ...isValid(), hasChildren()checkValid()(动作而非问题)
类 / 接口名词,PascalCaseUserRepository, OrderServiceRunTask(动词命名类)
常量全大写名词MAX_RETRIES, DEFAULT_TIMEOUTRETRY(含义不清)
事件 handleron + 名词 + 动词过去分词onUserCreated, onPaymentFaileduserCreate

单复数

这是中国程序员最容易翻车的英语点,因为中文没有名词复数变化。

// 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 本身就是复数(拉丁语)
// trap

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 = '...';
// rule

选一种风格在整个项目里贯彻——不要 HTTPServerHttpClient 在同一份代码里出现。语言生态的默认惯例是: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")。

// trap

很多中国程序员写成 "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): descriptionTAG: 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): ...          // 测试相关

这套标签的妙处是:

// adoption

很多大厂内部 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 地道" 对比表

中式地道说明
userInfomationuserProfile / userInfoinfo 拼写 + 概念词
checkIfUserExistuserExists问句变陈述布尔
doSomething()process() / handle()do something 是占位词
tempVarbuffer / scratchtemp 含义太泛
handleClickonClick事件 handler 用 on
userListusers复数本身就是 list
boolFlagisReady / hasError具体语义
get the user infofetch the user profile跨网络用 fetch
delete userremoveUser / deleteUserremoveXxx 不破坏数据,deleteXxx 真删
cancelRequestabortRequestfetch API 用 abort
err msgerrMsg / errorMessage缩写不要带空格
fileNumfileCountnum 多用于编号,count 用于数量
logErrlogErrorerr 是变量缩写,函数名一般展开
doneFlagisDone / completeddone 本身已是 ed
old userpreviousUser / existingUserold 含价值判断
new usercreatedUser / incomingUsernew 在 OOP 里有特殊含义
chineseNamelocalizedName / displayName避免文化绑定
realPathabsolutePath / resolvedPathreal 含义模糊
backendDataserverData / apiResponsebackend 是技术分层词,不该出现在数据名里
info 名词单用profile / detailsinfo 太空泛

剩余 30 条建议在你写代码时随时查 https://github.com/kettanaito/naming-cheatsheet 这个仓库——它是工程界比较公认的命名速查表。

2.9 本章小结

下一章我们进入工程协作的真正第一课——commit message。