【问题标题】:When is a good time to throw InvalidOperationException?什么时候是抛出 InvalidOperationException 的好时机?
【发布时间】:2012-02-27 14:52:15
【问题描述】:

我想我知道我的意思,但我不太确定......

Framework 文档将类型总结如下:

当方法调用对于对象的当前状态无效时引发的异常。

有一些明确的情况,想到的一种情况是操作需要打开数据库,但对象尚未使用所需信息初始化以连接到数据库。

(Tangent:另一方面,ADO.NET 还要求您显式打开连接的行为并不明确;DataAdapter 偏离了这一点,只需打开连接,当且仅当它在入口处关闭,我发现这很方便,并让自己成为一个使用这种模式的 ADO.NET 包装器.当然这意味着我冒着做 2 ExecuteNonQuery 的风险并且不必要地返回到池的连接,但我仍然可以在需要时打开和关闭连接,与获得异常相比,这种性能损失微不足道。)

我想我的问题的答案是,只有在这种明确的情况下,我们才应该抛出异常。但是在以下情况下哪种异常类型最合适:

公共类 FormatterMapping { 字典 formattersByName = new ...(); 公共 IFormatter GetFormatter(字符串键) { IFormatter f; if (formattersByName.TryGetValue(key, out f)) 返回 f; 别的 throw new ??Exception("解释为什么失败。"); } }

我的第一反应是抛出 ArgumentException。然后我开始认为映射缺少键也可能是参数“错误”。基本上“获取格式化程序 X”操作是无效的 因为 X 不在映射中,但我真的不知道 X 是否“应该在那里”或者要求X 这里。

我当然可以通过返回 null 来规避整个问题,但这会打开一个更大、更深的蠕虫罐。没有办法知道什么时候会使用返回值,所以后来发生 NullReferenceException 的代码可能与出错的地方没有明显的关系。要么是映射设置不当,要么是使用它的代码要求了一些不应该的东西。

另一种规避问题的方法是使用 TryGetFormatter 选项,但我打算使用此选项的方式实际上应该让调用者知道映射中的内容和不存在的内容,因此强制用户使用此模式代码也不好。

请不要回答我应该只是抛出 ApplicationException!无论您认为代码应该做什么,请提供原因。毕竟,这里真正有问题的是推理。

除非有人说服我,否则我倾向于 ArgumentException。从映射的角度来看,这个论点是错误的,所以至少有一个明确的推理支持这一点。 :)

【问题讨论】:

    标签: .net exception throw invalidoperationexception


    【解决方案1】:

    两者都不完美,两者都很好。或者您可能想要一些真正明确的东西:

    KeyNotFoundException.

    public class FormatterMapping
    {
        Dictionary<string, IFormatter> formattersByName = new ...();
    
        public IFormatter GetFormatter(string key) 
        {
            // validate the argument
            if (!formattersByName.ContainsKey(key))
                throw new KeyNotFoundException("No formatter exists for given key");
    
            return formattersByName[key];
        }
    }
    

    或者你可以让 Dictionary 扔掉它。

    我建议你选择一个,记录下来,然后继续。通常不值得浪费大量时间来选择要抛出的特定异常。记录行为更为重要。

    【讨论】:

    • 我在发布后实际上意识到我的示例代码会邀请答案“把它留给字典”。实际上,映射支持默认格式化程序,但不需要,并且当键丢失且没有默认值时出现抛出情况。
    • 也就是说,KeyNotFoundException 在这里是完美的。我忘记了它的存在,并想抛出一个框架异常(这是库代码,我希望它对已经编写了一段时间的 .net 的人尽可能熟悉)。谢谢!
    • 我对代码的唯一抱怨是我真的不喜欢让我的代码让机器两次做同样的工作,所以我从不使用 Contains 方法,除非我只想知道一个密钥存在,无论如何我都不会检索它。 TryGetValue 方法之所以存在,正是为了提供一种避免这种情况的方法。我知道这种模式看起来有点陌生,而且我以前不喜欢使用它(但不如我的代码完成 200% 必要工作的想法那么多),但从那以后我就喜欢上了它。
    • 很公平。我什至不知道为什么我用效率较低的风格重写它......我只是喜欢它的阅读方式,我猜。你的方式一点问题都没有。
    【解决方案2】:

    我会考虑使用ArgumentException 来代替:

    if (string.IsNullOrEmpty(key))
    {
        throw new ArgumentException("Expected a key");
    }
    

    对于您的示例,我认为InvalidOperationExceptionKeyNotFoundException 都比较合适,或者如果您认为合适,也可以自己编写。

    纯粹主义者可能不喜欢我的意见,但我的异常会在我工作的系统中自动通过电子邮件发送给我,所以最终在大多数情况下,我并不真正关心我捕获的异常类型只要我能从中得到足够有用的信息,看看发生了什么。这包括:

    1. 易于理解的错误消息
    2. 堆栈跟踪
    3. 必要时的内部异常
    4. 任何额外的上下文信息都是可选的。我的意思是,如果您可以在异常发生时捕获任何值,那么调试起来会容易得多。

    【讨论】:

    • 是的,当然 - 这是参数无效的明确案例之一不管对象的状态。我应该为此包含代码,以避免这种红鲱鱼。 :)
    • 关于“纯粹主义者”与否:我认为这实际上取决于代码的含义。库代码需要比我用半天时间整理的某些应用程序中的代码更严格、更一致,以使自己成为一个基本但高效的工具。当我为自己制作一个工具时,只有当我正确使用它时,我才能忍受它的正确行为。
    • 如果您正在为库编写代码,那么我同意异常类型更重要。找到合适的现有异常类型比重新设计轮子更可取。但在图书馆的情况下,我认为@igby 所说的关于文档的内容仍然更重要 - 一个合适的异常名称很好,但准确解释它发生的原因更好。
    猜你喜欢
    • 2011-03-22
    • 2011-11-10
    • 1970-01-01
    • 2011-08-19
    • 1970-01-01
    • 1970-01-01
    • 2011-10-27
    • 2021-09-05
    • 1970-01-01
    相关资源
    最近更新 更多