[转载] 别再把变量命名为 "flag" 了:布尔前缀的命名艺术
原文出处: https://thatamazingprogrammer.com/posts/stop-naming-your-variables-flag-the-art-of-boolean-prefixes/
作者: Christopher Johnson
原文发布日期: 2025-12-08
许可协议: 本文为原文的中文翻译,所有权利归原文作者所有。
问题所在
你接手了一个代码库,看到这样的代码:
public class OrderProcessor
{
private bool open;
private bool flag;
private bool done;
private bool status;
public void Process(bool check)
{
if (open && flag)
{
flag = false;
status = true;
}
if (done)
{
// 这里会发生什么?
}
}
}
能看懂吗?看不懂。
open 指的是什么?店铺开张了?还是要打开某个文件?flag 又是什么?这名字等于在明说「以后再说」。至于 done,它是问句(「完成了没?」)还是命令(「标记成完成」)?
布尔变量的命名是个含糊的雷区。表面上看着没事,等你凌晨三点在线上排查问题、得搞清楚它到底什么意思的时候,就有的受了。
含糊会养出 bug。review 的人看到 if (done),只能靠猜。改代码的人把 flag = false 一改,可能就无意中翻转了一段他自己都没搞懂的逻辑。if (!flag)……这是要打开它?还是关掉它?
布尔变量的名字之所以要紧,是因为它不是个抽象的标签,而是一个问题。代码抛出问题,布尔值用「是」或「否」作答。问题本身不清晰,答案也就没意义了。
解决方案:四个前缀
四个前缀就能覆盖 99% 的布尔命名场景。坚持用它们,每个布尔值都会变成一个清晰、读得通顺的问题。
1. IS:身份与状态
描述某个东西现在_是什么_,用 is,后面一般跟形容词。
- ✅
isActive、isDeleted、isEmpty。 - ❌
isAccess(词性搭不上,该用hasAccess)。
2. HAS:包含与特性
描述所有权或包含关系,用 has,后面跟名词。
- ✅
hasAccess、hasChildren、hasValidationErrors。 - ❌
hasActive(词性搭不上,该用isActive)。
3. CAN:能力
检查权限,或表示某个可能的操作,用 can。
- ✅
canEdit、canDelete、canRetry。 - ❌
canAdmin(太含糊。用isAdmin或canAdminister)。
4. SHOULD:意图
涉及业务规则、或系统得做出决策时,用 should。它把「能做什么」和「该做什么」区分开。
- ✅
shouldRetry、shouldCacheResponse。 - ❌
shouldUser(没说完。换成shouldCreateUser就对了)。
照着这张清单来。一旦混用——写成 isAccess 或 hasActive——读者就得停下来在脑子里重排一遍句子。这种卡顿,正是 bug 最容易冒出来的地方。
| 前缀 | 领域 | 作用 | 示例 |
|---|---|---|---|
| IS | 身份 / 状态 | 描述一个对象当前是什么。 | isActive、isEmpty |
| HAS | 包含 | 描述所有权或特性是否存在。 | hasChildren、hasAccess |
| CAN | 能力 | 描述权限或潜在的动作。 | canEdit、canRetry |
| SHOULD | 意图 / 逻辑 | 描述业务规则上的建议。 | shouldCache、shouldRetry |
「禁止否定」规则
最重要的一条规则:变量名里永远别用否定。
像 isNotEnabled、hasNoAccess、isDisabled 这种名字都不要用。
为什么?因为你迟早会需要判断相反的状态。
if (!isDisabled) 逼着你在脑子里转一道双重否定:「如果 not 是 disabled……」
对比一下这个:
if (isEnabled)
一眼就能看明白。
否定式的名字写的时候往往挺顺手(「我要看它是不是被禁用了」),但后面每一个读这段代码的人都要为它买单。重构工具更是雪上加霜:你反转一个 if,IDE 可能会把 isDisabled 顺手重命名成 isNotDisabled。最后留下的就是一段谁看着都难受的代码。
例外:
唯一能接受否定名字的情况,是照搬某个本来就是否定形式的外部 API 或 HTML 属性,比如 noValidate。即便如此,也得在边界处把它隔离开。领域逻辑里始终映射成肯定形式:bool shouldValidate = !request.noValidate。
上下文很重要(「布尔陷阱」)
上面的规则用在属性(状态)上很合适。但用在参数(函数实参)上就失效了。
典型的「布尔陷阱」长这样:
// 来自 NHibernate 的真实代码
schemaExport.Execute(false, true, false);
猜猜第二个 true 是什么意思?猜不出来。除非翻这个库的源码,否则无从得知。
看到 Execute(bool, bool, bool) 这种方法签名,你看到的就是一个设计缺陷。
如何修复:
1. 拆分方法 如果这个布尔值会完全改变方法的行为,就把它拆成两个方法。
// 坏
email.Send(message, true); // 这个 true 是「立即发送」?还是「高优先级」?
// 好
email.SendImmediately(message);
email.SendQueued(message);
2. 使用枚举 如果存在多种模式,就给它们命名。
// 坏
file.Write(data, true);
// 好
file.Write(data, WriteMode.Append);
3. 使用配置对象 如果你有多个标志位,就传一个对象。
// 坏
export.Execute(false, true, false);
// 好
export.Execute(new ExportOptions {
Script = false,
Export = true,
JustDrop = false
});
反模式
想清理自己的代码,做 review 时留意下面这几个坏味道。
1. 「耸肩」变量
像 flag、done、check 这种名字,根本没说清_在追踪什么_。
- ❌
if (check) - ✅
if (isPaymentVerified)
2. 词性搭不上 破坏前缀规则会打乱人的心智模型。
- ❌
if (user.hasActive) - ✅
if (user.isActive)
3. 双重否定
只要看到 ! 紧挨着 Not 或 No,就是。
- ❌
if (!isNotEnabled) - ✅
if (isEnabled)
4. 多用途布尔 一个变量暗中同时盯好几件事。
❌
isValid(实际上同时查了用户是否存在、有没有邮箱,_而且_是否活跃)。✅ 写明白。
bool isReadyForBilling = user.Exists && user.HasEmail && user.IsActive;
5. 漂移的标志位 一个局部布尔变量在不同逻辑步骤里被反复拿来复用。
❌
bool error = false; if (!Save()) error = true; if (!error && !SendEmail()) error = true; return error;✅ 用一个 Result 对象,或者提前返回。别把布尔变量当水桶来回倒。
结论
命名不是为了好看,是为了照顾后面读代码的人。
把布尔值命名为 flag,是写给现在的自己看。命名为 isProcessed,是写给六个月后要修这个文件里某个 bug 的那个倒霉蛋——那个人很可能就是你自己。对未来的自己好一点。