假设我们有一个接受枚举值的方法。在此方法检查该值是否有效后,它switch
超过可能的值。所以问题是,在验证了值范围之后,处理意外值的首选方法是什么?
例如:
enum Mood { Happy, Sad }
public void PrintMood(Mood mood)
{
if (!Enum.IsDefined(typeof(Mood), mood))
{
throw new ArgumentOutOfRangeException("mood");
}
switch (mood)
{
case Happy: Console.WriteLine("I am happy"); break;
case Sad: Console.WriteLine("I am sad"); break;
default: // what should we do here?
}
处理default
案例的首选方法是什么?
// can never happen
Debug.Fail()
(或Debug.Assert(false)
)throw new NotImplementedException()
(或任何其他例外)答案 0 :(得分:11)
我猜大多数上述答案都是有效的,但我不确定是否正确。
正确的答案是,你很少用OO语言切换,这表明你做错了。在这种情况下,它是您的Enum类有问题的完美指示。
你应该只是调用Console.WriteLine(mood.moodMessage()),并为每个状态定义moodMessage。
如果添加了新状态 - 您的所有代码都应自动调整,则不会有任何失败,抛出异常或需要更改。
编辑:回复评论。
在您的示例中,要成为“Good OO”,文件模式的功能将由FileMode对象控制。它可以包含一个具有“open,read,write ...”操作的委托对象,这些操作对于每个FileMode都是不同的,因此File.open(“name”,FileMode.Create)可以实现为(抱歉不熟悉API):
open(String name, FileMode mode) {
// May throw an exception if, for instance, mode is Open and file doesn't exist
// May also create the file depending on Mode
FileHandle fh = mode.getHandle(name);
... code to actually open fh here...
// Let Truncate and append do their special handling
mode.setPosition(fh);
}
这比尝试使用开关更简洁......(顺便说一句,这些方法既可以是包私有,也可以委托给“模式”类)
当OO运行良好时,每一种方法看起来都像几行真正可以理解的简单代码 - 太简单了。你总觉得有一些大杂乱的“芝士核”将所有小的nacho物体放在一起,但是你找不到它 - 它一直都是玉米片...
答案 1 :(得分:10)
我更喜欢throw new NotImplementedException("Unhandled Mood: " + mood)
。关键是枚举可能在将来发生变化,并且此方法可能不会相应更新。抛出异常似乎是最安全的方法。
我不喜欢Debug.Fail()
方法,因为该方法可能是库的一部分,并且可能无法在调试模式下测试新值。在这种情况下,使用该库的其他应用程序可能会面临奇怪的运行时行为,而在抛出异常的情况下,错误将立即被知道。
注意:commons.lang
中存在NotImplementedException
。
答案 2 :(得分:7)
在Java中,标准方法是抛出AssertionError
,原因有两个:
asserts
被禁用,也会引发错误。AssertionError
会比NotImplementedException
(Java无论如何)更好地记录您的假设。答案 3 :(得分:3)
我的观点是,因为它是一个程序员错误,你应该在它上面断言或抛出RuntimException(Java,或其他语言的等价物)。我有自己的UnhandledEnumException,它从我用于此的RuntimeException扩展。
答案 4 :(得分:3)
正确的程序响应将是死,以便开发人员能够轻松发现问题。 mmyers和JaredPar都提供了很好的方法。
为什么会死?这看起来非常极端!
原因在于,如果您没有正确处理枚举值而只是通过,那么您的程序将处于意外状态。一旦你处于一个意想不到的状态,谁知道发生了什么。这可能导致数据错误,难以追踪的错误,甚至安全漏洞。
另外,如果程序死了,那么你在质量保证中抓住它的可能性就大得多,因此它甚至没有出门。
答案 5 :(得分:2)
对于我的代码库中的几乎所有switch语句,我都有以下默认情况
switch( value ) {
...
default:
Contract.InvalidEnumValue(value);
}
该方法将抛出一个异常,详细说明检测到错误时枚举值。
public static void InvalidEnumValue<T>(T value) where T: struct
{
ThrowIfFalse(typeof(T).IsEnum, "Expected an enum type");
Violation("Invalid Enum value of Type {0} : {1}", new object[] { typeof(T).Name, value });
}
答案 6 :(得分:2)
对于C#,值得了解的是Enum.IsDefined()
is dangerous。你不能像你一样依赖它。获得不符合预期值的东西是抛出异常并大声死亡的好例子。
在Java中,它是不同的,因为枚举是不是整数的类,所以你真的不能得到意想不到的值(除非更新枚举而你的switch语句不是),这是我更喜欢Java枚举的一个重要原因。您还必须满足空值。但是获得一个你不认识的非空案例也是抛出异常的好例子。
答案 7 :(得分:1)
您可以使用默认值跟踪调用传递的枚举值。抛出异常是可以的,但在大型应用程序中,有几个地方你的代码不关心枚举的其他值。
因此,除非您确定代码打算处理枚举的所有可能值,否则您将不得不稍后返回并删除异常。
答案 8 :(得分:1)
这是证明为什么测试驱动开发如此重要的问题之一。
在这种情况下,我会选择NotSupportedException,因为字面上的值未处理,因此不受支持。 NotImplementedException提供了更多的想法:“这还没有完成”;)
调用代码应该能够处理这样的情况,并且可以创建单元测试以轻松测试这些情况。
答案 9 :(得分:0)
称之为意见或偏好,但枚举背后的想法是它代表可能值的完整列表。如果在代码中传递“意外值”,则枚举(或枚举背后的目的)不是最新的。我个人的偏好是每个枚举都带有默认的Undefined。鉴于枚举是一个已定义的列表,它永远不会与您的消费代码过时。
如果您的函数在我的情况下获得意外值或未定义,该怎么办,似乎不可能得到通用答案。对我来说,这取决于评估枚举值的原因的上下文:是代码执行应该停止的情况,还是可以使用默认值?
答案 10 :(得分:0)
调用函数负责提供有效的输入,并且隐含在枚举中的任何内容都是无效的(Pragmatic程序员似乎暗示这一点)。也就是说,这意味着无论何时更改枚举,都必须更改接受它作为输入的所有代码(以及将其作为输出生成的一些代码)。但无论如何,这可能是真的。如果你有一个经常更改的枚举,你可能应该使用枚举以外的东西,因为枚举通常是编译时实体。
答案 11 :(得分:0)
我通常尝试定义未定义的值(0):
enum Mood { Undefined = 0, Happy, Sad }
这样我总能说:
switch (mood)
{
case Happy: Console.WriteLine("I am happy"); break;
case Sad: Console.WriteLine("I am sad"); break;
case Undefined: // handle undefined case
default: // at this point it is obvious that there is an unknown problem
// -> throw -> die :-)
}
这至少是我通常这样做的方式。