一、异常
www:what why how
-
What
- 定义:《Java编程思想》:异常情形(Exception condition)是指阻止当前方法或作用域继续执行的问题。把异常情形与普通问题区分很重要,普通问题是指,在当前环境下能得到足够的信息,总能处理这个错误。异常情形,就不能继续下去,因为当前环境下无法获得必要的信息来解决问题。需要从当前环境退出,并且把问题抛给上一级环境。
例如:当进行除法运算,被除数等于0的情况,作为一种非法输入,需要抛出异常。
public class Calculator { public int devide(int num1, int num2) { if(num2 == 0) { throw new IllegalArgumentException("除数不能为零"); } return num1/num2; } }
- 异常的基本体系:
- Throwable ,作为所有异常与错误的超类,类似定义一切都可抛的东西,其中具有两个子类,Error与Exception。
- Error,用于指示合理的应用程序不应该试图捕获的严重问题。遇到Error,说明出现的错误以及不是代码层面的问题了,建议谷歌。
-
Exception,它指出了合理的应用程序想要捕获的条件。Exception又分为两类:一种是
CheckedException
,一种是UncheckedException
。这两种Exception的区别主要是CheckedException需要用try...catch...显示的捕获,而UncheckedException不需要捕获。通常UncheckedException又叫做RuntimeException
。《effective java》指出:对于可恢复的条件使用被检查的异常(CheckedException
),对于程序错误(言外之意不可恢复,大错已经酿成)使用运行时异常(RuntimeException
)。
-
Why
-
异常处理机制作用
-
告警
-
方便问题的排查
-
避免出现的异常问题影响下面的代码运行
-
针对出现的异常情况,进行捕获,以及可以做相应的恢复处理
-
自定义异常,符合面向对象思想,处理业务异常
-
-
自定义异常
public class NormandyCheckedException extends Exception { //异常传递cause public NormandyCheckedException(String message, Throwable cause) { super(message, cause); } }
- 表示与业务相关的问题
- 告知调用方需要在开发过程中考虑部分的逻辑
- 需要记录异常发生的栈信息以及message
-
-
How
-
异常的通常处理手段
-
对代码块用try..catch进行异常捕获处理;
-
在 该代码的方法体外用throws进行抛出声明,你需要谨慎处理。下游方在调用你的方法时,需要针对你抛出的异常进行处理。
此时有两种情况:
-
Exception及子类 必须手动 catch 或者 throws
-
RuntimeExcption及子类 可以选择忽略
-
-
在代码块用throw手动抛出一个异常对象,此时也有两种情况,跟2)中的类似:
- 如果抛出的异常对象是Exception,此方法的调用者必须显示地用try..catch块进行捕获或者继续向上层抛出异常。
- 如果抛出的异常对象是运行时异常,此方法的调用者可以选择地进行异常捕获处理。
- 如果最终将异常抛给main方法,则相当于交给jvm自动处理,jvm会简单地打印异常信息
-
-
-
Try catch
-
先catch子类Exception 若父类Exception在子类之前会覆盖
-
尽量进行小范围的try catch
-
尽量避免用Exception捕获异常,其会捕获受检异常与非受检异常
-
catch异常之后需要打印出堆栈信息(切记不能重复打印)
-
不能存在空catch块
-
不要捕获Error(RuntimeException)
-
尽量在高层进行异常的捕获操作
-
避免异常丢失(见代码)
-
-
throw/throws
throw:在方法内部主动抛出异常
throws:当方法中存在未catch的受检异常,需要在方法签名后面抛出
- 当捕获到异常之后,希望在下一级调用中处理,可以通过throw
- 异常传递:传递 cause 或者调用initCause方法
- catch块中throw出Exception,就不需要进行log日志记录,避免重复记录了异常记录
- 异常限制:在类继承的时候,方法覆盖时进行异常抛出声明,子类可以不抛出异常或则只能抛出父类的异常或其子类异常
- 尽量不要用异常作为控制流程的方式,带来额外的消耗
-
finally
- 跟随在
try
之后,在方法返回之前必然执行finally
- 一般在finally中进行资源的close操作
- 跟随在
-
try with resource(一定需要了解资源的
close
方法内部的实现逻辑。否则还是可能会导致资源泄露。)
imagepublic class Demo { public static void main(String[] args) { BufferedInputStream bin = null; BufferedOutputStream bout = null; try { bin = new BufferedInputStream(new FileInputStream(new File("test.txt"))); bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt"))); int b; while ((b = bin.read()) != -1) { bout.write(b); } } catch (IOException e) { e.printStackTrace(); } finally { if (bin != null) { try { bin.close(); } catch (IOException e) { e.printStackTrace(); } finally { if (bout != null) { try { bout.close(); } catch (IOException e) { e.printStackTrace(); } } } } } } }
public class TryWithResource {
public static void main(String[] args) {
try (BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new File("test.txt")));
BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt")))) {
int b;
while ((b = bin.read()) != -1) {
bout.write(b);
}
}
catch (IOException e) {
e.printStackTrace();
}
}
}
-
总 结
- 使用异常一般是内部系统调用,包括rpc调用方式。但是针对外部调用时,可以使用约定的code表示,而不应直接抛异常。
- 针对可恢复的情况使用受检异常,针对编程错误使用runtime exception
- 优先使用java提供的标准异常
- 抛出异常的方法,可以加上javaDoc 的
@throw
标记说明 - 当产生异常情况下,尽量使整体方法执行保持原子性
- 不要忽略异常:不要使用空catch块
思考
到处都是try catch 是好是坏?
二、日志
www:what why how
-
What
日志文件是用于记录系统操作事件的记录文件或文件集合,可分为事件日志和消息日志。具有处理历史数据、诊断问题的追踪以及理解系统的活动等重要作用。
-
Why
- 便于定位问题
- 观察分支是否执行
- 观察主流程是否成功运行
-
How
-
日志的级别
- Trace:特别详细的系统运行完成信息,业务代码中,建议不要使用
- Debug:针对开发测试环节中,打印需要测试的流程信息(线上一般需要关闭debug级别的日志)
- Info: 系正常运行信息
- **Warn: **不应该出现但是不影响程序、当前请求正常运行的异常情况
- **Error: **影响到程序正常运行、当前请求正常运行的异常情况
-
打日志的方式
-
必须使用参数化信息的方式
-
如有参数变量,使用[]隔离。分隔符用统一分隔符,便于统计(详情,@青华) "[]"的正则问题
logger.debug("Processing trade_with_id_:[{}] and symbol : [{}] ", id, symbol);
-
不要进行字符串拼接,那样会产生很多String对象,占用空间,影响性能。
反例:logger.debug("Processing trade with id: " + id + " symbol: " + symbol);
-
尽量打印日志的上下文(如if 分支),方便定位运行的流程 [远程调试方法]
-
遵循TraceId的原则,可以根据某个id在日志中体现出整体运行流程
-
如果日志中需要打印对象信息,尽量重新toString() 方法 或着 只打印对象的部分字段(id、no等关键信息)
log.info("user credit material uid:{}, materialInfo:{}", uid, JSON.toJSONString(materialApplyInfo));
-
遇到异常,在catch中需要记录,但避免重复记录
try{ doService(int id); }catch(Exception e) { log.error("Service Exception...,id:[{}]", id, ex); throw e; }
-
避免出现用户的隐私内容,用uid等代替用户信息
-
调用外部接口,打印出来入参与出参
-
-
Code Review
-
使用exception,保持处理流程的原子性。
-
通过log.error记录日志,并且进行数据统计。
-
在对外输出的时候,只是通过OpenResponse进行反馈,实现封装性。
-
在log 日志中,没有记录对应aid 或者能代表出问题的订单。算是无效的日志。
-
在遇到某些业务异常,可以直接return,但是通过 catch Exception 可能会 捕获到其他异常。
网友评论