程序异常和日志的设计方法

Posted 生而为人我很遗憾

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了程序异常和日志的设计方法相关的知识,希望对你有一定的参考价值。

任何一套程序都少不了异常和日志,从纯技术编码的角度来讲处理起来并不复杂。但这两块编码的目的更多是围绕非正常情况来设计的,所以从业务和运维的角度来讲想处理好日志和异常其实并不简单。所有的团队设计异常和日志的初衷都是好的,但有些团队在落地执行时并不会达到所预想的目的,甚至是为了制定而制定,产生大量无用编码的同时无形中增加了开发同学的工作量,同时也会影响程序的性能和代码的可读性。

  • 异常的目的:记录程序中异常数据,重在结果;
  • 日志的目的:记录程序中流程数据,重在过程;

以上是笔者个人的归纳,下文中也会围绕这两个核心点从理论到场景适配展开讲述。因每套系统的业务情况和复杂度都不太一样,所以建议按需设计,同时文中所描述的是笔者个人的观点,能力有限,不妥的地方欢迎指导留言以免带偏。

首先还是来一些基础,复盘一下java的API。

一、异常

如果某个方法不能按照正常的途径完成任务,就可以通过另一种路径退出方法。在这种情况下会抛出一个封装了错误信息的对象。此时,这个方法会立刻退出同时不返回任何值。另外,调用这个方法的其他代码也无法继续执行,异常处理机制会将代码执行行交给异常处理器。java程序中一般会有编译错误、逻辑错误、运行时错误(异常处理)这几种非正常的情况。

程序异常和日志的设计方法_日志处理

1.1、异常的分类

1.1.1、Error

指系统错误,java 运行时系统的内部错误和资源耗尽错误,应用程序不会抛出该类对象。如果出现了这样的错误,除了告知用户,剩下的就是尽力使程序安全的终止。

1.1.2、Exception

指程序异常,异常有两个分支,一个是运行时异常 RuntimeException,一个是CheckedException。

CheckedException

外部错误,这种异常发生在代码编译阶段,Java 编译器会强制程序去捕获此类异常,然后要求把这段可能出现异常的程序进行 try catch包装(如果在代码中没有找到相关的catch语句会抛出ThreadGroup.uncaughtException()方法,在字节码编程时会出现这种情况)。

需查异常是在方法定义时用throws关键字声明的再用catch捕获后可以进行恢复,在catch中最简单的处理方式是声明e.printStackTrace()语句。使用时两个三点:

  1. throws时不要直接抛出Exception,这样系统会从Exception子类一个一个搜索match很费时间;
  2. catch时要分层catch即要从子类到父类一层层catch;
private static void throwOldException() throws IOException 
throw new IOException("a forced exception");//需查异常
RuntimeException

如字面意思,指运行时错误,这种异常都发生在程序运行阶段,程序员的错误造成。API都需要继承RuntimeException,由程序员设计的异常规则。不需查异常是被设计来强化方法的约定(方法的约定指方法使用者和调用者之间的一种约定,包括调用方法和返回结果的约定等)的。

因为执行了异常检查的语块比没执行异常的语块运行速度要慢很多,所以不建议把整个方法的实现通通包装起来,在使用时几点建议可以参考:

  1. 用时间频度来确定,也就是说不会发生异常的地方不要加入catch语句;
  2. 包装可选方法,​不允许调用的方法不实现具体的功能,直接抛出UnsupportedOperationException来强制不能调用此方法。
  3. 对经常需要调用的语句不要进行异常处理,在逻辑上也显然不能作为异常来处理。可以有两种方法来代替异常处理功能:1、是使用特殊的返回值;二、是前导检查法,例如string.length在使用前先判断是否越界了。
private static void throwNestedException() throws NestedException 
try
String s = "abc";
if(s.equals("abc"))
throwOldException();//不需查异常

catch (IOException e)
throw new NestedException(e);

1.1.3、throws和throws的区别

位置不同

throws 用在函数上,后面跟的是异常类(可以跟多个), throw 用在函数内,后面跟的是异常对象,一次只能一个对象。

功能不同

  1. throws 用来声明异常,让调用者只知道该功能可能出现的问题,可以给出预先的处理方式;throw 抛出具体的问题对象,执行到 throw,功能就已经结束了,跳转到调用者,并将具体的问题对象抛给调用者。也就是说 throw 语句独立存在时,下面不要定义其他语句,因为执行不到;
  2. throws 表示出现异常的一种可能性,并不一定会发生这些异常;throw 则是抛出了异常,执行 throw 则一定抛出了某种异常对象;
  3. 两者都是消极处理异常的方式,只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调用处理;

1.2、自定义异常的方法

1.2.1、自定义参数

class MyException2 extends Exception 
private int x;
public MyException2()
public MyException2(String msg) super(msg);
public MyException2(String msg, int x)
super(msg);
this.x = x;

public int val() return x;
public String getMessage()
return "Detail Message: "+ x + " "+ super.getMessage();

1.2.2、重新捕获异常

public class Rethrowing 
public static void f() throws Exception
System.out.println("originating the exception in f()");
throw new Exception("thrown from f()");

public static void g() throws Exception
try
f();
catch(Exception e)
System.out.println("Inside g(),e.printStackTrace()");
e.printStackTrace(System.out);
throw e;


public static void main(String[] args)
try
g();
catch(Exception e)
System.out.println("main: printStackTrace()");
e.printStackTrace(System.out);


1.2.3、异常链

想要在捕获一个异常后抛出另一个异常,并且希望把原始异常的信息保存下来,这称为异常链。如果把自定义的异常链起来,应该使用initCause()方法,而不是原始的cause参数。

class DynamicFieldsException extends Exception 

public class DynamicFields

public Object setField(String id, Object value) throws DynamicFieldsException
DynamicFieldsException dfe = new DynamicFieldsException();
dfe.initCause(new NullPointerException());//用此方法封装了原始的异常信息
throw dfe;

public static void main(String[] args)
DynamicFields df = new DynamicFields();
try
Object field = df.setField("d", null); // Exception
catch(DynamicFieldsException e)
e.printStackTrace(System.out);


1.2.4、异常掩盖

class VeryImportantException extends Exception 
public String toString()
return "A very important exception!";



class HoHumException extends Exception
public String toString()
return "A trivial exception";



public class LostMessage
void f() throws VeryImportantException
throw new VeryImportantException();

void dispose() throws HoHumException
throw new HoHumException();

public static void main(String[] args)
try
LostMessage lm = new LostMessage();
try
lm.f();
finally
lm.dispose();

catch(Exception e)
System.out.println(e);//此处的异常被finally给替换了


1.2.5、把CheckedException转换为RuntimeException

class WrapCheckedException 
void throwRuntimeException(int type)
try
switch(type)
case 0: throw new FileNotFoundException();
case 1: throw new IOException();
case 2: throw new RuntimeException("Where am I?");
default: return;

catch(Exception e) // Adapt to unchecked:
throw new RuntimeException(e);


//这种技术不会导致异常丢失也不会破坏异常链

1.3、异常处理程序的架构设计

一些互联网系统的特点一般都是分布式的,而且每个应用的集群节点都不少。这类系统的问题基本全是通过日志(包含异常日志信息和业务日志信息)来排查的,但如果从几百上千台服务中找到所需要的日志信息也并不简单,虽然有些公司会提供线上日志的搜索功能,但这个搜索功能相对通用。如果在架构设计时采用必要的手段可以做下互补收益还是很大的,本小节描述一下笔者一直使用的异常处理的一种方法,此设计围绕异常发现和处理来设计的,大体的设计流程如下:

下述的设计是用在一个相对比较复杂

以上是关于程序异常和日志的设计方法的主要内容,如果未能解决你的问题,请参考以下文章

201771010135杨蓉庆 《面对对象程序设计(java)》第九周学习总结

201671010111 2016-2017-2《面型对象的程序设计》 异常处理

201671010115 2016-2017-2《Java程序设计》第九周Java心得

马凯军201771010116《面向对象与程序设计Java》第九周学习总结

异常与日志

201671010135 2016--2017--《java程序设计:第七章 异常,断言和日志》