处理意外枚举值的首选方法是什么?

时间:2009-03-06 18:38:32

标签: c# java .net language-agnostic enums

假设我们有一个接受枚举值的方法。在此方法检查该值是否有效后,它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()(或任何其他例外)
  • 我没有想过的其他方式

12 个答案:

答案 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,原因有两个:

  1. 这可确保即使asserts被禁用,也会引发错误。
  2. 您断言没有其他枚举值,因此AssertionError会比NotImplementedException(Java无论如何)更好地记录您的假设。

答案 3 :(得分:3)

我的观点是,因为它是一个程序员错误,你应该在它上面断言或抛出RuntimException(Java,或其他语言的等价物)。我有自己的UnhandledEnumException,它从我用于此的RuntimeException扩展。

答案 4 :(得分:3)

正确的程序响应将是,以便开发人员能够轻松发现问题。 mmyersJaredPar都提供了很好的方法。

为什么会死?这看起来非常极端!

原因在于,如果您没有正确处理枚举值而只是通过,那么您的程序将处于意外状态。一旦你处于一个意想不到的状态,谁知道发生了什么。这可能导致数据错误,难以追踪的错误,甚至安全漏洞。

另外,如果程序死了,那么你在质量保证中抓住它的可能性就大得多,因此它甚至没有出门。

答案 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 :-)
}

这至少是我通常这样做的方式。