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

Posted

技术标签:

【中文标题】布尔值作为方法参数是不可接受的吗? [关闭]【英文标题】:Are booleans as method arguments unacceptable? [closed] 【发布时间】:2010-09-13 05:53:37 【问题描述】:

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

什么更容易理解?

file.writeData( data, true );

或者

enum WriteMode 
  Append,
  Overwrite
;

file.writeData( data, Append );

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

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

【问题讨论】:

这是一个非常有趣的阅读,我会更频繁地实现这个方法。 hrm,我以前做过,但从未意识到这是多么好的设计模式。所以枚举进入文件? 从语义角度来看,枚举当然更有意义。另一方面,看看一些程序员想出什么来处理模糊逻辑会很有趣。 如果这是不可接受的,请询问冒险时间的柠檬人 另见:boolean parameters — do they smell? 【参考方案1】:

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

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

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

    这显然很糟糕,因为它太容易混淆参数,而且你不知道你在指定什么。不过只有一个布尔值还不错。

    因为通过简单的是/否分支来控制程序流可能意味着您有两个完全不同的功能,它们以一种尴尬的方式包装成一个。例如:

    public void Write(bool toOptical);
    

    真的,这应该是两种方法

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

    因为这些代码可能完全不同;他们可能必须进行各种不同的错误处理和验证,甚至可能必须以不同的方式格式化输出数据。您无法仅通过使用 Write() 甚至 Write(Enum.Optical) 来判断这一点(当然,如果您愿意,您可以使用其中任何一种方法,只需调用内部方法 WriteOptical/Mag)。

我想这取决于。除了#1,我不会在这方面做太多的事情。

【讨论】:

非常好的点!确实,一个方法中的两个布尔参数看起来很糟糕(当然,除非你很幸运有命名参数)。 不过,这个答案可能会受益于重新格式化! ;-)【参考方案2】:

这是一篇旧帖子的后期条目,它位于页面下方以至于没有人会阅读它,但是因为没有人已经说过......

内联注释对于解决意外的bool 问题大有帮助。最初的例子特别令人发指:想象一下试图在函数 declearation 中命名变量!应该是这样的

void writeData( DataObject data, bool use_append_mode );

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

file.writeData( data, true );

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

【讨论】:

【参考方案3】:

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

例如

public void writeData(Stream data, boolean is_overwrite)

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

【讨论】:

【参考方案4】:

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

不要对您的设计教条主义。 选择最适合您的代码用户的内容。 不要仅仅因为你喜欢这个月的形状,就试图把星形钉子砸进每个洞!

【讨论】:

【参考方案5】:

有时使用重载对不同的行为进行建模会更简单。继续您的示例将是:

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

【讨论】:

【参考方案6】:

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

【讨论】:

【参考方案7】:

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

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

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

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

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

public bool IsFoo
public bool IsBar

同时让它们都为真是错误的,你实际上得到的是三个有效状态,更好地表达为:

enum FooBarType  IsFoo, IsBar, IsNeither ;

【讨论】:

【参考方案8】:

对我来说,使用布尔值和枚举都不是一个好方法。 Robert C. Martin 在他的Clean Code Tip #12: Eliminate Boolean Arguments 中非常清楚地捕捉到了这一点:

布尔参数大声声明函数做不止一件事。它们令人困惑,应该消除。

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

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

【讨论】:

这不是说接受长度为 N 的 ASCII 字符串的函数会做 128^N 件事情吗? @delty 这是一个严肃的评论吗?如果是,您是否经常在字符串的所有可能值上编写 if 代码?是否可以与布尔参数的情况进行比较? 我相信当您在对象内设置布尔值时它们是可以接受的。一个完美的例子是setVisible(boolean visible) mVisible = visible; 。你会建议什么替代方案? @Brad show() mVisible = true hide() mVisible = false @Oswaldo,虽然仍然正确,但我认为使用两种不同的方法将布尔值分配给不同的值并不完全有意义。你没有 setIntToOne(), setIntToTwo() setIntToThree() 对吧?当您只能有两个可能的值但为了清洁起见在这种情况下使用布尔值时,它会更加模棱两可。【参考方案9】:

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

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

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

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

【讨论】:

一个有趣的想法。但是你能重新排序参数吗?省略参数?如果这是可选的,编译器如何知道您绑定到哪个重载?另外,您是否必须命名列表中的所有参数?【参考方案10】:

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

【讨论】:

【参考方案11】:

枚举当然可以使代码更具可读性。还有一些事情需要注意(至少在 .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] 枚举,所以没有理由不使用它们。请注意,这是另一个陷阱,因为现在您甚至无法更改枚举的名称而不会破坏代码。

所以,有时当您问自己时,您需要权衡以上所有因素我可以只用一个布尔值吗?

【讨论】:

【参考方案12】:

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

但是,将它们用作方法参数有点可疑,仅仅是因为如果不深入研究它们应该做什么,你就看不到它们,因为它们让你看到真/假 实际上意味着什么 em>

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

【讨论】:

【参考方案13】:

还记得在cuban missile crisiscuban missile crisis联合国大使佐林提出的问题吗?

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

如果您的方法中的标志具有这样一种性质,您可以将其归结为 二元决策,并且该决策永远不会变成三-way 或 n-way 决定,选择布尔值。说明:你的旗帜叫isXXX

如果是模式开关,请勿将其设为布尔值。在编写方法时,总是有比您想的多一种模式

一种模式的困境有例如闹鬼的 Unix,文件或目录今天可能具有的权限模式会导致模式的奇怪双重含义,具体取决于文件类型、所有权等。

【讨论】:

【参考方案14】:

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

file.writeData(data, overwrite=true)

或:

[file writeData:data overwrite:YES]

【讨论】:

恕我直言,writeData() 是使用布尔参数的一个坏例子,无论是否支持命名参数。不管你如何命名参数,False值的含义并不明显!【参考方案15】:

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

lock.setIsLocked(True);

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

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

【讨论】:

在你的例子中我宁愿有两种方法。 lock.lock() lock.release() 和 lock.IsSet,但这一切都取决于什么对消费代码最有意义。 这是一个公平的评论,但我认为它也说明了更大的一点,即有很多方法可以对给定问题进行建模。您应该使用适合您的情况的最佳模型,还应该使用编程环境提供的最佳工具来适应您的模型。 我完全同意 :),我只是在评论特定的伪代码提供了另一种观点。我同意你的回答。【参考方案16】:

我不同意这是一个好的规则。显然,Enum 在某些情况下提供了更好的显式或详细代码,但通常它似乎超出了范围。

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

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

或者我更喜欢更通用的

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

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

【讨论】:

虽然我同意布尔参数在很多情况下是可以接受的,但是在这个 writeData() 例子中,像 shouldAppend 这样的布尔参数是非常不合适的。原因很简单:False 的含义不是很明显。【参考方案17】:

它确实使事情变得更加明确,但确实开始大量扩展接口的复杂性 - 在诸如附加/覆盖之类的纯粹布尔选择中,它似乎有点矫枉过正。如果您需要添加更多选项(在这种情况下我无法想到),您始终可以执行重构(取决于语言)

【讨论】:

作为第三种可能选项的前置怎么样? ;-))【参考方案18】:

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

【讨论】:

或 Win32 的 GetMessage():TRUE、FALSE 或 -1。 @Rich: thedailywtf.com/Articles/What_Is_Truth_0x3f_.aspx【参考方案19】:

当您有一个明显的切换时,布尔值是有意义的,它只能是两件事之一(即灯泡的状态,开或关)。除此之外,最好以一种很明显的方式编写它,以使您传递的内容很明显-例如磁盘写入——无缓冲的、行缓冲的或同步的——应该这样传递。即使您现在不想允许同步写入(因此您仅限于两个选项),也值得考虑让它们更详细,以便乍一看知道它们在做什么。

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

【讨论】:

【参考方案20】:

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

【讨论】:

【参考方案21】:

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

CommentService.SetApprovalStatus(commentId, false);

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

【讨论】:

【参考方案22】:

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

KeepWritingData (DataAvailable());

在哪里

bool DataAvailable()

    return true; //data is ALWAYS available!


void KeepWritingData (bool keepGoing)

   if (keepGoing)
   
       ...
   

布尔方法参数似乎非常有意义。

【讨论】:

有一天你需要添加“如果你有空闲空间就继续写”,然后你会从 bool 转到 enum。 然后你会有重大变化,或者过时的重载,或者可能是 KeppWritingDataEx :) @Ilya 或者你不会!创建一个当前不存在的可能情况并不否定解决方案。 杰西说得对。计划这样的改变是愚蠢的。做有意义的事。在这种情况下,布尔值既直观又清晰。 c2.com/xp/DoTheSimplestThingThatCouldPossiblyWork.html @Derek,在这种情况下,甚至不需要布尔值,因为 DataAvailable 总是返回 true :)【参考方案23】:

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

【讨论】:

只要让方法描述性很强,这样就可以清楚地知道 true 或 yes 的含义。【参考方案24】:

我认为您几乎自己回答了这个问题,我认为最终目标是使代码更具可读性,在这种情况下,枚举做到了,IMO 总是最好查看最终目标而不是笼统的规则,也许认为它更多地作为指导原则,即枚举在代码中通常比通用布尔值、整数等更具可读性,但规则总会有例外。

【讨论】:

【参考方案25】:

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

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

【讨论】:

此外,方法名称必须清楚参数 yes 或 no 的作用,即 void turnLightOn(bool) 明确设置 true 或 yes 将打开灯。 虽然在这种情况下,我可能更愿意使用 turnLightOn() 和 turnLightOff(),具体取决于具体情况。 "turnLightOn(false)" 的意思是“不要开灯”?混乱。 setLightOn(bool) 怎么样。 迟来的评论,但是@Jay Bazuzi:如果你的方法调用了turnLightOn,而你传入了false,那你还不如根本不调用该方法,传入false表示不要开灯在。如果灯已经亮了,这并不意味着将其关闭,这意味着不要将其打开...如果您有一个方法 'turnLight' 一个带有 'On' 和 'Off' 的枚举有意义,turnLight(开),关灯(关)。我同意 skaffman 的观点,我宁愿使用两种不同的显式方法,turnLightOn() 和 turnLightOff()。 (顺便说一句:这东西在鲍勃叔叔的书“清洁代码”中有解释)【参考方案26】:

仅当您不打算扩展框架的功能时,才可以使用布尔值。 Enum 是首选,因为您可以扩展 enum 而不会破坏函数调用的先前实现。

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

【讨论】:

以上是关于布尔值作为方法参数是不可接受的吗? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

Python 的布尔值是按值传递的吗?

如何输入布尔值并作为布尔值返回?

Rest api如何获取参数?

从调用 Int 的方法返回布尔值

DOM事件处理程序

如何将布尔数据类型作为存储过程的参数传递?