使用IBM静态工具优化Java代码.doc_第1页
使用IBM静态工具优化Java代码.doc_第2页
使用IBM静态工具优化Java代码.doc_第3页
使用IBM静态工具优化Java代码.doc_第4页
使用IBM静态工具优化Java代码.doc_第5页
已阅读5页,还剩10页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

使用 IBM 静态工具优化 Java 代码,第 2 部分:分析错误报告文档选项打印本页窗体顶端窗体底端将此页作为电子邮件发送级别: 初级丁 钦浩(), 软件开发工程师, IBM2008 年 7 月 03 日通过本系列第 1 部分的介绍,读者应该可以轻松地在 Windows 上运行 BEAM,并能够对自己的 Java 工程进行代码分析了。本文我们将对其输出的报告结果文件进行分析,修改代码中的潜在错误,从而达到最根本的目的:提高代码质量。概述BEAM 报告的结果文件是通过 build.xml 中-beam:complaint_file所定义的,在这里,本文假设其为 BEAM-messages。BEAM-messages记录着报出的所有代码缺陷,这些缺陷分为ERROR,MISTAKE和WARNING三大类,严重程度依次递减。每一个具体的ERROR,MISTAKE和WARNING都代表着一个错误模式,本文接下来就通过实例分析理解其中的某些重要错误模式,告诉读者在写 Java 代码时如何避免这些错误模式的发生,从而写出高质量的代码。由于篇幅原因,本文只主要重点介绍四个常见的错误模式,并在最后简单介绍一下在编程时还应该注意的一些其它技巧,文章结构如下: 操作空对象 数组访问越界 除 0 错误 内存泄漏 其它技巧回页首操作空对象这是报出的ERROR2错误模式。据个人项目经验,这种错误模式出现最为频繁,但是编程人员却往往很难发现,因为这种编译器发现不了的错误可能在代码运行很长时间时都不会发生,可是一旦出现,程序就会终止运行,并抛出 runtime 异常java.lang.NullPointerException。通常有以下这些情况会导致操作空对象错误模式的发生。 调用空对象的方法 访问或修改空对象的域 访问或修改空数组对象的数组元素 同步空对象 传入空对象参数下面让我们用简单易懂的例子一一介绍它们。调用空对象的方法清单 1. 调用空 String 对象的 charAt() 方法 String str = null; int a = 0; if( a 0 ) str = new String developer , Works; char ch = str.charAt(0); 这是最典型的调用空对象方法的例子,调用一个未初始化的String对象的chatAt()方法。清单 2. 调用未初始化数组成员的方法 Integer array = null; try array = new Integer new Integer(2/0), new Integer(3), new Integer(4) ; catch ( Exception e ) /Do nothing here int i = Value(); 数组array的三个Integer成员因为除数为 0 的异常并没有被初始化(这里只是用典型的除数为 0 的异常举例,其实实际工程中,初始化时发生的异常有时很难被发现,没有如此明显),但是接下来仍然调用其第 0 个成员的intValue()方法。总结:调用空对象方法的错误非常常见,导致其出现的原因通常有两点:1. 在某个方法开始处定义了空对象,程序员准备在其后的代码中对其进行初始化,初始化完毕后再调用该对象的方法。但是有时由于初始化代码中的某个不常见的if之类的条件不成立或者for/while循环的条件不成立,导致接下来的赋值动作并没有进行,其结果就是之前定义的空对象并没有被初始化,然后又调用该对象的方法,从而造成了java.lang.NullPointerException,如清单 1 所示。2. 初始化对象时出现了异常,但是没有对异常进行特殊处理,程序接下来继续运行,导致最终调用了该空对象的方法,如清单 2 所示。这种代码缺陷在大型代码工程中往往很难被发现,因为编译器不会报错,而且代码在实际运行中,可能 99% 的时候if条件都是满足的,初始化也是成功的,所以程序员很难在测试中发现该问题,但是这种代码一旦交付到用户手中,发现一次就是灾难性的。建议的解决方法:一定要明确知道即将引用的对象是否是空对象。如果在某个方法中需要调用某个对象,而此对象又不是在本方法中定义(如:通过参数传递),这时就很难在此方法中明确知道此对象是否为空,那么一定要在调用此对象方法之前先判断其是否为空,如果不为空,然后再调用其方法,如:if( obj != null ) obj.method() 。访问或修改空对象的域定义了某个类的对象,在没有对其初始化之前就试图访问或修改其中的域,同样会导致java.lang.NullPointerException异常。这种情况也非常常见,举一个比较典型的数组对象的例子,如清单 3 所示:清单 3. 访问未初始化数组的 length String str = null; int a = 0; while( a 0 ) str = new Stringdeveloper, Works; System.out.println( str.length ); 数组str由于某些条件并没有被初始化,但是却访问其public final域length想得到其长度。总结:访问或修改某个空对象的域的起因与调用空对象的方法类似,通常是由于某些特殊情况导致原本应该初始化的数组对象没有被初始化,从而接下来访问或修改其域时产生java.lang.NullPointerException异常。建议的解决方法:与调用空对象的方法类似,尽量在访问或修改某些不能够明确判断是否为空对象的域之前,对其进行空对象判断,从而避免对空对象的操作。访问或修改空数组对象的数组元素当某个数组为空时,试图访问或修改其数组元素时都会抛出java.lang.NullPointerException异常。清单 4. 访问或修改空数组对象的数组元素 1 String str = null; 2 System.out.println( str0); 3 str0 = developerWorks ; 第 2 行和第 3 行都会导致 ERROR2 错误,其中第 2 行试图访问空数组对象str的第 0 个元素,第 3 行试图给空数组对象str的第 0 个元素赋值。总结:访问或修改某个空数组对象的数组元素的起因与调用空对象的方法类似,通常是由于某些特殊情况导致原本应该初始化的数组对象没有被初始化,从而接下来访问或修改其数组元素时产生java.lang.NullPointerException异常。建议的解决方法:与调用空对象的方法类似,尽量在访问或修改某些不能够明确判断是否为空空数组对象的数组元素之前,对其进行空对象判断,从而避免对空数组对象的操作。同步空对象清单 5. 同步空对象 String s = null; int a = 0; switch( a ) case 1: s = new String(developer); case 2: s = new String(Works); default: ; synchronized( s ) 对空对象s进行同步。总结:同步空对象的起因与调用空对象的方法类似,通常是由于某些特殊情况导致原本应该初始化的对象没有被初始化,从而接下来导致同步空对象,并产生java.lang.NullPointerException异常。建议的解决方法:与调用空对象的方法类似,尽量在同步某些不能够明确判断是否为空的对象之前,对其进行空对象判断,从而避免对空对象的操作。传入空对象参数清单 6 传入空对象参数 static int getLength( String string ) return string.length(); public static void main(String args) String string = null; int len = getLength( string ); 将空String对象string传入getLength方法,从而导致在getLength方法内产生java.lang.NullPointerException异常。总结:导致传入空对象参数的原因通常是在传参前忘记对参数对象是否为空进行检查,或者调用了错误的方法,或者假定接下来传参的函数允许空对象参数。建议的解决方法:如果函数的参数为对象,并且在函数体中需要操作该参数(如:访问参数对象的方法或域,试图修改参数对象的域等),一定要在函数开始处对参数是否为空对象进行判断,如果为空则不再执行函数体,并最好作特殊处理,达到避免操作空对象的目的。回页首数组访问越界这是报出的ERROR7错误模式。什么是数组访问越界呢?如果一个数组(在 Java 中,Vector,ArrayList和List也算是数组类型)定义为有 n 个元素,那么对这 n 个元素(0n-1)的访问都是合法的,如果对这 n 个元素之外的访问,就是非法的,称为“越界”。这种错误同样不会造成编译错误,会危险地“埋伏”在你的程序中。在 C/C+ 中遇到数组访问越界,可导致程序崩溃,甚至宕机;在 Java 中,会抛出 runtime 异常java.lang.ArrayIndexOutOfBoundsException或java.lang.IndexOutOfBoundsException,并终止程序运行。请看程序员容易犯的几个典型数组访问越界的例子:清单 7. 越界访问 String 数组元素 1 int index = 2; String names = new String developer, Works ; System.out.println( namesindex ); index为 2,而数组只有两个元素,最后一个元素的下标索引是 1,所以导致数组访问越界。注意,如果index为负数,仍然是数组访问越界。清单 8. 越界访问 Vector Vector vec = new Vector(); for ( int i = 0; i 0 ) names = new String developer, Works ; else names = new String developerWorks ; buf.append( names0 ).append( names1 ); 程序员调用append时以为数组names中有两个元素,其实只有一个。清单 10. 越界访问 ArrayList ArrayList arrList = new ArrayList(); int len = 5; for( int i = 0; i len; i+ ) arrList.add( String.valueOf(i) ); arrList.remove( len - 1 ); System.out.println(arrList.get( len - 1 ); ArrayList中最后一个元素已经被 remove 了,所以该位置已经没有任何东西,访问它将导致java.lang.ArrayIndexOutOfBoundsException。总结:导致数组访问越界主要有以下几个原因:1. 使用某个变量作为数组索引时,没有之前对该变量值进行检查,变量的取值可能会超出合法的数组索引范围,从而导致数组访问越界,如清单 7 。2. 使用与数组元素个数相同的值作为数组索引,因为数组的最后一个元素的索引是“数组大小 -1 ”,所以导致数组访问越界,如清单 8 。3. 数组初始化代码中某个不起眼的if之类的条件不成立或者for/while循环的条件不成立,导致接下来的赋值动作并没有进行,从而接下来访问了未初始化完全的数组,导致数组访问越界,如清单 9 。4. 程序员编码时忘记Vector,ArrayList或List中某些位置的元素已经被 remove 了,后来仍然对该位置元素进行访问,可能会导致数组访问越界,如清单 10 。建议的解决方法:在判断数组是否有效不为空的同时,也要对访问的数组元素的索引是否超出了上下限进行检查,如果索引是个变量,一定要确保变量取值在数组范围之类(反例是清单 7);如果索引不是个变量,在确保索引正确的同时还要确保之前定义的数组足够大(反例是清单 9)。最好是使用try/catch访问数组,并对数组访问越界异常进行捕获,进行特殊处理,如清单 11 。清单 11 利用 try/catch 安全访问数组 try / 访问数组 catch( IndexOutOfBoundsException e ) / 捕获数组访问越界的异常并做特殊处理 回页首除 0 错误这是报出的ERROR22错误模式。在 Java 中,如果除数为 0,会导致 runtime 异常java.lang.ArithmeticException并终止程序运行,如清单 12 所示。清单 12 除数为 0 int num = 0; int a = 5 / num; 总结:导致除 0 错误的主要原因是使用变量作为除数,并且程序员在写除法语句时,以为变量值到此已经被改变(不是 0),但是实际上可能某条不被注意的语句路径导致除数为 0,从而造成了错误。建议的解决方法:做除法前,一定不能将除数直接写为 0 ;如果除数为变量,而且该变量值在进行除法前经过了很多运算,导致不能确定在被除前是否为 0,则在除法前,先对除数变量进行是否为 0 的判断,并对除数为 0 的情况做特殊处理。回页首内存泄漏这是报出的ERROR23错误模式。内存泄漏的后果非常严重,即使每次运行只有少量内存泄漏,但是长期运行之后,系统仍然会面临彻底崩溃的危险。在 C/C+ 中,内存泄漏(Memory Leak)一直是程序员特别头疼的问题,因为它出错时的表现特征经常很不稳定(比如:错误表象处不唯一,出错频率不定等),而且出现问题的表象处经常与内存泄漏错误代码相隔甚远,所以很难被定位查出。在 Java 中,垃圾回收器 (Garbage Collection,GC) 的出现帮助程序员实现了自动管理内存的回收,所以很多程序员认为 Java 不存在内存泄漏问题,其实不然,垃圾回收器并不能解决所有的内存泄漏问题,所以 Java 也存在内存泄漏,只是表现与 C/C+ 不同。为什么 Java 会出现内存泄漏呢?因为垃圾回收器只回收那些不再被引用的对象。但是有些对象的的确确是被引用的(可达的),但是却无用的(程序以后不再使用这些对象),这时垃圾回收器不会回收这些对象,从而导致了内存泄漏,抛出异常java.lang.OutOfMemoryError。以下是导致内存泄漏的常见的例子(其中某些例子 BEAM 很难查出,这里列出只是为了给读者提供一个反例进行学习)。清单 13. 内存泄漏的 Hashtable public class HashtableLeakDemo static Hashtable names = new Hashtable(); void leakingHash( int num ) for( int i = 0; i num; i+ ) names.put( new Integer(i) , developerWorks); / 接下来是继续对 names 哈希表进行的操作,但是忘了移除其中的表项 leakingHash会往Hashtable中不停地加入元素,但是却没有相应的移除动作(remove),而且static的Hashtable永远都会贮存在内存中,这样必将导致Hashtable越来越大,最终内存泄漏。清单 14. 内存泄漏的 Vector public class VectorLeakDemo static Vector v = new Vector(); void leakingVector( int num ) for( int i = 0; i 0; i- ) v.remove( i ); 每次调用leakingVector都会少remove一个String元素,如果Vector中的元素不是String,而是数据库中一些非常大的记录(record),那么不停调用leakingVector将很快导致内存耗光。清单 15. 内存泄漏的 Buffer public class BufferLeakDemo private byte readBuffer; public void readFile( String fileName ) File f = new File( fileName ); int len = (int)f.length(); /readBuffer 的长度只增不减 if ( readBuffer = null | readBuffer.length = 0 ) / S 是 String 对象,它的长度永远大于等于 0,条件恒正确例 2: / 程序员本来的意图是想介于 MIN 和 MAX 之间的值才成立,却误将” & ”写成” | ”,导致条件恒成立 if ( x = MIN | x = MAX ) 例 3: final boolean singleConnection = true; / final 型的 singleConnection 永远为 true,所以该条件恒成

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

最新文档

评论

0/150

提交评论