布尔作为方法论证是不可接受的吗?

时间:2008-09-25 20:31:27

标签: coding-style boolean enumeration

我的一位同事表示布尔值作为方法参数是不可接受的。它们应由枚举代替。起初我没有看到任何好处,但他给了我一个例子。

什么更容易理解?

file.writeData( data, true );

或者

enum WriteMode {
  Append,
  Overwrite
};

file.writeData( data, Append );

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

那么,您对此主题有何看法?

26 个答案:

答案 0 :(得分:130)

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

但如果它是两个选项之间的选择,两者都不是明显是或否,那么枚举有时可能更具可读性。

答案 1 :(得分:50)

Enums还允许将来进行修改,您现在需要第三种选择(或更多)。

答案 2 :(得分:32)

使用最能模拟问题的那个。在您给出的示例中,枚举是更好的选择。但是,有时布尔值会更好。这对你更有意义:

lock.setIsLocked(True);

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

在这种情况下,我可能会选择布尔选项,因为我认为它非常明确且毫不含糊,而且我很确定我的锁定不会有两个以上的状态。尽管如此,第二种选择是有效的,但不必要的复杂,恕我直言。

答案 3 :(得分:13)

我认为你自己几乎已经回答了这个问题,我认为最终的目的是让代码更具可读性,在这种情况下,枚举就是这样做的,IMO总是最好看看最终目标而不是一揽子规则,也许是它更像是一个指导原则,即代码中的枚举通常比通用的bool,int等更具可读性,但规则总会有例外。

答案 4 :(得分:13)

对我而言,使用布尔值或枚举都不是一个好方法。罗伯特·C·马丁在他的Clean Code Tip #12: Eliminate Boolean Arguments

中非常清楚地捕捉到了这一点
  

布尔参数大声声明该函数不止一件事。它们令人困惑,应该被淘汰。

如果方法不止一件事,你应该写两种不同的方法,例如在你的情况下:file.append(data)file.overwrite(data)

使用枚举不会使事情更清楚。它没有改变任何东西,它仍然是一个旗帜论点。

答案 5 :(得分:13)

我遇到的两个原因是坏事:

  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)来判断这一点(当然,如果你愿意的话,你可以使用其中任何一种方法调用内部方法WriteOptical / Mag。)

  3. 我想这只是取决于。除了#1之外,我不会对它做太大的交易。

答案 6 :(得分:13)

还记得Adlai Stevenson在cuban missile crisis期间在联合国大使Zorin提出的问题吗?

  

“你在世界的法庭上   现在的意见,你可以回答   是或否。你否认[导弹]   存在,我想知道我是否   我正确理解你了......我是   准备等到我的答案,直到   地狱冻结了,如果这是你的   决定。“

如果您的方法中的旗帜具有这样的性质,您可以将其固定为二元决策,并且该决定永远不会变成三-way或n-way决定,去布尔值。适应症:您的旗帜名为 isXXX

如果出现模式切换,请不要将其设为布尔值。在首先编写方法时,总有多一个模式

一个多模式的困境有:困扰Unix,文件或目录今天可能拥有的权限模式会导致模式的奇怪双重含义,具体取决于文件类型,所有权等。

答案 7 :(得分:7)

枚举更好,但我不会将布尔参数称为“不可接受”。有时候抛出一个小布尔值并继续前进会更容易(想想私有方法等)。

答案 8 :(得分:6)

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

file.writeData(data, overwrite=true)

或:

[file writeData:data overwrite:YES]

答案 9 :(得分:4)

您的同事可能更好地遵守的一些规则是:

  • 不要对你的设计说教。
  • 为您的代码用户选择最适合的内容。
  • 不要因为你喜欢这个月的形状而试图将星形钉钉入每个洞!

答案 10 :(得分:4)

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

此规则只是一个样式问题,而不是错误或运行时性能的潜在问题。一个更好的规则是“出于可读性的原因,更喜欢使用布尔值”。

查看.Net框架。布尔值被用作很多方法的参数。 .Net API并不完美,但我不认为使用布尔值作为参数是一个大问题。工具提示始终为您提供参数的名称,您也可以构建此类指南 - 在方法参数中填写XML注释,它们将出现在工具提示中。

我还应该补充说,有一种情况,你应该明确地将布尔值重构为枚举 - 当你的班级或你的方法参数中有两个或更多的布尔值时,并且并非所有状态都有效(例如它无效)让他们都设置为真。)

例如,如果您的类具有类似

的属性
public bool IsFoo
public bool IsBar

同时让两者都成立是错误的,你实际得到的是三种有效状态,更好地表达为:

enum FooBarType { IsFoo, IsBar, IsNeither };

答案 11 :(得分:4)

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

然而,使用它们作为方法参数有点可疑,仅仅是因为你不能在没有深入研究它们应该做什么的情况下看到它们,因为它们让你看到真实/虚假实际意味着什么

属性(尤其是使用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的原因之一: - (

答案 12 :(得分:4)

我不同意这是一个好的规则。显然,在某些情况下,Enum会提供更好的显式或冗长的代码,但通常情况下它似乎无法实现。

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

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

或者我更喜欢更一般的

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

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

答案 13 :(得分:3)

如果您不打算扩展框架的功能,则只能接受布尔值。 Enum是首选,因为您可以扩展枚举,而不会破坏函数调用的先前实现。

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

答案 14 :(得分:2)

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

答案 15 :(得分:2)

枚举肯定可以使代码更具可读性。还有一些事情需要注意(至少在.net中)

因为枚举的底层存储是一个int,所以默认值为零,所以你应该确保0是一个合理的默认值。 (例如,结构在创建时将所有字段设置为零,因此无法指定0以外的默认值。如果没有0值,则无法在不转换为int的情况下测试枚举,这将是糟糕的风格。)

如果您的代码专用于您的代码(从未公开曝光),那么您可以在这里停止阅读。

如果您的枚举以任何方式发布到外部代码和/或保存在程序之外,请考虑明确编号。编译器会自动从0开始编号,但是如果重新排列枚举而不给它们赋值,则最终会出现缺陷。

我可以合法地写

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

为了解决这个问题,任何使用您无法确定的枚举的代码(例如公共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.IsDefined将为true,但旧代码不一定能处理它。糟糕。

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

我还要注意,为了便于携带,您应该在枚举上使用ToString(),并在重新阅读时使用Enum.Parse()ToString()Enum.Parse()也可以处理[Flags]枚举,因此没有理由不使用它们。请注意,这是另一个陷阱,因为现在你甚至无法在不破坏代码的情况下更改枚举的名称。

所以,有时候当你问自己时,你需要权衡以上所有内容吗?我可以侥幸逃脱吗?

答案 16 :(得分:2)

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

CommentService.SetApprovalStatus(commentId, false);

但是在大​​多数情况下,例如您提到的示例,最好使用枚举。 .NET Framework本身有很多例子没有遵循这个约定,但这是因为他们在周期中很晚才引入了这个设计指南。

答案 17 :(得分:2)

如果方法提出如下问题:

KeepWritingData (DataAvailable());

,其中

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

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

布尔方法参数似乎具有绝对完美的意义。

答案 18 :(得分:1)

恕我直言,对于任何可能有两种以上选项的情况来说,枚举似乎是明显的选择。但是肯定会出现需要布尔值的情况。在这种情况下,我会说使用一个bool可以工作的枚举将是一个使用7个单词时4个单词的例子。

答案 19 :(得分:0)

当你有一个明显的切换时,布尔有意义,这只能是两件事之一(即灯泡的状态,打开或关闭)。除此之外,以这样一种方式写它是很好的,这样很明显你传递的东西 - 例如磁盘写入 - 无缓冲,行缓冲或同步 - 应该这样传递。即使你现在不想允许同步写入(因此你只限于两个选项),为了了解他们乍一看他们做的事情,值得考虑使它们更加冗长。

也就是说,您也可以使用False和True(布尔值0和1)然后如果您需要更多值,请将函数展开以支持用户定义的值(例如,2和3),以及您的旧0 / 1值将很好地移植,因此您的代码不应该破坏。

答案 20 :(得分:0)

有时,使用重载来模拟不同的行为会更简单。从你的例子继续是:

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

如果您有多个参数,每个参数都允许一组固定的选项,则此方法会降级。例如,打开文件的方法可能有多种文件模式(打开/创建),文件访问(读/写),共享模式(无/读/写)的排列。配置总数等于各个选项的笛卡尔积。当然,在这种情况下,多次过载是不合适的。

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

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

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

后者的重载在.NET框架的更高版本中可用,而不是第一个。

如果你知道只有两个选择,布尔可能没问题。枚举可以通过不会破坏旧代码的方式进行扩展,尽管旧库可能不支持新的枚举值,因此无法完全忽略版本控制。


修改

在较新版本的C#中,可以使用命名参数,IMO可以使命令更清晰,就像枚举一样。使用与上面相同的例子:

Enum.Parse(str, ignoreCase: true);

答案 21 :(得分:0)

我同意Enums是一个很好的方法,在你有2个选项的方法中(只有两个选项,你可以在没有枚举的情况下具有可读性。)

e.g。

public void writeData(Stream data, boolean is_overwrite)

喜欢Enums,但布尔也很有用。

答案 22 :(得分:0)

这是旧帖子上的一个较晚的条目,并且在页面上这么远,没有人会读它,但是因为没有人说过它......

内联评论在解决意外bool问题方面有很长的路要走。最初的例子特别令人发指:想象试图在函数declearation中命名变量!它就像

void writeData( DataObject data, bool use_append_mode );

但是,为了举例,让我们说这是宣言。然后,对于一个原因不明的布尔参数,我将变量名称放在内联注释中。比较

file.writeData( data, true );

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

答案 23 :(得分:-1)

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

答案 24 :(得分:-1)

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

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

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

C#3.0允许构造函数中的命名参数。我不知道他们为什么不能用方法做到这一点。

答案 25 :(得分:-1)

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