【问题标题】:What exception should I throw when a factory method returns null?当工厂方法返回 null 时,我应该抛出什么异常?
【发布时间】:2012-03-14 16:53:41
【问题描述】:

考虑一个我可能控制也可能不控制的工厂方法,作为Func<T>传递给另一个类:

// this.FactoryMethod is an external dependency passed into this class
// through constructor or property injection assume that it is not null
// but there is no guarantee that it will return a non-null reference.
Func<IModel> FactoryMethod;

public IModel GetPopulatedModel(int state, FileInfo someFile)
{       
   // Argument validation omitted for brevity...

   // This operation could return null.
   var model = this.FactoryMethod()
   if (model == null)
   {
      throw new SomeException("Factory method failed to produce a value.");
   }

   // Safe to assign to properties at this point.
   model.Priority = state;
   model.File = someFile;
   return model;
}

我想抛出一些表明操作失败的东西。

ArgumentNullException 会产生误导,因为这些参数没有任何问题:

空引用时抛出的异常(Visual 中的Nothing Basic) 被传递给一个不接受它作为有效的方法 论据。

InvalidOperationException 似乎不太准确:

方法调用无效时抛出的异常 对象的当前状态。

将局部变量视为the object's current state 的一部分似乎很奇怪。

抛出什么是好的异常?我很惊讶System 中没有OperationFailedException。我应该编写自己的异常,还是有一个好的异常?

【问题讨论】:

  • 空对象模式在这里也可能有用(但只有在广泛了解该工厂的使用情况下才适用)——这不是解决方案,而是需要考虑的其他问题。
  • 对,但Func&lt;T&gt; 中没有任何内容可以保证这一点,所以我无法使用内置委托,我必须构建自己的类或委托类型,我'想避免。
  • 如果我返回了一个实现空对象模式的对象,我仍然需要证明model 中的引用不为空。我想我可以使用struct 来实现该模式。但随后我将不得不针对该结构而不是IModel 接口进行编程。

标签: c# .net


【解决方案1】:

如果您觉得可用的异常不足,您可以随时创建自己的异常。

更新 虽然,我同意@TomTom 和@dtb 的可能选项。特别是NotImplementedException作为工厂确实没有实现

【讨论】:

  • 是的,但我希望有人能在我走这条路之前提供一个好的建议。我过去经常编写自定义异常,但后来放弃了这种做法,转而使用内置异常。在这种情况下,我宁愿写一个工厂class,它使用契约来保证非空结果,但我也想避免这种工作。
  • 是的,你可以,但我不会——有一个完美的。
  • @TomTom 我同意,InvalidOperationException 确实适合这里。我只是提供替代方案……但是,您绝对是正确的。我会支持你的,但我已经达到了我的每日支持次数上限 :) 我明天会努力记住 :)
【解决方案2】:

.NET Framework 中的两个示例:

CloneCore 本质上是克隆当前对象的工厂方法。

我会使用 NotImplementedException,因为工厂方法没有实现它所请求的内容。

【讨论】:

  • NotImplementedException 似乎是最合适的。
【解决方案3】:

我会创建自己的异常,称为类似

FactoryNullException

然后你甚至可以创建你想要的默认消息,例如,你的例子中的那个

Factory method failed to produce a value.

【讨论】:

    【解决方案4】:

    InvalidOperationException。

    局部变量是根据定义定义对象的状态。没什么不好。必须设置工厂方法,否则无法创建对象。 InvalidOperation - 工厂此时不能创建对象,因为工厂的内部状态是错误的。

    【讨论】:

    • “局部变量是根据定义定义对象的状态”:并非如此。对象字段形成状态。
    【解决方案5】:

    NotImplementedException 绝对不是这里的正确选择。 Its intended meaning is "not implemented yet",它几乎不应该被生产代码抛出。

    在这种情况下抛出什么最重要的标准是什么对异常的使用者最有用。如果您期望调用代码可能需要对该异常做出不同于对任意异常的反应,则异常类型非常重要。另一方面,调用代码对这种异常唯一能做的就是记录它,它的消息非常重要,它的类型不太重要。在后一种情况下,带有显式消息的 InvalidOperationException 可能非常合适。

    还值得考虑的是,工厂方法返回的 null 值是否应该被视为违反其合同。在这种情况下您想要抛出异常的事实表明它可能应该是,在这种情况下您可能想要稍微更改设计以便注入接口实例而不是Func&lt;&gt;。这将允许您在接口方法上定义一个文档化的协定,指定它永远不会返回 null,并且如果它无法创建模型实例,它将引发异常。如果您使用Code Contracts,您甚至可以将非空返回值作为其显式契约的一部分。

    最后一点,如果您认为在此处抛出 NullReferenceException 是可以接受的(您对另一个答案的回复似乎表明了这一点),您只需让 model.Priority = state 行执行即可完成此操作。唯一的问题是(GetPopulatedModel 在从工厂接收到空模型实例后抛出异常的任何其他方法共享)是异常的源位置对试图计算的开发人员不是特别有帮助找出为什么检索到的模型为空。这就是为什么“鼓励”工厂抛出自己的异常可能是最好的候选方法。

    【讨论】:

    • 很好的答案。事实上,正是代码契约分析器促使我更仔细地查看引发这个问题的代码。我的第一个想法是抛出一个异常,但在稍微远离代码之后,我意识到让调用者处理 null 更合适。所以最后,我接受了Func&lt;&gt;提出的合同,而不是与之抗争。虽然我说NotImplementedException 是最合适的,但对我来说还是太大了。
    【解决方案6】:

    为什么不使用 InvalidOperationException(),因为您无法控制所使用的方法,如果它失败则意味着它是无效的。

    【讨论】:

      【解决方案7】:

      也许

      NullReferenceException
      

      是你想要的。

      【讨论】:

      • MSDN 上的指导似乎暗示只有框架应该抛出这个。虽然,你是对的,没有什么“技术上”可以阻止我扔它。
      • 这相当于获取你的空值并尝试对其采取行动。
      【解决方案8】:

      我不确定这是否真的是您在此处实施的工厂模式。但要回答您的问题,只需按如下方式自定义例外:

      public class FactoryNullException : Exception
      {
          public FactoryNullException() : base("Factory was null")
          {
      
          }
      }
      

      【讨论】:

        【解决方案9】:

        另一个选择:不要抛出异常。在实际应用程序中,该方法是从 try/catch 块中调用的,并且该异常会被捕获并记录下来。在这种情况下抛出异常没有任何好处。

        所以我没有抛出异常,而是记录了消息并返回了 null。将其转换为示例代码如下所示:

        // this.FactoryMethod is an external dependency passed into this class
        // through constructor or property injection assume that it is not null
        // but there is no guarantee that it will return a non-null reference.
        Func<IModel> FactoryMethod;
        
        public IModel GetPopulatedModel(int state, FileInfo someFile)
        {       
           // Argument validation omitted for brevity...
        
           // This operation could return null.
           var model = this.FactoryMethod()
           if (model == null)
           {
              this.Logger.Write("Factory method failed to produce a value.");
              return null;
           }
        
           // Safe to assign to properties at this point.
           model.Priority = state;
           model.File = someFile;
           return model;
        }
        

        如果抛出异常是必需的/有用的,我会选择NotImplementedException,但在这种情况下,不抛出异常更有意义。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-04-27
          • 2020-02-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-04-18
          • 2019-06-23
          • 2010-09-15
          相关资源
          最近更新 更多