[03] 为什么要使用异常机制


1007017-20180615095609138-968132307.jpg
 
因为代码经验和见识等原因,说实话现在对于异常的使用,我也算是理解甚少。为什么用?什么时候用?即便是在查阅了部分资料以后,也只能在这里提炼出部分自己能够理解的,以供参考和讨论。

1、使用异常的好处

1.1 隔离常规代码和错误处理代码

实际上,我们希望程序不要出现问题,用户操作永远逻辑清晰而正确,一切都按照我们祈祷的那样运行,然而这是不可能的。必然会有错误必然会要我们去处理, 但是错误的处理并不是我们代码的核心。

就像用户取钱的操作,我们核心的代码应该是账户金额变动和更新,而过程中可能出现的各种意外如余额不足,取钱超出额度等夹杂在我们的正常逻辑里,代码必然显得混乱,可读性差。而异常机制将这些意外情况剥离了出来。

我们用个简单的例子来说明:

//假如我们要实现将一个文件读入内存,实际上真正核心只需要下面5步  
readFile {
    open the file;
    determine its size;
    allocate that much memory;
    read the file into memory;
    close the file;
}
8
 
1
//假如我们要实现将一个文件读入内存,实际上真正核心只需要下面5步  
2
readFile {
3
    open the file;
4
    determine its size;
5
    allocate that much memory;
6
    read the file into memory;
7
    close the file;
8
}

//为了处理文件不能打开、不能确定文件大小、内存分配不足等可能出现的意外,我们可能最终写成如下
errorCodeType readFile {
    initialize errorCode = 0;
   
    open the file;
    if (theFileIsOpen) {
        determine the length of the file;
        if (gotTheFileLength) {
            allocate that much memory;
            if (gotEnoughMemory) {
                read the file into memory;
                if (readFailed) {
                    errorCode = -1;
                }
            } else {
                errorCode = -2;
            }
        } else {
            errorCode = -3;
        }
        close the file;
        if (theFileDidntClose && errorCode == 0) {
            errorCode = -4;
        } else {
            errorCode = errorCode and -4;
        }
    } else {
        errorCode = -5;
    }
    return errorCode;
}
31
 
1
//为了处理文件不能打开、不能确定文件大小、内存分配不足等可能出现的意外,我们可能最终写成如下
2
errorCodeType readFile {
3
    initialize errorCode = 0;
4
   
5
    open the file;
6
    if (theFileIsOpen) {
7
        determine the length of the file;
8
        if (gotTheFileLength) {
9
            allocate that much memory;
10
            if (gotEnoughMemory) {
11
                read the file into memory;
12
                if (readFailed) {
13
                    errorCode = -1;
14
                }
15
            } else {
16
                errorCode = -2;
17
            }
18
        } else {
19
            errorCode = -3;
20
        }
21
        close the file;
22
        if (theFileDidntClose && errorCode == 0) {
23
            errorCode = -4;
24
        } else {
25
            errorCode = errorCode and -4;
26
        }
27
    } else {
28
        errorCode = -5;
29
    }
30
    return errorCode;
31
}

如此我们得到的是混乱糟糕的代码,可读性极差,将来一旦出现需要维护的情况,更是苦不堪言。而当我们使用异常机制来处理时,清晰的处理逻辑和代码可读性不言而喻:
readFile {
    try {
        open the file;
        determine its size;
        allocate that much memory;
        read the file into memory;
        close the file;
    } catch (fileOpenFailed) {
      doSomething;
    } catch (sizeDeterminationFailed) {
        doSomething;
    } catch (memoryAllocationFailed) {
        doSomething;
    } catch (readFailed) {
        doSomething;
    } catch (fileCloseFailed) {
        doSomething;
    }
}
19
 
1
readFile {
2
    try {
3
        open the file;
4
        determine its size;
5
        allocate that much memory;
6
        read the file into memory;
7
        close the file;
8
    } catch (fileOpenFailed) {
9
      doSomething;
10
    } catch (sizeDeterminationFailed) {
11
        doSomething;
12
    } catch (memoryAllocationFailed) {
13
        doSomething;
14
    } catch (readFailed) {
15
        doSomething;
16
    } catch (fileCloseFailed) {
17
        doSomething;
18
    }
19
}

(笔者:如上例所有核心代码都用try包裹,确实代码清晰;而事实上倡导的是尽量减小try块,我个人在写一些涉及到IO的方法时,代码经常因为这个原则被try-catch分割得七零八落,基本上和使用if-else无差,所以个人认为try块对于代码的粒度控制,不必完全基于尽量最小的原则,毕竟同时try-catch的性能影响微乎其微)

1.2 延迟处理

throws关键字的使用,使得可能出现的错误不必在当前逻辑中立即处理,而是留待给它的调用者来处理。

因为很多时候,某些底层方法是不知道要如何去处理这些错误的,而只有业务层根据实际的业务逻辑和需求,才知道如何处理,比如业务层可能会将错误信息显示给用户,以起提示和引导操作。

实际上,也可以选择层层抛出的方式,即“ catch语句中处理异常后,再次throw抛出该异常 ”,继续抛出异常可使得调用方法能够再次获得并处理异常。比如程序员可以在底层方法中抓到异常后,打印错误日志以供开发者查看,同时再次抛出给上层调用者,以便业务层调用时使用,如显示错误信息给用户。

同时,受检类型的异常也起到了提醒的作用,告知调用者这个方法可能发生异常,那么你必须进行捕获并考虑处理。

1007017-20180615095609753-745772895.jpg

1.3 异常的精确定位

com.test9.MyException: 文件没有找到--02
    at com.test9.Test.g(Test.java:31)
    at com.test9.Test.main(Test.java:38)
Caused by: com.test9.MyException: 文件没有找到--01
    at com.test9.Test.f(Test.java:22)
    at com.test9.Test.g(Test.java:28)
    ... 1 more
Caused by: java.io.FileNotFoundException: G:\myfile\struts.txt (系统找不到指定的路径。)
    at java.io.FileInputStream.open(Native Method)
    at java.io.FileInputStream.<init>(FileInputStream.java:106)
    at java.io.FileInputStream.<init>(FileInputStream.java:66)
    at java.io.FileReader.<init>(FileReader.java:41)
    at com.test9.Test.f(Test.java:17)
    ... 2 more
 
1
com.test9.MyException: 文件没有找到--02
2
    at com.test9.Test.g(Test.java:31)
3
    at com.test9.Test.main(Test.java:38)
4
Caused by: com.test9.MyException: 文件没有找到--01
5
    at com.test9.Test.f(Test.java:22)
6
    at com.test9.Test.g(Test.java:28)
7
    ... 1 more
8
Caused by: java.io.FileNotFoundException: G:\myfile\struts.txt (系统找不到指定的路径。)
9
    at java.io.FileInputStream.open(Native Method)
10
    at java.io.FileInputStream.<init>(FileInputStream.java:106)
11
    at java.io.FileInputStream.<init>(FileInputStream.java:66)
12
    at java.io.FileReader.<init>(FileReader.java:41)
13
    at com.test9.Test.f(Test.java:17)
14
    ... 2 more

如果采用 if-else-print 的方式在控制台打印错误信息,以达到出错时提示的目的,那么在debug时无疑是不如异常机制的,如上图例可以看到,使用异常机制,一旦出现异常,控制台不光有提示,更精确定位了出错的代码位置。

编程5分钟,找虫2小时,善用异常机制能够节约不少调试的时间。


2、异常使用的注意事项

  • 异常捕获后不做任何处理,就是耍流氓,挖坑埋自己
  • 异常机制不要用来做流程或条件控制,因为处理效率较低
  • try-catch若不触发catch是不影响性能的,但是try块仍然不要滥用包裹大量代码 (详见参考链接中相关文章)
  • 方法出错该抛异常就抛异常,而不是返回一些错误码


3、参考链接



4、异常方面的教训记录

4.1 关于try-catch和自定义Exception

因为知道try块不触发异常并不影响性能,于是我个人为了代码更好的可读性,扩大了try块的范围。后来便给自己造成了一点麻烦的事就是,假如: A底层方法抛出异常,B调用A时会捕获异常并打印信息,然后再次抛出该异常以供调用者使用。

于是我在调用B时要求处理异常,为了便于异常信息的使用,我到B的代码中去看是哪里抛出的异常,结果try块太庞大几乎包裹了所有代码,以至于我无法判断真正抛出异常的A在B中代码的哪个部分。所以try块也不能滥用。

另外,自定义异常的范围不明确,这才以至于我需要追踪到底层去判断捕获的异常如何处理,是内部记录还是做弹窗给用户提示?所以自定义异常的命名和创建也是需要明确范围和目的,是哪种类型的业务就建相应的异常,而不要一股脑就单独一个BusinessException。



转载于:https://www.cnblogs.com/deng-cc/p/7462539.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值