`
ray_yui
  • 浏览: 220202 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

Java高质量代码之 — 异常

    博客分类:
  • Java
阅读更多

前言:由于上一个星期工作繁忙,利用上下班和晚上睡前空余的时间拜读了秦小波老师的《改善Java程序的151建议》,感觉廓然开朗,注意到了很多平时在编写代码中并不会注意的问题,甚至感觉自己对Java只是略懂皮毛,不足以登大雅之堂,特此与读者分享读书笔记,以下内容摘自《改善Java程序的151建议》一书和笔者的理解


Java高质量代码系列文章
      面向对象篇:http://ray-yui.iteye.com/blog/1926984
      数据类型篇:http://ray-yui.iteye.com/blog/1927251
          字符串篇:http://ray-yui.iteye.com/blog/1927647
      数组与集合(1):http://ray-yui.iteye.com/blog/1928170
      数组与集合(2):http://ray-yui.iteye.com/blog/1930155
      枚举与注解:http://ray-yui.iteye.com/blog/1931408
      泛型与发射:http://ray-yui.iteye.com/blog/1933127
                  异常:http://ray-yui.iteye.com/blog/1938946
                      杂:http://ray-yui.iteye.com/blog/1942591


程序是不完美的,开发者是没有办法穷举所有软件运行中可能遇到的问题,异常的情况随时都会出现,而我们就需要异常来帮助我们描述我们程序当中发生的意外问题.


1.提倡异常封装
      Java语言的异常处理机制可以确保程序的健壮性,提供系统的可用率,但异常的API通常是比较"低级" 什么是低级别?那就是不包含业务的,只是程序员才能看懂的异常信息,而对于用户来说,基本就是看天书,什么是NullPointException?这些都是纯计算机语言的描述,而对于用户而言是需要业务级别的异常信息,所以我们提倡对异常的封装,异常封装有3个优势

      1.提高系统的友好性,由于系统提供的异常都是低级的,例如打开一个文件,如果文件不存在,则会报出FileNotFoundException,此时若然我们不对异常进行封装直接将栈信息输出到用户端,那用户根本就不知道是什么意思,我们应该对异常进行业务性的说明,例如提示文件不存在等.

      2.提高系统的可维护性,我们很多时候喜欢将多个异常信息直接使用一个异常的父类Exception或RuntimeException来表达,这种做法是不推荐的,就算将异常栈信息输出,维护人员还是要追寻到代码的层面才可以定位到异常发生的地方,这对系统的维护性造成了很大的影响,我们应该分开编写异常信息.

      3.解决Java异常机制自身的缺憾,Java当中的只要代码触发异常,就会跳到这个异常对应的catch块当中处理,而忽略其他catch块,这对于单独的一个代码块是没有问题的,但若然是多个代码块或一个链式的处理,链当中可能多一个节触发了异常,但我们最终是会看到一个异常,所以使用异常封装就能实现记录多个异常信息,请观察以下代码

public class MyException extends RuntimeException {

	private static final long serialVersionUID = 701064983280219053L;
	private List<Throwable> exceptionList = new ArrayList<Throwable>();

	public MyException(List<? extends Throwable> cause) {
		this.exceptionList.addAll(cause);
	}

	// 调用时
	public static void main(String[] args) {
		List<Throwable> exceptionList = new ArrayList<Throwable>();

		// 第一代码片段
		try {

		} catch (Exception e) {
			exceptionList.add(e);
		}

		// 第二代码片段
		try {

		} catch (Exception e) {
			exceptionList.add(e);
		}

		// 第三代码片段
		try {

		} catch (Exception e) {
			exceptionList.add(e);
		}

		// 正式抛出异常
		if (exceptionList.size() > 0) {
			throw new MyException(exceptionList);
		}
	}
}



2.采用异常链传递异常
      我们的JavaEE项目一般是三层结构,持久层,逻辑层,展现层,持久层是负责读取数据的,例如我们现在需要加载一个文件作为数据,但此时报出FileNotFoundException,由于该异常是检查性异常,所以必须在持久层处理或往上抛,但此时若然使用上抛的方式,逻辑层在调用时基本无法知道持久层为什么会出现这个错误,这个错误应该是持久层最清楚的,所以我们应该把异常封装好后传递到逻辑层,请观察以下代码

public void test() {
		File file = new File("/test.xml");
		try {
			FileInputStream fis = new FileInputStream(file);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			throw new MyException("文件不存在", e);
		}
	}


在持久层封装好自定义的异常类信息后抛出到逻辑层中,逻辑层到展现层亦同理


3.检查性异常尽量转化为非检查性异常
      把所有检查性异常(Checked Exception)都转换为非检查性异常(UncheckedException)是不现实的,因为检查性异常是正常逻辑的一种补充处理手段,在某些条件下必须抛出检查性异常以便上层为底层的错误进行处理,那为什么非要把受检异常转换为非受检异常呢?

      1.检查性异常使接口声明脆弱我们应该面向接口编程,但由于检查性异常大多需要抛出,这就使得在声明接口的时候,需要把抛出的异常类型也一并声明,若然在开发迭代的过程中发现实现类需要抛出更多的异常,那我们就必须要修改接口,这就是类影响接口的情景,其实我们可以看出,在很多著名的框架当中,都在使用着非检查性异常,例如Hibernate,Spring等,特别是Hibernate框架,把底层的JDBC检查性异SQLException全部隐藏了起来而只使用了Hibernate定义的非检查性异常.

      2.检查性异常使代码可读性降低当我们逻辑层调用持久层时,若然抛出了检查性异常,我们的逻辑层必须要对其进行try catch处理,或者再往展现层抛出,这样对持久层的调用者来说意味着,每次调用持久层的方法都需要增加4行代码才能成功调用,这样大大降低了代码的可读性

      3.受检异常增加了开发工作量我们从上面所述可以知道,我们推荐对异常进行封装,上层模块才能更好的处理和显示给用户,但这也导致了底层没完没了的封装,无疑是加重了开发的工作量,但在软件当中,几乎是不存在完美的解决方案,就例如数据库设计中的容量换效率和效率换容量一样,我们只能取其平衡的地方,适当的对异常进行封装

检查性异常有如此多的缺点,所以我们在开发当中通常会将检查性异常转换为非检查性异常,检查性异常的父类为Exception,非检查性的异常父类为RuntimeException,只要我们封装的异常类型是继承RuntimeException就可以抛出非检查性异常,检查性异常提出的是“法律下的自由”,而非检查性异常则是“约定的自由”



4.在使用日志框架时需要注意异常吞噬
      使用开源日志框架,例如log4j等已经在项目当中广泛的使用,请留意以下代码

public class Test {
	Logger logger = LoggerFactory.getLogger(this.getClass());

	public void test() {
		try {

		} catch (Exception e) {
			e.printStackTrace();
			logger.warn("错误信息", e);
			new MyException("错误信息", e);
		}
	}
}


      以上代码是使用日志框架对检查性异常时的公认写法,有项目维护或开发经验的人知道,在项目维护阶段出现错误时,通常客户会将日志文件发送给维护人员,然后维护人员根据日志文件中的错误定位程序进行修复,而不是将在控制台的信息直接复制给维护人员,读者要分清控制台输出和日志文件的概念,printStackTrace方法是将错误的栈信息输出到控制台中,若然此时我们没有使用logger对异常信息进行记录,那我们将会把此异常信息吞噬掉,导致定位错误时将一头雾水,当然项目可能会设置将控制台的信息一并记录到日志文件中,但在开发阶段,我们应该尽量避免这种不稳定的事情发生.


5.不要在finally中处理返回值
      在finllay中处理返回值,是考试和面试题中经常出现的题目,但请谨记,在项目当中绝对不要在finally中处理返回值,请留意以下代码

public static int doSomething(int num) throws Exception {
	try {
		if (num < 0) {
			throw new Exception("error");
		}else{
			return num;
		}
	} catch (Exception e) {
		throw e;
	} finally {
		return -1;
	}
}

public static void main(String[] args) {
	try {
		doSomething(-1);
		doSomething(100);
	} catch (Exception e) {
		System.out.println("永远不会到达");
	}
}


      我们都知道无论是否触发异常运行catch块,finally都会被执行,以上的答案两个结果都返回为-1,而且调用该方法不会抛出异常,而且覆盖了try中的返回值,而且告诉了JVM,我这个方法调用正常返回,没有问题,屏蔽了异常,在Eclipse等IDE中若在finally增加返回值还会提示警告信息,所以切记finally中不要出现返回值,finally应该作释放资源用


6.不要在构造函数中抛出异常
      我们知道类的创建会执行构造函数,其实从语法层面来说完全可以在构造函数中抛出异常,但从系统设计角度来说,尽量不要在构造函数中抛出异常,这样的行为不仅使子类扩展父类的构造函数受到限制,而且也违背了里氏替换原则,除非该类不应该被创建或不想被反射技术创建,则应该在构造函数增加异常


7.使用Throwable获取栈信息
      如何做到对同一个方法(同参数)的调用返回两个不同的结果?请观察以下代码

public static boolean doSomething(){
	StackTraceElement[] stes = new Throwable().getStackTrace();
	
	for(StackTraceElement ste : stes){
		if(ste.getMethodName().equals("test")){
			return true;
		}
	}
	throw new RuntimeException("除test方法外,我不允许其他人调用");
}

public static void test(){
	doSomething();
}

public static void test1(){
	//我报错了
	doSomething();
}


      这里对同一个方法调用,产生了两种结果,为什么呢?因为在创建一个Throwable类时,JVM会记录下栈信息,然后生成Throwable对象,这样我们就可以知道类之间调用的顺序,方法名称和当前行号等等,就可以完成只要调用者不是test方法,就产生错误


8.异常只为异常服务
      异常原本的用意是正常逻辑的一个补充,有时甚至会被当做业务主体来使用,就例如在finally和catch中增加业务代码,但这种做法是不推荐的,导致代码产生了坏味道,所以并不推荐在异常中做过多的业务逻辑处理,除非必须,否者应该尽量避免


9.多使用异常作为信息
      由于我们的应用大多是三层架构,若然不使用异常进行信息的封装传递,我们就必须定义记录信息的变量在三层中互相传递,这样的做法降低了对代码的可读性,而且忽视了Java本身提供的信息传递途径,而在持久层通过异常的封装传递到展现层,最终显示给用户,通过异常类这个载体,可以很好的完成这项工作,例如Struts2都提供了标签让我们更好的输出异常中的信息,更提供了'值栈'给我们,使用<s:debug />看一下,你会看到Exception在里面


总结:
      笔者在本文章中只从《改善Java程序的151建议》中提取部分进行归纳性叙述,推荐各位读者购买这本书,该书不仅从事例中学习,而且涉及到原理,底层的实现,不仅告诉你应该怎么做,还告诉你为什么要这样做.
8
7
分享到:
评论
5 楼 windshome 2013-09-10  
这个话题非常好,有空也看看博主推荐的这本书。

个人认为:Java异常处理这个话题,在开发过程中,最大的问题在于缺乏设计层面的规划,如果有了规划和整体的设计,后边编码就都会顺畅很多。

编码和技巧当然也很重要,但不能从根本上改善情况,解决问题。

我关于异常方面的文章: http://windshome.iteye.com/blog/1860742
4 楼 ray_yui 2013-09-09  
cugbzc 写道
4:在使用日志框架时需要注意异常吞噬。不知道你到底要说什么。
还有最后new MyException("错误信息", e);  就new了一个对象,什么都不做?

new MyException是将检查异常转换为非检查异常抛给上层,这里只是一个事例,不存在业务,请问想怎么做?异常吞噬是指只输出异常到控制台没有记录到日志当中,请问兄台维护项目时看日志文件还是控制台?
3 楼 cugbzc 2013-09-09  
4:在使用日志框架时需要注意异常吞噬。不知道你到底要说什么。
还有最后new MyException("错误信息", e);  就new了一个对象,什么都不做?
2 楼 ray_yui 2013-09-09  
matychen 写道
第4个,logger.warn()。不建议这么写
可以这样,注意逗号,以及不要e.getMessage().
logger.error("some accompanying message", e);
参见:http://slf4j.org/faq.html#exception_message

非常感谢指出错误,已修改
1 楼 matychen 2013-09-09  
第4个,logger.warn()。不建议这么写
可以这样,注意逗号,以及不要e.getMessage().
logger.error("some accompanying message", e);
参见:http://slf4j.org/faq.html#exception_message

相关推荐

    敏捷开发中编写高质量Java代码

    【敏捷开发中编写高质量Java代码】的实践策略 在敏捷开发模式下,代码质量的提升是项目成功的关键因素。为了确保Java项目的代码质量,我们可以遵循五个关键步骤: 1. **统一编码规范与代码样式** - 编码规范是...

    如何编写高质量Java代码

    ### 如何编写高质量Java代码 #### 一、引言 随着软件工程的发展,尤其是敏捷开发理念的普及,软件项目的质量成为衡量开发团队能力和产品成功的关键因素之一。在Java项目开发中,通过实施一系列最佳实践和技术手段...

    编写高质量代码 改善Java程序的151个建议 PDF高清完整版

    《编写高质量代码 改善Java程序的151个建议》是一本专注于提升Java编程质量的专业书籍。书中详细列举了151条实用的建议,旨在帮助开发者编写出更高效、可读性更强且易于维护的Java代码。以下是对这些建议的详细解读...

    高质量java编程

    通过学习《高质量Java程序设计》,开发者不仅可以掌握Java语言的核心技术,还能理解如何编写出高效、可扩展、易于维护的代码,这对于任何Java开发者的专业成长都至关重要。这本书的PDF版本可以在超星阅览等平台上...

    高质量Java程序设计 源代码

    《高质量Java程序设计》是一本深入探讨如何编写高效、可靠且易于维护的Java代码的书籍。源代码提供了书中实例的实现,是理解理论知识并进行实践操作的重要资源。以下是基于这个主题的一些Java编程核心知识点: 1. *...

    高级JAVA源代码

    同时,了解不同类型的异常以及自定义异常的创建也是提高代码质量的重要环节。 IO流是Java中处理输入输出的重要部分。资料可能会涵盖字节流、字符流、缓冲流、对象序列化等主题。同时,NIO(非阻塞I/O)是Java 1.4...

    java 7编程高级进阶代码

    通过学习和掌握这些Java 7的高级特性,开发者可以编写出更加高效、可靠且易于维护的代码,提高软件开发的质量和效率。在实际项目中,理解并合理运用这些特性,可以显著提升程序的运行效率,减少潜在的错误,并简化...

    高性能java代码优化建议

    以上只是部分知识点,"编写高质量代码++改善Java程序的151个建议.pdf"这本书中应该包含了更多深入的细节和实践案例。通过深入学习和实践,开发者可以不断提升自己的Java编程技能,编写出更高性能的代码。

    基于JavaParser的代码调用链分析,可以用于分析Java代码的方法调用链.zip

    JavaParser是一个强大的开源库,主要用于解析、操作和生成Java源代码。这个压缩包中的内容可能包含了一系列使用JavaParser...通过深入学习和应用JavaParser,我们可以更好地理解和优化Java代码,提升软件的质量和性能。

    Java中常见异常类型及分析.pdf

    ### Java中常见异常类型及分析 #### 一、概述 在Java编程中,异常处理是一...掌握这些异常的处理技巧对于编写高质量、可靠的Java应用程序至关重要。在实际开发过程中,应始终遵循最佳实践,确保代码健壮性和鲁棒性。

    Java2Pas(Java代码转换成Delphi代码)

    Java2Pas是一款强大的命令行工具,专为程序员设计,它能有效地将Java源代码(.java文件)转换成Delphi的Pascal源代码(.pas...然而,使用Java2Pas时需了解其限制,并准备进行必要的手动调整,以确保转换后的代码质量。

    高质量 Java 程序设计

    在Java编程领域,高质量的程序设计是至关重要的。这不仅涉及到代码的可读性、可维护性和性能,还包括良好的设计模式、错误处理、线程安全和资源管理等多个方面。以下是一些关于高质量Java程序设计的关键知识点: 1....

    Java学习部分代码

    Java是一种广泛使用的面向对象的编程语言,以其跨平台、健壮性和安全性著称。这个"Java学习部分代码"的资源集合旨在为初学者和有一定...同时,别忘了查阅官方文档和其他高质量的学习资料,以获取最新的信息和技术趋势。

    Java初级课堂代码

    【Java初级课堂代码】是赛斯特培训学校针对Java初学者提供的教学资源,包含了学员在学习过程中所接触的各种实例代码,特别是实训课程中的彩票程序代码。这个压缩包旨在帮助初学者通过实际操作,深入理解Java编程的...

    java版源代码下载

    Java版源代码下载通常指的是获取使用Java编程语言编写的软件项目的源码,这对于开发者来说具有极大的价值,因为源代码...此外,还需关注代码的异常处理、并发控制、性能优化等方面,这些都是高质量Java编程的关键要素。

    java实用代码源代码

    Java是一种广泛使用的面向对象的编程语言,以其跨平台、高性能和丰富的类库而著名。"java实用代码源代码"这个标题表明我们即将探讨的是实际应用中的Java编程代码,这些代码可能涵盖各种功能和应用场景,旨在提高开发...

    Java工程师不得不看的几千份代码

    2. **异常处理**:Java的异常处理机制是其强项之一,通过try-catch-finally语句块来捕获和处理运行时错误。代码库可能包含各种异常处理的实例,展示了如何优雅地处理程序中的错误。 3. **多线程编程**:Java提供了...

    《java课程设计代码》

    Java课程设计代码通常涵盖了许多Java编程的基础到高级概念,这些概念是学习Java开发的重要组成部分。在本项目中,我们可以预见到一系列的Java编程实践,包括但不限于面向对象编程、数据结构与算法、图形用户界面...

    编写高质量代码:改善Java程序的151个建议pdf和源码 高清

    《编写高质量代码:改善Java程序的151个建议》是一本专为Java开发者准备的实战指南,旨在提升代码质量并帮助程序员从初级迈向高级。这本书涵盖了从基础到高级的各种编程实践,通过151个具体的建议,为读者提供了一个...

Global site tag (gtag.js) - Google Analytics