现在我们有一组所谓的“服务”类,它们的方法都有共同的签名:它们的结果类型有一个属性T?错误,其中T是枚举。我们为每个方法都有一个单独的枚举,并为特定的方法定义了一组值。
只要我们从控制器的操作中使用这些服务的方法的最后位置-这些错误被返回到客户端,在那里它们由javascript处理,这就相当好地工作。
但有时我们想要组合一些调用其他服务方法的方法,这似乎是我遇到问题的地方。假设我们有服务的方法A(),它有AError类型的错误。此A()方法在内部调用具有BError类型错误的方法B()。
首先,我们必须将可能的BError映射到AError。也有可能忘记检查B的错误,并且它的存在将保持不被观察到。
当然,我知道使用异常来指示方法失败是很常见的。现在,所有的控制器都有一个过滤器,它可以拦截未处理的异常,并返回一个值为“InternalServerError”的单一属性错误的答案。但是,如果我们开始使用异常,我们将失去一个我认为重要的特性:现在,在其签名中显式指定了一组可能的方法错误,如果我们使用异常,这将丢失。我知道在xml-documentation中有一个列出异常类型的标记,但它只是文档,不是由编译器检查的。
我也不明白如何在我们的代码中使用异常:假设我们有一些方法首先检查订单的状态。现在,如果订单的状态对于当前操作无效,它将返回'InvalidOrderStatus‘错误。如果我们使用异常,我们可以创建一个异常InvalidOrderStatusException,但是我们怎么知道我们内部调用的代码会抛出它呢?
我们还可以创建一个助记符规则:方法A应该有错误类型AError,并且在它内部应该抛出一些通用的异常(假设是ErrorException<>),由这个AError参数化。我们可以在所有A的调用中截获这个通用异常ErrorException<AError>,并观察它的错误代码。但编译器不会检查这一点:方法A可以抛出任何其他异常或ErrorException<>,但由一些其他错误代码参数化。
所以我的问题是:什么是最好的方法,a)总是知道方法可以抛出什么样的异常,以及它可以返回什么样的错误,以及b)不能忘记观察方法的结果错误?
发布于 2014-03-25 15:59:39
如何将枚举AError交换为以下内容:
class ErrorHolder<T> // T would be AError, BError
{
T ErrorCode {get;}
object[] InnerErrors {get;}
// other payload could go here like inner exceptions etc.
}因此,您有枚举的错误代码,可以通过某种方式进行检查,并且可以添加所需的任何有效负载。
发布于 2014-03-25 17:03:27
创建一些具有所需行为的基本异常,为其处理提供基本机制,并将其转换为发送到javascript的结果。这并不意味着你需要知道你的方法的所有可能的异常的列表(因为可能的非业务异常,这样的列表可能总是一个谎言)。因此,派生异常可以只是错误代码的替代,包含特定的消息和其他数据(错误代码:)。正如你所说的-“一组可能的方法错误在其签名中明确指定”imho这不是一个重要的特性。您应该是“面向对象的”,考虑一般的异常处理(在控制器方法代码级别,如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; }
}发布于 2014-03-26 19:43:36
所以我的问题是:什么是最好的方法: a)总是知道方法可以抛出什么类型的异常,以及它可以返回什么类型的错误,以及b)不能忘记观察方法的结果错误?
寻址"a":
在编译时很难做到这一点。但是你可以在运行时通过反射来实现。请参见ErrorHandlerFor<T>类中的静态enumValues字段。
要解决"b",你可以这样做:
简而言之:准备错误处理程序(以前在case部分中)作为lambda,并将它们全部放入ErrorHandlerFor<T>类中,并将其传递给函数,而不是在调用后使用switch。这增加了向函数提供反馈的额外好处,无论是继续还是中止。
你也可以这样想:
假设你想给一个人一些工作。这项工作可能会以几种方式失败。
传统上,你把工作交给那个家伙,然后等到它完成,可能会出错。然后,如果需要,您可以处理这些错误。
现在,你也给他一些“电话号码”,当某些错误出现时,你可以给他打电话。如果工作可以继续或需要中止,对呼叫的应答甚至可以指导研究员。
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
{
}
}https://stackoverflow.com/questions/22628358
复制相似问题