T31-Day5异常

一、java的异常

1.C语言的“异常”烦恼

/**
 * 获取集合中指定索引的int值
 *
 */
int getElementFromList(List *l, int *target, int index)
{
  if (NULL ==1)
  {
    return -3; /*集合不能为空*/
  }
  if(0 == 1->len)
  {
    return 1; /*集合中没有元素*/
  }
  if (index < 0 || index > l->len-1)
  {
    return -1; /*参数index越界*/
  }

  *target = l->data[index];
  return 0;
}
  1. 代码可读性差
  2. 返回值与异常值相近时,容易混淆
  3. 需要调用方来分析异常,增加多余的工作量

2.java语言异常处理

2.1 java异常流程

在这里插入图片描述
其中红线表示未处理异常

2.2 Java异常体系
通过一个机票例子说明异常体系:

Error:比如战争地震等我们不可能天天去考虑这方面事情。

Exception:

  1. 受检类异常:不在个人可控范围之内
    1. 引起注意类型
    2. 坦然处置型:堵车,我们可以改变路线或者早出发
  2. 非受检异常(Runtime Exception unchecked):
    1. 可预测异常:没带护照,只是提前检查即可
      尽量提前检查处理:
    2. 需捕获异常:去机场路上车抛锚,虽然难以预料,但必须得处理,可以换别的交通工具
    3. 可透出异常:售票机器故障,交由航空公司处理,我们不用关心
      例如spring框架封装好的异常

注意:针对 Java编译期:checked异常在编译期会报错,非受检异常(Runtime Exception unchecked)不会报错。

问题:
  1. 一段try代码可能抛出多种异常,怎么捕获?
    1. 流程一致时 1.用父类 2.&& 、|| 方式
  2. 为什么调用不是本系统的异常时只能用 error和exception 的父类throwable
    1. 因为不知道第三方系统会抛出什么类型异常(error or exception)

3.异常处理设计与实践

3.1 异常抛出捕获原则
  1. 非必要不使用异常
    异常主要针对非稳定性代码,避免一个try捕获整个方法代码,类似数组越界空字符串等可以提前处理的就提前处理。
  2. 使用描述性消息抛出异常
    除了堆栈信息外 再附加当前发生异常的上下文信息(参数,环境等)
  3. 力所能及的异常一定要处理
    当前方法/层的代码是对当前异常最清楚的,如果不处理抛出将增加上层处理的成本。
  4. 异常忽略要有理有据
3.2 【try…catch…finally】流程解析

drawio
注:try和catch的优先级是一样的,都会在finally语句之前,如果有catch语句块,return的逻辑与流程图一致。

对应流程图看一段代码,下面方法1和方法2代码返回值分别是1和2,说明finally语句块修改不了try/catch中用来存放return返回值的局部变量。

private int finallyReturn() {
	int i = 0;
	try {
                i = 1;
		return i;
	} finally {
		i = 10;
	}
}

private int finallyReturn2() {
	int i = 1;
	try {
		return i/0;
	}catch (ArithmeticException e){
		// e.printStackTrace();
               i = 2
		return i;
	}finally {
		i = 10;
	}
}

最后修改以下代码用来解释规约“不要在finally块中使用return”。运行代码会发现2个方法return结果都是10,finally中return会忽略try/catch中的返回值。

private int finallyReturn() {
	int i = 0;
	try {
                i = 1;
		return i;
	} finally {
		i = 10;
return i;
	}
}

private int finallyReturn2() {
	int i = 1;
	try {
		return i/0;
	}catch (ArithmeticException e){
		// e.printStackTrace();
               i = 2
		return i;
	}finally {
		i = 10;
                return i;
	}
}

3.3 关于【try with resource】

try with resource流程

在这里插入图片描述

try with resource需要注意的问题,如下代码 。

try (FileInputStream fin = new FileInputStream(new File("download urls.txt"));
                // 这里的FileOutputStream并不会正常close,需要肚里起一行
		// GZIPOutputStream out = new GZIPOutputStream(new FileOutputStream(new File("out.txt")))
        FileOutputStream fout = new FileOutputStream(new File("out.txt"));
             GZIPOutputStream out = new GZIPOutputStream(fout)
) {
		byte[] buffer = new byte[4 * 1024];
		int read;
		while ((read = fin.read(buffer)) != -1) {
			out.write(buffer, 0, read);
		}
	} catch (Exception e) {
		e.printStackTrace();
	}

3.4 NPE处理

  1. 级联调用时可以使用Optional来处理
    JDK8的Optional类可以优雅的防止连续属性调用出现的NPE问题 ,(这块有时间要深入研究内部原理)
  2. 特殊异常场景及其处理对策
    foreach遍历集合的异常
    1. 不要再foreach循环里进行元素的remove/add操作
    2. foreach循环会自动跳过遍历空集合 ,如果对于有null的集合,碰到null时需要注意NPE

二、日志

日志命名:

  1. 当天日志命名:以“应用名.log”来保存,
  2. 过往日志命名:以 {logname}.log.{保存日期}命名 ,日期格式标准为yyyy-MM-dd
  3. 日志保存实践:
    1. 至少保存15天, 便于排查某些以周为频次发生的异常
    2. 敏感操作信息联机存储6个月(网络安全法规)

日志规约

  1. 系统应依赖使用日志框架(SLF4J,JCL)的API而不是具体日志库中的API
  2. 在日志输出时,字符串变量之间的拼接使用占位符的方式
  3. 日志打印时禁止直接用JSON工具将对象转换成String
  4. 尽量用英文 来描述日志信息
  • logback框架使用之核心配置对象及属性分析

日志输出规则

  1. 日志级别开关判断
    对于trace/debug/info级别的日志输出,必须进行日志级别的开关判断
  2. 异常日志信息要完整
    异常信息应该包含 案发现场信息和堆栈异常信息
  3. 避免重复打印日志
    应避免重复日志,重复日志会浪费磁盘空间,注意在配置中设置additivity=false
  4. 扩展日志和错误日志存储规范
    1. 扩展日志应该单独存储,如应用中的(打点、临时监控、访问日志等)
    2. 业务日志应该与错误日志分开存储

错误码规约

错误码的功能:简单易容的让人知晓错误来源,快速判断是谁的问题,使团队快速对错误原因达成一致。增进任何系统

错误码规则

  1. 定义时要有字母也要有数字
  2. 要分级分类管理
  3. 不能这直接输出给用户座位提示信息使用
  4. 不要与业务架构或者组织架构挂钩
  5. 使用者避免随意定义新的错误码
  6. 便于不同语言的开发者之间协作

三、综合实践

1.在项目Controller层统一捕获异常

注意在分布式部署成多台机器时,异常日志需要在每层单独记录。

2.全局异常处理组件的定义和使用

可以使用@RestControllerAdvice 进行统一拦截,其中区分业务异常(车票不足、用户已注册等) 全局异常(RPC请求超时、RPC服务异常等)

3.API层异常涉及实践
  • 严格约束条件判断:API层要严格校验保证进入系统的数据是合法合规的
    • 基本判断约束:null值等基本判断
    • 实体属性约束 :满足JSR303基础判断
  • 客户端返回要友好
    • API层异常要给客户端返回状态码及其对应的错误消息
  • 下层异常转移:service、manager层异常转移成API层异常
  • 错误码文档要规范
    • 系统状态码对应的异常或错误信息以及可能发生异常的原因,要整理成便于用户查阅的文档,同步给接口调用方
4.Service层异常涉及实践
  • 严格约束条件判断避免脏数据

    • 基本判断约束:null值等基本判断
    • 实体属性约束 :满足JSR303基础判断
    • 业务条件越苏:需求提出的不同的业务约束
  • 抛出指定类型的异常

    • service层抛出带状态码或指定类型的异常
  • 转译DAO异常

    • 将DAO层的异常转译为Service层或者更高层能够理解的异常
5.DAO数据处理层异常日志实践
  • 通用DaoException

    使用继承自Runtime Exception的通用DaoException封装Dao层异常并向上抛出

  • 框架层面有选择性的记录数据操作

    在DAO层(框架层面)有选择的记录数据操作的有效信息,比如:每次操作的原始SQL语句及其执行时间

6.链路追踪

分布式链路追踪:将一次分布式请求还原成调用链路,将一次分布式请求的调用情况集中展示(服务器上节点耗时,每个服务节点请求状态等)

链路跟踪主要功能:快速定位故障、性能可视化、链路分析

7.用有限的理业类处理业务中复杂多变的无限可能多变的无限可能
  • 通用ServiceException

    定义继承自RuntimeEXception的通用ServiceException业务异常

  • 结合ErrorCode

    结合与业务关联的ErrorCode实现复杂多变的业务异常需求

8.降低系统的维护难度与国度涉及、冗余的手段

系统维护由代码的维护、系统运营的维护组成。

  • 通用模块单独拆分

  • 合理的领域划分

  • 合适的工程结构

四、总结

异常、日志的完善是区分demo和企业级项目明显的标志,通过健全的异常机制保护系统的健壮性,通过完备日志记录和存储保证系统问题排查的精准高效。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值