代码之家  ›  专栏  ›  技术社区  ›  Hosam Aly

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

  •  16
  • Hosam Aly  · 技术社区  · 15 年前

    假设我们有一个方法接受一个枚举的值。在该方法检查该值是否有效之后, 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 回复  |  直到 11 年前
        1
  •  11
  •   Bill K    15 年前

    我想以上大多数答案都是有效的,但我不确定是否正确。

    正确的答案是,您很少使用OO语言切换,这表明您的OO操作是错误的。在本例中,这是一个完美的迹象,表明枚举类存在问题。

    您只需调用console.writeline(mood.moodmessage())并为每个状态定义moodmessage。

    如果添加了一个新的状态——您的所有代码都应该自动适应,没有什么会失败、引发异常或需要更改。

    编辑:对评论的回应。

    在您的示例中,为了实现“良好的OO”,文件模式的功能将由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完成得很好时,每个方法看起来都像几行真正可以理解的简单代码——太简单了。你总是觉得有一个大而凌乱的“奶酪核”把所有的小纳乔物体聚集在一起,但你永远找不到它——它一直都是纳乔的……

        2
  •  10
  •   Jim G.    11 年前

    我更喜欢 throw new NotImplementedException("Unhandled Mood: " + mood) . 关键是枚举将来可能会改变,并且此方法可能不会相应地更新。抛出异常似乎是最安全的方法。

    我不喜欢 Debug.Fail() 方法,因为该方法可能是库的一部分,而新值可能不会在调试模式下进行测试。在这种情况下,使用该库的其他应用程序可能会面临奇怪的运行时行为,而在抛出异常的情况下,错误将立即被发现。

    注: NotImplementedException 存在于 commons.lang .

        3
  •  7
  •   Michael Myers KitsuneYMG    15 年前

    在Java中,标准的方法是抛出一个 AssertionError ,有两个原因:

    1. 这确保即使 asserts 如果禁用,则引发错误。
    2. 您断言没有其他枚举值,因此 断言错误 记录你的假设比 NotImplementedException (无论如何Java都没有)。
        4
  •  3
  •   TofuBeer    15 年前

    我的意见是,因为它是程序员错误,您应该断言它或抛出RunTimeExchange(Java,或者任何其他语言的等价物)。我有自己的unhandledenumException,它从我为此使用的RuntimeException扩展而来。

        5
  •  3
  •   Community pid    7 年前

    正确的程序响应将是 死亡 以一种能让开发人员容易发现问题的方式。 mmyers JaredPar 他们都给了很好的方法。

    为什么死? 这似乎太极端了!

    原因是,如果您没有正确地处理枚举值, 跌倒 ,您正在将程序置于意外状态。一旦你处于意想不到的状态,谁知道发生了什么。这可能导致坏数据、更难跟踪的错误,甚至安全漏洞。

    另外,如果程序死了,那么你在QA中抓住它的机会就大得多,因此它甚至都不会出门。

        6
  •  2
  •   JaredPar    15 年前

    对于我的代码库中的几乎每个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 });
    }
    
        7
  •  2
  •   Hosam Aly    15 年前

    对于C,值得知道的是 Enum.IsDefined() is dangerous . 你不能像现在这样依赖它。得到一些不符合期望值的东西是一个很好的例子,可以抛出一个例外,然后大声地死去。

    在Java中,它是不同的,因为枚举是类而不是整数,所以你真的不能得到意想不到的值(除非枚举被更新,并且你的转换语句不存在),这也是我更喜欢Java枚举的一个主要原因。您还必须满足空值的要求。但是,获得一个您不认识的非空案例对于抛出异常也是一个很好的案例。

        8
  •  1
  •   Mohit Chakraborty    15 年前

    可以对默认值进行跟踪,以调用传递的枚举的值。抛出异常是可以的,但在大型应用程序中,有几个地方您的代码不关心枚举的其他值。
    因此,除非您确定代码打算处理枚举的所有可能值,否则稍后必须返回并移除异常。

        9
  •  1
  •   Jeroen Landheer    15 年前

    这就是那些证明测试驱动开发为什么如此重要的问题之一。

    在本例中,我将使用NotSupportedException,因为实际上该值是未处理的,因此不受支持。NotImplementedException提供了更多的概念:“这还没有完成”;)

    调用代码应该能够处理这样的情况,并且可以创建单元测试来轻松测试这种情况。

        10
  •  0
  •   jro    15 年前

    称之为意见或偏好,但枚举背后的想法是它代表可能值的完整列表。如果在代码中传递“意外值”,则枚举(或枚举背后的目的)不是最新的。我个人的偏好是,每个枚举都带有未定义的默认赋值。考虑到枚举是一个已定义的列表,它不应该与您的消费代码一起过期。

    至于在我的例子中,如果函数得到了一个意外的值或未定义的值该怎么办,一般的答案似乎是不可能的。对我来说,这取决于评估枚举值的原因的上下文:是应该停止代码执行,还是可以使用默认值?

        11
  •  0
  •   Brian    15 年前

    调用函数的责任是提供有效的输入,并且隐式地,枚举中没有的任何内容都是无效的(实用程序员似乎暗示了这一点)。也就是说,这意味着无论何时更改枚举,都必须更改所有接受它作为输入的代码(以及一些将其作为输出生成的代码)。但无论如何,这可能是真的。如果您有一个经常更改的枚举,那么考虑到该枚举通常是编译时实体,您可能应该使用的不是枚举。

        12
  •  0
  •   Hosam Aly    15 年前

    我通常试图定义未定义的值(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 :-)
    }
    

    这至少是我通常的做法。