Chapter 7: Software Construction for Robustness
7.2 Error and Exception Handling
一、Errors
错误(errors)的分类有多种,常见的有代码错误和设备的物理限制所引发的错误,比如硬盘满了或者内存不够等。
解决错误的本质是将控制权从发生错误的位置转移到可以处理错误的程序上。
常见的程序中解决错误的方法有:
1.返回一个中性值,比如空指针、空串、0等
2.向日志添加警告信息
3.利用Exception处理错误
4.直接停机
二、Error and Exception Handling in Java
java中存在两个继承java.lang.Throwable类的子类,java.lang.Exception和java.lang.Error。
Error类:多数情况下无法处理,是由java虚拟机内部错误所引起的。
Exception类:可处理的,且一定要处理。这类错误在程序中可以被捕获并解决。
Exception:
当程序出现错误时,异常处理机制允许代码将错误或者异常事件传递给调用它的代码,方法抛出异常,不执行后续代码。一层层找到调用者的异常处理代码后,这段代码捕获异常然后处理异常。
一段关于文件读写异常处理的示例
FileInputStream fileInput = null;
try{
fileInput = new FileInputStream(fileName);
DataInput dataInput = new DataInputStream(fileInput);
return dataInput.readInt();
}catch(FileNotFoundException e){
System.out.println("can not open file " + fileName);
}catch(IOException e){
System.out.println("can not read file " + e);
}finally{
if (fileInput != null) fileInput.close();
}
Exception的分类:
分类方式1:源自RuntimeException的一类和除此之外的所有其他Exception构成的一类。
分类方式2:checked Exception 和 unchecked Exception
所谓uncheckedException是error和RuntimeException,不要求捕获;而checkedException则要求必须捕获。
Checked Exception Handling:
1.“throws”
置于方法名后提示这个方法可能在运行时抛出异常
public FileInputStream(String name) throws FileNotFoundException
2.“throw”
用于抛出异常
throw new ArrayIndexOutOfBoundsException("some information");
3.“try……catch……finally……”
常用的捕获异常的代码结构,try中可能抛出异常之前的代码将正常运行,而异常发生时,try中的后续代码将不被执行,而由catch的语句抛出异常,finally中的语句不管有没有抛出异常都将被执行。
InputStream in = new FileInputStream(……);
try{
//1
code that might throw exceptions
//2
}catch(IOException e){
//3
show error message
//4
}finally{
//5
in.close();
}
4.”try-with-resource“
对于类似于下列结构的代码
open a resource
try{
work with the resource
}finally{
close the resource
}
我们通常可以简化为下列结构
try(Resource res = ……){
work with res
}
自定义Exception:
通常继承Exception类,习惯上同时给出一个默认构造函数和一个包含详细信息的构造函数
public class FileFormatException extends Exception{
public FileFormatException(){
}
public FileFormatException(String msg){
super(msg);
}
}
注意:
1.如果子类方法重写了父类方法,父类方法没有抛出异常,则子类方法中需要catch所有的checked异常,不能继续传播
2.子类继承自父类的方法,在子类中不能增加异常
3.不要过分地细化异常
4.根据抽象级别抛出合适的异常,高层应该捕获低层的异常,并抛出在高层抽象可以理解的异常
7.3 Assertions and Defensive Programming
一、Assertions
断言用于在运行时检查代码正确性
如果boolean表达式为假,那么java虚拟机将会抛出一个AssertionError
通常断言有两种形式:
1.
assert condition;
2
assert condition : expression
示例:
public class AssertionTest{
public static void main(String[] args){
int number = -5;
assert (number >=0 ) : "number is negative : " + number;
//do something
System.out.println("the number is " + number);
}
}
assert常用于:
判断输入值是否在指定范围;文件或流是否打开/关闭;文件或流的当前指针是否在最开始或最后;文件或流的读写性控制;只读输入参数是否被修改等。
内部不变量、类的不变量、方法的pre/pos条件均可用断言检查。
一般断言只在开发和调试阶段用于发现错误和异常,而在发布时被禁用。
另一方面,在单元测试中,常常使用assertTrue(),assertEquals()等方法用于验证方法结果的正确性。
注意:
断言不能用来测试外部条件比如文件是否存在等,外部因素导致的失败应该利用Exception机制处理。
二、Defensive Programming
防御性编程的主要思想是,如果某个方法被传入了不良数据,即使这是另一个子程序的错误,方法本身也不会受到破坏。
防御性编程常用Assertions、Exceptions、Barricade、Debugging aids等技术。
Barricade(隔栏):
容损策略,外部的错误不影响内部的正确性。类的public方法中假设数据是不安全的,负责检查和清洗;private方法则认为被public方法接收处理后的数据是安全的。隔栏外部的内容如果处理不了就抛出异常。
Debugging Aids:
使用攻击性编程,在开发阶段让异常尽可能明显地展示出来,而在产品运行时让异常能自我恢复;尽可能使程序失败。
7.4 Debugging
一、Debugging vs. Testing
测试发现现象,调试发现本质
debug常使用的工具有breakpoint(断点)、watchpoint、inspecting variables等
二、Logging
日志框架提供了包含可配置日志记录的功能,可以在运行时和单个功能部件中启用、禁用或增加详细信息。
使用Log4j2完成日志功能
Apache官网下载Log4j2,http://logging.apache.org/log4j/2.x/download.html
向项目中导入jar包,基本上只需要导入一下两个包,xx为版本号:
log4j-core-xx.jar
log4j-api-xx.jar
接下来需要配置文件,于log4j1不同,2的版本中配置文件不能是properties,只能采用.xml、.json或者.jsn格式,以下相当于缺省配置文件
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="OFF">
<appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</appenders>
<loggers>
<root level="error">
<appender-ref ref="Console"/>
</root>
</loggers>
</configuration>
log4j2的使用非常简单,只需要用LogManager的getLogger方法获取一个logger就可以使用日志记录功能了,以下为一个简单示例:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class MyLog{
private static Logger logger = LogManager.getLogger("MyLog.class.getName()");
public static void main(String[] args){
logger.entry();
logger.info("Hello, World!");
//do something
logger.error("Hello, World!");
logger.exit();
}
}
log4j2有8种日志隔离级别,分别是:
1.all:最低等级,打开所有日志
2.trace:追踪,相当于追踪程序的执行,一般不怎么使用
3.debug:一般在开发中,都将其设置为最低的日志级别
4.info:输出感兴趣的信息
5.warn:警告,有些时候虽然程序不报错,但需要告诉程序员
6.error:错误
7.fetal:极其重大的错误,这个一旦发生,程序也基本上要停止
8.off:最高等级,用于关闭所有日志记录