【问题标题】:How can I make this exception handling code adhere to the DRY principle?我怎样才能使这个异常处理代码遵守 DRY 原则?
【发布时间】:2012-08-03 17:52:34
【问题描述】:

我有一种特殊情况,我需要捕获异常并将一个对象返回给客户端以代替异常。我不能将异常处理逻辑置于更高级别,即将 Foo 包装在 try 子句中。

最好用一些示例代码来演示。异常处理逻辑模糊了方法的意图,如果我有许多类似意图的方法,在 Foo 类中,我发现自己重复了大部分的 catch 逻辑。

在下面的代码中包装常见异常功能的最佳技术是什么?

public class Foo
{
     public Bar SomeMethodThatCanThrowExcepetion()
     {
          try
          {
              return new Bar().Execute();
          }
          catch(BazException ex)
          {
              WriteLogMessage(ex, Bar.ErrorCode);
              return new Bar() { ErrorMessage = ex.Message, ErrorCode = Bar.ErrorCode;}                  
          }
     }

     public Baz SomeMethodThatCanThrowExcepetion(SomeObject stuff)
     {
          try
          {
              return new Baz(stuff).Execute();
          }
          catch(BazException ex)
          {
              WriteLogMessage(ex, Baz.ErrorCode);
              return new Baz() { ErrorMessage = ex.Message, ErrorCode = Baz.ErrorCode;}                  
          }
     }
 } 

【问题讨论】:

  • 难道你不能把你的异常处理放到更高的层次,然后抛出一个异常链吗?
  • 不幸的是,我不能。我在这里受到限制。
  • BarBaz 之间是否有共同的基类?
  • 不幸的是没有,这是另一个限制,因为 Foo 是一个公共接口。
  • 如果您无法将异常处理移至另一层,那么您的实现就会陷入困境。

标签: c# .net design-patterns exception-handling


【解决方案1】:

根据 Lee 的评论更新


一种可能性是使用通用辅助方法。像这样的:

T TryExecute<T>(Func<T> action, int ErrorCode)
{
    try
    {
        return action();
    }
    catch (Exception ex)
    {
        result = Activator.CreateInstance<T>();
        typeof(T).GetProperty("ErrorMessage").SetValue(result, ex.Message, null);
        typeof(T).GetProperty("ErrorCode").SetValue(result, ErrorCode, null);
        return result;
    }
    return result;
}

如果您可以修改 BarBaz,那么您可以通过对 T 提出要求来改进它:

public interface IError
{
    public string ErrorMessage { get; set; }
    public int ErrorCode { get; set; }
}

T TryExecute<T>(Func<T> action, int ErrorCode) where T : IError
{
    try
    {
        return action();
    }
    catch (Exception ex)
    {
        result = Activator.CreateInstance<T>();
        result.ErrorMessage = ex.Message;
        result.ErrorCode = ErrorCode;
        return result;
    }
}

然后你会使用:

return TryExecute<Bar>(new Bar().Execute, Bar.ErrorCode);

还有:

return TryExecute<Baz>(new Baz(stuff).Execute, Baz.ErrorCode);

对于您的特定设计,这可能是也可能不是过度抽象;魔鬼在细节中。

【讨论】:

  • 如果抛出异常则返回 null
  • @Lee 的想法是在 catch 块内根据需要将 result 设置为错误对象。
  • 好吧,如果没有对 T 的任何限制或任何创建方式,您将无法做到这一点。
  • @Lee 谢谢,很好。我更新了我的答案,不幸的是现在使用了反射和魔术字符串,但至少它有效。
【解决方案2】:

基类怎么样:

public class ErrorCapable {
  public string ErrorMessage { set; get; }
  public int ErrorCode { set; get; }

  public static ErrorCapable<T> Oops(Exception exc) where T : ErrorCapable, new() {
    // Code for logging error here
    return new T() { ErrorMessage = exc.Message, ErrorCode = exc.ErrorCode };
  }
}

public class Bar : ErrorCapable {
  //...
}
public class Baz : ErrorCapable {
  //...
}

然后在catch中,就用,例如:

return ErrorCapable.Oops<Bar>(ex);

【讨论】:

    【解决方案3】:

    您真的需要在每个方法中显式登录吗?不要在每个方法中都有异常逻辑,而是在程序的Main 方法中使用一个处理程序并通用处理异常。

    此外,如果您确实需要在那里进行日志记录,则无需从 catch 块返回任意对象,只需使用 throw; 让它在堆栈中游荡。

    【讨论】:

    • 对我来说似乎是正确的答案,但在问题 cmets 中,提问者说由于某种原因他被限制这样做。
    • 查看有问题的评论。我被限制这样做。 Cosider Foo 作为公共接口,我无法控制处理该级别的异常。
    【解决方案4】:

    我认为你能做的最好的事情是:

    public T TryOrDefault<T>(Func<T> act, int errorCode, Func<BazException, T> onError)
    {
        try
        {
            return act;
        }
        catch(BazException ex)
        {
            WriteLogMessage(ex, errorCode);
            return onError(ex);
        }
    }
    

    然后您可以根据它编写其他方法:

    public Bar SomeMethodThatCanThrowException()
    {
        Bar b = new Bar();
        return ExecOrDefault(() => b.Execute(), Bar.ErrorCode, ex => new Bar { ErrorMessage = ex.Message, ErrorCode = Bar.ErrorCode });
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-05-27
      • 2011-06-26
      • 1970-01-01
      • 1970-01-01
      • 2012-09-02
      • 1970-01-01
      相关资源
      最近更新 更多