学习《阿里巴巴JAVA开发手册》之(二)异常日志

学习《阿里巴巴JAVA开发手册》系列

1、异常处理

(1)异常捕获

  • 不要捕获 Java 类库中定义的继承自 RuntimeException 的运行时异常类,如: IndexOutOfBoundsException / NullPointerException,这类异常由程序员预检查来规避,保证程序健壮性;
  • 异常不要用来做流程控制、条件控制,因为异常的处理效率比条件分支低;
  • 捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容;
  • 捕获异常与抛异常,必须是完全匹配,或者捕获异常是抛异常的父类;

(2)try、catch、finally

  • 对大段代码进行 try-catch,这是不负责任的表现。catch 时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的 catch 尽可能进行区分 异常类型,再做对应的异常处理;
  • 有 try 块放到了事务代码中,catch 异常后,如果需要回滚事务,一定要注意手动回滚事务;
  • finally 块必须对资源对象、流对象进行关闭,有异常也要做 try-catch;
  • 不能在 finally 块中使用 return,finally 块中的 return 返回后方法结束执行,不会再执行 try 块中的 return 语句;
  • try、catch、finally执行顺序:
    a、try{} catch(){}finally{} return;
    程序按顺序执行;
    b、不管有没有出现异常,finally块中代码都会执行;
    c、当try和catch中有return时,finally仍然会执行;
    d、try{ return; }catch(){} finally{} return;
    程序执行try块中return之前(包括return语句中的表达式运算)代码;
    再执行finally块,最后执行try中return;
    finally块之后的语句return,因为程序在try中已经return所以不再执行;
    e、finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,不管finally中的代码怎么样,返回的值都不会改变,仍然是之前保存的值),所以函数返回值是在finally执行前确定的;
    f、finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值;
    g、try{ } catch(){return;} finally{} return;
    程序先执行try,如果遇到异常执行catch块:
    1)若有异常:则执行catch中return之前(包括return语句中的表达式运算)代码,再执行finally语句中全部代码,最后执行catch块中return,finally之后的return代码不再执行。
    2)若无异常:执行完try再finally再return;
    h、try{ return; }catch(){} finally{return;}
    程序执行try块中return之前(包括return语句中的表达式运算)代码,再执行finally块,因为finally块中有return所以提前退出;
    i、try{ return;}catch(){return;} finally{return;}
    程序执行try块中return之前(包括return语句中的表达式运算)代码;
    1)若有异常:执行catch块中return之前(包括return语句中的表达式运算)代码;则再执行finally块,因为finally块中有return所以提前退出;
    2)若无异常:则再执行finally块,因为finally块中有return所以提前退出;
    j、任何执行try 或者catch中的return语句之前,都会先执行finally语句,如果finally存在的话。如果finally中有return语句,那么程序就return了,所以finally中的return是一定会被return的;

(3)关于NPE

  • 方法的返回值可以为 null,不强制返回空集合,或者空对象等,必须添加注释充分说明什么情况下会返回 null 值。调用方需要进行 null 判断防止 NPE 问题;
  • 防止 NPE 是调用者的责任,即使被调用方法返回空集合或者空对象,对调用者来说,也并非高枕无忧,必须考虑到远程调用失败,运行时异常等场景返回 null 的情况;
  • 防止 NPE,是程序员的基本修养,注意 NPE 产生的场景:
    a、返回类型为包装数据类型,有可能是null,返回int值时注意判空;
    b、 数据库的查询结果可能为null;
    c、集合里的元素即使isNotEmpty,取出的数据元素也可能为null;
    d、远程调用返回对象,一律要求进行NPE判断;
    e、对于Session中获取的数据,建议NPE检查,避免空指针;
    f、级联调用obj.getA().getB().getC();一连串调用,易产生NPE;

(4)异常返回

  • 在代码中使用“抛异常”还是“返回错误码”:
    a、对于公司外的 http/api 开放接口必须使用“错误码”;
    b、而应用内部推荐异常抛出;
    c、跨应用间 RPC 调用优先考虑使用 Result 方式,封 装 isSuccess、“错误码”、“错误简短信息”等;
  • 关于 RPC 方法返回方式使用 Result 方式(或者类似的方式)的理由:
    a、使用抛异常返回方式,调用方如果没有捕获到就会产生运行时错误;
    b、如果不加栈信息,只是new自定义异常,加入自己的理解的error message,对于调用端解决问题的帮助不会太多。如果加了栈信息,在频繁调用出错的情况下,数据序列化和传输的性能损耗也是问题。
  • 避免直接使用 RuntimeException 抛出,更不允许抛出 Exception 或者 Throwable,应使用有业务含义的自定义异常。推荐业界已定义 过的自定义异常,如:DAOException / ServiceException等;

(5)其他

  • 避免出现重复的代码(Don’t Repeat Yourself),即DRY原则;
  • 随意复制和粘贴代码,必然会导致代码的重复,在以后需要修改时,需要修改所有的副本,容易遗漏。必要时抽取共性方法,或者抽象公共类,甚至是共用模块;

2、日志规约

  • 应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架SLF4J 中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一,使用方式如下:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(Abc.class);
  • 对 trace/debug/info 级别的日志输出,必须使用条件输出形式或者使用占位符的方式,如:
logger.debug("Processing trade with id: {} symbol : {} ", id, symbol);
  • 异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么往上抛,如:
logger.error(各类参数或者对象toString + "_" + e.getMessage(), e);
  • 可以使用 warn 日志级别来记录用户输入参数错误的情况,避免用户投诉时,无所适从;
  • 注意日志输出的级别,error 级别只记录系统逻辑出错、异常等重要的错误信息。如非必要,请不要在此场景打出 error 级别。
  • 谨慎地记录日志:
    a、日志文件推荐至少保存 15 天,因为有些异常具备以“周”为频次发生的特点;
    b、生产环境禁止输出 debug 日志;
    c、有选择地输出 info 日志;
    d、如果使 用 warn 来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘撑爆,并记得及时删除这些观察日志;
    e、大量地输出无效日志,不利于系统性能提升,也不利于快速定位错误点。记录日志时需要思考:这些日志真的有人看吗?看到这条日志能做什么?能不能给问题排查带来好处?

おすすめ

転載: blog.csdn.net/lydia_cmy/article/details/96278558