繁体   English   中英

异常与错误代码

[英]Exceptions vs Error codes

现在,我们有一组所谓的“服务”类,这些类具有具有共同签名的方法:其结果类型具有属性T? 错误,其中T是枚举。 而且,每种方法都有一个单独的枚举,并为特定方法定义了一组值。

只要我们最终使用这些服务的方法的地方是控制器的动作,这就很好用了-这些错误返回给客户端,并由javascript处理。

但是有时候我们想组成一些其他服务方法的调用方法,这似乎是我遇到问题的地方。 假设我们有服务的方法A(),其错误类型为AError 并且此A()方法在内部调用错误类型为BError方法B()。

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

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

另外,我也不了解如何在代码中使用异常:假设我们有一些方法可以首先检查订单的状态。 现在,如果订单状态对于当前操作无效,则返回“ InvalidOrderStatus”错误。 如果使用异常,则可以创建一个InvalidOrderStatusException异常,但是我们如何知道内部调用的代码将其抛出呢?

我们还可以创建一条记忆规则:方法A应该具有错误类型AError,并且在其中应抛出由该AError参数化的一些通用异常(例如, ErrorException<> )。 而且我们可以在所有A的调用中拦截此通用异常ErrorException<AError>并观察其错误代码。 但这不会由编译器检查:方法A可以引发任何其他异常或ErrorException<> ,但可以通过其他一些错误代码进行参数化。

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

如何将枚举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”问题,您可以这样做:

简而言之:您可以将错误处理程序(以前称为case组件)准备为lambda,而不是在调用后进行switch ,然后将它们全部放入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
    {

    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM