我的一位同事说,布尔值作为方法参数是不可接受的。它们应由枚举代替。起初我没有看到任何好处,但是他给了我一个例子。
什么更容易理解?
file.writeData( data, true );
要么
enum WriteMode {
Append,
Overwrite
};
file.writeData( data, Append );
现在我懂了!;-)
这绝对是一个示例,其中枚举作为第二个参数使代码更具可读性。
那么,您对此主题有何看法?
我的一位同事说,布尔值作为方法参数是不可接受的。它们应由枚举代替。起初我没有看到任何好处,但是他给了我一个例子。
什么更容易理解?
file.writeData( data, true );
要么
enum WriteMode {
Append,
Overwrite
};
file.writeData( data, Append );
现在我懂了!;-)
这绝对是一个示例,其中枚举作为第二个参数使代码更具可读性。
那么,您对此主题有何看法?
Answers:
布尔值表示“是/否”选择。如果要表示“是/否”,则使用布尔值,这应该是不言自明的。
但是,如果在两个选项之间进行选择,而这两个选项都不是肯定或否定的,那么枚举有时会更具可读性。
setLightOn(bool)
。
使用最能模拟问题的模型。在您给出的示例中,枚举是一个更好的选择。但是,在其他情况下,布尔值会更好。这对您来说更有意义:
lock.setIsLocked(True);
要么
enum LockState { Locked, Unlocked };
lock.setLockState(Locked);
在这种情况下,我可能会选择布尔值选项,因为我认为它非常清楚且明确,而且我很确定我的锁不会有两个以上的状态。第二种选择仍然是有效的,但不必要的麻烦,恕我直言。
对我来说,既不使用布尔值也不使用枚举不是一个好的方法。罗伯特·C·马丁(Robert C. Martin)在他的“ 干净代码提示12:消除布尔参数”中非常清楚地说明了这一点:
布尔参数大声地声明该函数可以执行多项操作。它们令人困惑,应予以消除。
如果一个方法做多件事情,你应该写,而两种不同的方法,例如在你的情况:file.append(data)
和file.overwrite(data)
。
使用枚举并不能使事情变得更清晰。它没有任何改变,仍然是一个标志参数。
setVisible(boolean visible) { mVisible = visible; }
。您会建议什么替代方案?
我认为您几乎自己回答了这个问题,我认为最终目的是使代码更具可读性,在这种情况下,枚举是这样做的,IMO始终最好着眼于最终目标,而不是一揽子规则,也许应该多考虑一下作为准则,即枚举在代码中通常比通用布尔,整数等更具可读性,但该规则始终存在例外。
还记得阿德莱·史蒂文森在古巴导弹危机期间向佐林大使在联合国提出的问题吗?
“您现在处于世界舆论法庭上,您可以回答 是或否。您否认[导弹]存在,我想知道我是否正确理解了您...。我准备等待直到你死定了,直到我死定为止。”
如果您的方法中具有的标志具有可将其固定为二进制决策的性质,并且该决策永远不会变成三向或n向决策,请使用布尔值。适应症:您的国旗叫isXXX。
如果是模式切换的话,不要设为布尔值。首先编写方法时,总会有比您想到的模式更多的模式。
一个以上的模式难题就困扰着Unix,如今,文件或目录可能具有的许可模式可能会导致奇怪的双重含义,具体取决于文件类型,所有权等。
我遇到这样的坏事有两个原因:
因为有些人会编写如下方法:
ProcessBatch(true, false, false, true, false, false, true);
这显然是不好的,因为它很容易混淆参数,并且您不了解所指定的内容。虽然只有一个布尔值还算不错。
因为通过一个简单的yes / no分支控制程序流可能意味着您有两个完全不同的功能,它们以一种笨拙的方式包装在一起。例如:
public void Write(bool toOptical);
真的,这应该是两种方法
public void WriteOptical();
public void WriteMagnetic();
因为其中的代码可能完全不同;他们可能必须执行各种不同的错误处理和验证,甚至可能必须以不同的方式格式化输出数据。您不能仅仅通过使用Write()
甚至使用它来告诉您Write(Enum.Optical)
(尽管您当然可以让这些方法之一仅调用内部方法WriteOptical / Mag)。
我想这取决于。除了第一名,我不会做太大的事情。
我不同意这是一个好规则。显然,在某些情况下,Enum可以提供更好的显式或冗长的代码,但通常来说,它似乎无法实现。
首先让我举一个例子:拥有布尔参数并不会真正危害程序员编写好的代码的责任(和能力)。在您的示例中,程序员可以通过编写以下代码来编写冗长的代码:
dim append as boolean = true
file.writeData( data, append );
还是我更一般
dim shouldAppend as boolean = true
file.writeData( data, shouldAppend );
第二:您给出的Enum示例只是“更好”,因为您正在传递CONST。在大多数应用程序中,最有可能传递给函数的时间参数中的至少一些(如果不是大多数的话)是变量。在这种情况下,我的第二个示例(给变量起好名字)要好得多,而Enum不会给您带来什么好处。
枚举有一定的好处,但是您不应该只是用枚举代替所有的布尔值。实际上,在很多地方,对/错是代表所发生情况的最佳方法。
但是,将它们用作方法参数有点让人怀疑,这仅仅是因为您必须深入研究它们应该做的事才能看到它们,因为它们可以让您看到对/错的实际含义。
属性(尤其是使用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用作参数”会很疯狂。
该规则只是样式问题,而不是潜在的错误或运行时性能问题。更好的规则是“出于可读性考虑,应将枚举优选为布尔值”。
看一下.Net框架。在许多方法中,布尔值都用作参数。.Net API并不完美,但我认为使用布尔值作为参数并不是一个大问题。工具提示总是为您提供参数的名称,您也可以构建这种指导-在方法参数上填写XML注释,它们将出现在工具提示中。
我还应该补充一点,在某些情况下,您应该将布尔值清楚地重构为枚举-当您的类或方法参数中有两个或多个布尔值,并且并非所有状态都有效时(例如,使它们无效)都设为true)。
例如,如果您的班级具有以下属性
public bool IsFoo
public bool IsBar
同时使它们都正确是一个错误,实际上您得到的是三个有效状态,更好地表示为:
enum FooBarType { IsFoo, IsBar, IsNeither };
仅当您不打算扩展框架的功能时,布尔值才是可接受的。最好使用Enum,因为您可以扩展枚举而不破坏函数调用的先前实现。
枚举的另一个优点是更易于阅读。
如果该方法问一个问题,例如:
KeepWritingData (DataAvailable());
哪里
bool DataAvailable()
{
return true; //data is ALWAYS available!
}
void KeepWritingData (bool keepGoing)
{
if (keepGoing)
{
...
}
}
布尔方法参数似乎完全是合理的。
这取决于方法。如果该方法所做的事情很明显是对/错,那么就很好了,例如,下面[虽然不是我不是说这是此方法的最佳设计,但这只是用法显而易见的一个示例]。
CommentService.SetApprovalStatus(commentId, false);
但是,在大多数情况下,例如您提到的示例,最好使用枚举。.NET Framework本身中有许多示例未遵循此约定,因为这是在周期的较晚时间引入了此设计指南的。
枚举当然可以使代码更具可读性。仍然有一些注意事项(至少在.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]
枚举,因此没有理由不使用它们。提醒您,这是另一个陷阱,因为现在您甚至不能在不破坏代码的情况下更改枚举的名称。
因此,有时当您问自己时,需要权衡以上所有内容。
有时,使用重载为不同的行为建模更简单。继续您的示例将是:
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);
我确实同意枚举是个不错的选择,在您有2个选项的方法中(只有两个选项,无需枚举即可具有可读性。)
例如
public void writeData(Stream data, boolean is_overwrite)
喜欢枚举,但布尔值也很有用。
在示例中使用枚举而不是布尔确实有助于使方法调用更具可读性。但是,这替代了我在C#中最喜欢的愿望项目,即方法调用中的命名参数。这个语法:
var v = CallMethod(pData = data, pFileMode = WriteMode, pIsDirty = true);
完全可读,然后您就可以执行程序员应该做的事情,即为方法中的每个参数选择最合适的类型,而不必考虑它在IDE中的外观。
C#3.0允许在构造函数中使用命名参数。我不知道为什么他们也不能使用方法来做到这一点。