为什么我们需要异常处理



我可以检查输入,如果它是来自用户的无效输入,我可以使用一个简单的"if condition",打印"input invalid,please recenter"(如果有无效输入)。

这种"如果存在故障的可能性,使用if条件进行验证,然后在遇到故障时指定正确的行为……"的方法对我来说已经足够了。

如果我基本上可以用这种方法覆盖任何类型的故障(除以零等),为什么我需要整个异常处理机制(异常类和对象、已检查和未检查等)?

假设您有func1调用func2并带有一些输入。

现在,假设func2由于某种原因而失败。

您的建议是在func2中处理失败,然后返回func1

func1如何"知道"func2中发生了什么错误(如果有的话),以及如何从这一点开始?

想到的第一个解决方案是func2将返回的错误代码,其中通常,零值表示"OK",其他每个(非零)值表示已发生的特定错误。

此机制的问题在于,它限制了添加/处理新错误代码的灵活性。

有了异常机制,您就有了一个通用的Exception对象,它可以扩展到任何特定类型的异常。在某种程度上,它类似于错误代码,但它可以包含更多信息(例如,错误消息字符串)。

当然,你仍然可以争论,"那么try/catch是用来做什么的?为什么不简单地返回这个对象呢?"。

幸运的是,这个问题已经在这里得到了非常详细的回答:

在C++中,使用异常和try/catch而不仅仅返回错误代码有什么好处?

一般来说,与错误代码相比,异常有两个主要优点,这两个优点都是正确编码的不同方面:

  1. 对于异常,程序员必须处理它或将其"向上"抛出,而对于错误代码,程序员可能会错误地忽略它。

  2. 有了异常机制,您可以编写更"干净"的代码,并使所有内容都"自动处理",因此,对于错误代码,您必须实现"乏味"的switch/case,可能在"调用堆栈"上的每个函数中。

异常是一种比返回代码更面向对象的处理异常执行流的方法。返回代码的缺点是,您必须想出"特殊"值来指示不同类型的异常结果,例如:

public double calculatePercentage(int a, int b) {
if (b == 0) {
return -1;
}
else {      
return 100.0 * (a / b);
}
}

上面的方法使用-1的返回代码来表示失败(因为它不能除以零)。这是可行的,但您的调用代码需要了解这个约定,例如,这可能会发生:

public double addPercentages(int a, int b, int c, int d) {
double percentage1 = calculatePercentage(a, b);
double percentage2 = calculatePercentage(c, c);
return percentage1 + percentage2;
}

上面的代码乍一看很好。但是当b或d为零时,结果会出乎意料。calculatePercentage将返回-1,并将其添加到其他可能不正确的百分比中。编写addPercentages的程序员在测试之前并没有意识到这段代码中存在错误,即使这样,也只有在他真正检查结果的有效性的情况下。

除了例外,你可以这样做:

public double calculatePercentage(int a, int b) {
if (b == 0) {
throw new IllegalArgumentException("Second argument cannot be zero");
}
else {      
return 100.0 * (a / b);
}
}

调用此方法的代码将在没有异常处理的情况下进行编译,但当使用不正确的值运行时,它将停止。这通常是首选的方式,因为它让程序员决定是否以及在哪里处理异常。

如果你想强迫程序员处理这个异常,你应该使用一个检查过的异常,例如:

public double calculatePercentage(int a, int b) throws MyCheckedCalculationException {
if (b == 0) {
throw new MyCheckedCalculationException("Second argument cannot be zero");
}
else {      
return 100.0 * (a / b);
}
}

请注意,calculatePercentage必须在其方法签名中声明异常。被检查的异常必须像这样声明,调用代码要么捕获它们,要么在自己的方法签名中声明它们。

我认为目前许多Java开发人员都认为检查异常有点侵入性,所以大多数API最近倾向于使用未检查异常。

上面检查的异常可以这样定义:

public class MyCheckedCalculationException extends Exception {
public MyCalculationException(String message) {
super(message);
}
}

如果您正在开发一个具有多个类和方法的组件,而这些类和方法被其他几个组件使用,并且您希望使API(包括异常处理)非常清晰,那么创建这样的自定义异常类型是有意义的。

(参见Throwable类层次结构)

让我们假设您需要为某个对象编写一些代码,该对象由n个不同的资源(n>3)组成,这些资源将在构造函数中分配,并在析构函数中释放。

甚至可以说,其中一些资源是相互依赖的。例如,为了创建某个文件的内存映射,首先必须成功打开该文件,然后执行用于内存映射的OS功能。

如果没有异常处理,您将无法使用构造函数来分配这些资源,但您可能会使用两步初始化。你必须自己照顾好建筑和破坏的秩序--因为你不再使用构造函数了。

如果没有异常处理,您将无法向调用方返回丰富的错误信息——这就是为什么在无异常软件中,通常需要调试器和调试可执行文件来确定某些复杂软件突然失败的原因。

这再次假设,并不是每个库都能够简单地将其错误信息转储到stderr。stderr在某些情况下不可用,这反过来会使所有使用stderr进行错误报告的代码都不可用。

使用C++异常处理,您只需将封装匹配系统调用的类链接到基类或成员类关系AND编译器将注意构造和销毁的顺序,并且只为未失败的构造函数调用析构函数。

首先,方法通常是程序中的代码块或语句,它使用户能够重用相同的代码,这最终节省了对内存的过度使用。这意味着现在计算机上没有内存浪费。

最新更新