码出高效读书笔记:异常与日志

2023-10-13 23:50

本文主要是介绍码出高效读书笔记:异常与日志,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1、区分throws和throw

(1)throws

  1. 定义一个方法时,可以用throws关键字声明,使用throws关键字声明的方法表示此方法不处理异常,而交给方法调用处进行处理。
  2. throws关键字格式:public 返回值类型 方法名称(参数列表,,,)throws 异常类{}。

请看下面一段代码:

package Test;public class iTest {public static int divtest(int m, int n) throws Exception {return m / n;}
}
/*** 因为divtest()方法中可能抛出异常,所以在Test中必须要用try-catch把调用了divtest()方法的代码包起来,* 要么就向上交给主方法处理,那么能不能向上交给main()方法处理呢?*/
class Test {public static void main(String[] args){try {System.out.println(iTest.divtest(1, 0));} catch (Exception e) {e.printStackTrace();}System.out.println("能执行到这里吗");}
}

执行结果如下:

那么我们把异常交给main()方法处理呢?请看下面这段代码:

package Test;public class iTest {public static int divtest(int m, int n) throws Exception {return m / n;}
}
/*** 如果把异常交给main()方法处理,main()方法本身不能处理任何一场,它会向上交给JVM处理* 所以说如果在main()方法中使用了throws关键字的话,表示一切异常由JVM处理*/
class Test {public static void main(String[] args) throws Exception {System.out.println(iTest.divtest(1, 0));System.out.println("能执行到这里吗");}
}

执行结果如下图所示:

(2)throw

throw关键字作用是抛出一个异常,抛出的时候是抛出的是一个异常类的实例化对象。示例代码如下所示:

package Test;public class iTest {public static int divtest(int m, int n) throws Exception {try{return m / n;}catch(Exception e){//抛出的异常对象中应该包含局部变量,参数等关键信息,以便排查问题throw new Exception("除数为:"+n,e);}}
}class Test {public static void main(String[] args){try {System.out.println(iTest.divtest(1, 0));} catch (Exception e) {e.printStackTrace();}System.out.println("能执行到这里吗");}
}

执行结果如下图所示:

码出高效书中提出的建议:

  1. 无论使用哪种方式来处理异常,都严禁捕获异常后什么都不做或打印一行日志了事。
  2. 如果在方法内部处理异常,需要根据不同的业务场景进行定制处理,如重试、回滚等操作。
  3. 如果向上抛出异常,需要在异常对象中添加上下文参数、局部变量、运行环境等信息,这样有利于排查问题。

2、异常分类

JDK中定义了一套完整的异常机制,所有异常都是Throwable的子类,分为Error(致命异常)和Exception(非致命异常)。Error是一种非常特殊的异常类型,它的出现标识这系统发生了不可控的错误,例如StackOverflowError、OutOfMemoryError。针对此类错误,程序无法处理,只能人工介入。Exception又分为checked异常(受检异常)和unchecked异常(非受检异常)。

checked异常可以进一步细分为两类:

  1. 无能为力、引起注意型。针对此类异常,程序往往无法处理,如字段超长等导致的SQLException,即使做再多的重试对解决异常也没有任何帮助,一般处理此类异常做法是完整保存异常线长,供开发工程师介入解决。
  2. 利索能力、坦然处置型。如发生未授权异常(UnAuthorizedException),程序可跳转至权限申请页面。

unchecked异常是运行时异常,继承自RuntimeException,不需要程序进行显式地捕捉和处理,它可以分为3类:

  1. 可预测异常:常见的可预测异常包括NullPointException、IndexOutOfBoundException等,此类异常不应该被产生或抛出,而应该提前做好边界检查、空指针判断等处理。显式地声明或者捕获此类异常会对程序的可读性和运行效率产生很大影响。
  2. 需捕捉异常。
  3. 可透出异常。


在码出高效中,写了一个的事例来比喻各种异常类型:

我们为了保准准时乘坐某一航班,早早的赶到机场,却因为忘带身份证而导致无法登机,或者由于突降大雨而使航班延误等都会使我们的活动受阻。

  • 机场地震,属于不可抗力,对应异常分类中的Error。在制定出行计划时,根本不需要把这部分的异常考虑进去(属于天灾人祸,我们无能为力,不需要考虑)。
  • 堵车属于checked异常,应对这种异常,我们可以提前出发,或者改签机票。而飞机延误异常,虽然也需要check,但我们无能为力,只能持续关注航班动态(checked异常在写代码前我们需要考虑,但是也没有办法去处理可能出现的异常)。
  • 没有带护照,明显属于可提前预测的异常,只要出发前检查即可避免(可预测异常,在写代码前就要考虑周到,避免出现这样的异常);去机场路上车子抛锚,这个异常是突发的,虽然难以预料,但是必须处理,属于需要捕捉的异常,可以通过更换交通工具应对(需捕捉异常是突发情况,虽然没法预测,但是也必须处理);检票机器故障则属于可透出异常,交由航空公司处理,我们无需关心(可透出异常是系统或框架自己产生的异常,跟我们的代码无关,由系统或框架自行处理)。

3、try代码块

try-catch-finally是处理程序异常的三部曲。当存在try时,可以只有catch代码块,也可以只有finally代码块,就是不能单独只有try这个光杆司令。下面说一下各个代码块的作用:

  • try代码块:监视代码执行过程,一旦发现异常则直接跳转至catch,如果没有catch,则直接跳转至finally。
  • catch代码块:可选执行的代码块,如果没有任何异常发生则不会执行;如果发现异常则进行处理或向上抛出。这一切都是在catch代码块中执行。
  • finally代码块:必选执行的代码块,不管是否有异常产生,即使发生OutOfMemoryError也会执行,通常用于处理善后清理工作。如果finally代码块没有执行,那么有三种可能:(1)没有进入try代码块;(2)进入try代码块,但是代码运行中出现了死循环或死锁状态;(3)进入try代码块,但是执行了System.exit()操作。

注意!finally是在return表达式运行后执行的,此时将要return的结果已经被暂存起来,待finally代码块执行结束后再讲之前暂存的结果返回,如下代码所示:

package Test;public class iTest {public static int finallyNotWork(){int temp = 10000;try{throw new Exception();}catch(Exception e){return ++temp;}finally{temp = 99999;}}public static void main(String[] args){System.out.println(iTest.finallyNotWork());}
}运行结果10001

参照字节码:

//对变量temp进行+1操作
IINC 0 1
ILOAD 0
//return表达式的计算结果存储在slot_2上
ISTORE 2
//finally存储99999到slot_0上
LDC 99999
ISTORE 0//方法返回的时候,直接提取的是slot_2的值,即10001
ILOAD 2
IRETURN

码出高效作者建议:不要在finally代码块中使用return语句!!!这样会使得代码的返回值变得非常不可控!!!

如下段代码所示:

package Test;public class TryCatchFinally {static int x = 1;static int y = 10;static int z = 100;public static void main(String[] args){int value = finallyReturn();System.out.println("value="+value);System.out.println("x="+x);System.out.println("y="+y);System.out.println("z="+z);}//此段代码中没有出现异常,所以++x执行了,x=2,finally代码块中的++z也执行了,z=101public static int finallyReturn(){try{return ++x;}catch(Exception e){return ++y;}finally {return ++z;}}
}
//执行结果如下:
//value=101
//x=2
//y=10
//z=101

上面这是没有出现异常的情形,再看下面出现了异常的代码:

package Test;public class TryCatchFinally {static int x = 1;static int y = 10;static int z = 100;public static void main(String[] args){int value = finallyReturn();System.out.println("value="+value);System.out.println("x="+x);System.out.println("y="+y);System.out.println("z="+z);}public static int finallyReturn(){try{//此处设定一个异常int i = 1 / 0;return ++x;}catch(Exception e){return ++y;}finally {return ++z;}}
}
//执行结果如下:
//value=101
//x=1
//y=11
//z=101

4、日志

4.1、什么是日志?

日志这个词最高见于航海领域,是记录航行主要情况的载体文件,内容包括操作指令、气象、潮流、航向、航速、旅客、货物等,是处理还是纠纷或者海难的原始依据之一。

同理,记录应用系统日志主要有三个原因:

  1. 记录操作轨迹:记录操作行为及操作轨迹数据,可以数据化地分析用户偏好,有助于优化业务逻辑,为用户提供个性化的服务。例如通过access.log记录用户的操作频度和跳转链接,有助于分析用户的后续行为。
  2. 监控系统运行状况:全面有效的日志系统有助于建立完善的应用监控体系,由此工程师可以实时监控系统运行状况,及时预警,避免故障发生。
  3. 回溯系统故障:当系统发生线上问题的时候,完整的现场日志有助于工程师快速定位问题。例如当系统内存溢出时,如果日志系统记录了问题发生现场的堆信息,就可以通过这个日志分析是什么对象在大量产生并且没有释放内存,回溯系统故障,从而定位问题。

4.2、日志的级别

日志是由级别的。针对不同的场景,日志被分为五种不同的级别,按照重要程度由低到高排序:

  • DEBUG级别日志记录对调试程序有帮助的信息。
  • INFO级别日志用来记录程序运行现场,虽然此处并未发生错误,但是对排查其他错误具有指导意义。
  • WARN级别日志也可以用来记录程序运行现场,但是更偏向于表明此处有出现潜在错误的可能。
  • ERROR级别日志表明当前程序运行发生了错误,需要被关注。但是当前发生的错误,没有影响系统的继续运行。
  • FATAL级别日志表明当前程序运行出现了严重的错误事件,并且将会导致应用程序中断。

4.3、日志框架

日志框架分为散发部分,包括日志门面、日志适配器、日志库。利用门面设计模式,即Facade来进行解耦,使日志使用变得更加简单。日志结构框架如下图所示:

  1. 日志门面:门面设计模式是面向对象设计模式中的一种,日志框架采用的就是这种模式,类似JDBC的设计理念。它只提供一套接口规范,自身不负责日志功能的实现,目的是让使用者不需要关注底层具体是哪个日志库来负责日志打印及具体的使用细节等。目前使用最广泛的日志门面有两种:slf4j和commons-logging。
  2. 日志库:具体实现了日志的相关功能,主流日志库有三个,分别是log4j、log-jdk、logback。
  3. 日志适配器:日志适配器分两种应用场景,(1)日志门面适配器,因为slf4j规范是后来提出的,在此前的日志库是没有实现slf4j的接口的,例如log4j;所以在工程里要想使用slf4j+log4j的模式,就额外需要一个适配器(slf4j-log4j12)来解决接口不兼容的问题。(2)日志库适配器,在一些老的工程里,一开始为了开发简单而直接使用了日志库的API来完成日志打印,随着时间的推移想要将原来直接调用日志库的模式改为业界标准的门面模式(例如slf4j+logback组合),但老工程到吗里打印日志的地方太多,难以改动,所以需要一个适配器来完成从旧日志库的API到slf4j的路由,这样在不改动原有代码的情况下也能使用slf4j来统一管理日志,而且后续自由替换具体日志库也不成问题。

4.4、在Maven中进行日志集成

如果是新工程,推荐使用slf4j+logback模式。因为logback自身实现了slf4j的接口,无需额外引入适配器,另外logback是log4j的升级版,具备比log4j更多的有点,可通过如下代码配置:

<dependency><groupid>org.slf4j</groupid><artifactid>slf4j-api</artifactid><version>${slf4j-api.version}</version>
</dependency>
<dependency><groupid>ch.qos.logback</groupid><artifactid>logback-classic</artifactid><version>${logback-classic.verison}</version>
</dependency>
<dependency><groupid>ch.qos.logback</groupid><artifactid>logback-core</artifactid><version>${logback-core.version}</version>
</dependency>

至此工程就完成了日志框架的集成,再加上一个日志配置文件(如logback.xml、log4j.xml等),并在工程启动时加载,然后就可以进行日志打印了,实例代码如下:

private static final Logger logger = LoggerFactory.getLogger(Abc.class);

注意,logger被定义为static变量,是因为这个logger与当前类绑定,避免每次都new一个新对象,造成资源浪费,甚至引发OutOfMemeryError问题。

这篇关于码出高效读书笔记:异常与日志的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/206719

相关文章

C#使用SQLite进行大数据量高效处理的代码示例

《C#使用SQLite进行大数据量高效处理的代码示例》在软件开发中,高效处理大数据量是一个常见且具有挑战性的任务,SQLite因其零配置、嵌入式、跨平台的特性,成为许多开发者的首选数据库,本文将深入探... 目录前言准备工作数据实体核心技术批量插入:从乌龟到猎豹的蜕变分页查询:加载百万数据异步处理:拒绝界面

Spring Boot + MyBatis Plus 高效开发实战从入门到进阶优化(推荐)

《SpringBoot+MyBatisPlus高效开发实战从入门到进阶优化(推荐)》本文将详细介绍SpringBoot+MyBatisPlus的完整开发流程,并深入剖析分页查询、批量操作、动... 目录Spring Boot + MyBATis Plus 高效开发实战:从入门到进阶优化1. MyBatis

SpringBoot日志配置SLF4J和Logback的方法实现

《SpringBoot日志配置SLF4J和Logback的方法实现》日志记录是不可或缺的一部分,本文主要介绍了SpringBoot日志配置SLF4J和Logback的方法实现,文中通过示例代码介绍的非... 目录一、前言二、案例一:初识日志三、案例二:使用Lombok输出日志四、案例三:配置Logback一

golang 日志log与logrus示例详解

《golang日志log与logrus示例详解》log是Go语言标准库中一个简单的日志库,本文给大家介绍golang日志log与logrus示例详解,感兴趣的朋友一起看看吧... 目录一、Go 标准库 log 详解1. 功能特点2. 常用函数3. 示例代码4. 优势和局限二、第三方库 logrus 详解1.

Java异常架构Exception(异常)详解

《Java异常架构Exception(异常)详解》:本文主要介绍Java异常架构Exception(异常),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1. Exception 类的概述Exception的分类2. 受检异常(Checked Exception)

SpringBoot使用OkHttp完成高效网络请求详解

《SpringBoot使用OkHttp完成高效网络请求详解》OkHttp是一个高效的HTTP客户端,支持同步和异步请求,且具备自动处理cookie、缓存和连接池等高级功能,下面我们来看看SpringB... 目录一、OkHttp 简介二、在 Spring Boot 中集成 OkHttp三、封装 OkHttp

如何自定义Nginx JSON日志格式配置

《如何自定义NginxJSON日志格式配置》Nginx作为最流行的Web服务器之一,其灵活的日志配置能力允许我们根据需求定制日志格式,本文将详细介绍如何配置Nginx以JSON格式记录访问日志,这种... 目录前言为什么选择jsON格式日志?配置步骤详解1. 安装Nginx服务2. 自定义JSON日志格式各

Java报NoClassDefFoundError异常的原因及解决

《Java报NoClassDefFoundError异常的原因及解决》在Java开发过程中,java.lang.NoClassDefFoundError是一个令人头疼的运行时错误,本文将深入探讨这一问... 目录一、问题分析二、报错原因三、解决思路四、常见场景及原因五、深入解决思路六、预http://www

使用Python高效获取网络数据的操作指南

《使用Python高效获取网络数据的操作指南》网络爬虫是一种自动化程序,用于访问和提取网站上的数据,Python是进行网络爬虫开发的理想语言,拥有丰富的库和工具,使得编写和维护爬虫变得简单高效,本文将... 目录网络爬虫的基本概念常用库介绍安装库Requests和BeautifulSoup爬虫开发发送请求解

SpringBoot项目使用MDC给日志增加唯一标识的实现步骤

《SpringBoot项目使用MDC给日志增加唯一标识的实现步骤》本文介绍了如何在SpringBoot项目中使用MDC(MappedDiagnosticContext)为日志增加唯一标识,以便于日... 目录【Java】SpringBoot项目使用MDC给日志增加唯一标识,方便日志追踪1.日志效果2.实现步