2014-03-25 75 views
0

现在我们有一套所谓的'服务' - 类,它们具有共同签名的方法:它们的结果类型有一个属性T?错误,其中T是枚举。 对于每种方法,我们都有一个单独的枚举值,并为特定方法定义了一组值。异常与错误代码

只要我们使用这些服务的方法的最后一个地方是控制器的操作,这些工作就会很好,这些错误会返回给客户端,并由javascript处理。

但有时候我们想编写一些调用其他服务方法的方法,这是我似乎有问题的地方。 假设我们有服务的方法A(),它有AError类型的错误。这个A()方法在内部调用方法B(),它有一个BError类型的错误。

首先,我们必须将可能的BError映射到AError。 也有可能忘记检查B的错误,它的存在将保持不可见。

对于cource,我知道使用异常来指示方法失败是很常见的。 现在所有的控制器都有一个过滤器来拦截未处理的异常,并返回一个只有单个属性的错误值为'InternalServerError'的错误。 但是,如果我们开始使用异常,我们将失去一个我认为重要的特性:现在,一个可能的方法错误集在其签名中明确指定,并且在我们使用异常的情况下会丢失。 我知道在xml文档中有一个标签来列出异常类型,但它只是文档,不会被编译器检查。

另外我不明白如何在我们的代码上使用异常: 假设我们有一些方法首先检查订单的状态。现在,如果订单状态对当前操作无效,则返回“InvalidOrderStatus”错误。 如果我们使用异常,我们可以创建一个异常InvalidOrderStatusException,但我们怎么能知道我们内部调用的代码会抛出它?我们也可以创建一个助记规则:方法A应该有错误类型AError,并且在它内部应该抛出一些通用异常(比方说,ErrorException<>),通过这个AError进行参数化。我们可以截取所有A的调用中的通用异常ErrorException<AError>并观察其错误代码。但是这不会被编译器检查:方法A可以抛出任何其他异常,或ErrorException<>,但通过其他错误代码进行参数化。

所以我的问题是:什么是最好的方法a)总是知道什么样的异常方法可以抛出,它可以返回哪种错误,以及b)不能忘记观察方法的结果错误?

+0

您可以添加一些自定义或预定义的属性来标记可能的异常,但为什么您需要知道它们? – lavrik

+0

如果我将错误代码转换为异常,它们将是业务异常,并且无论如何我必须将它们捕获到控制器中并转换为错误代码,这些代码稍后将由JavaScript处理程序或服务器响应消耗。而在JavaScript中,我可以为不同的错误代码提供不同的错误消息。 –

回答

0

这个怎么样的东西交换枚举AError

class ErrorHolder<T> // T would be AError, BError 
{ 
    T ErrorCode {get;} 
    object[] InnerErrors {get;} 
    // other payload could go here like inner exceptions etc. 
} 

因此你有可能以某种方式检查所列举的错误代码,再加上你可以添加你需要的任何有效载荷。

+0

这就是我们现在使用它的方式。每个方法的结果都是一个实现接口的类IHaveError {T?错误{get; set;}} –

+0

让结果具有“ErrorHolder”类型的属性,它本身包含枚举。 – Onur

+0

这种方法有什么好处?我应该在InnerErrors中存储什么?如果内部调用方法的错误(如同样的ErrorHolders或异常),我认为这不是一个好主意,因为调用者不仅必须知道如何处理其直接调用者的错误,而且也要知道他们的调用者的错误,因此变得高度依赖关于他们的变化。 –

0

创建一些基本的异常与所需的行为,提供基础mechanizm处理,处理和转换为结果被发送到JavaScript。这并不意味着你需要知道你的方法的所有possbile例外列表(由于可能的非业务例外,这种列表总是会是谎言)。因此,派生异常可以仅仅是错误代码的替代,包含特定的消息和其他数据(错误代码:))。正如你所说 - “一套可能的方法错误在其签名中明确规定”,恕我直言,这不是一个重要的功能。您应该将“面向对象”看作是一般的异常处理(在HandleError(ExecuteService())等控制器方法代码级别或操作筛选级别)。此外,您的错误代码可能不会出现例外情况,但会显示一些成功或失败状态的“执行结果”,但它不是一种例外行为,如“未找到实体”,而是服务的预期结果。在这种情况下,我使用下面的代码

public class ExecutionResult 
{ 
    public ExecutionResult() : this(null) 
    { 
    } 
    public ExecutionResult(ExecutionResult result) 
    { 
     if (result != null) 
     { 
      Success = result.Success; 
      Errors = new List<ErrorInfo>(result.Errors); 
     } 
     else 
     { 
      Errors = new List<ErrorInfo>(); 
     } 
    } 
    private bool? _success; 
    public bool Success 
    { 
     get { return _success ?? Errors.Count == 0; } 
     set { _success = value; } 
    } 
    public IList<ErrorInfo> Errors { get; private set; } 
} 

/*T is for result (any business object)*/ 
public class ExecutionResult<T> : ExecutionResult 
{ 
    public ExecutionResult() : this(null) 
    { 
    } 
    public ExecutionResult(T result) : this(null) 
    { 
     Value = result; 
    } 
    public ExecutionResult(ExecutionResult result) 
     : base(result) 
    { 
     var r = result as ExecutionResult<T>; 
     if (r != null) 
     { 
      Value = r.Value; 
     } 
    } 
    public T Value { get; set; } 
} 
+0

假设我们有一个非常复杂的控制器动作,它在很多其他方法中调用,其中一些可以抛出一个业务异常(我说的一个业务异常,例如,它应该向用户显示为一条消息,如'未找到订单','订单无效状态','禁止'等)。我怎么能不探索这个动作的所有调用树,向我们的Web开发人员提供一个可能的错误代码列表,以便她能够处理它们,为每个人展示独特的消息?错误代码虽然有前面提到的几个缺点,但它提供了列出可能的错误的方法 –

+0

我明白了,所有可能的错误都是软件规范的一部分。你也可以使用一些属性来引发异常并通过一些单元测试生成错误列表,这些属性可以用来查找这个属性的用法。 – lavrik

0

所以我的问题是:什么是一个最好的方式)总是知道什么样的 例外方法可以抛出什么样的错误,它可以返回,并b)不能忘记观察方法的结果错误?

为了解决“一”:

很难在编译时做到这一点。但是你可以在运行时通过反射来完成。请参阅ErrorHandlerFor<T>类中的静态enumValues字段。

为了解决“B”,你可以做这样的:

简单:不是一个switch通话结束后,你准备错误处理程序(以前在case份)lambda表达式,并把所有的人在ErrorHandlerFor<T>类中,并将其传递给该函数。这增加了对功能是否继续或中止给予反馈的额外好处。

你也可以认为这是这样的:

假设你想给一些工作的研究员。这项工作可能会以多种方式失败。

传统上,你给这个家伙工作,一直等到它完成,可能有错误。如有必要,您可以处理这些错误。

现在你给这个家伙一些“电话号码”,以便在发生某些错误时调用。如果工作能够继续下去或者需要中止,那么电话的答案甚至可以指导家庭成员。

enum AError 
{ 
    AError1, 
    AError2, 
    AError3, 
    AError4, 
    AError5, 

} 


delegate bool SingleErrorHandlerDelegate<T>(T error, object someOtherPayload); 

interface IHandle<T> 
{ 
    bool Handle(T error, object someOtherPayload); // return true if handled; 
} 

class ErrorHandlerFor<T> : IHandle<T> 
{ 
    private Dictionary<T, SingleErrorHandlerDelegate<T>> handlers; 
    private static T[] enumValues = Enum.GetValues(typeof(T)).Cast<T>().ToArray(); 
    public ErrorHandlerFor(IEnumerable<KeyValuePair<IEnumerable<T>, SingleErrorHandlerDelegate<T>>> handlers) 
     : this(handlers.SelectMany(h => h.Key.Select(key => new KeyValuePair<T, SingleErrorHandlerDelegate<T>>(key, h.Value)))) 
    { 
    } 

    public ErrorHandlerFor(IEnumerable<KeyValuePair<IEnumerable<T>, SingleErrorHandlerDelegate<T>>> handlers, SingleErrorHandlerDelegate<T> fallbackHandler) 
     : this(handlers.SelectMany(h => h.Key.Select(key => new KeyValuePair<T, SingleErrorHandlerDelegate<T>>(key, h.Value))), fallbackHandler) 
    { 
    } 



    public ErrorHandlerFor(IEnumerable<KeyValuePair<T, SingleErrorHandlerDelegate<T>>> handlers) 
    { 
     this.handlers = new Dictionary<T, SingleErrorHandlerDelegate<T>>(); 
     foreach (var handler in handlers) 
     { 
      Debug.Assert(handler.Value != null); 
      this.handlers.Add(handler.Key, handler.Value); 
     } 

     checkHandlers(); 
    } 

    public ErrorHandlerFor(IEnumerable<KeyValuePair<T, SingleErrorHandlerDelegate<T>>> handlers, SingleErrorHandlerDelegate<T> fallbackHandler) 
    { 
     this.handlers = new Dictionary<T, SingleErrorHandlerDelegate<T>>(); 
     foreach (var handler in handlers) 
     { 
      Debug.Assert(handler.Value != null); 
      this.handlers.Add(handler.Key, handler.Value); 
     } 
     foreach (var enumValue in enumValues) 
     { 
      if (this.handlers.ContainsKey(enumValue) == false) 
      { 
       this.handlers.Add(enumValue, fallbackHandler); 
      } 
     } 

     checkHandlers(); 
    } 



    private void checkHandlers() 
    { 
     foreach (var enumValue in enumValues) 
     { 
      Debug.Assert(handlers.ContainsKey(enumValue)); 
     } 
    } 

    public bool Handle(T error, object someOtherPayload) 
    { 
     return handlers[error](error: error, someOtherPayload: someOtherPayload); 
    } 
} 



class Test 
{ 
    public static void test() 
    { 
     var handler = new ErrorHandlerFor<AError>(
      new[]{ 
       new KeyValuePair<IEnumerable<AError>, SingleErrorHandlerDelegate<AError>>(
       new []{AError.AError1, AError.AError2, AError.AError4,}, 
       (AError error, object payload) => { Console.WriteLine(@"handled error 1, 2 or 4!"); return true;} 
       ), 
       new KeyValuePair<IEnumerable<AError>, SingleErrorHandlerDelegate<AError>>(
       new []{AError.AError3, AError.AError5,}, 
       (AError error, object payload) => { Console.WriteLine(@"could not handle error 3 or 5!"); return false;} 
       ), 
      } 
      ); 

     var result = Services.foo(handler); 


     var incompleteHandlerButWithFallbackThatWillPassTheTest = new ErrorHandlerFor<AError>(
      new[]{ 
       new KeyValuePair<IEnumerable<AError>, SingleErrorHandlerDelegate<AError>>(
       new []{AError.AError1, AError.AError2, AError.AError4,}, 
       (AError error, object payload) => { Console.WriteLine(@"handled error 1, 2 or 4!"); return true;} 
       ), 
       new KeyValuePair<IEnumerable<AError>, SingleErrorHandlerDelegate<AError>>(
       new []{AError.AError5}, 
       (AError error, object payload) => { Console.WriteLine(@"could not handle error 3 or 5!"); return false;} 
       ), 
      } 
      // AError.AError3 is not handled! => will go in fallback 
      , (AError error, object payload) => { Console.WriteLine(@"could not handle error in fallback!"); return false; } 
      ); 

     var result2 = Services.foo(incompleteHandlerButWithFallbackThatWillPassTheTest); 


     var incompleteHandlerThatWillBeDetectedUponInstantiation = new ErrorHandlerFor<AError>(
      new[]{ 
       new KeyValuePair<IEnumerable<AError>, SingleErrorHandlerDelegate<AError>>(
       new []{AError.AError1, AError.AError2, AError.AError4,}, 
       (AError error, object payload) => { Console.WriteLine(@"handled error 1, 2 or 4!"); return true;} 
       ), 
       new KeyValuePair<IEnumerable<AError>, SingleErrorHandlerDelegate<AError>>(
       new []{AError.AError3}, 
       (AError error, object payload) => { Console.WriteLine(@"could not handle error 3 or 5!"); return false;} 
       ), 
      } // AError.AError5 is not handled! => will trigger the assertion! 
      ); 

    } 

} 


class Services 
{ 
    public static Result foo(IHandle<AError> errorHandler) 
    { 
     Debug.Assert(errorHandler != null); 

     // raise error... 
     var myError = AError.AError1; 
     var handled = errorHandler.Handle(error: myError, someOtherPayload: "hello"); 
     if (!handled) 
      return new Result(); 

     // maybe proceed 

     var myOtherError = AError.AError3; 
     errorHandler.Handle(error: myOtherError, someOtherPayload: 42); //we'll return anyway in this case... 
     return new Result(); 

    } 



    public class Result 
    { 

    } 
} 
相关问题