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

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

什么更容易理解?

file.writeData( data, true ); 

要么

 enum WriteMode { Append, Overwrite }; file.writeData( data, Append ); 

现在我懂了! 😉
这绝对是一个枚举作为第二个参数使得代码更具可读性的例子。

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

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

但是如果在两个选项之间进行select,两者都不是肯定的,那么枚举有时可以更易读。

枚举还允许将来的修改,现在你想要第三个select(或更多)。

使用最适合你的问题的模型。 在你给的例子中,枚举是一个更好的select。 但是,布尔值会更好一些。 这对你更有意义:

 lock.setIsLocked(True); 

要么

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

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

我认为你自己几乎可以回答这个问题,我认为最终的目的是让代码更具可读性,在这种情况下,枚举就是这样做的,IMO总是最好的看待最终目标而不是一揽子规则,或许想想更多作为一个指导,即枚举通常比通用的布尔,整数等代码更易读,但总是会有例外的规则。

请记住Adlai Stevenson在古巴导弹危机期间在联合国向佐林大使提出的问题吗?

“你现在在世界舆论的审判室,你可以回答是或否 ,你否认(导弹)存在,我想知道我是否正确地理解了你……我准备等待因为我的答案直到地狱冻结,如果这是你的决定。

如果你的方法中的标志具有这样的性质,那么你可以把它归结为一个二元决定 ,而这个决定永远不会变成一个三向或者多向的决策,那就去布尔型。 适应症:你的国旗叫做isXXX

如果是模式切换 ,请不要布尔值。 首先写出这个方法总是会有一个比你想象的更多的模式

一个多模式的困境有例如闹鬼的Unix,在这种模式下,文件或目录可能具有的权限模式会根据文件types,所有权等导致模式的双重含义。

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

布尔参数大声地声明该函数不止一件事情。 他们混乱,应该被淘汰。

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

使用枚举不会使事情变得更清晰。 它不会改变任何东西,它仍然是一个标志论证。

我遇到这个问题有两个原因是不好的:

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

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

    这显然是不好的,因为混淆参数太容易了,你不知道你在指定什么。 只是一个布尔人不是太糟糕了。

  2. 因为通过简单的“是/否”分支来控制程序stream可能意味着你有两个完全不同的function,它们以一种不寻常的方式被包装成一个。 例如:

     public void Write(bool toOptical); 

    真的,这应该是两种方法

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

    因为这些代码可能完全不同; 他们可能必须做各种不同的error handling和validation,或者甚至必须以不同的方式格式化传出的数据。 你不能通过使用Write()甚至Write(Enum.Optical) (当然,如果你愿意的话,你可以使用其中的任何一种方法调用内部方法WriteOptical / Mag)。

我想这只是取决于。 除了#1以外,我不会做太多的事情。

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

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

 file.writeData(data, overwrite=true) 

要么:

 [file writeData:data overwrite:YES] 

我不同意这是一个很好的规则 。 很明显,在某些情况下,Enum提供了一个更好的明确或冗长的代码,但通常情况下它似乎超过了达到。

首先让我看看你的例子:编写好代码的程序员的责任(和能力)并不会因为布尔参数而受到威胁。 在你的例子中,程序员可以写成如下冗长的代码:

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

或者我更喜欢更一般的

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

第二:你给的Enum例子只是“更好”,因为你传递了一个CONST。 在大多数应用程序中,至less有一些(如果不是大部分)传递给函数的参数是VARIABLES。 在这种情况下,我的第二个例子(给名称variables)好得多,Enum会给你一点好处。

枚举有一个确定的好处,但是你不应该用枚举来replace所有的布尔值。 真/假实际上是很多地方代表正在发生的事情。

然而,把它们作为方法论点是有点可疑的,因为如果不深入挖掘它们应该做的事情,你就看不到它们,因为它们让你看到真假什么意思

属性(尤其是使用C#3对象初始值设定项)或关键字参数(一个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的原因之一:-(

虽然在很多情况下,枚举比布尔变得更具可读性和可扩展性,但“绝对不能接受”的绝对规则是愚蠢的。 这是僵化和适得其反 – 它没有留下人的判断余地。 在大多数语言中,它们是一种基本的内置types,因为它们很有用 – 可以考虑将其应用于其他内置types:比如说“永远不要使用int作为参数”。

这个规则只是风格的问题,而不是bug或运行时性能的潜在问题。 一个更好的规则是“为了可读性的原因,更喜欢布尔types的枚举”。

看看.Net框架。 在很多方法中,布尔值都被用作参数。 .Net API并不完美,但我不认为使用布尔值作为参数是一个大问题。 工具提示总是给你参数的名称,你也可以build立这样的指导 – 在方法参数中填写你的XML注释,它们会出现在工具提示中。

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

例如,如果你的类有类似的属性

 public bool IsFoo public bool IsBar 

如果两者同时为真是错误的,实际上你得到的是三个有效的状态,更好地expression为:

 enum FooBarType { IsFoo, IsBar, IsNeither }; 

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

  • 不要用你的devise来教条。
  • select适合您的代码的用户最适合的内容。
  • 不要因为你喜欢这个月的形状而把星形钉子撞到每个洞里!

布尔值只有在你不打算扩展框架的function时才可以接受。 枚举是首选,因为您可以扩展枚举,而不会破坏以前的函数调用的实现。

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

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

 KeepWritingData (DataAvailable()); 

哪里

 bool DataAvailable() { return true; //data is ALWAYS available! } void KeepWritingData (bool keepGoing) { if (keepGoing) { ... } } 

布尔方法的参数似乎使绝对完美的感觉。

这取决于方法。 如果这个方法做了非常明显的真/假事情,那么它是好的,例如在下面[虽然不是我不是说这是这个方法的最好的devise,它只是一个例子,用法是显而易见的]。

 CommentService.SetApprovalStatus(commentId, false); 

但是在大多数情况下,比如你提到的例子,最好使用一个枚举。 在.NET框架本身中有很多不遵循这个约定的例子,但那是因为他们在周期的晚些时候才引入了这个devise原则。

它确实使事情变得更加明确,但是却开始大量扩展接口的复杂性 – 在一个纯粹的布尔select中,例如追加/覆盖它看起来像是矫枉过正。 如果你需要添加更多的选项(在这种情况下我想不出来),你总是可以执行一个重构(取决于语言)

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

由于枚举的底层存储是一个int,默认值将为零,所以你应该确保0是一个合理的默认值。 (例如,结构体在创build时被设置为零,所以没有办法指定一个不是0的默认值。如果没有0值,那么甚至不能testing枚举而不投射到int,这将是不好的风格)。

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

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

我可以合法地写

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

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

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

Enum.IsDefined的唯一警告是它使用reflection,速度较慢。 它也遭受版本问题。 如果您需要经常检查枚举值,那么最好使用以下命令:

 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]枚举可以做更多的乐趣,validation代码稍有不同。

我还会注意到,为了便于携带,您应该在枚举上使用ToString() ,并在重新读Enum.Parse()使用Enum.Parse()ToString()Enum.Parse()都可以处理[Flags]枚举那么,没有理由不使用它们。 请注意,这是另一个陷阱,因为现在你甚至不能改变枚举的名字而不可能破坏代码。

所以,有时你需要在问自己的时候权衡以上所有的问题我可以用一个bool来解决吗?

恕我直言,这似乎是一个枚举将是任何情况下,有两个以上的select是可能的明显的select。 但是,肯定有一些布尔型是你所需要的。 在这种情况下,我会说,使用一个布尔将工作的枚举将是一个例子,当4将使用7个字。

当你有一个明显的切换,只能是两件事情之一(即灯泡的状态,打开或closures),布尔有道理。 除此之外,编写这样的代码非常好,因为它明显地传递了什么 – 例如,磁盘写入 – 无缓冲,线路缓冲或同步 – 应该这样传递。 即使你现在不想允许同步写入(所以你只限于两种select),为了知道他们乍一看是什么,我们有必要考虑使它们更加冗长。

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

有时,用重载对不同的行为build模就更简单了。 继续从你的例子将是:

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

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

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

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

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

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

如果你知道只会有两个select,布尔可能会很好。 枚举是可扩展的,不会破坏旧代码,尽pipe旧库可能不支持新的枚举值,因此版本控制不能完全忽略。


编辑

在更新版本的C#中,可以使用命名参数,IMO可以使枚举的调用代码更清晰。 使用与上面相同的示例:

 Enum.Parse(str, ignoreCase: true); 

在那里我同意Enums是好的方法,在你有两个选项的方法(只有两个选项,你可以有可读性没有枚举。)

例如

 public void writeData(Stream data, boolean is_overwrite) 

爱的枚举,但布尔也是有用的。

这是一个旧的post晚了,它是如此之低,没有人会阅读,但由于没有人已经说过了….

内联评论大大解决了意想不到的bool问题。 原来的例子特别令人发指:试想在函数删除中命名variables! 它会是这样的

 void writeData( DataObject data, bool use_append_mode ); 

但是,为了举例,让我们说这是宣言。 然后,对于另外一个无法解释的布尔参数,我把variables名放在一个内联注释中。 比较

 file.writeData( data, true ); 

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

这实际上取决于论证的确切性质。 如果它不是一个是/否或真/假,那么枚举使其更具可读性。 但有了枚举,你需要检查参数或者有可接受的默认行为,因为可以传递基础types的未定义值。

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

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

将是完全可读的,然后你可以做一个程序员应该做的事情,这就是为方法中的每个参数select最合适的types,而不考虑它在IDE中的外观。

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

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