布尔作为方法参数是不可接受的吗?[关闭]


123

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

什么更容易理解?

file.writeData( data, true );

要么

enum WriteMode {
  Append,
  Overwrite
};

file.writeData( data, Append );

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

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


7
这是一篇非常有趣的文章,我将更频繁地实现此方法。
萨拉·奇普斯

人力资源部,香港专业教育学院以前这样做,但从未意识到这是多么好的设计模式。所以枚举进入文件?
肖恩

从语义角度来看,枚举当然更有意义。另一方面,很有趣的是看到一些程序员想出了什么来处理模糊逻辑。
James P.

2
只是在Adventure Time上问柠檬人,这是否不可接受
ajax333221 2012年

Answers:


131

布尔值表示“是/否”选择。如果要表示“是/否”,则使用布尔值,这应该是不言自明的。

但是,如果在两个选项之间进行选择,而这两个选项都不是肯定或否定的,那么枚举有时会更具可读性。


3
另外,方法名称必须清楚自变量yes或no的含义,即void turnLightOn(bool)明确地将true或yes设置为true可以使灯亮起。
simon

10
尽管在这种情况下,根据情况,我宁愿使用turnLightOn()和turnLightOff()。
skaffman's

14
“ turnLightOn(false)”的意思是“不打开灯”?混乱
杰伊·巴祖兹

17
如何setLightOn(bool)
Finbarr 2010年

10
最新评论,但@Jay Bazuzi:如果您的方法名为turnLightOn,并且传递了false,那么您也可能根本不调用该方法,传递false表示不要打开灯。如果灯已经亮了,就不意味着要把它关掉,就意味着不要把它打开。...如果您有一个方法“ turnLight”,那么一个带有“ On”和“ Off”的枚举是有意义的,turnLight(开启),将Light(关闭)。我同意skaffman tho的观点,我宁愿使用两种不同的显式方法turnLightOn()和turnLightOff()。(顺便说一句:在Bobs叔叔的书“ Clean Code”中对此内容进行了解释)
Phill 2010年


32

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

lock.setIsLocked(True);

要么

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

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


2
在您的示例中,我宁愿有两种方法。lock.lock()lock.release()和lock.IsSet,但这全取决于对使用代码最有意义的内容。
罗伯特·保尔森

3
这是一个公平的评论,但我认为这也说明了更大的一点,即有许多方法可以为给定问题建模。您应该使用适合自己情况的最佳模型,还应该使用编程环境提供的适合模型的最佳工具。
杰里米·布尔克

我完全同意:),我只是在评论提供的另一种特殊的伪代码。我同意你的回答。
罗伯特·保尔森

14

对我来说,既不使用布尔值也不使用枚举不是一个好的方法。罗伯特·C·马丁(Robert C. Martin)在他的“ 干净代码提示12:消除布尔参数”中非常清楚地说明了这一点:

布尔参数大声地声明该函数可以执行多项操作。它们令人困惑,应予以消除。

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

使用枚举并不能使事情变得更清晰。它没有任何改变,仍然是一个标志参数。


7
这是否意味着接受长度为N的ASCII字符串的函数可以执行128 ^ N项操作?
确实是2010年

@delty这是认真的评论吗?如果是,您是否经常在字符串的所有可能值上编写if?与布尔参数的情况有可能比较吗?
Pascal Thivent 2010年

我相信当您在对象内设置布尔值时,它们是可以接受的。一个完美的例子是setVisible(boolean visible) { mVisible = visible; }。您会建议什么替代方案?
Brad

2
@Brad show(){mVisible = true} hide(){mVisible = false}
Oswaldo Acauan

@Oswaldo,尽管仍然正确,但我认为使用两种不同的方法将布尔值分配给不同的值并没有什么意义。您没有setIntToOne(),setIntToTwo()setIntToThree()吗?如果您只能有两个可能的值,但是为了简洁起见,请在此情况下使用布尔值,这会更加模棱两可。
Brad 2013年

13

我认为您几乎自己回答了这个问题,我认为最终目的是使代码更具可读性,在这种情况下,枚举是这样做的,IMO始终最好着眼于最终目标,而不是一揽子规则,也许应该多考虑一下作为准则,即枚举在代码中通常比通用布尔,整数等更具可读性,但该规则始终存在例外。


13

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

“您现在处于世界舆论法庭上,您可以回答 是或否。您否认[导弹]存在,我想知道我是否正确理解了您...。我准备等待直到你死定了,直到我死定为止。”

如果您的方法中具有的标志具有可将其固定为二进制决策的性质,并且该决策永远不会变成三向或n向决策,请使用布尔值。适应症:您的国旗叫isXXX

如果是模式切换的话,不要设为布尔值。首先编写方法时,总会比您想到的模式更多的模式

一个以上的模式难题就困扰着Unix,如今,文件或目录可能具有的许可模式可能会导致奇怪的双重含义,具体取决于文件类型,所有权等。


13

我遇到这样的坏事有两个原因:

  1. 因为有些人会编写如下方法:

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

    这显然是不好的,因为它很容易混淆参数,并且您不了解所指定的内容。虽然只有一个布尔值还算不错。

  2. 因为通过一个简单的yes / no分支控制程序流可能意味着您有两个完全不同的功能,它们以一种笨拙的方式包装在一起。例如:

    public void Write(bool toOptical);
    

    真的,这应该是两种方法

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

    因为其中的代码可能完全不同;他们可能必须执行各种不同的错误处理和验证,甚至可能必须以不同的方式格式化输出数据。您不能仅仅通过使用Write()甚至使用它来告诉您Write(Enum.Optical)(尽管您当然可以让这些方法之一仅调用内部方法WriteOptical / Mag)。

我想这取决于。除了第一名,我不会做太大的事情。


很好点!在一种方法中,两个布尔参数看起来确实很糟糕(当然,除非您很幸运拥有命名参数)。
Yarik's

不过,此答案可能会因重新格式化而受益!;-)
Yarik

7

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


只是使该方法具有很强的描述性,因此很清楚是对是什么意思。
simon

6

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

file.writeData(data, overwrite=true)

要么:

[file writeData:data overwrite:YES]

1
恕我直言,无论是否支持命名参数,writeData()都是使用布尔参数的错误示例。无论您如何命名参数,False值的含义都不明显!
Yarik's

4

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

首先让我举一个例子:拥有布尔参数并不会真正危害程序员编写好的代码的责任(和能力)。在您的示例中,程序员可以通过编写以下代码来编写冗长的代码:

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

还是我更一般

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

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


1
尽管我同意在许多情况下布尔参数都是可以接受的,但是在此writeData()示例的情况下,像shouldAppend这样的布尔参数是非常不合适的。原因很简单:False的含义不是立即显而易见。
Yarik's

4

枚举有一定的好处,但是您不应该只是用枚举代替所有的布尔值。实际上,在很多地方,对/错是代表所发生情况的最佳方法。

但是,将它们用作方法参数有点让人怀疑,这仅仅是因为您必须深入研究它们应该做的事才能看到它们,因为它们可以让您看到对/错的实际含义。

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


4

虽然在许多情况下枚举确实比布尔值更易读和可扩展,但“布尔值不可接受”的绝对规则是愚蠢的。它不灵活且适得其反-不会留下人类判断的空间。它们是大多数语言中基本的内置类型,因为它们很有用-考虑将其应用于其他内置类型:例如说“从不将int用作参数”会很疯狂。

该规则只是样式问题,而不是潜在的错误或运行时性能问题。更好的规则是“出于可读性考虑,应将枚举优选为布尔值”。

看一下.Net框架。在许多方法中,布尔值都用作参数。.Net API并不完美,但我认为使用布尔值作为参数并不是一个大问题。工具提示总是为您提供参数的名称,您也可以构建这种指导-在方法参数上填写XML注释,它们将出现在工具提示中。

我还应该补充一点,在某些情况下,您应该将布尔值清楚地重构为枚举-当您的类或方法参数中有两个或多个布尔值,并且并非所有状态都有效时(例如,使它们无效)都设为true)。

例如,如果您的班级具有以下属性

public bool IsFoo
public bool IsBar

同时使它们都正确是一个错误,实际上您得到的是三个有效状态,更好地表示为:

enum FooBarType { IsFoo, IsBar, IsNeither };

4

您的同事可能会遵循的一些规则是:

  • 不要对您的设计教条。
  • 选择最适合您的代码用户的内容。
  • 不要仅仅因为您喜欢本月的形状就将星形钉砸向每个孔!

3

仅当您不打算扩展框架的功能时,布尔值才是可接受的。最好使用Enum,因为您可以扩展枚举而不破坏函数调用的先前实现。

枚举的另一个优点是更易于阅读。


2

如果该方法问一个问题,例如:

KeepWritingData (DataAvailable());

哪里

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

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

布尔方法参数似乎完全是合理的。


有一天,您将需要添加“如果有可用空间,请继续写作”,然后无论如何您都会从bool变成枚举。
Ilya Ryzhenkov

然后您将发生重大变化,或者过时的过载,或者可能是类似KeppWritingDataEx的东西:)
Ilya Ryzhenkov

1
@Ilya,也许你不会!创建当前不存在的可能情况不会否决该解决方案。
Jesse C. Slicer

1
杰西是对的。计划这样的变化很愚蠢。做有意义的事情。在这种情况下,布尔值既直观又清晰。 c2.com/xp/DoTheSimplestThingThatCouldPossfullyWork.html
Derek Park

@Derek,在这种情况下,甚至不需要布尔值,因为DataAvailable始终返回true :)
Ilya Ryzhenkov

2

这取决于方法。如果该方法所做的事情很明显是对/错,那么就很好了,例如,下面[虽然不是我不是说这是此方法的最佳设计,但这只是用法显而易见的一个示例]。

CommentService.SetApprovalStatus(commentId, false);

但是,在大多数情况下,例如您提到的示例,最好使用枚举。.NET Framework本身中有许多示例未遵循此约定,因为这是在周期的较晚时间引入了此设计指南的。


2

它的确使事情变得更加明确,但是确实开始极大地扩展了界面的复杂性-在纯粹的布尔选择(如追加/覆盖)中,这似乎是过分的。如果您需要添加其他选项(在这种情况下我无法想到),则可以始终执行重构(取决于语言)


1
作为第三个可能的选择呢?;
Yarik

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;
}

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

[Flags]枚举可以为您带来更多的乐趣,并且验证代码略有不同。

我还将注意到,出于可移植性,您应ToString()在枚举上使用call ,并Enum.Parse()在读回它们时使用。它们ToString()Enum.Parse()也可以处理[Flags]枚举,因此没有理由不使用它们。提醒您,这是另一个陷阱,因为现在您甚至不能在不破坏代码的情况下更改枚举的名称。

因此,有时当您问自己时,需要权衡以上所有内容


1

恕我直言,对于任何可能有两个以上选择的情况,枚举似乎都是显而易见的选择。但是在某些情况下,您只需要布尔值即可。在那种情况下,我想说的是使用一个布尔值可以工作的枚举将是使用7个单词(而4个可以使用)的示例。


0

当您具有明显的切换时,布尔值才有意义,该切换只能是以下两种情况之一(即,灯泡的状态,打开或关闭)。除此之外,最好以一种显而易见的方式编写它,例如,磁盘写入-无缓冲,行缓冲或同步-应该这样传递。即使您现在不想允许同步写入(因此您只能使用两个选项),还是值得考虑使它们更加冗长,以便一眼就知道它们的功能。

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


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);

0

我确实同意枚举是个不错的选择,在您有2个选项的方法中(只有两个选项,无需枚举即可具有可读性。)

例如

public void writeData(Stream data, boolean is_overwrite)

喜欢枚举,但布尔值也很有用。


0

这是旧帖子的最新条目,而且页面太远了,没人能读,但是因为没人已经说过。

内联注释对于解决意外bool问题大有帮助。原始示例特别令人发指:假设尝试在函数清除中命名变量!就像

void writeData( DataObject data, bool use_append_mode );

但是,为了举例说明,我们说的是声明。然后,对于一个否则无法解释的布尔参数,我将变量名称放入内联注释中。比较

file.writeData( data, true );

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

-1

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


-1

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

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

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

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


一个有趣的想法。但是您可以重新排序参数吗?省略参数?如果这是可选的,编译器将如何知道您要绑定到哪个重载?另外,您是否必须命名列表中的所有参数?
德鲁·诺阿克斯

-1

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.