JAVA基础之异常

异常

一、异常概念

1.1、异常

异常,通俗点来说就是不正常的意思。

在生活中:医生说,你的身体某个部位有些异常,该部位和正常相比有点不同,该部位的功能将受影响。

在程序中:程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常停止

1.2、异常引发后果

如果说程序因为错误而到了JVM停止运行,那么就会导致整个应用程序停止,从而无法对外停止服务。那么这种是属于生产事故。

1.3、异常解决

程序导致了错误,导致了JVM停止,那么有没有办法做到:程序即使出现了错误,也不应该让程序停止呢?

这个时候应该提出异常捕获的概念:为了保证程序的健壮性,就算了是程序出现了异常,那么也不应该让JVM停止运行。

出现了异常之后,需要注意到异常是否是在我们的可控范围之内?

那么什么是可控范围之内?首先可以肯定的是:程序在运行时我们不敢保证一定不会出现问题,而编译异常我们可以来解决。

而运行时异常不是可控的吗?说起来这个,也有一定的可控范围。比如说常见的NPE,这个是因为程序代码的逻辑性不够严谨。

除了让其暴露出来之外,我们还有另外的方式来处理,比如说检查出来了是NPE,那么重新赋值,而不是说直接让程序终止运行。

再比如说,我们可以在捕捉到异常之后,在异常逻辑中写对应的代码。不一定是一些回滚、删除之类的逻辑。也就是说,第一种情况不合适,那么用第二种方式来试试;第二种不合适,那么使用第三种来试试。

但是,如果出现了运行时异常,而没有来进行处理,导致main方法提交异常给JVM来处理,而JVM无法处理,将会停止JVM运行,所以这种情况不要让其发生,尤其是在生产上

1.4、异常体系

在Java等面向对象的编程语言中,异常本身是一个类,产生异常就是创建异常对象并抛出了一个异常对象。Java处理异常的方式是中断处理。

异常机制其实是帮助我们定位、排查程序中的问题,既然异常是个类,那么来看下对应的体系结构

异常的根类是Java.lang.Throwable,其下有两个子类:Java.lang.ErrorJava.lang.Exception,平常所说的异常指Java.lang.Exception

Throwable类

  • Error:严重错误Error,无法通过处理的错误,只能事先避免,好比绝症。
  • Exception:表示异常,异常产生后程序员可以通过代码的方式纠正,使程序继续运行,是必须要处理的。好比感冒、阑尾炎等症状。
Throwable中的属性

detailMessage:这个是极其重要的属性,这个值就是e.getMessage()返回的值;

那么这个属性会在什么时候赋值呢,追溯源码发现,该属性只会在Throwable构造函数中赋值。

public Throwable() {
    // 在默认构造函数中不会给detailMessage属性赋值
    fillInStackTrace();
}
public Throwable(String message) {
    fillInStackTrace();
    // 直接将参数赋值给detailMessage
    detailMessage = message;
}
public Throwable(String message, Throwable cause) {
    fillInStackTrace();
    // 直接将参数赋值给detailMessage
    detailMessage = message;
    this.cause = cause;
}
public Throwable(Throwable cause) {
    fillInStackTrace();
    // 当传入的Throwable对象不为空时,为detailMessage赋值
    detailMessage = (cause==null ? null : cause.toString());
    this.cause = cause;
}
protected Throwable(String message, Throwable cause,
                        boolean enableSuppression,
                        boolean writableStackTrace) {
    if (writableStackTrace) {
        fillInStackTrace();
    } else {
        stackTrace = null;
    }
    // 直接将参数赋值给detailMessage
    detailMessage = message;
    this.cause = cause;
    if (!enableSuppression)
        suppressedExceptions = null;
}

显然,从源码中可以看到在Throwable的默认构造函数中是不会给detailMessage属性赋值的。

也就是说,当异常对象是通过默认构造函数实例化的,或者实例化时传入的message为空字符串,那么调用getMessage()方法时返回值就为空。所以,在程序日志中不要单纯使用getMessage()方法获取异常信息(返回值为空时,不利于问题排查)。

Throwable常用方法
  • public void printStackTrace():打印异常的详细信息。

    包含了异常的类型,异常的原因,还包括异常出现的位置,在开发和调试阶段,都得使用printStackTrace。

  • public String getMessage():获取发生异常的原因。

    提示给用户的时候,就提示错误原因。

  • public String toString():获取异常的类型和异常描述信息(不用)。

写个程序来测试一个三个方法:

        try {
            int i = 1 /0;
        }catch (Exception e){
            e.printStackTrace();
        }

对应的打印信息:可以看到哪个方法中的哪行出现了什么异常,对应的异常信息是什么

Java.lang.ArithmeticException: / by zero
	at com.guang.exception.TestException.main(TestException.Java:9)
try {
  int i = 1 /0;
}catch (Exception e){
  System.out.println(e.getMessage());
}

对应的打印信息:

/ by zero
try {
  int i = 1 /0;
}catch (Exception e){
  System.out.println(e);
}

输出信息:

Java.lang.ArithmeticException: / by zero

小结

  • 1、根据异常信息来定位问题的时候,需要根据顺序来查找具体的问题:因为方法是在栈中进行操作的,所以是先进后出结构,打印的顺序也是这个结构,报错第一行先打印出来肯定是最先报错的地方,最下面一方是调用方法出现的问题,这里是根本方法。如果是方法中调用方法,这种排查方式是最佳选择方式。

  • 2、异常提供的方法中,肯定是e.printStackTrace()这个方法最能够满足我们的需求,所以使用这个是最佳推荐

1.5、异常分类

我们平常说的异常就是指Exception,因为这类异常一旦出现,我们就要对代码进行更正,修复程序。

异常(Exception)的分类:根据在编译时期还是运行时期去检查异常

  • 编译时期异常 :checked异常。在编译时期,就会检查,如果没有处理异常,则编译失败。(如日期格式化异常)
  • 运行时期异常 :runtime异常。在运行时期,检查异常.在编译时期,运行异常不会编译器检测(不报错)。(如数学异常)

二、异常处理

Java为了保证程序中出现了异常,因为需要对异常进行进行处理,所以Java也提供的了对应的处理机制。

Java异常处理的五个关键字:try、catch、finally、throw、throws

2.1、抛出异常throw

在编写程序时,我们必须要考虑程序出现问题的情况。比如,在定义方法时,方法需要接受参数。那么,当调用方法使用接受到的参数时,首先需要先对参数数据进行合法的判断,数据若不合法,就应该告诉调用者,传递合法的数据进来。这时需要使用抛出异常的方式来告诉调用者。

在Java中,提供了一个throw关键字,它用来抛出一个指定的异常对象。那么,抛出一个异常具体如何操作呢?

  1. 创建一个异常对象。封装一些提示信息(信息可以自己编写)。

  2. 需要将这个异常对象告知给调用者。怎么告知呢?怎么将这个异常对象传递到调用者处呢?通过关键字throw就可以完成。throw 异常对象。

    throw用在方法内,用来抛出一个异常对象,将这个异常对象传递到调用者处,并结束当前方法的执行。

例如:

第一种:

throw new NullPointerException("要访问的arr数组不存在");

throw new ArrayIndexOutOfBoundsException("该索引在数组中不存在,已超出范围");

第二种:

try{
  int i = 1 / 0;
  processService();
}catch(Exception e){
  logger.error("当前程序处理过程中出现异常");
  // 将异常丢给调用者来进行处理
  throw e;
}

注意:如果产生了问题,我们就会throw将问题描述类即异常进行抛出,也就是将问题返回给该方法的调用者。

那么对于调用者来说,该怎么处理呢?一种是进行捕获处理,另一种就是继续讲问题声明出去,使用throws声明处理。

2.2、声明异常throws

声明异常:将问题标识出来,报告给调用者。如果方法内通过throw抛出了编译时异常,而没有捕获处理,那么必须通过throws进行声明,让调用者去处理。如果调用者也不去进行处理,那么异常最终将会抛给main方法,main线程中一旦出现了异常,将会抛给JVM,最终导致当前的JVM工作系统奔溃。

关键字throws运用于方法声明之上,用于表示当前方法不处理异常,而是提醒该方法的调用者来处理异常(抛出异常).

声明异常格式:

修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2…{   }	

声明异常的代码演示:

public class ThrowsDemo {
    // main方法也不来进行处理,如果出现问题将会导致JVM停止运行
    public static void main(String[] args) throws FileNotFoundException {
        read("a.txt");
    }

    // 如果定义功能时有问题发生需要报告给调用者。可以通过在方法上使用throws关键字进行声明
    public static void read(String path) throws FileNotFoundException {
        if (!path.equals("a.txt")) {//如果不是 a.txt这个文件 
            // 我假设  如果不是 a.txt 认为 该文件不存在 是一个错误 也就是异常  throw
            throw new FileNotFoundException("文件不存在");
        }
    }
}

throws用于进行异常类的声明,若该方法可能有多种异常情况产生,那么在throws后面可以写多个异常类,用逗号隔开。

public class ThrowsDemo2 {
    public static void main(String[] args) throws IOException {
        read("a.txt");
    }

    public static void read(String path)throws FileNotFoundException, IOException {
        if (!path.equals("a.txt")) {//如果不是 a.txt这个文件 
            // 我假设  如果不是 a.txt 认为 该文件不存在 是一个错误 也就是异常  throw
            throw new FileNotFoundException("文件不存在");
        }
        if (!path.equals("b.txt")) {
            throw new IOException();
        }
    }
}

2.4、捕获异常try…catch

如果异常出现的话,会立刻终止程序。所以我们得处理异常:

  1. 该方法不处理,而是声明抛出,由该方法的调用者来处理(throws)。
  2. 在方法中使用try-catch的语句块来处理异常。

try-catch**的方式就是捕获异常,Java中对异常有针对性的语句进行捕获,可以对出现的异常进行指定方式的处理。

捕获异常语法如下:

try{
     编写可能会出现异常的代码
}catch(异常类型  e){
     // 常用用法
     //  记录日志/打印异常信息
     //  继续抛出异常
}

try:该代码块中编写可能产生异常的代码。

catch:用来进行某种异常的捕获,实现对捕获到的异常进行处理。

注意:try和catch都不能单独使用,必须连用。

演示如下:

public class TryCatchDemo {
  public static void main(String[] args) {
    try {// 当产生异常时,必须有处理方式。要么捕获,要么声明。
      read("b.txt");
    } catch (FileNotFoundException e) {// 括号中需要定义什么呢?
      //try中抛出的是什么异常,在括号中就定义什么异常类型
      System.out.println(e);
    }
    System.out.println("over");
  }
    /*
     *
     * 我们 当前的这个方法中 有异常  有编译期异常
     */
  public static void read(String path) throws FileNotFoundException {
    if (!path.equals("a.txt")) {//如果不是 a.txt这个文件 
      // 我假设  如果不是 a.txt 认为 该文件不存在 是一个错误 也就是异常  throw
      throw new FileNotFoundException("文件不存在");
    }
  }
}

如何获取异常信息:

Throwable类中定义了一些查看方法:

  • public String getMessage():获取异常的描述信息,原因(提示给用户的时候,就提示错误原因。

  • public String toString():获取异常的类型和异常描述信息(不用)。

  • public void printStackTrace():打印异常的跟踪栈信息并输出到控制台。

    *包含了异常的类型,异常的原因,还包括异常出现的位置,在开发和调试阶段,都得使用printStackTrace。*
    

在开发中呢也可以在catch将编译期异常转换成运行期异常处理。

2.5、finally代码块

finally:有一些特定的代码无论异常是否发生,都需要执行。另外,因为异常会引发程序跳转,导致有些语句执行不到。而finally就是解决这个问题的,在finally代码块中存放的代码都是一定会被执行的。

什么时候的代码必须最终执行?

当我们在try语句块中打开了一些物理资源(磁盘文件/网络连接/数据库连接等),我们都得在使用完之后,最终关闭打开的资源。

finally的语法:

try...catch....finally:自身需要处理异常,最终还得关闭资源。

注意:finally不能单独使用。

比如在我们之后学习的IO流中,当打开了一个关联文件的资源,最后程序不管结果如何,都需要把这个资源关闭掉。

finally代码参考如下:

public class TryCatchDemo4 {
  
    public static void main(String[] args) {
        try {
            read("a.txt");
        } catch (FileNotFoundException e) {
            //抓取到的是编译期异常  抛出去的是运行期 
            throw new RuntimeException(e);
        } finally {
            System.out.println("不管程序怎样,这里都将会被执行。");
        }
        System.out.println("over");
    }
  
    /*
     *
     * 我们 当前的这个方法中 有异常  有编译期异常
     */
    public static void read(String path) throws FileNotFoundException {
        if (!path.equals("a.txt")) {//如果不是 a.txt这个文件 
            // 我假设  如果不是 a.txt 认为 该文件不存在 是一个错误 也就是异常  throw
            throw new FileNotFoundException("文件不存在");
        }
    }
}

当只有在try或者catch中调用退出JVM的相关方法,此时finally才不会执行,否则finally永远会执行。

2.6、异常注意事项

  • 1、运行时异常被抛出可以不处理。即不捕获也不声明抛出。

  • 2、如果父类的方法抛出了多个异常,子类覆盖(重写)父类方法时,只能抛出相同的异常或者是他的子集。

  • 3、父类方法没有抛出异常,子类覆盖父类该方法时也不可抛出异常。此时子类产生该异常,只能捕获处理,不能声明抛出

  • 4、当多异常分别处理时,捕获处理,前边的类不能是后边类的父类

  • 5、在try/catch后可以追加finally代码块,其中的代码一定会被执行,通常用于资源回收。

  • 6、多个异常使用捕获又该如何处理呢?

    1. 多个异常分别处理。
    2. 多个异常一次捕获,多次处理。
    3. 多个异常一次捕获一次处理。

    一般我们是使用一次捕获多次处理方式,格式如下:

    try{
         编写可能会出现异常的代码
    }catch(异常类型A  e){  当try中出现A类型异常,就用该catch来捕获.
         处理异常的代码
         //记录日志/打印异常信息/继续抛出异常
    }catch(异常类型B  e){  当try中出现B类型异常,就用该catch来捕获.
         处理异常的代码
         //记录日志/打印异常信息/继续抛出异常
    }
    

    注意:这种异常处理方式,要求多个catch中的异常不能相同,并且若catch中的多个异常之间有子父类异常的关系,那么子类异常要求在上面的catch处理,父类异常在下面的catch处理。

三、自定义异常

3.1、自定义异常概述

为什么需要自定义异常类:

我们说了Java中不同的异常类,分别表示着某一种具体的异常情况,那么在开发中总是有些异常情况是SUN公司没有定义好的,此时我们根据自己业务的异常情况来定义异常类。,例如年龄负数问题,考试成绩负数问题。

在上述代码中,发现这些异常都是JDK内部定义好的,但是实际开发中也会出现很多异常,这些异常很可能在JDK中没有定义过,例如年龄负数问题,考试成绩负数问题.那么能不能自己定义异常呢?

异常类如何定义:

  1. 自定义一个编译期异常: 自定义类 并继承于Java.lang.Exception
  2. 自定义一个运行时期的异常类:自定义类 并继承于Java.lang.RuntimeException

3.2、自定义异常练习

要求:我们模拟注册操作,如果用户名已存在,则抛出异常并提示:亲,该用户名已经被注册。

首先定义一个注册异常类RegisterException:

// 业务逻辑异常
public class RegisterException extends Exception {
    // 程序中必须有的两个异常构造函数
    /**
     * 空参构造 【但是通常都会设置有参,而不是无参】
     */
    public RegisterException() {
    }

    /**
     *
     * @param message 表示异常提示
     */
    public RegisterException(String message) {
        super(message);
    }
}

模拟登陆操作,使用数组模拟数据库中存储的数据,并提供当前注册账号是否存在方法用于判断。

public class Demo {
    // 模拟数据库中已存在账号
    private static String[] names = {"bill","hill","jill"};
   
    public static void main(String[] args) {     
        //调用方法
        try{
              // 可能出现异常的代码
            checkUsername("nill");
            System.out.println("注册成功");//如果没有异常就是注册成功
        }catch(LoginException e){
            //处理异常
            e.printStackTrace();
        }
    }

    //判断当前注册账号是否存在
    //因为是编译期异常,又想调用者去处理 所以声明该异常
    public static boolean checkUsername(String uname) throws LoginException{
        for (String name : names) {
            if(name.equals(uname)){//如果名字在这里面 就抛出登陆异常
                throw new LoginException("亲"+name+"已经被注册了!");
            }
        }
        return true;
    }
}

​ ​

四、异常规范

将工作中使用异常的总结:
1、【强制要求】【重点】不要捕获Java类库中定义的继承自RuntimeException的运行时异常类,如:IndexOutOfBoundsException 、 NullPointerException,这类异常由程序员预检查来规避,保证程序健壮性。
在我们程序编码中最常见的就是:

if(obj != null){                                                                                                                                                                               
// 操作                                                                                                                                                                                          
}                                                                                                                                                                                              

而不是:

try {                                                                                                                                                                                          
// 可能存在的问题                                                                                                                                                                                     
obj.method();                                                                                                                                                                                  
} catch(NullPointerException e){                                                                                                                                                               
// 对异常处理                                                                                                                                                                                       
}                                                                                                                                                                                              


2、【强制】异常不要用来做流程控制,条件控制,因为异常的处理效率比条件分支低。
3、【强制】对大段代码进行try-catch,这是不负责任的表现。catch时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的catch尽可能进行区分异常类型,再做对应的异常处理。

4、【强制】【重点】捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。

通常我们会在service业务层这样子来写:

try{                                                                                                                                                                                                                                              
}catch(Exception e){                                                                                                                                                                           
log.error();                                                                                                                                                                                   
throw new RunTimeException();                                                                                                                                                                  
}                                                                                                                                                                                              

然后在controller层中这样子来写:

try{                                                                                                                                                                                           
xxxService.yyyy(zz);                                                                                                                                                                           
}catch(Exception e){                                                                                                                                                                           
// 在这里对抛出来的异常进行处理                                                                                                                                                                              
// 这里有不同的几种处理情况,下面分别介绍。                                                                                                                                                                        
}                                                                                                                                                                                              


5、【强制】【重点】finally块必须对资源对象、流对象进行关闭,有异常也要做try-catch

6、【强制】【重点】捕获异常与抛异常,必须是完全匹配,或者捕获异常是抛异常的父类。 说明:如果预期对方抛的是绣球,实际接到的是铅球,就会产生意外情况。

7、【强制】【重点】方法的返回值可以为null,不强制返回空集合,或者空对象等,必须添加注释充分说明什么情况下会返回null值。调用方需要进行null判断防止NPE(NullPointException)问题。 说明:本规约明确防止NPE是调用者的责任。即使被调用方法返回空集合或者空对象,对调用者来说,也并非高枕无忧,必须考虑到远程调用失败,运行时异常等场景返回null的情况。

8、【推荐】防止NPE,是程序员的基本修养,注意NPE产生的场景:

(1) 返回类型为包装数据类型,有可能是null,返回int值时注意判空。 反例:public int f(){ return Integer对象}; 如果为null,自动解箱抛NPE。

反例:public int f(){ return Integer对象}; 如果为null,自动拆箱将会抛出NPE



(2)数据库的查询结果可能为null



(3) 集合里的元素即使isNotEmpty,取出的数据元素也可能为null。 如map,但是更多的是针对对象属性操作的。



(4) 远程调用返回对象,一律要求进行NPE判断。



( 5) 对于Session中获取的数据,建议NPE检查,避免空指针。



( 6) 级联调用obj.getA().getB().getC();一连串调用,易产生NPE。



(7)、【推荐】【重点】在代码中使用“抛异常”还是“返回错误码”,对于公司外的http/api开放接口必须使用“错误码”;而应用内部推荐异常抛出;跨应用间RPC调用优先考虑使用Result方式,封装isSuccess、“错误码”、“错误简短信息”。

说明:关于RPC方法返回方式使用Result方式的理由:

(1)使用抛异常返回方式,调用方如果没有捕获到就会产生运行时错误。                                                                                                                                                              
                                                                                                                                                                                               
(2)如果不加栈信息,只是new自定义异常,加入自己的理解的error message,对于调用端解决问题的帮助不会太多。如果加了栈信息,在频繁调用出错的情况下,数据序列化和传输的性能损耗也是问题。                                                                                            

所以:注意写的接口是对外开放的,还是系统内开放的。对外开放,利用错误码显示;系统内部中调用的返回自定义的Result来进行返回;
(8)、【推荐】定义时区分unchecked / checked 异常,避免直接使用RuntimeException抛出,更不允许抛出Exception或者Throwable,应使用有业务含义的自定义异常。推荐业界已定义过的自定义异常,如:DAOException / ServiceException等。

(9)、【参考】避免出现重复的代码(Don’t Repeat Yourself),即DRY原则。 说明:随意复制和粘贴代码,必然会导致代码的重复,在以后需要修改时,需要修改所有的副本,容易遗漏。必要时抽取共性方法,或者抽象公共类,甚至是共用模块。 正例:一个类中有多个public方法,都需要进行数行相同的参数校验操作,这个时候请抽取:

​ private boolean checkParam(DTO dto){...}

五、简单的面试题

public class TestFinally {
    public static void main(String[] args) {
        Boolean b = get();
        System.out.println("最终成功的是:"+b); // false
        System.out.println("=====================");
        int i = testOne();
        System.out.println("对应的i的值是:"+i);
        System.out.println("====================="); // 20
        int i1 = testTwo();
        System.out.println("对应的i1的值是:"+i1); // 10
    } 

    public static int testOne(){
        int i = 10;
        try {
            return i;
        }finally {
           return i = 200;
        }
    }

    public static int testTwo(){
        int i = 10;
        try {
            return i;
        }finally {
             i = 200;
        }
    }

    public static boolean get(){
        try {
            return Boolean.TRUE;
        }finally {
            return Boolean.FALSE;
        }
    }
}

可以执行一下,看一下对应的答案。

首先必须要来确定的是:finally中的代码块是一定为执行的。

1、finnally中如果有return,那么返回的时候肯定返回的是return的,而不是try中的;

2、如果没有return,那么返回的将会是try中的值;

3、如果说程序中出现了对应的异常信息,try和catch代码块中出现了异常,那么首先也是会先执行finally中的代码块,然后再抛出异常;

六、阿里巴巴异常规范

1、【强制】Java 类库中定义的可以通过预检查方式规避的 RuntimeException 异常不应该通过catch 的方式来处理,比如:NullPointerException,IndexOutOfBoundsException 等等。
说明:无法通过预检查的异常除外,比如,在解析字符串形式的数字时,可能存在数字格式错误,不得不通过 catch NumberFormatException 来实现。
正例:if (obj != null) {...}
反例:try { obj.method(); } catch (NullPointerException e) {…}

2、【强制】异常不要用来做流程控制,条件控制。
说明:异常设计的初衷是解决程序运行中的各种意外情况,且异常的处理效率比条件判断方式要低很多。

如下面所示:

try{
  ...
}catch(NullPointerException e){
  fun(); 
}

也就是说,不要在catch中做业务逻辑运算,因为异常是用来解决程序中不可控的意外情况,而不是给你做条件分支的。

同时,异常的处理效率比条件判断方式要慢很多。实际这么使用的的情况还是比较少。

3、【强制】catch 时请分清稳定代码非稳定代码稳定代码指的是无论如何不会出错的代码
对于非稳定代码的 catch 尽可能进行区分异常类型,再做对应的异常处理。
说明:对大段代码进行 try-catch,使程序无法根据不同的异常做出正确的应激反应,也不利于定位问题,这是一种不负责任的表现。
正例:用户注册的场景中,如果用户输入非法字符,或用户名称已存在,或用户输入密码过于简单,在程序上作出分门别类的判断,并提示给用户

4、【强制】捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。【可以理解web中必须返回的是用户能够看得懂的】

5、【强制】事务场景中,抛出异常被 catch 后,如果需要回滚,一定要注意手动回滚事务

6、【强制】finally 块必须对资源对象、流对象进行关闭,有异常也要做 try-catch。
说明:如果 JDK7 及以上,可以使用 try-with-resources 方式。

7、【强制】不要在 finally 块中使用 return。
说明:try 块中的 return 语句执行成功后,并不马上返回,而是继续执行 finally 块中的语句,如果此处存在 return 语句,则在此直接返回,无情丢弃掉 try 块中的返回点。

反例:

private int x = 0;
public int checkReturn() {
  try {
    // x 等于 1,此处不返回
    return ++x;
  } finally {
    // 返回的结果是 2
    return ++x;
  }
}

8、【强制】捕获异常与抛异常,必须是完全匹配,或者捕获异常是抛异常的父类。
说明:如果预期对方抛的是绣球,实际接到的是铅球,就会产生意外情况。

9、【强制】在调用 RPC、二方包、或动态生成类的相关方法时,捕捉异常必须使用 Throwable类来进行拦截。
说明:通过反射机制来调用方法,如果找不到方法,抛出 NoSuchMethodException。什么情况会抛出NoSuchMethodError 呢?二方包在类冲突时,仲裁机制可能导致引入非预期的版本使类的方法签名不匹配,或者在字节码修改框架(比如:ASM)动态创建或修改类时,修改了相应的方法签名。这些情况,即使代码编译期是正确的,但在代码运行期时,会抛出 NoSuchMethodError。

10、【推荐】方法的返回值可以为 null,不强制返回空集合,或者空对象等,必须添加注释充分说明什么情况下会返回 null 值。
说明:本手册明确防止 NPE 是调用者的责任。即使被调用方法返回空集合或者空对象,对调用者来说,也并非高枕无忧,必须考虑到远程调用失败、序列化失败、运行时异常等场景返回 null 的情况。

11、【推荐】防止 NPE,是程序员的基本修养,注意 NPE 产生的场景:
1) 返回类型为基本数据类型,return 包装数据类型的对象时,自动拆箱有可能产生 NPE。
反例:public int f() { return Integer 对象}, 如果为 null,自动解箱抛 NPE。
2) 数据库的查询结果可能为 null。
3) 集合里的元素即使 isNotEmpty,取出的数据元素也可能为 null。
4) 远程调用返回对象时,一律要求进行空指针判断,防止 NPE。
5) 对于 Session 中获取的数据,建议进行 NPE 检查,避免空指针。
6) 级联调用 obj.getA().getB().getC();一连串调用,易产生 NPE。
正例:使用 JDK8 的 Optional 类来防止 NPE 问题。

12、.【推荐】定义时区分 unchecked / checked 异常,避免直接抛出 new RuntimeException(),
更不允许抛出 Exception 或者 Throwable,应使用有业务含义的自定义异常。推荐业界已定义过的自定义异常,如:DAOException / ServiceException 等。

13、【参考】对于公司外的 http/api 开放接口必须使用“错误码”;而应用内部推荐异常抛出;
跨应用间 RPC 调用优先考虑使用 Result 方式,封装 isSuccess()方法、“错误码”、“错误简短信息”;而应用内部推荐异常抛出。
说明:关于 RPC 方法返回方式使用 Result 方式的理由:
1)使用抛异常返回方式,调用方如果没有捕获到就会产生运行时错误。
2)如果不加栈信息,只是 new 自定义异常,加入自己的理解的 error message,对于调用端解决问题的帮助不会太多。如果加了栈信息,在频繁调用出错的情况下,数据序列化和传输的性能损耗也是问题。

14、【参考】避免出现重复的代码(Don't Repeat Yourself),即 DRY 原则。
说明:随意复制和粘贴代码,必然会导致代码的重复,在以后需要修改时,需要修改所有的副本,容易遗漏。
必要时抽取共性方法,或者抽象公共类,甚至是组件化。
正例:一个类中有多个 public 方法,都需要进行数行相同的参数校验操作,这个时候请抽取:
private boolean checkParam(DTO dto) {...}

七、总结

1、打印日志的时候不要打印大对象,尤其是使用JSON频繁打印占据日志文件,而日志文件就是为了方便我们来定位和查看的,所以不要把日志信息搞的一团糟;

2、打印关键参数,不需要打印完整的数据。也就是说重要的数据打印出来即可;

3、使用logger.info({},{},e1,e2); 而不是使用+在后面来进行拼接;

posted @ 2021-08-04 11:33  写的代码很烂  阅读(228)  评论(0编辑  收藏  举报