网站首页 文章专栏 介绍一个好用的插件findbugs,检查代码质量
介绍一个好用的插件findbugs,检查代码质量


 前言 :写出高质量的代码是所有程序员都想要的结果,实际上由于精力有限,有的代码会暴露简单的错误,或者bug,findbugs就是一款帮助查找bug的好用插件,很多大公司都要求安装这个,提交代码之前不允许有findbugs的中级告警以及高级告警,另外还可以搭配checkstyle插件一块使用,本文先介绍findbugs。



据说嘲讽一个程序员只需要一个表情包就行了

bugs.jpg

实际上程序员确实最怕的就是bug,怎么避免呢? 看老夫教你一招

timg (2).jpg

一,安装

现在使用较多的应该是idea了,本文就介绍idea安装吧。


1,打开idea,进入setting,选择Plugins,搜索findbugs,install就行了,我这里已经安装过了,安装后需要重启idea。


安装2.png


2,安装后,左下角会出现新的窗口,点击进去就可以看到信息。

显示.png


3,使用时,可以右键你想要检查的项目(可以是整个项目,可以是一个模块,也可以是单个类)。

选择.png


4,选择后findbugs就会开始运行加载,然后告诉你你的代码哪里有问题,不同的模块代表的严重程度不一样。


findbugs.png


二,列举一些常见的代表含义:

Bad practice 代码中的一些坏习惯

Class names should start with an upper case letter 主要包括类名的命名,以大写字母开头 。

Method names should start with a lower case letter 方法名以小写字母开头 。
Field names should start with a lower case letter 字段名以小写字母开头。 
equals()method does not check for null argument equals()方法应该检查非空 。
Class defines equals() and uses Object.hashCode() 一个类覆写了equals方法,没有覆写hashCode方法,使用了Object对象的hashCode方法 。
Method ignores exceptional return value 方法忽略返回值的异常信息 。
Equals method should not assume anything about the type of its argument equals(Object o)方法不能对参数o的类型做任何的假设。比较此对象与指定的对象。当且仅当该参数不为 null,并且是表示与此对象相同的类型的对象时,结果才为 true。 
Comparison of String objects using == or != 用==或者!=去比较String类型的对象 。
Method might ignore exception 方法可能忽略异常 。
Method invokes System.exit() 在方法中调用System.exit(…)语句,考虑用RuntimeException来代替 。
Method ignores result of InputStream.read() InputStream.read方法忽略返回的多个字符,如果对结果没有检查就没法正确处理用户读取少量字符请求的情况。 

Dodgy code 糟糕的代码

Switch statement found where default case is missing Switch没有默认情况下执行的case语句 。

Switch statement found where one case falls through to the next case Switch语句中一个分支执行后又执行了下一个分支。通常case后面要跟break 或者return语句来跳出。 
Dead store to local variable 该指令为局部变量赋值,但在其后的没有对她做任何使用。通常,这表明一个错误,因为值从未使用过。 
Write to static field from instance method 在实例方法写入静态字段 
Redundant nullcheck of value known to be non-null 方法中对不为空的值进行为空的判断。 
Method uses the same code for two branches 此方法使用相同的代码,以实现两个有条件的分支。检查以确保这是不是一个编码错误 
Exception is caught when Exception is not thrown 在try/catch块中捕获异常,但是异常没有在try语句中抛出而RuntimeException又没有明确的被捕获 
Integral division result cast to double or float 整形数除法强制转换为double或者float类型。 
Possible null pointer dereference due to return value of called method 方法的返回值没有进行是否为空的检查就重新赋值,这样可能会出现空指针异常。 
Useless object created 对象创建了并没有用 。
Unread public/protected field 没有用到的字段 。


        Internationalization 关于代码国际化相关方面的。

Consider using Locale parameterized version of invoked method 使用平台默认的编码格式对字符串进行大小写转换,这可能导致国际字符的转换不当。使用以下方式对字符进行转换 。


        Performance 关于代码性能相关方面的。

Boxing/unboxing to parse a primitive 类型转换 比如字符串转换成int 应该使用Integer.parseInt(“”) 代替Integer.valueOf(“”) 。
Method concatenates string using + in aloop  每次循环里的字符串+连接,都会新产生一个string对象,在java中,新建一个对象的代价是很昂贵的,特别是在循环语句中,效率较低 ,解决办法:使用StringBuffer或者StringBuilder重用对象。 
Private method is never called 私有方法没有被调用 。
Explicit garbage collection;extremely dubious except in benchmarking code 在代码中显式的调用垃圾回收命名,这样做并不能起作用。在过去,有人在关闭操作或者finalize方法中调用垃圾回收方法导致了很多的性能浪费。这样大规模回收对象时会造成处理器运行缓慢。 
Unread field:should this field be static? 没有用到的static 字段 。
should be a static inner class 此内部类应该使用static修饰 。


        Experimental 

Method may fail to clean up stream or resource on checked exception 这种方法可能无法清除(关闭,处置)一个流,数据库对象,或其他资源需要一个明确的清理行动 ,解决方法:流的关闭都写在finally里面 。

Malicious code vulnerability 关于恶意破坏代码相关方面的。

May expose internal representation by incorporating reference to mutable object 此代码把外部可变对象引用存储到对象的内部表示。如果实例受到不信任的代码的访问和没有检查的变化危及对象和重要属性的安全。存储一个对象的副本,在很多情况下是更好的办法。 

Field isn’t final but should be 此字段前应该加final 。
Field isn’t final and can’t be protected from malicious code 此字段前应该加final 。
Field should be package protected  一个静态字段是可以被恶意代码或其他的包访问修改。可以把这种类型的字段声明为final类型的以防止这种错误。 


        Multithreaded correctness 关于代码正确性相关方面的

Static DateFormat DateFormat 在多线程中本身就是不安全的,如果在线程范围中共享一个DateFormat的实例而不使用一个同步的方法在应用中就会出现一些奇怪的行为。 

Call to static DateFormat DateFormats多线程使用本事就是不安全的,改进方法:需要创建多实例或线程同步 。
Correctness 关于代码正确性相关方面的。

Nullcheck of value previously dereferenced 此代码之前废弃null值检查。解决办法 进行null检查 。

Possible null pointer dereference 可能为null 。
Null pointer dereference 对象赋为null值后 没有被重新赋值 。
Possible null pointer dereference in method on exception path 在异常null值处理分支调用的方法上,可能存在对象去除引用操作 。
value is null and guaranteed to be dereferenced on exception path exception分支上,存在引用一个null对象的方法,引发空指针异常。 
Self comparison of value with itself 方法中对一个局部变量自身进行比较运算,并可说明错误或逻辑错误。请确保您是比较正确的事情。 
An apparent infinite recursive loop 明显的无限迭代循环,将导致堆栈溢出.


三,实战代码中的问题

1,

Reliance on default encoding

Found a call to a method which will perform a byte to String (or String to byte) conversion, and will assume that the default platform encoding is suitable. This will cause the application behaviour to vary between platforms. Use an alternative API and specify a charset name or Charset object explicitly

public static void write(String data, String filePath) {
    byte[] bytes = data.getBytes();

    OutputStream outputStream = null;

    try {
        outputStream = new FileOutputStream(filePath);
        outputStream.write(bytes);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if (outputStream != null) {
            try {
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

]6H990{N~UYFDQ0TK8@L$UR.png


这是一个高级告警,出问题的就是这一句:

byte[] bytes = data.getBytes();

至于为什么会出问题,是因为如果不声明编码, 它就会用操作系统默认的编码。 那么你的项目部署到其他服务器上的时候,就有可能发生错误了,改正:

byte[] bytes = data.getBytes("utf-8");

指定一个编码格式,类似的还有

 InputStreamReader fileData = new InputStreamReader(file ,"utf-8");
 out = new PrintWriter(new OutputStreamWriter(conn.getOutputStream(), "utf-8"));

等等。。。


2,

equals method overrides equals in superclass and may not be symmetric

This class defines an equals method that overrides an equals method in a superclass. Both equals methods methods use instanceof in the determination of whether two objects are equal. This is fraught with peril, since it is important that the equals method is symmetrical (in other words, a.equals(b) == b.equals(a)). If B is a subtype of A, and A's equals method checks that the argument is an instanceof A, and B's equals method checks that the argument is an instanceof B, it is quite likely that the equivalence relation defined by these methods is not symmetric.

@Data
public class QueryDictionaryDetailListPageReqVO extends PageQueryRequest{

    @NotBlank(message = "dictType不能为空!")
    private String dictType;

}

@Data
public class PageQueryRequest {

    private int pageNo   = 1;
    private int pageSize = 20;

}


这里就是一个简单的vo类继承自一个pageRequest类,使用lombok的注解,自动生成get,set等方法。

告警解读:

子类覆盖了父类中的equals方法,而两个类中都是用了instanceof来判断两个对象是否相等,这个操作存在风险。equals方法应该具有对称性(a.equals(b) == b.equals(a)),但是当B是A的子类时,A的equals方法检查参数是A的实例,B的equals方法检查参数是B的实例,则这些方法定义的equal关系很可能不是对称的。


修改:

@Data
@EqualsAndHashCode(callSuper = true)
public class QueryDictionaryDetailListPageReqVO extends PageQueryRequest{

    @NotBlank(message = "dictType不能为空!")
    private String dictType;

}

加上@EqualsAndHashCode注解。


3,

May expose internal representation by incorporating reference to mutable object

This code stores a reference to an externally mutable object into the internal representation of the object.  If instances are accessed by untrusted code, and unchecked changes to the mutable object would compromise security or other important properties, you will need to do something different. Storing a copy of the object is better approach in many situations.

private Date createTime;


这里告警的就是这么一行代码,就是一个po的时间类型的属性,是不是感觉这能有什么问题?

实际上我们测试一下如下代码:


    public static void main(String args[]) throws Exception{
        Test3 obj = new Test3();
        Date now = new Date();
        
        obj.setRegDate(now);    
        now.setYear(4000);  //问题所在!
        
        System.out.println(obj.getRegDate());
    }
    
    public class Test3 {
     
        private Date regDate ;    
    
        public void setRegDate(Date regDate) {
            this.regDate = regDate;
        }
    
        public Date getRegDate() {
            return regDate;
        }    
    }


这时候运行会发生什么呢,答案就是obj这个对象的属性被修改了,而我们修改的只是now这个时间对象,这就违背我们的意思了,发生这种情况的原因就是因为JAVA里,对象是引用传递的,所以怎么修改呢?

方式一:

setObj的时候,对象不要直接赋值(this.regDate = regDate),而是赋值传入对象的拷贝(this.regDate = (Date)regDate.clone();)。

OK~现在我们把代码this.regDate = regDate替换成this.regDate = (Date)regDate.clone();

再测试就没问题了。

方式二:

不要使用Date了,直接使用 java.time 包下的 LocalDateTime类,就不会发生这种情况了


实际开发中会出现更多种类的告警,具体情况需要具体分析,个人觉得findbugs还是挺好用的,一定程度上能提升代码质量,最后祝各位代码永无bug。


timg (3).jpg




版权声明:本文由星尘阁原创出品,转载请注明出处!

本文链接:http://www.52xingchen.cn/detail/52




赞助本站,网站的发展离不开你们的支持!
来说两句吧
大侠留个名吧,或者可以使用QQ登录。
: 您已登陆!可以继续留言。
最新评论