在平常的编程过程当中,不可避免地须要处理错误的状况,而每一种编程语言都自有其错误处理逻辑,其背后的考量是什么?下面来探讨一下各编程语言中的错误处理,尝试总结出一些通用的方法与原则。
讨论一个问题以前,第一步就是要明晰下它所涉及的概念。
首先,标题所说的错误是广义的错误,它包括异常(Exception)与错误(Error)。下文中提到的『错误』均为狭义的区别与异常的错误。html
程序中的异常(Exception)是指发生在程序执行过程当中非频繁非正常的事件,它位于程序正常流程以外。
异常大体可分为两类:java
编程语言中的异常则属于软件异常。编程
而程序中的错误(Error),一般是指发生在程序执行过程当中正常的事件,它就在程序正常流程范围以内。编程语言
与异常概念最容易混淆的就是错误。
两者一般能够经过下面三个维度来区分:是否正常/可预期/终止程序ide
概念 | 是否正常 | 是否可预期 | 是否终止程序 |
---|---|---|---|
异常 | 否 | 否 | 是 |
错误 | 是 | 是 | 否 |
前两个维度主要是对概念的描述,最后一个维度(是否终止程序,便是否可恢复)建议做为定义错误与异常的标准。若是一个事件它不可恢复应该定义为异常,及时终止程序退出,避免程序进入不可预知的状态(如形成数据不一致);若是一个事件能够预测出错误,那么就应该check,并作一些相应的恢复处理。如Golang中的Error与Panic就是遵循该原则而设计。函数
区分了异常与错误,下一步则是考虑针对错误的处理机制。
正确地区分了异常与错误的概念,咱们就能够根据具体场景,正确地定义出异常与错误,以及安排相应的错误处理。性能
一般,编程语言中的错误处理能够分为两类:操作系统
虽然目前主流编程语言中的异常处理均采用『try/catch』式的原则,可是大都数在写代码过程当中都是左右开弓的,依据具体的场景,选择合适的处理方式(抛异常 or 检查返回值)。设计
最先的C语言是经过检查函数返回值(一般零值/非空成功;非零值/空失败)来进行错误处理的。
如定义一个函数:code
int foo() { // <try something here> if (failed) { return 1; } return 0; }
调用者则在进行下一步操做以前,须要判断foo函数返回值:
int err = foo(); if (err) { // Error! Deal with it. }
基于C或者底层级别的系统均是经过这种检查返回值的方式来处理错误的。如Window和Linux操做系统级别的调用(API)。
这种方式很简单,代码可读性也较好,可是写起来很是繁琐,这意味着你须要对每个函数在调用以前的都须要手动check一下。并且,一旦忘记检查,很容易出现bug。
Golang则在C语言的基础上增长了更符合现代编程语言的语法和库。它容许函数有两个返回值,一般最后一个返回值为Error类型,调用者能够经过检查该类型返回值来检查函数返回状况,没有错误则使用第二个返回值,继续接下来的业务逻辑操做。
如:
func foo() (int, error){ // <try something here> if (failed) { return -1, errors.New("something error") } return 0, nil; }
调用:
if sum, err := foo(); err != nil { // Deal with the error. } // do something with sum ...
Golang的实现方式看起来比C语言更加优雅一些,可是频繁地检查返回值仍然不可避免。
C语言在不使用goto语句的状况下,异常代码复用几乎不可能,Golang也难以解决这个问题。
因而在后来发展起来的面向对象编程语言中,大部分都引入了相似try/catch式的异常处理机制。
下面主要以Java语言举例说明
Java中全部的错误处理均基于Throwable顶层父类,其下有两个子类:Error,它表示不但愿被程序捕获或者是程序没法处理的错误。另外一个是Exception,它表示用户程序可能捕捉的异常状况或者说是程序能够处理的异常。
这是Java对异常与错误的划分,而且在此基础上为进一步提升程序健壮性,引入了checked异常与unchecked异常概念:针对那些除了RuntimeException与其子类,以及错误(Error),其余的都是须要编译时强制检查的异常,不然编译器会报错。
try { method(); } catch (IOException ioe) { System.out.println("I/O failure"); } // ... void method() throws IOException { throw new IOException("some text"); }
也正是Java的这种处理方式让人诟病:checked异常容易让相关代码里充斥着大量的try/catch,使代码一样变得晦涩难懂。同时,checked异常所起到的做用也只是将捕获的异常,包装成运行时异常,而后再从新抛出。
正如,前文所言,每一门编程语言在设计之初都有自身的考量,且在进行实际的错误处理时均会同时考虑『check』与『try/catch』两种方式。
在C#中,并无引入checked异常概念,而是把检查的义务又『还给』了开发者。
除此以外,try/catch式异常处理一般会有很大的性能开销,故应当慎用。
即便发展至今,关于异常处理(『try/catch』)与检查返回值(『check』)这两种错误处理方式仍然争议不断。
check式与try/catch式两种错误处理的方式,没有哪种是绝对优点的,都有各自的优缺点,这有赖于语言设计者当时的权衡与抉择。可是无论哪一种编程语言,基本衍生于这两类处理方式。
http://www.cs.tut.fi/~popl/ny...
http://joeduffyblog.com/2016/...
https://www.zhihu.com/questio...
https://nedbatchelder.com/tex...
https://www.javaworld.com/art...