布尔值作为方法参数是否不可接受?

我的一个同事说 布尔型作为方法参数是不可接受的。它们应被枚举所取代。起初我没有看到任何好处,但他给了我一个例子。

什么更容易理解?

file.writeData( data, true );

或者

enum WriteMode {
Append,
Overwrite
};


file.writeData( data, Append );

现在我明白了! ; -)
这绝对是一个将枚举作为第二个参数使代码更具可读性的示例。

那么,你对这个话题有什么看法?

8571 次浏览

枚举还允许未来的修改,您现在需要第三个选择(或更多)。

只有在不打算扩展框架功能的情况下,布尔值才是可以接受的。Enum 是首选的,因为您可以扩展枚举,而不会中断函数调用的先前实现。

Enum 的另一个优点是更容易阅读。

布尔值表示“是/否”选项。如果你想表示“是/否”,那么使用布尔值,它应该是不言而喻的。

但是如果需要在两个选项中进行选择,而这两个选项显然都不是“是”或“否”,那么枚举有时候可以更容易阅读。

我想你自己也差不多回答了这个问题,我认为最终的目的是让代码更具可读性,在这种情况下,枚举做到了这一点,我认为最好看看最终的目的,而不是一概而论的规则,也许可以把它更多地看作是一个指导原则,即枚举在代码中通常比泛型布尔、整型等更具可读性,但总会有规则的例外。

枚举更好,但我不会把布尔参数称为“不可接受”。有时候只需要输入一个小小的布尔值就可以继续(想想私有方法等等)

如果该方法提出下列问题:

KeepWritingData (DataAvailable());

哪里

bool DataAvailable()
{
return true; //data is ALWAYS available!
}


void KeepWritingData (bool keepGoing)
{
if (keepGoing)
{
...
}
}

布尔方法参数似乎完全有意义。

这取决于方法。如果这个方法做了一些非常明显是真/假的事情,那么它就是正确的,例如下面的例子[虽然我不是说这是这个方法的最佳设计,它只是一个用法很明显的例子]。

CommentService.SetApprovalStatus(commentId, false);

However in most cases, such as the example you mention, it is better to use an enumeration. There are many examples in the .NET Framework itself where this convention is not followed, but that is because they introduced this design guideline fairly late on in the cycle.

恕我直言,枚举似乎是任何可能有两种以上选择的情况下的显而易见的选择。但是肯定有一些情况下,你只需要一个布尔值。在这种情况下,我会说,使用一个枚举,其中一个 bool 将工作将是一个例子,使用7个单词,而4将做。

当你有一个明显的开关时,布尔值是有意义的,它只能是两件事情中的一件(即灯泡的状态,开或关)。除此之外,最好使用这样的方式来编写它: 传递的内容显而易见——例如磁盘写入——未缓冲、行缓冲或同步——应该以这种方式传递。即使您现在不想允许同步写操作(因此您只有两个选项) ,也值得考虑让它们更详细,以便第一眼就能知道它们是做什么的。

也就是说,您也可以使用 False 和 True (布尔值0和1) ,然后如果以后需要更多的值,将函数展开以支持用户定义的值(比如2和3) ,旧的0/1值将很好地移植过来,所以您的代码应该不会中断。

它确实使事情变得更加明确,但是确实开始大规模地扩展接口的复杂性——在一个纯粹的布尔选择(如附加/覆盖)中,它似乎有些过头了。如果您需要添加一个进一步的选项(在这种情况下我想不到) ,您总是可以执行重构(取决于语言)

我不同意这是一个良好的 rule。显然,在某些情况下,Enum 可以提供更好的显式代码或者更详细的代码,但是作为一条规则,它似乎有些过头了。

首先让我举个例子: 程序员编写好代码的责任(和能力)并没有因为布尔参数而受到影响。在您的示例中,程序员可以通过编写以下代码来编写冗长的代码:

dim append as boolean = true
file.writeData( data, append );

或者我更喜欢笼统一点

dim shouldAppend as boolean = true
file.writeData( data, shouldAppend );

Second: 您给出的 Enum 示例只是“更好”,因为您正在传递一个 CONST。在大多数应用程序中,传递给函数的时间参数很可能至少有一些(如果不是大部分的话)是 VARIABLES。在这种情况下,我的第二个示例(给出名称好的变量)要好得多,而 Enum 不会给您带来什么好处。

使用最适合你的问题的模型。在您给出的示例中,枚举是一个更好的选择。但是,在其他情况下,布尔值会更好。这对你来说更有意义:

lock.setIsLocked(True);

或者

enum LockState { Locked, Unlocked };
lock.setLockState(Locked);

在这种情况下,我可能会选择布尔选项,因为我认为它非常清楚和明确,而且我非常肯定我的锁不会有两个以上的状态。不过,恕我直言,第二个选择是有效的,但不必要地复杂。

还记得阿德莱 · 史蒂文森在 古巴导弹危机会议期间向佐伦大使提出的问题吗?

”你在世界的法庭上 opinion right now, and you can answer yes or no. You have denied that [the missiles] 存在,我想知道我是否 正确地理解了你... ... 我是 准备等待我的答复,直到 地狱结冰了,如果那是你的 决定”

如果您的方法中的标志具有这样的性质,您可以将其固定到 二进制决策,并且该决策将 永远不会转换为三方决策或 n 方决策,那么选择布尔值。指示: 您的标志称为 isXXX

Don't make it boolean in case of something that is a mode switch. There is always one more mode than you thought of when writing the method in the first place.

The one-more-mode dilemma has e.g. haunted Unix, where the possible permission modes a file or directory can have today result in weird double meanings of modes depending on file type, ownership etc.

在具有命名参数的语言(如 Python 和 Objective-C)中,布尔值可能没有问题,因为名称可以解释参数的作用:

file.writeData(data, overwrite=true)

或:

[file writeData:data overwrite:YES]

有两个原因让我觉得这是件坏事:

  1. 因为有些人会写这样的方法:

    ProcessBatch(true, false, false, true, false, false, true);
    

    这显然是不好的,因为它太容易混淆参数,而且您根本不知道您正在指定什么。不过一瓶也不错。

  2. 因为通过一个简单的是或否分支来控制程序流可能意味着您有两个完全不同的函数,它们以一种尴尬的方式封装在一个函数中。例如:

    public void Write(bool toOptical);
    

    实际上,这应该是两种方法

    public void WriteOptical();
    public void WriteMagnetic();
    

    因为它们中的代码可能完全不同; 它们可能必须执行各种不同的错误处理和验证,甚至可能必须以不同的方式格式化传出的数据。仅仅使用 Write()或者甚至 Write(Enum.Optical)都不能说明这一点(当然,如果需要的话,这两种方法中的任何一种都可以调用内部方法 WriteOptic/Mag)。

我想这要看情况。除了第一条,我不会把它看得太重。

枚举有一定的好处,但是您不应该只是用枚举替换所有的布尔值。在许多地方,true/false 实际上是表示正在发生的事情的最佳方式。

然而,使用它们作为方法参数有点可疑,因为如果不深入研究它们应该做什么,就无法看到它们,因为它们让您看到什么是真/假 实际上


[编辑2022年的当前状态]

在现代的 C # 或其他支持这一点的语言中,最好的方法是使用命名参数:

var worker = new BackgroundWorker(workerReportsProgress: true);

如果您的语言不允许使用命名参数,那么您可能会发现属性也是一种合理的解决方案


[2008年留给子孙后代的原始答案]

属性(特别是 C # 3对象初始化器)或关键字参数(la ruby 或 python)是使用布尔参数的更好方法。

C # 例子:

var worker = new BackgroundWorker { WorkerReportsProgress = true };

Ruby 的例子

validates_presence_of :name, :allow_nil => true

Python 例子

connect_to_database( persistent=true )

我能想到的唯一正确的布尔方法参数是在 java 中,在这里既没有属性也没有关键字参数。这是我讨厌 java 的原因之一:

枚举当然可以使代码更具可读性。还有一些事情需要注意。网)

因为枚举的底层存储是 int,所以默认值为0,所以您应该确保0是一个合理的默认值。(例如,在创建 struct 时,所有字段都设置为0,因此除了0之外,没有其他方法可以指定默认值。如果没有0值,甚至不能测试枚举而不将类型强制转换为 int,这将是糟糕的风格。)

如果您的枚举对您的代码是私有的(从未公开公开) ,那么您可以停止在这里阅读。

If your enums are 出版 in any way to external code and/or are saved outside of the program, consider numbering them explicitly. The compiler automatically numbers them from 0, but if you rearrange your enums without giving them values you can end up with defects.

我可以合法地写作

WriteMode illegalButWorks = (WriteMode)1000000;
file.Write( data, illegalButWorks );

为了解决这个问题,任何使用不能确定的枚举的代码(例如 public API)都需要检查枚举是否有效。你通过这个

if (!Enum.IsDefined(typeof(WriteMode), userValue))
throw new ArgumentException("userValue");

Enum.IsDefined唯一需要注意的是它使用反射并且速度较慢。它还存在版本控制问题。如果需要经常检查枚举值,最好执行以下操作:

public static bool CheckWriteModeEnumValue(WriteMode writeMode)
{
switch( writeMode )
{
case WriteMode.Append:
case WriteMode.OverWrite:
break;
default:
Debug.Assert(false, "The WriteMode '" + writeMode + "' is not valid.");
return false;
}
return true;
}

版本控制问题是,旧代码可能只知道如何处理您拥有的2个枚举。如果添加第三个值,则 Enum.IsDefix 将为 true,但是旧代码不一定能够处理它。哎呀。

使用 [Flags]枚举可以带来更多的乐趣,而且验证代码与 [Flags]枚举略有不同。

我还要指出,为了便于移植,应该对枚举调用 ToString(),并在读取它们时使用 Enum.Parse()ToString()Enum.Parse()都可以处理 [Flags]枚举,所以没有理由不使用它们。请注意,这是另一个陷阱,因为现在您甚至不能更改枚举的名称而不破坏代码。

所以,当你问自己 我可以只拿一块布吗?的时候,有时候你需要权衡以上所有因素

这确实取决于论点的确切性质。如果它不是 yes/no 或 true/false,那么枚举会使它更易读。但是对于枚举,您需要检查参数或具有可接受的默认行为,因为可以传递基础类型的未定义值。

虽然在许多情况下枚举确实比布尔型更具可读性和可扩展性,但“布尔型不可接受”的绝对规则是愚蠢的。这种做法缺乏灵活性,而且适得其反——它没有给人类的判断留下空间。在大多数语言中,它们都是内置类型的基础,因为它们很有用——考虑将其应用于其他内置类型: 例如,说“永远不要使用 int 作为参数”将是疯狂的。

这个规则只是一个风格的问题,而不是潜在的错误或运行时性能的问题。一个更好的规则是“出于可读性的考虑,更喜欢枚举而不是布尔值”。

看看。网络架构。布尔值在很多方法中被用作参数。那个。NetAPI 并不完美,但是我认为使用布尔值作为参数并不是一个大问题。工具提示总是给出参数的名称,您也可以构建这种指导——填写关于方法参数的 XML 注释,它们将出现在工具提示中。

我还要补充的是,有一种情况下,你应该清楚地重构布尔值到一个枚举-当你有两个或更多的布尔值在你的类,或在你的方法参数,并不是所有的状态都是有效的(例如,它是无效的设置它们都为真)。

例如,如果类的属性类似于

public bool IsFoo
public bool IsBar

如果两个状态同时为真,那就错了,你实际上得到的是三个有效状态,更好的表达方式是:

enum FooBarType { IsFoo, IsBar, IsNeither };

在您的示例中使用枚举而不是布尔值确实有助于使方法调用更具可读性。但是,这是 C # 中我最喜欢的愿望项目(方法调用中的命名参数)的替代品。这种语法:

var v = CallMethod(pData = data, pFileMode = WriteMode, pIsDirty = true);

将是完全可读的,然后您可以做程序员应该做的事情,即为方法中的每个参数选择最合适的类型,而不考虑它在 IDE 中的外观。

C # 3.0允许在构造函数中使用命名参数。

有时候,用重载来建立不同的行为模型会更简单:

file.appendData( data );
file.overwriteData( data );

如果您有多个参数,每个参数都允许一组固定的选项,那么这种方法就会退化。例如,一个打开文件的方法可能有几种不同的文件模式(open/create)、文件访问(read/write)、共享模式(none/read/write)。配置的总数等于各个选项的笛卡尔积。在这种情况下,多重重载自然是不合适的。

在某些情况下,枚举可以使代码更具可读性,尽管在某些语言(例如 C #)中验证确切的枚举值可能很困难。

布尔参数通常作为新的重载附加到参数列表中。举个例子。NET 是:

Enum.Parse(str);
Enum.Parse(str, true); // ignore case

后一个重载在较晚版本的.NET 框架中比第一个更可用。

如果您知道只有两种选择,那么使用布尔值可能没问题。枚举是可扩展的,不会破坏旧的代码,尽管旧的库可能不支持新的枚举值,因此不能完全忽视版本控制。


剪辑

In newer versions of C# it's possible to use named arguments which, IMO, can make calling code clearer in the same way that enums can. Using the same example as above:

Enum.Parse(str, ignoreCase: true);

对我来说,使用布尔值和枚举都不是一个好的方法。罗伯特• C •马丁(Robert C. Martin)在他的作品《 清除代码提示 # 12: 消除布尔参数:

Boolean arguments loudly declare that the function does more than one thing. They are confusing and should be eliminated.

如果一个方法执行多个操作,那么您应该编写两个不同的方法,例如在您的示例中: file.append(data)file.overwrite(data)

Using an enumeration doesn't make things clearer. It doesn't change anything, it's still a flag argument.

你的同事最好遵守以下几条规则:

  • 不要对你的设计武断。
  • 选择最适合代码用户的方式。
  • Don't try to bash star-shaped pegs into every hole just because you like the shape this month!

布尔值仅为 true/false。因此,它代表什么还不清楚。Enum可以有有意义的名称,例如 OVERWRITEAPPEND等。所以枚举更好。

我同意 Enums 是一个很好的方法,在方法中有两个选项(只有两个选项可以不使用 enum 而具有可读性)

例如:。

public void writeData(Stream data, boolean is_overwrite)

我喜欢 Enums,但是 boolean 也很有用。

这是一篇旧文章的最后一篇,它在页面的最下面,没有人会读到它,但是因为没有人已经说过了... ..。

内联注释对于解决意想不到的 bool问题大有帮助。原来的例子尤其令人发指: 想象一下在函数声明中命名变量的情景!就像是

void writeData( DataObject data, bool use_append_mode );

但是,举个例子,我们假设这就是声明。然后,对于一个无法解释的布尔参数,我将变量名放在一个内联注释中。比较一下

file.writeData( data, true );

file.writeData( data, true /* use_append_mode */);