[转载] 别再把变量命名为 "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,后面一般跟形容词。

2. HAS:包含与特性 描述所有权或包含关系,用 has,后面跟名词。

3. CAN:能力 检查权限,或表示某个可能的操作,用 can

4. SHOULD:意图 涉及业务规则、或系统得做出决策时,用 should。它把「能做什么」和「该做什么」区分开。

照着这张清单来。一旦混用——写成 isAccesshasActive——读者就得停下来在脑子里重排一遍句子。这种卡顿,正是 bug 最容易冒出来的地方。

前缀领域作用示例
IS身份 / 状态描述一个对象当前是什么。isActiveisEmpty
HAS包含描述所有权或特性是否存在。hasChildrenhasAccess
CAN能力描述权限或潜在的动作。canEditcanRetry
SHOULD意图 / 逻辑描述业务规则上的建议。shouldCacheshouldRetry

「禁止否定」规则

最重要的一条规则:变量名里永远别用否定。

isNotEnabledhasNoAccessisDisabled 这种名字都不要用。

为什么?因为你迟早会需要判断相反的状态。 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. 「耸肩」变量flagdonecheck 这种名字,根本没说清_在追踪什么_。

2. 词性搭不上 破坏前缀规则会打乱人的心智模型。

3. 双重否定 只要看到 ! 紧挨着 NotNo,就是。

4. 多用途布尔 一个变量暗中同时盯好几件事。

5. 漂移的标志位 一个局部布尔变量在不同逻辑步骤里被反复拿来复用。

结论

命名不是为了好看,是为了照顾后面读代码的人。

把布尔值命名为 flag,是写给现在的自己看。命名为 isProcessed,是写给六个月后要修这个文件里某个 bug 的那个倒霉蛋——那个人很可能就是你自己。对未来的自己好一点。