为什么要使用异常及java异常的使用--及js、php、golang的异常与错误机制

为什么要使用异常及java异常的使用--及js、php、golang的异常与错误机制packagetestE 总结使用异常的好处 和 die 区别 1 分离业务代码和错误代码 2 可以中止整个代码的运行仅仅 return 还是回执行后面的很多语言似乎没有 die 这个函数 3 可以在异常类中定义很多方法或者进行复杂处理 die 只能输出一句错误原因 4 使用异常类可以捕获很多个异常 不止子类 还可以少些代码复用 public 修饰的类必须为文件名 publicclassT public 异常机制的作用

为什么要使用异常

使用场景引入

阻断整个流程

要区分解释型语言和编译型语言的阻断流程
解释型语言直接阻断没啥大问题
但是编译型 比如整个服务是一个应用,你杀掉要区分是杀掉这次请求 还是让整个服务程序退出
一般应该是杀掉请求 而非让整个程序挂掉

对于无法编译时确定会发生的错误进行处理

好处

1、如上例子,可以中途阻断整个程序的运行。

应该用整个流程更合适
这里要区分下 对于解释型语言 用整个程序可能是合适的
但是编译型 比如go 是一个整体 所以就要区分是阻断流程 还是整个程序退出了

2、使得代码逻辑更清晰;少写if语句,以及将其与正常的业务逻辑分离开,以及很多还需要错误信息提示,使得逻辑嵌套更加复杂

if(用户没有登陆) { 
    //dosth //然后提示用户 //return 不能继续之后正常登录的流程了 } elseif(登陆了且被封号){ 
    //dosth //然后提示用户 //return 不能继续之后正常登录的流程了 }elseif(登陆了&没有被封号&用户没有购买资格){ 
    //dosth //然后提示用户 //return 不能继续之后正常登录的流程了 }elseif(商品没库存){ 
    //dosth //然后提示用户 //return 不能继续之后正常登录的流程了 } …… if(登陆、没封号、) …… 

如果没有异常,我们必须每一步都判断要不要继续以及每一步都要给用户一个提示 ,如果之后我们想修改这个提示 比如给它加逻辑(类似记录日志)就会很麻烦,即使我们将其封装为一个函数,但是也不够优雅 而且不能够阻断正常的流程

if(!A()){ 
    return; } if(!B()){ 
    return; } if(!C()){ 
    return; } 

引入异常之后我们可以假定一个操作正常退出即成功:

//比如A B C函数 都通过throw 丢出了一个提示消息型的错误 错误类型都相同 只是提示语不同 try { 
    A(); B(); C(); }catch(SomeException e){ 
    //集中处理错误 } 

3、你可能说,很多东西可以通过限制,比如不让用户输入负数。这种属于可以控制的犯错,但是,总有些东西是你控制不了的!
比如,数据库连接超时。非空、参数类型还可以通过if-else 但是数据库超时这种咋弄?
比如说你的程序需要读取某个文件 或者需要连接网络 又或者使用数据库这样的情况
虽然程序是你写的 但是你绝对不能保证文件一定存在 网络一定畅通 数据库一定会打开!
这个时候为了程序能继续运行 就需要把上边说的情况解释成异常 要想运行就必须这些处理异常!

此处要探讨错误和异常的区别 后续再深入 先这么理解着吧

4、即使是php这样的语言,由于mvc分层等原因,在service层中die也不合适,而且简单的die无法给更多的信息,如错误码、错误信息数组等。
5、异常能捕获到没有想到的错误 而if else只能捕获想到的错误
6、可以在异常类中定义很多方法或者进行复杂处理,die只能输出一句错误原因
7、使用异常类 可以捕获很多个异常(不止子类) 还可以少些代码 复用
8、人类无法穷举所有错误的情况,需要异常来帮助
9、异常处理可以交给上层函数调用者去完成 很多时候 写函数的时候 不知道怎么处理这个异常:
比如这个错误处理不了,要返回给上一层的调用者。在比较复杂的程序中,套个三四五六层跟玩儿一样(参考Spring等等),那么你就需要在这每一次嵌套的调用中都写上if(xxx) xxx else xxxx:// caller 1

int fun1(){ 
    int err; // ... if((err=fun2())<0){ 
    return err } // ... } // caller 2 int fun2(){ 
    int err; // ... if((err=fun3())<0){ 
    return err } // ... } // caller 3 int fun3(){ 
    int err; // ... if((err=fun4())<0){ 
    return err } // ... } // caller 4 int fun4(){ 
    int err; // ... if((err=fun5())<0){ 
    return err } // ... } 

//…平白无故就多出来很多boilerplate代码,不必要的变量声明,并穿插在逻辑代码中,影响可读性(搭眼看过去全tm是错误处理了)。而且如果一不小心,很容易漏写某些代码。有兴趣可以去读一下诸如linux内核这样的大型C代码程序,上面这样子的代码数不胜数,你i了吗>_>

11没法准确判断程序运行时哪里可能出现问题。异常处理能更加快的找到问题出现的位置

12 异常处理完了 程序就能接着运行 而这是简单的die无法实现的

异常的好处(什么时候使用异常) 精要总结

1、很多地方如果不符合条件 比如数据库连接错误、文件不存在。需要中断整个流程的运行,只用return 只能终止本层,如果多个函数嵌套调用,那么用return就是一场噩梦
2、提供了一种统一在一个地方处理异常的方法,减少代码量。如果不用 就得数组那种 有点像go
3、在处理完异常后,程序还能接着运行,这是die做不到的,比如数据库连接错误 就换一个从库试试
4、提供更多的报错信息,普通的die封装成函数——这其实就是异常类的处理方式了。而且die封装也始终只是函数 用类的方法能提供更多的功能、比如getmessage gettrace
5、能够交给上层处理 自己不知道咋弄.还能设置默认的未捕获的处理方法
6、将错误处理与正常逻辑流程分开,代码更清晰
7、finally:主要用于回收在try块里打开的物力资源(如数据库连接、网络连接和磁盘文件),异常机制总是保证finally块总是被执行。只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者 throw等终止方法的语句,则就不会跳回执行,直接停止。

不要滥用异常

比如一个函数 isPermission 检测是否有权限的
使用这个函数,如果有权限 返回true 没有返回false。只有比如用户id不存在应该抛出异常。
异常用于有问题应该应用于不处理需要直接终止脚本运行的场景 或者返回值提示信息很多种类的情况。

异常的使用–最简单的直观展示,以js为例

x = document.getElementById("demo").value; try { 
    if(x == "") throw "值为空"; if(isNaN(x)) throw "不是数字"; x = Number(x); if(x < 5) throw "太小"; if(x > 10) throw "太大"; } catch(err) { 
    message.innerHTML = "错误: " + err; } 

当然这是简单的处理,所以看起来和if else区别不大。更详细的使用–面向对象的方式 看java的

Java错误与异常体系

体系

在这里插入图片描述
在这里插入图片描述

Error与Exception

Java把所有的非正常情况分两种,一种是Error,如系统崩溃、虚拟机错误等,一般是和虚拟机相关的问题,这类问题无法恢复或者不可能捕获,所以程序不应该捕获error错误。另一种是Exception异常。

  • 我开着车走在路上,一头猪冲在路中间,我刹车。这叫一个异常。
  • 我开着车在路上,发动机坏了,我停车,这叫错误。系统处于不可恢复的崩溃状态。发动机什么时候坏?我们普通司机能管吗?不能。发动机什么时候坏是汽车厂发动机制造商的事。

Error: Error 类以及他的子类的实例,代表了JVM本身的错误。错误不能被程序员通过代码处理。是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如Java虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java中,错误通过Error的子类描述。

EXception: Exception 以及他的子类,代表程序运行时发送的各种不期望发生的事件。可以被Java异常处理机制使用,是异常处理的核心。

Cheked与Runtime异常

只有java提供了Checked异常,其他语言都没有提供(这是真的么?)

对于Checked异常,必须被显式处理,具体下面两种

  1. 当前方法知道如何处理, try catch块
  2. 当前方法不知道如何处理 就throw 出去 让调用这个方法的去处理

Runtime异常无需显示声明抛出,如果需要捕获 也用try catch语法

Checcked异常体现了java的设计哲学,而且可以提醒粗心的程序员别忘了处理,但是很繁琐,所以很多争议,特别是很多方法也不知道怎么处理异常 所以只是把它声明了出来 catch了而不处理

使用runtime异常简化异常处理

之前的写法

//是在函数定义部分写用哪个异常类 public void withdraw(double amount) throws InsufficientFundsExpection { 
    //此处是函数内容部分 throw写具体的构造函数,变成对象  throw new InsufficientFundsExpection(need); } 

现在的写法

//函数定义部分 无throws 函数内容里有throw  //这种设计很聪明 省的还写 runtime 复杂……没必要 public void withdraw(double amount) { 
    throw new InsufficientFundsExpection(need); } 

实例

0、最简单的异常----RuntimeException

后续会介绍runtime与Checked异常的区别

public static void main(String[] args){ 
    Runtime(-2);//无需catch无需try } public static void Runtime(int a){ 
    //和js那的区别仅仅在于此处是面向对象的 if(a<0) throw new RuntimeException("runtime异常"); } 

runtime异常有很多子类 比如 ArithmeticException 名字上感觉和runtime看不出关系

1 最简单的普通Checked异常—直接调用

区别:runtime异常可以不写try和catch块!不会编译失败,更方便!但是会导致程序停止

public static void main(String[] args) { 
    int a =-1; try { 
    if(a<0) throw new Exception("不能为负数"); }catch (Exception e) { 
    //e就是你new MyException()出来的对象名 System.out.println(e.getMessage());//打印错误信息 } } 

2 将异常处理封装成函数

可能我们不想只让它在主函数中调用,或者主函数中需要多次调用 此时可以将其封装为函数

public class TestException2 { 
    public static void main(String[] args) { 
    int a =-1; check(a); } public static void check(int i) { 
    try { 
    if(i<0) throw new Exception("不能为负数"); }catch (Exception e) { 
    System.out.println(e.getMessage()); } } } 

3 使用自定义异常类–去自定义错误处理函数

我们可能需要更复杂的逻辑,比如发生错误的时候发邮件给管理员,此时可用自定义异常类

public class TestException2 { 
    public static void main(String[] args) { 
    int a =-1; try { 
    if(a<0) throw new MyException(); }catch (MyException e) { 
    e.checkAdd(a);//这里就能用我们自定义的异常处理函数了 } } } class MyException extends Exception{ 
    public void checkAdd(int j) { 
    System.out.println("e是负数"); if(j+5>0) System.out.println("但是加5可以大于0"); } } 

4、带自定义异常类和throws的函数交给上层处理

package testExpection; /* * 总结 使用异常的好处(和die区别) * 1、分离业务代码和错误代码 * 2、可以中止整个代码的运行 在子函数中仅仅return还是会执行后面的函数 很多语言似乎没有die这个函数 * 3、可以在异常类中定义很多方法或者进行复杂处理,die只能输出一句错误原因 * 4、使用异常类 可以捕获很多个异常(不止子类) 还可以少些代码 复用 */ //------------------------------------------------------- /* 总结 使用异常的流程 * 1、写一个异常类继承Exception(非必须,可用系统写好的异常类) * 2、在被调用的函数中(事实上,这一步也可在主方法的try块中直接写) * A:函数申明部分throw出用到的异常类 * B:函数方法体,不符合条件时,throw出异常对象(new 异常构造器) * 3、主方法中:A: * A: try块写throw过异常的被调用方法和逻辑 * B:catch块捕获异常 catch (InsufficientFundsExpection e) { 具体怎么处理 其中e可调用方法} */ public class TestException { 
   //public修饰的类必须为文件名 //step1 写功能 class Account{ 
    private double balence; private int card_number; public Account(int card_number) { 
    this.card_number=card_number; } //存钱 public void deposit(double amount) { 
    balence+=amount; } public double getBalence() { 
    return balence; } public int getCardNumber() { 
    return card_number; } //和之前php的区别 php一般是在这个函数里 如果不符合要求return false 或者余额不足 成功返回余额 或者直接die(errmeg)中止运行 //而java是通过另一个方法getBalence 去获取这个值 可能是因为php通常是从数据库中拿东西 感受不到 //函数申明部分要指明类,throw部分要指明对象,其实很好理解,声明类与新建对象的语法 public void withdraw(double amount) throws InsufficientFundsExpection { 
    //是在函数处写用哪个异常类 if(amount<=balence) { 
    balence -=amount; System.out.println("余额为:"+ balence); }else { 
    double need = amount-balence; throw new InsufficientFundsExpection(need);//此处throw写具体的构造函数,变成对象 } } } //Step2 定义异常类 //如果希望写一个检查性异常类,则需要继承 Exception 类。 //如果你想写一个运行时异常类,那么需要继承 RuntimeException 类 class InsufficientFundsExpection extends Exception{ 
    private double need; public InsufficientFundsExpection(double need) { 
    this.need=need; } public double getNeed() { 
    return need; } } //Step3 使用异常 public static void main(String[] args) { 
    Account a = new Account(10086);//前为类名 后为构造函数 System.out.println("存款500");//必须双引号 a.deposit(500); //开始异常块---使用了异常的函数 需要放到try_catch块中 try { 
    System.out.println("取款100"); a.withdraw(100); System.out.println("取款1000"); a.withdraw(1000); } //catch块通常直接跟括号后面,这么写是为了注释 //具体的处理异常的逻辑放这里  //至于有哪些异常 在这个函数中定义 函数内部仅仅定义异常类型 不规定怎么处理(如果通用也可以抽象出来放进去) catch (InsufficientFundsExpection e) { 
    System.out.println("取款失败,欠缺"+ e.getNeed()); //此处可以调用这个异常定义的方法,这就是面向对象的好处 e.printStackTrace();//trace的方法 异常类都有 } //catch块可以写多个 //finally块可选 } } 

异常其他

  • 不管程序是否处于try中,甚至包括catch块中的代码,只要执行代码块时出现了异常,系统总会生成一个异常对象,如果程序没有对这段代码定义catch块,则ajva运行时就在这里退出。
  • catch块中也可使用continue,用于外层是while等 如五子棋下棋的场景。
  • try块中的变量,catch块不能访问,且必须加括号 不能省略
  • 常见异常:数组越界异常、数字格式异常
  • JAVA7新增多异常捕获,
//竖线分隔 catch(AException|BException e) 变量e有final限制,不能赋新值,如e=new AException("TEST"); 
  • java7新增的自动关闭资源的特性
try(需要自动关闭资源的代码写这里(只写声明和初始化)) { 
    正常的try语句,如使用资源的逻辑 写这里 } //java7 重写了所有的资源类,改写后都实现了AutoCloseable或Choseable接口(能够被自动关闭的资源必须实现这两个类之一) 

ps throw也可多个异常块 但不是java7的 throw E1,E2的语法就行

异常常见的方法

  • getMessage() 返回该异常的详细描述字符串
  • printStackTrace()将该异常的详细跟踪栈信息输出到标准错误输出
  • printStackTrace(printStream s) 同上 不过是输出到指定输出流
  • getStackTrace()返回该异常的跟踪栈信息

异常规范

  • 通常在finally块中回收try块打开的物理资源,如db等,异常机制会保证finally块总被执行!即使try中有return语句,也会保证先执行finally里的(除了System.exit(1)这种退出虚拟机的语法)。所以少在finally块中用return、throw避免出现奇奇怪怪的错误
  • 程序总是把Exception类的异常放后面,这是因为所有异常对象都是Exception或者其子类的实例,所以把它放后面
try{ 
    } catch (IndexOutOfBundsException e){ 
    do sth } catch (Exception e){ 
    } finally 
  • throw的真正含义,当前方法不知道怎么处理这个异常 而交给调用它的方法去处理!所以写了throw块 当前方法就不用写catch块了,如果调用它的也throw 那么就接着下一级。注,main方法也可用throw!这样会把异常交给jvm处理,而jvm的做法是打印Stack并终止代码运行
  • 异常用于分离正常业务逻辑和错误处理,所以不要通过异常的方便去吧正常业务逻辑写进去
  • try块不应该过大 如果真的很多 分离开 不同的try catch块
  • 遇到异常 尽量不要捕获而不处理 实在不行交给上层调用它的去处理

异常链-异常转译 ----分级显示异常

如用户不关心sql层的异常 用户只想看到报错信息 所以分层是有必要的

try{ 
    正常逻辑 } catch (底层异常 如sql异常){ 
    //这里记录原始异常给管理员 ................... throw new 上层异常('给用户的错误信息') } } 

Jdk1.4 后 异常类可新增一个构造器 具体以后再看吧···

php的错误处理机制和异常

错误和异常的区别

1 php异常和错误的定义

实际上 好像php的ERROR类似java的runtiom异常吧

如果抛出的异常未被捕获,则导致 Fatal error,并使得代码停止执行。

由于历史原因,php一开始被设计为一门面向过程的语言,所以异常处理没有使用像Java一样的 try / catch 机制。这就是错误的诞生。

2 php的异常机制相较php错误机制的好处

错误处理机制

1简单的die()函数

die是退出当前接口 不是退出php-fpm
缺点在于提供的信息不够多。当然也可先print_r 然后再die,但是每次都写很麻烦 而且不统一。
当然也可以一起封装为函数----事实上 这就是错误!
但是这种错误比较简单,没办法自定义错误级别和自动触发错误(自动触发错误 比如堆栈信息、发邮件 需要语法错误 手动触发的很像异常)

2自定义错误和错误触发器、错误日志

2.1一个简单的例子
set_error_handler(function ($errno,$errstr,$errfile,$errline)//常用的四个参数 { 
    echo "错误等级:".$errno."<br>错误信息:".$errstr."<br>错误的文件名:".$errfile."<br>错误的行号:".$errline; exit();//否则出错后还可正常运行 }); $a = 1/0; //自动触发的错误 trigger_error("this is a error");//自行触发的错误 echo '正常'; 
2.2、自定义错误处理函数

这个例子和上面的区别在于,将处理错误的函数单独写出来了,而且规定了错误级别。方便复用

<?php /* * 1, 创建自定义错误函数 * 2,使用set_err_handler指定使用我们的错误处理函数 * 3,触发错误,分自动触发和手动触发 */ /* * 我们可以自定义错误处理函数 * 语法 * error_function(error_level,error_message,error_file,error_line,error_context) * 函数名可变,前两个参数是必选项 */ function customError($errno, $errstr) { 
    echo "<b>Error:</b> [$errno] $errstr<br>"; die(); //进阶error_log() 函数可向服务器错误记录、文件或远程目标发送一个错误。 } /* // 设置使用我们自定义的错误处理函数 //set_error_handler() 仅需要一个参数,可以添加第二个参数来规定错误级别。 //如果第二个不传代表所有错误 */ set_error_handler("customError",E_USER_WARNING); /* 触发错误,分为自动触发和手动触发,此处为自动触发,下面的章节介绍手动触发的 */ echo($test);//自动触发错误Error: [8] Undefined variable: test  $test = 2; if ($test>1) { 
    trigger_error("变量值必须小于等于 1");//手动触发错误 } } ?> 
2.3 错误记录—error_log

在默认的情况下,根据在 php.ini 中的 error_log 配置,PHP 向服务器的记录系统或文件发送错误记录。通过使用 error_log() 函数,您可以向指定的文件或远程目的地发送错误记录

<?php // 错误处理函数 function customError($errno, $errstr) { 
    echo "<b>Error:</b> [$errno] $errstr<br>"; echo "已通知网站管理员"; error_log("Error: [$errno] $errstr",1, "someone@example.com","From: webmaster@example.com"); //err_log会自动die } // 设置错误处理函数 set_error_handler("customError",E_USER_WARNING); // 触发错误 $test=2; if ($test>1) { 
    trigger_error("变量值必须小于等于 1",E_USER_WARNING); } ?> 
2.4其他

Logging 函数允许用户对应用程序进行日志记录,并把日志消息发送到电子邮件、系统日志或其他的机器。----其实就是err_log

php7 对异常的更改:
在这里插入图片描述在这里插入图片描述

异常

1、最简单的异常–类似java的Runtime的异常
<?php // 创建一个有异常处理的函数---只需要throw而无需catch function checkNum($number) { 
    if($number>1) { 
    throw new Exception("Value must be 1 or below"); } return true; } // 触发异常 checkNum(2); /* Fatal error: Uncaught exception 'Exception' with message 'Value must be 1 or below' in /www/runoob/test/test.php:7 Stack trace: #0 /www/runoob/test/test.php(13): checkNum(2) #1 {main} thrown in /www/runoob/test/test.php on line 7 */ ?> 
2、带catch的异常----和java的区别,无需使用throws交给上层处理(默认就可)
<?php // 创建一个有异常处理的函数 function checkNum($number) { 
    if($number>1) { 
    throw new Exception("变量值必须小于等于 1"); } return true; } // 在 try 块 触发异常 try { 
    checkNum(2); // 如果抛出异常,以下文本不会输出 echo '如果输出该内容,说明 $number 变量'; } // 捕获异常 catch(Exception $e) { 
    echo 'Message: ' .$e->getMessage(); } ?> 

注意: 1、如果定义异常的时候就捕获异常 try块中的会中断代码运行,外面的不会

$a=1; try { 
    if ($a == 1) throw new Exception("获取access_token错误"); print_r('try里'); } catch (Exception $e) { 
    print_r($e->getMessage()); } print_r('try外'); //result: try外部 

2 、如果内层函数throw,php没有java的throws

function outer(){ 
    echo 1; inner(); echo 4 ; } function inner(){ 
    echo 2; throw new Exception('error'); echo 3; } try{ 
    outer(); }catch(Exception $e){ 
    echo 'catch'; } echo 5; //out 1 2 catch 5 //总结:执行范围为throw块的上面 和继续执行捕获异常以及之后的部分 
3、自定义的异常类
<?php class customException extends Exception { 
    public function errorMessage() { 
    // 错误信息 $errorMsg = '错误行号 '.$this->getLine().' in '.$this->getFile() .': <b>'.$this->getMessage().'</b> 不是一个合法的 E-Mail 地址'; return $errorMsg; } } $email = "someone@example...com"; try { 
    // 检测邮箱 if(filter_var($email, FILTER_VALIDATE_EMAIL) === FALSE) { 
    // 如果是个不合法的邮箱地址,抛出异常 throw new customException($email); //区别在上面,可以自定义参数、处理方法等 比如加通知管理员的 } } catch (customException $e) { 
    //display custom message echo $e->errorMessage(); } ?> 
4、异常分级处理
上面自定义异常类,我们可能不想让用户看到具体的行号错误文件, 所以需要定义多个异常,分级输出异常。也就是重新抛出异常 <?php class customException extends Exception { 
    public function errorMessage() { 
    // 错误信息 $errorMsg = $this->getMessage().' 不是一个合法的 E-Mail 地址。'; return $errorMsg; } } $email = "someone@example.com"; try { 
    try { 
    // 检测 "example" 是否在邮箱地址中 if(strpos($email, "example") !== FALSE) { 
    // 如果是个不合法的邮箱地址,抛出异常 throw new Exception($email); } } catch(Exception $e) { 
    // 重新抛出异常 throw new customException($email); } } catch (customException $e) { 
    // 显示自定义信息,最终只给用户展示最外层的异常! echo $e->errorMessage(); } ?> 

但是这样很麻烦 有优化方式么?

5、设置顶层异常处理器—便捷程序员不想写catch块的情况
//set_exception_handler() 函数可设置处理所有未捕获异常的用户定义函数。 function myException($exception) { 
    echo "<b>Exception:</b> " , $exception->getMessage(); } set_exception_handler('myException');//和错误那里很像 //不用写catch块了 throw new Exception('Uncaught Exception occurred'); 
6、其他
错误转变成异常
set_exception_handler(function (Exception $e) { 
    echo "我自己定义的异常处理".$e->getMessage(); }); set_error_handler(function ($errno, $errstr, $errfile, $errline ) { 
    throw new ErrorException($errstr, 0, $errno, $errfile, $errline);//转换为异常 }); trigger_error("this is a error");//自行触发错误 运行结果: 我自己定义的异常处理this is a error 

结合看错误那里 php7对错误的更改

异常还可接受错误码 用于给网页返回
可以定义必须处理的异常类

即使设置了全局默认异常处理类 对特定的类 还是可以再抛出异常

总之(怎么用二者)

新版本的php 建议默认都用exception了。
错误用于自动触发的场景,但是一半都是语法类错误 ,平常不多关注。(但是可以设置 变量为定义的话 强制停止?)
异常用于手动触发的场景,虽然错误也可手动触发,但是不建议用错误了(有时候还可能遇到莫名其妙的问题 不展开了……)。异常更强大,也可以设置默认处理程序,还能手动catch

使用exception的时候,可以写一个处理handler,用于未捕获异常的处理(一般必须写)。还可以自定义异常类(可写可不写 看需求)

GO错误与异常机制

首先分清go的错误与异常含义

编译应用和解释语言应用的区别

Go 的错误和异常处理的设计意图

理解了错误和异常的真正含义,我们就能理解 Go 的错误和异常处理的设计意图。

go错误出发点是好的,鼓励处理每个错误,但是实现是狗屎,导致写起来非常啰嗦

为什么少用try……catch?

  1. 在 go 语言里是没有 try catch 的概念的,因为 try catch 会消耗更多资源,而且

其实 panic、recover、defer,就类似 try catch 但是还是和这里的主张一样,少用

  1. 不管从 try 里面哪个地方跳出来,都是对代码正常结构的一种破坏。
    对于有异常的编程系统,假如对程序的核心状态需要多步update语句,如果中间被异常中断,程序的核心状态就会失去完整性,这是绝大多数buq的来源
    由于没有异常,分析代码的时候可以线性一行一行思考,完全不需要像有异常的系统那样每一行都担心跳出异常怎么办,大大减轻了思考负担
  2. 越早crash越好 因为老是throw给后面的调用者 那debug的时候可能就很痛苦 不知道到底哪里有问题
  3. 传统的try…catch…结构,很容易让开发人员把错误和异常混为一谈,甚至把业务错误处理的一部分当做异常来处理,于是你会在程序中看到一大堆的 catch…

为何以及何时用错误?

所以,Go 开发团队认为错误应该明确地当成业务的一部分,任何可以预见的问题都需要做错误处理。

于是在 Go 代码中,任何调用者在接收函数返回值的同时也需要对错误进行处理,以防遗漏任何运行时可能的错误。

为何以及何时用异常?

异常处理场景

  1. 空指针引用
  2. 下标越界
  3. 除数为0
  4. 不应该出现的分支,比如default
  5. 输入不应该引起函数错误

另外,在 Go 中除了触发异常,还可以终止异常并可选的对异常进行错误处理,也就是说,错误和异常是可以相互转换的

所以 go 语言的设计思想中主张

  • 如果一个函数可能出现异常,那么应该把异常作为返回值,没有异常就返回 nil
  • 每次调用可能出现异常的函数时,都应该主动进行检查,并做出反应
  • 异常应该总是掌握在我们的手上,保证每次操作产生的影响达到最小,保证程序即使部分地方出现问题,也不会影响整个程序的运行,及时的处理异常,这样就可以减轻上层处理异常的压力。
  • 错误转异常,比如程序逻辑上尝试请求某个URL,最多尝试三次,尝试三次的过程中请求失败是错误,尝试完第三次还不成功的话,失败就被提升为异常了。
  • 异常转错误,比如panic触发的异常被recover恢复后,将返回值中error类型的变量进行赋值,以便上层函数继续走错误处理流程。

总结来说就是 :

对于真正意外的情况,那些表示不可恢复的程序错误,不可恢复才使用 panic。对于其他的错误情况,我们应该是期望使用 error 来进行判定

go 源代码很多地方写 panic,但是工程实践业务代码不要主动写 panic。毕竟服务器程序不能因为某个接口有问题导致整个程序服务的宕机

理论上 panic 只存在于server 启动阶段,比如 config 文件解析失败,端口监听失败等等,所有业务逻辑禁止主动panic,所有异步的 goroutine都要用 recover 去兜底处理。

defer 是崩溃后,仍然会被调用的语句,那程序在什么情况下会崩溃呢?

recover:
出现 panic 以后程序会终止运行,所以我们应该在测试阶段发现这些问题,然后进行规避,但是如果在程序中产生不可预料的异常(比如在线的web或者rpc服务一般框架层),即使出现问题(一般是遇到不可预料的异常数据)也不应该直接崩溃,应该打印异常日志,关闭资源,跳过异常数据部分,然后继续运行下去,不然线上容易出现大面积血崩。
然后再借助运维监控系统对日志的监控,发送告警给运维、开发人员,进行紧急修复。

go开发者对冗长的回应

Go语言的错误处理机制可以从支持函数多返回值说起!

关于 panic/recover 机制,Yuval 认为也不够出色,因为连 Go 的标准库都不怎么用这种机制:为什么索引溢出的数组要比错误格式的字符 串或者失败的网络连接更需要 panic 呢?Go 语言希望能够完全避免异常,但实际上不能,总有一些异常会在某处发生,让开发人员在错误出现时感到困惑。

针对 Yuval 的批评,Go 的开发者 Russ Cox 做出了回应:

Russ Cox 针对 “为什么数组越界造成的麻烦会比错误的网址或断掉的网络引出的问题要大?” 这个问题给出了自己的答案:

最后,Russ Cox 指出 Go 语言是为大型软件设计的:

我们都喜欢程序简洁清晰,但对于一个由很多程序员一起开发的大型软件,维护成本的增加很难让程序简洁。异常捕捉模式的错误处理方式的一个很有吸引力 的特点是,它非常适合小程序。但对于大型程序库,如果对于一些普通操作,你都需要考虑每行代码是否会抛出异常、是否有必要捕捉处理,这对于开发效率和程序 员的时间来说都是非常严重的拖累。我自己做开发大型 Python 软件时感受到了这个问题。Go 语言的返回错误方式,不可否认,对于调用者不是很方便,但这 样做会让程序中可能会出错的地方显的很明显。对于小程序来说,你可能只想打印出错误,退出程序。对于一些很精密的程序,根据异常的不同,来源的不同,程序 会做出不同的反应,这很常见,这种情况中,try + catch 的方式相对于错误返回模式显得冗长。当然,Python 里的一个 10 行的代码放到 Go 语言里很可能会更冗长。毕竟,Go 语言主要不是针对 10 行 规模的程序的。

忘了咋分类的了

如何避免多层err?
在这里插入图片描述

没有碰到第三方库直接 log.Fatal 导致根本无法找到出错点情况的就不要给没有 exception 洗地了。在某些方面 Go 的设计比 Java 更偏向于无脑人士,也难怪上面某些人会如此赞赏。发布于 2015-04-13 11:53​赞同 22​​收起评论​分享​收藏​喜欢​理性发言,友善互动13 条评论默认最新FanZ错误使用log.Fatal的库就不要用了。第三方库凭什么判定程序需要退出了。

code实践

错误的形式

我们应该让异常以这样的形式出现

func errorDemo() (int, error){ 
    //具体内容略 } 

我们应该让异常以这样的形式处理(卫述语句)

_,err := errorDemo() if err!=nil{ 
    fmt.Println(err) return } 

自定义异常

比如程序有一个功能为除法的函数,除数不能为 0 ,否则程序为出现异常,我们就要提前判断除数,如果为 0 返回一个异常。那他应该这么写。

func divisionInt(a, b int) (int, error) { 
    if b == 0 { 
    return -1, errors.New("除数不能为0") } return a / b, nil } 

这个函数应该被这么调用

a, b := 4, 0 res, err := divisionInt(a, b) if err != nil { 
    fmt.Println(err.Error()) return } fmt.Println(a, "除以", b, "的结果是 ", res) 

可以注意到上面的两个知识点

  • 创建一个异常 errors.New(“字符串”)
  • 打印异常信息 err.Error()
    只要记得这些,你就掌握了自定义异常的基本方法。

但是 errors.New("字符串") 的形式我不建议使用,因为他不支持字符串格式化功能,所以我一般使用 fmt.Errorf 来做这样的事情。

err = fmt.Errorf("产生了一个 %v 异常", "喝太多") 

详细的异常信息

上面的异常信息只是简单的返回了一个字符串而已,想在报错的时候保留现场,得到更多的异常内容怎么办呢?这就要看看 errors 的内部实现了。其实相当简单。

errors 实现了一个叫 error 的接口,这个接口里就一个 Error 方法且返回一个 string ,如下

type error interface { 
    Error() string } 

只要结构体实现了这个方法就行,源码的实现方式如下

type errorString struct { 
    s string } func (e *errorString) Error() string { 
    return e.s } // 多一个函数当作构造函数 func New(text string) error { 
    return &errorString{ 
   text} } 

所以我们只要扩充下自定义 error 的结构体字段就行了。

这个自定义异常可以在报错的时候存储一些信息,供外部程序使用

type FileError struct { 
    Op string Name string Path string } // 初始化函数 func NewFileError(op string, name string, path string) *FileError { 
    return &FileError{ 
   Op: op, Name: name, Path: path} } // 实现接口 func (f *FileError) Error() string { 
    return fmt.Sprintf("路径为 %v 的文件 %v,在 %v 操作时出错", f.Path, f.Name, f.Op) } 

调用

f := NewFileError("读", "README.md", "/home/how_to_code/README.md") fmt.Println(f.Error()) 

输出

路径为 `/home/how_to_code/README.md 的文件 README.md`,在 读 操作时出错 

defer

上面说的内容很简单,在工作里也是最常用的,下面说一些拓展知识。

Go 中有一种延迟调用语句叫 defer 语句,它在函数返回时才会被调用,如果有多个 defer 语句那么它会被逆序执行。

比如下面的例子是在一个函数内的三条语句,他是这么怎么执行的呢?

defer fmt.Println("see you next time!") defer fmt.Println("close all connect") fmt.Println("hei boy") 

输出如下, 可以看到两个 defer 在程序的最后才执行,而且是逆序。

hei boy close all connect see you next time! 

这一节叫异常处理详解,终归是围绕异常处理来讲述知识点, defer 延迟调用语句的用处是在程序执行结束,甚至是崩溃后,仍然会被调用的语句,通常会用来执行一些告别操作,比如关闭连接,释放资源(类似于 c++ 中的析构函数)等操作。

涉及到 defer 的操作

  • 并发时释放共享资源锁
  • 延迟释放文件句柄
  • 延迟关闭 tcp 连接
  • 延迟关闭数据库连接

这些操作也是非常容易被人忘记的操作,为了保证不会忘记,建议在函数的一开始就放置 defer 语句。

如何避免过多的err语句?

更多内容看 爆肝3天只为Golang 错误处理最佳实践https://zhuanlan.zhihu.com/p/ 写的不错

关于这里提到的如何避免满屏飘的error check,Rob Pike大佬早就给出了最佳实践。Go 错误处理最大特点恐怕就是满屏飘的if err != nil。典型的代码调用,如下// 简单处理

func funcA() error { 
    // do something return errors.New("funcA error") } func funcB() error { 
    // do something return fmt.Errorf("funcB error %d", 1) } func funcC() error { 
    // do something return fmt.Errorf("funcC error %d", 2) } func TestSimple(t *testing.T) { 
    err := funcA() if err != nil { 
    t.Logf("err %v", err) return } err = funcB() if err != nil { 
    t.Logf("err %v", err) return } err = funcC() if err != nil { 
    t.Logf("err %v", err) return } } 

关于如何优化,Rob Pike给了两种典型优化和处理套路。
嵌套函数
计算机中没什么是加一层不能解决的,这里引入嵌套函数——一次run过程中,每一步都过统一的err检查,最后做统一的err判断处理。

// 简单处理 func funcAA() error { 
    // do something return errors.New("funcA error") } func funcBB() error { 
    // do something return errors.New("funcB error") } func funcCC() error { 
    // do something return errors.New("funcC error") } func TestSimpleTidy(t *testing.T) { 
    // 外包函数判断 var err error callFunc := func(f func() error) { 
    if err != nil { 
    return } err = f() } // 顺序调用 callFunc(funcAA) callFunc(funcBB) callFunc(funcCC) // 统一判断 if err != nil { 
    t.Logf("Error - %v", err) } } 

嵌套接口实现和嵌套函数实现类似,这里接口封装定义的结构体保存了错误处理相关信息,对外暴露信息少,而且很容易实现链式调用和错误处理,如下

type WorkRunner struct { 
    err error } func NewWorkRunner() *WorkRunner { 
    return &WorkRunner{ 
   } } func (w *WorkRunner) run(f func() error) { 
    if w.err == nil { 
    w.err = f() } } func (w *WorkRunner) funcAA() *WorkRunner { 
    // do something w.run(func() error { 
    return errors.New("funcA error") }) return w } func (w *WorkRunner) funcBB() *WorkRunner { 
    // do something w.run(func() error { 
    return errors.New("funcB error") }) return w } func (w *WorkRunner) funcCC() *WorkRunner { 
    // do something w.run(func() error { 
    return errors.New("funcC error") }) return w } func TestSimpleTidy2(t *testing.T) { 
    // 对象统一管理判断 w := NewWorkRunner() // 顺序链式调用 w.funcAA().funcBB().funcCC() // 统一判断 if w.err != nil { 
    t.Logf("Error - %v", w.err) } } 
今天的文章 为什么要使用异常及java异常的使用--及js、php、golang的异常与错误机制分享到此就结束了,感谢您的阅读。
编程小号
上一篇 2024-12-07 12:11
下一篇 2024-12-07 12:06

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/bian-cheng-ji-chu/80269.html