处理意外枚举值的首选方法是什么?
假设我们有一个接受枚举值的方法。 此方法检查该值是否有效后,它会切换
可能的值。 所以问题是,在验证值范围后处理意外值的首选方法是什么?
例如:
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发生
-
Debug.Fail()
(或Debug.Assert(false)
) -
throw new NotImplementedException ()
(或任何其他例外) - 我没有想到的其他方式
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(12)
我想上面的大部分答案都是有效的,但我不确定是否正确。
正确的答案是,你很少切换到面向对象语言,这表明你的面向对象方法是错误的。 在这种情况下,这完美地表明您的 Enum 类有问题。
您应该只调用 Console.WriteLine(mood.moodMessage()),并为每个状态定义心情消息。
如果添加新状态 - 所有代码都应自动适应,任何内容都不应失败、引发异常或需要更改。
编辑:回复评论。
在您的示例中,要成为“Good OO”,文件模式的功能将由 FileMode 对象控制。 它可以包含一个具有“打开、读取、写入...”操作的委托对象,这些操作对于每个 FileMode 都是不同的,因此 File.open("name", FileMode.Create) 可以实现为(抱歉,不熟悉API):
这比尝试使用开关来实现要简洁得多...(顺便说一下,这些方法都是包私有的,并且可能委托给“Mode”类)
当 OO 做得很好时,每个方法看起来就像几行真正可以理解的简单代码——太简单了。 你总是感觉有一些大而凌乱的“奶酪核”将所有的小玉米片物体粘在一起,但你永远找不到它——它一直是玉米片......
I guess most of the above answers are valid, but I'm not sure any are correct.
The correct answer is, you very rarely switch in an OO language, it indicates you are doing your OO wrong. In this case, it's a perfect indication that your Enum class has problems.
You should just be calling Console.WriteLine(mood.moodMessage()), and defining moodMessage for each of the states.
If a new state is added--All Your Code Should Adapt Automatically, nothing should fail, throw an exception or need changes.
Edit: response to comment.
In your example, to be "Good OO" the functionality of the file mode would be controlled by the FileMode object. It could contain a delegate object with "open, read, write..." operations that are different for each FileMode, so File.open("name", FileMode.Create) could be implemented as (sorry about the lack of familiarity with the API):
This is much neater than trying to do it with switches... (by the way, the methods would be both package-private and possibly delegated to "Mode" classes)
When OO is done well, every single method looks like a few lines of really understandable, simple code--TOO simple. You always get the feeling that there is some big messy "Cheese Nucleus" holding together all the little nacho objects, but you can't ever find it--it's nachos all the way down...
我更喜欢抛出 new NotImplementedException("Unhandled Mood: " + Mood)。 重点是,将来枚举可能会发生变化,这个方法可能不会相应更新。 抛出异常似乎是最安全的方法。
我不喜欢 Debug.Fail() 方法,因为该方法可能是库的一部分,并且新值可能不会在调试模式下进行测试。 在这种情况下,使用该库的其他应用程序可能会面临奇怪的运行时行为,而在引发异常的情况下,将立即知道错误。
注意:
NotImplementedException
存在于commons.lang
。I prefer to
throw new NotImplementedException("Unhandled Mood: " + mood)
. The point is that the enumeration may change in the future, and this method may not be updated accordingly. Throwing an exception seems to be the safest method.I don't like the
Debug.Fail()
method, because the method may be part of a library, and the new values might not be tested in debug mode. Other applications using that library can face weird runtime behaviour in that case, while in the case of throwing an exception the error will be known immediately.Note:
NotImplementedException
exists incommons.lang
.在 Java 中,标准方法是抛出
AssertionError
,原因有两个:asserts
被禁用,也会抛出错误。AssertionError
比NotImplementedException
(Java 没有)更好地记录了您的假设。In Java, the standard way is to throw an
AssertionError
, for two reasons:asserts
are disabled, an error is thrown.AssertionError
documents your assumptions better thanNotImplementedException
(which Java doesn't have anyway).我的观点是,由于这是一个程序员错误,您应该对其进行断言或抛出 RuntimException(Java 或其他语言的等效项)。 我有自己的 UnhandledEnumException,它扩展自我用于此目的的 RuntimeException。
My opinion is that since it is a programmer error you should either assert on it or throw a RuntimException (Java, or whatever the equivalent is for other languages). I have my own UnhandledEnumException that extends from RuntimeException that I use for this.
正确的程序响应是以一种允许开发人员轻松发现问题的方式死亡。 mmyers 和 JaredPar 都提供了很好的方法去做。
为什么要死?这看起来太极端了!
原因是,如果您没有正确处理枚举值并且只是失败,那么您就会将程序置于意外状态。 一旦你处于意想不到的状态,谁知道会发生什么。 这可能会导致不良数据、难以追踪的错误,甚至安全漏洞。
另外,如果程序死掉了,你在 QA 中发现它的可能性就会大得多,因此它甚至不会被淘汰。
The correct program response would be to die in a manner that will allow the developer to easily spot the problem. mmyers and JaredPar both gave good ways to do that.
Why die? That seems so extreme!
The reason being that if you're not handling an enum value properly and just fall through, you're putting your program into an unexpected state. Once you're in an unexpected state, who knows what's going on. This can lead to bad data, errors that are harder to track down, or even security vulnerabilities.
Also, if the program dies, there's a much greater chance that you're going to catch it in QA and thus it doesn't even go out the door.
对于我的代码库中的几乎每个 switch 语句,我都有以下默认情况
该方法将抛出一个异常,详细说明检测到错误时的枚举值。
For pretty much every switch statement in my code base, I have the following default case
The method will throw an exception detailing the value of the enum at the point an error was detected.
对于 C#,值得了解的是
Enum.IsDefined ()
是危险的。 你不能像现在这样依赖它。 得到不符合预期值的东西是抛出异常并大声死亡的好例子。在 Java 中,情况有所不同,因为枚举是类而不是整数,因此您实际上无法获得意外的值(除非更新了枚举,而您的 switch 语句没有更新),这是我更喜欢 Java 枚举的一大原因。 您还必须考虑空值。 但是,获得您不认识的非空情况也是抛出异常的好情况。
For C#, something worth knowing is that
Enum.IsDefined()
is dangerous. You can't rely on it like you are. Getting something not of the expected values is a good case for throwing an exception and dying loudly.In Java, it's different because enums are classes not integers so you really can't get unexpected values (unless the enum is updated and your switch statement isn't), which is one big reason why I much prefer Java enums. You also have to cater for null values. But getting a non-null case you don't recognize is a good case for throwing an exception too.
您可以跟踪默认调用传递的枚举值。 抛出异常是可以的,但在大型应用程序中,有几个地方您的代码不关心枚举的其他值。
因此,除非您确定代码打算处理枚举的所有可能值,否则您必须稍后返回并删除异常。
You could have a trace for the default calling out the value of the passed enum. Throwing exceptions is OK but in a large application there will be several places where your code does not care about other values of the enum.
So, unless you are sure that the code intends to handle all possible values of the enum, you'll have to go back later and remove the exception.
这是证明测试驱动开发为何如此重要的问题之一。
在这种情况下,我会选择 NotSupportedException,因为实际上该值未处理,因此不受支持。 NotImplementedException 给出了更多这样的想法:“这还没有完成”;)
调用代码应该能够处理这样的情况,并且可以创建单元测试来轻松测试此类情况。
This is one of those questions that proves why test driven development is so important.
In this case I'd go for a NotSupportedException because literally the value was unhandled and therefore not supported. A NotImplementedException gives more the idea of: "This is not finished yet" ;)
The calling code should be able to handle a situation like this and unit tests can be created to easily test these kind of situations.
称其为意见或偏好,但枚举背后的想法是它代表可能值的完整列表。 如果在代码中传递“意外值”,则枚举(或枚举背后的目的)不是最新的。 我个人的偏好是每个枚举都带有默认的未定义分配。 鉴于枚举是一个已定义的列表,它永远不应该与您使用的代码一起过时。
至于在我的情况下,如果您的函数获得意外值或未定义该怎么办,似乎不可能给出通用答案。 对我来说,这取决于评估枚举值的原因的上下文:是代码执行应该停止的情况,还是可以使用默认值?
Call it an opinion or a preference, but the idea behind the enum is that it represents the full list of possible values. If an "unexpected value" is being passed around in code, then the enum (or purpose behind the enum) is not up to date. My personal preference is that every enum carry a default assignment of Undefined. Given that the enum is a defined list, it should never be out-of-date with your consuming code.
As far as what to do if your function is getting either an unexpected value or Undefined in my case, a generic answer doesn't seem possible. For me, it depends on the context of the reason for evaluating the enum value: is it a situation where code execution should halt, or can a default value be used?
调用函数有责任提供有效的输入,并且隐含地任何不在枚举中的内容都是无效的(务实的程序员似乎暗示了这一点)。 也就是说,这意味着任何时候更改枚举时,都必须更改接受它作为输入的所有代码(以及将其作为输出的某些代码)。 但无论如何,这可能是真的。 如果您有一个经常更改的枚举,则考虑到枚举通常是编译时实体,您可能应该使用枚举以外的其他东西。
It is the responsibility of the calling function to provide valid input and implicitely anything not in the enum is invalid (Pragmatic programmer seems to imply this). That said, this implies that any time you change your enum, you must change ALL code that accepts it as input (and SOME code that yields it as output). But that is probably true anyways. If you have an enum that changes often, you probably should be using something other than an enum, considering that enums are normally compile-time entities.
我通常尝试定义未定义值(0):
这样我总是可以说:
至少我通常是这样做的。
I usually try to define undefined value (0):
That way I can always say:
This is at least how I usually do this.