异常vs错误码



现在我们有一组所谓的"服务"——类,它们的方法有共同的签名:它们的结果类型有一个属性T?错误,其中T是enum。每个方法都有一个单独的枚举,其中包含为特定方法定义的一组值。

只要我们从控制器的动作中使用这些服务的方法的最终位置——这些错误被返回到客户端,在那里它们被javascript处理——这工作得相当好。

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

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

当然,我知道使用异常来指示方法失败是很常见的。现在所有的控制器都有一个过滤器来拦截未处理的异常,并返回一个带有单个属性Error的答案,其值为'InternalServerError'。但是,如果我们开始使用异常,我们将失去一个我认为重要的特性:现在一组可能的方法错误在其签名中被显式指定,如果我们使用异常,这将丢失。我知道xml-documentation中有一个标记来列出异常类型,但它只是文档,编译器不会检查它。

我也不明白如何在我们的代码中使用异常:假设我们有一个首先检查订单状态的方法。现在,如果订单的状态对当前操作无效,则返回'InvalidOrderStatus'错误。如果我们使用异常,我们可以创建一个异常InvalidOrderStatusException,但是我们怎么知道我们内部调用的代码会抛出它呢?

我们也可以创建一个助记规则:方法a应该有错误类型AError,并且在它内部应该抛出一些泛型异常(假设是ErrorException<>),由这个AError参数化。我们可以在所有A的调用中拦截这个通用异常ErrorException<AError>,并观察它的错误代码。但是这不会被编译器检查:方法a可以抛出任何其他异常,或ErrorException<>,但由其他错误代码参数化。

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

如何将enum AError与这样的东西交换:

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

因此,你有枚举的错误代码,可以以某种方式检查,并且你可以添加任何你需要的有效负载。

创建一些具有期望行为的基本异常,为其处理、处理和转换提供基本机制,并将其转换为发送给javascript的结果。这并不意味着您需要知道方法的所有可能异常的列表(由于可能的非业务异常,这样的列表可能总是一个谎言)。因此,派生的异常可以只是错误代码的替代,包含特定的消息和其他数据(错误代码:))。正如你所说的——"一组可能的方法错误在它的签名中被显式地指定了",虽然这不是一个重要的特性。您应该"面向对象"地考虑一般异常处理(在控制器方法代码级别,如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; }
}

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

处理"a":

在编译时很难这样做。但你可以在运行时通过反射来实现。参见ErrorHandlerFor<T>类中的静态enumValues字段。

要处理"b",你可以这样做:

简而言之:在调用之后不是switch,而是将错误处理程序(以前在case部分中)准备为lambdas,并将它们全部放在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
    {
    }
}

相关内容

  • 没有找到相关文章

最新更新