Syntax 为什么我们需要异常处理?

Syntax 为什么我们需要异常处理?,syntax,exception-handling,Syntax,Exception Handling,我可以检查输入,如果用户输入无效,我可以使用一个简单的“如果条件”打印“输入无效,请重新输入”(如果存在无效输入) 这种“如果存在潜在故障,请使用if条件进行验证,然后在遇到故障时指定正确的行为…”的方法对我来说已经足够了 如果我基本上可以用这种方法覆盖任何类型的故障(除零等),那么为什么我需要这种完整的异常处理机制(异常类和对象,检查和取消检查,等等)?假设您有func1调用func2,并有一些输入 现在,假设func2由于某种原因失败 您的建议是在func2中处理故障,然后返回到func1

我可以检查输入,如果用户输入无效,我可以使用一个简单的“如果条件”打印“输入无效,请重新输入”(如果存在无效输入)

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


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

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

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

您的建议是在
func2
中处理故障,然后返回到
func1

func1
如何“知道”在
func2
中发生了什么错误(如果有)以及如何从该点开始

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

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

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

当然,你仍然可以争辩,“那么,
try/catch
的作用是什么?为什么不简单地返回这个对象呢?”

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

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

  • 除了一个例外,程序员必须要么处理它,要么“向上”抛出它,而对于错误代码,程序员可能会错误地忽略它

  • 使用异常机制,您可以编写更“干净”的代码,并使所有内容“自动处理”,对于错误代码,您必须在“调用堆栈上”的每个函数中实现一个“冗长的”
    开关/case


  • 让我们假设您需要为某个对象编写一些代码,该对象由n个不同的资源(n>3)组成,这些资源将在构造函数中分配,并在析构函数中释放。 我们甚至可以说,其中一些资源相互依赖。 例如,为了创建某个文件的内存映射,首先必须成功打开该文件,然后执行操作系统的内存映射功能。 如果没有异常处理,您将无法使用构造函数来分配这些资源,但您可能会使用两步初始化。 你必须自己注意建造和破坏的顺序 --因为你不再使用构造函数了。 如果没有异常处理,您将无法向调用者返回丰富的错误信息——这就是为什么在无异常软件中,通常需要调试器和调试可执行文件来确定某些复杂软件突然出现故障的原因。 这再次假设,并非每个库都能够简单地将其错误信息转储到stderr。在某些情况下,stderr是不可用的,这反过来使所有使用stderr进行错误报告的代码都不可用。
    使用C++异常处理,您只需将匹配的系统调用封装到基础或成员类关系>强>和强>中。编译器将关心构造和销毁顺序,并只调用不失败构造函数的析构函数。

    < P>例外是一种面向对象的方法。处理异常执行流而不是返回代码。返回代码的缺点是,您必须提供“特殊”值来指示不同类型的异常结果,例如:

    public double calculatePercentage(int a, int b) {
        if (b == 0) {
            return -1;
        }
        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);
        }
    }
    
    上述方法使用返回代码-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的程序员在测试之前并不知道这段代码中有bug,甚至只有在他真正检查结果的有效性时才知道

    除例外情况外,您可以这样做:

    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) {
        if (b == 0) {
            return -1;
        }
        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(包括异常处理)非常清晰,那么创建这样的自定义异常类型是有意义的


    (请参阅)

    问题在于您无法涵盖所有内容。这个问题更适合程序员。stackexchange.com对于极其原始的情况,您的策略是