打印有关异常的所有信息?

3
我正在开发一些后台程序,并且不时地执行“某些”操作。 但是,“某些”可能出现问题,我想编写日志以便找出问题所在,错误可能有不同的原因。 我正在使用一个闭源库(目前没有可用文档),我不知道抛出的异常的结构,堆栈跟踪不能提供足够的信息。
问题:如何使我不想知道的异常类显示“所有可能的信息”?
也就是说,有没有办法让异常自行解释,而无需任何其他知识?
例如(小心,冗长 :-)):
让我们考虑以下银行账户的示例。 账户有一个函数提供“取款”功能,显然(或者我应该说可悲的是)会引发异常“资金不足异常”。
public class Account {
   [...]
   private double balance;
   public void withdraw(double amountWithDraw) throws InsufficientFundsException {
      if(amountWithDraw <= balance) {
         balance -= amountWithDraw;
      } else {
         throw new InsufficientFundsException(balance, amountWithDraw);
      }
   }
}

异常类的外观如下

public class InsufficientFundsException extends Exception {
   private double balance;
   private double amountWithDraw;

   public InsufficientFundsException(double balance, double amountWithDraw) {
      this.amountWithDraw = amountWithDraw;
   }

   public double getBalance() {
      return amountWithDraw;
   }
   public double getAmountWithDraw() {
      return amountWithDraw;
   }
}

现在,在主调用代码中,我可能会做类似以下的事情:
try {
  account.withdraw(amountWithDraw);
} catch (InsufficientFundsException e) {
  System.out.println("Tried to reduce " + e.getBalance() + " by " + e.getgetAmountWithDraw() + " but taking this much amount of money from the account is not possible!");
}

在这之前,一切都还不错,但是假设主要代码收到了许多不同的“请求”(添加资金、减去资金、发送包含账户金额的信息等),每个请求都可能出现不同的失败情况,如AddMoneyException、NetworkUnreachableException,因此主代码将如下所示:

try {
  handleRequest(...);
} catch (InsufficientFundsException e) {
  System.out.println("Tried to reduce " + e.getBalance() + " by " + e.getgetAmountWithDraw() + " but taking this much amount of money from the account is not possible!");
} catch (NetworkUnreachableException e) {
  System.out.println("Network unreachable.");
} catch (AddMoneyException e) {
  ...
} and so on and so on

因此,一方面,代码变得非常笨拙(我需要在函数调用堆栈中拖动许多不同的异常),另一方面,我不知道AddMoneyException和NetworkUnreachableException的原因(除了它们的名称,我不知道为什么网络无法访问或者为什么我们不能向账户中添加一些钱)。
所以我认为,在抛出异常的地方分发异常并创建一个新的异常'RequestFailedException'是个好主意,然后可以像这样统一处理:
try {
  handleRequest(...);
} catch (RequestFailedException e)
  System.out.println("Request failed. Reason=" + e.getReason());
}

现在,提款功能看起来像这样:
public void withdrawUnified(double amountWithDraw) throws RequestFailedException{
  try {
    widthDraw(amountWithDraw);
  } catch (InsufficientFundsException e) {
    throw new RequestFailedException("Tried to reduce " + e.getBalance() + " by " + e.getgetAmountWithDraw() + " but taking this much amount of money from the account is not possible!");
  } catch (NetworkUnreachableException e) {
    throw new RequestFailedException("Network is unreachable!");
  }
}

因此,我需要将可能发生的所有异常转换为包含异常信息的字符串(例如“自定义属性”如余额和提款金额,而不仅仅是 StackTrace)。但我不知道 / 无法猜测这些属性。是否有一种统一的函数类似于 toString 可以实现?只需执行:

public void withdrawUnified(double amountWithDraw) throws RequestFailedException{
  try {
    widthDraw(amountWithDraw);
  } catch (Exception e) {
    throw new RequestFailedException("Exception=" e);
  }
}

这种方法无法正常工作,因为它只会打印出堆栈跟踪的第一行。

谢谢并致以问候。

FW


也许你可以重写 Throwable.getMessage() 方法? - Arnaud
我无法访问来自第三方库的专用异常源代码,即我希望它们已经覆盖了toString或getMessage方法,但它们没有... - Fabian Werner
然后,您可以编写某种异常转换器:一个静态方法,它以异常作为参数并返回用户可读的字符串。 - StephaneM
@GhostCat:没事,这次我走了最复杂的路线,建立了一个“异常树”,但它只有2层高,所以复杂度仍然可行...我使用了一个静态类“ExceptionTools”,统一处理所有异常并将它们转换为某种格式,我能够在任何地方拖着它们(一组字符串对)。所以,一切都好,我接受。 - Fabian Werner
我很感激你的快速回复;-) - GhostCat
2个回答

2

如果您打算使用自定义异常作为消息传递器,我建议您在类中重写getMessage方法以反映有关失败原因的信息:

public class InsufficientFundsException extends Exception {
   private double balance;
   private double amountWithDraw;

   public InsufficientFundsException(double balance, double amountWithDraw) {
      this.amountWithDraw = amountWithDraw;
   }

   public double getBalance() {
      return amountWithDraw;
   }
   public double getAmountWithDraw() {
      return amountWithDraw;
   }

   public String getMessage() {
      "Tried to reduce " + balance() + " by " + amountWithDraw() + " but taking this much amount of money from the account is not possible!"
   }
}

在你的代码中:

public void withdrawUnified(double amountWithDraw) throws RequestFailedException{
  try {
    widthDraw(amountWithDraw);
  } catch (Exception e) {
    throw new RequestFailedException("Request Failed", e);
  }
}

请看我的评论:这是一个闭源库,我无法访问异常类。 - Fabian Werner
@FabianWerner,一个解决方法是使用AspectJ环绕给定函数,并抛出一个新的自定义异常,该异常扩展了InsufficientFundsException而不是函数原本会抛出的异常。然而,这会使调试变得有些困难,我不建议在生产代码中使用。 - Pooya

1

您在这里混淆了两件事情:

  • 异常首先是一个编程方式,用于传达特定问题的本质。
  • 另一方面,也希望从异常中提取有用的用户信息。

真正的解决方案是退后一步,清楚地分离这两个问题。例如,您可以创建自己的异常基类,并向其中添加方法,例如getErrorMessageForUser()。现在,您的每个子类都可以提供一个特定的、易于理解的错误描述。

同时,您的顶层代码只捕获该特定异常,然后使用异常中提供的消息。

(我建议不要使用内置的异常消息。至少在较大的系统中,您很快就会考虑国际化,例如:如何确保错误消息以用户特定的语言呈现)


是的,我想在区分不同的错误和...嗯...不区分它们之间取得折衷。问题是,这样做后,我需要详细检查由封闭库抛出的异常,以便从中获取所有相关信息并将其放入此人类可读字符串中 :-( - Fabian Werner
1
是的,这很难。我自己也曾经多次尝试过。尝试了一些复杂的异常层次结构......遭受了它的缺点打击,还尝试在侧面使用数字错误代码,这需要你在某个地方拥有表格。这也不好。 - GhostCat
谢谢@GhostCat...我以为我错过了什么非常简单的东西,但现在我感觉聪明一点了 :-D - Fabian Werner

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接