乐趣区

关于java:代码Bug太多给新人Code-Review头都大了快来试试SpotBugs

如果你须要一个自动化的工具帮忙你或者你的团队发现代码中的缺点,在晋升代码品质同时缩小人工 Code Review 的老本,那这篇文章十分的适宜你。本文围绕 SpotBugs 与 Gradle 集成,将相干配置和应用进行了具体介绍,并提供了各种可能为你的我的项目定制化配置的伎俩。起源和出处都已在文中要害处以超链接给出,纵情享受吧。

SpotBugs 是什么?

SpotBugs 是一个开源的 Java 动态剖析工具,旨在帮忙开发人员检测代码中的潜在缺点和破绽。SpotBugs 能够通过扫描 Java 字节码来发现潜在的问题,例如空指针援用、类型转换谬误、未应用的变量等等。它还能够检测代码中的潜在安全漏洞,例如 SQL 注入、XSS 攻打等。SpotBugs 提供了一个用户敌对的 GUI 和命令行接口,能够轻松地与各种构建工具和 IDE 集成,例如 Ant、Maven、Gradle、Eclipse 和 IntelliJ IDEA。SpotBugs 还反对插件和自定义规定,使得开发人员能够依据我的项目的特定需要和规范对其进行定制化配置。更多详细信息能够查看 SpotBugs 官网文档。

SpotBugs 是 FindBugs 的一个分支,它在 FindBugs 的根底上进行了改良和降级,它应用了更先进的算法和技术来进步剖析的准确性和效率。SpotBugs 还增加了对新的 Java 版本的反对,例如 Java 8 和 Java 11。SpotBugs 还提供了更好的用户界面和命令行界面,并反对更多的构建工具和 IDE 集成。

FindBugs 也是一款十分风行的 Java 动态剖析工具,然而曾经进行更新。停更的起因仿佛是我的项目拥有者不愿持续在这个我的项目上花工夫,代码贡献者因为没有权限也无奈持续迭代和公布新的版本,在 FindBugs GitHub 首页的 README 文件中提供了两个文档我的项目状态 2016-November 和我的项目状态 2017-September,外面讲述了代码贡献者过后的一些担心和无奈。

SpotBugs 与 Gradle 集成

开始之前先简略介绍下本文中将会提到的几个依赖以及它们之间的关系,以便于了解后续内容:

  • com.github.spotbugs.snom:spotbugs-gradle-plugin是一个 Gradle 插件,它将 SpotBugs 集成到 Gradle 构建中,生成 SpotBugsTask 并能够通过配置属性来扩大。
  • com.github.spotbugs:spotbugs这个依赖根本蕴含了所有 SpotBugs 检测器,这些检测器实现了 Bug descriptions 中提到的相干 Bug 检测项的逻辑。
  • com.h3xstream.findsecbugs:findsecbugs-plugincom.github.spotbugs:spotbugs 的根底上减少了平安相干的查看器也就是 Bug descriptions#Security 中形容的相关检查项。
  • com.github.spotbugs:spotbugs-annotations是 Spotbugs 的一个扩大 / 辅助工具,开发者应用这外面注解能够让 Spotbugs 依照咱们的用意来查看代码。

spotbugs-gradle-plugin

spotbugs-gradle-plugin是一个 Gradle 插件,它将 SpotBugs 集成到 Gradle 构建中,生成 SpotBugsTask 并提供相干配置来扩大,运行 SpotBugsTask 就能够执行查看,生成报告等。

默认状况下,这个插件外面曾经蕴含了一个 spotbugs,所以利用了这个插件后个别无需在另外增加com.github.spotbugs:spotbugs。从 SpotBugs version mapping 中能够晓得spotbugs-gradle-plugin 不同版本中默认蕴含的 spotbugs 版本之间的关系,比方:com.github.spotbugs.snom:spotbugs-gradle-plugin:5.0.13蕴含了com.github.spotbugs:spotbugs:4.7.3

SpotBugs Gradle 插件要求 Gradle 版本为 7.0 或者更高,JDK 版本为 1.8 或者更高。

这个插件会为每个 sourceSets 生成 SpotBugsTask。例如我的项目中有两个 sourceSets main 和 test,插件将生成两个 SpotBugsTask(spotbugsMain 和 spotbugsTest)。

如果不想主动生成 SpotBugsTask,能够应用 SpotBugs Base 插件从头开始配置,参考 com.github.spotbugs-base。

生成的 SpotBugsTask 执行时须要编译后的.class 文件作为输出,因而它们将在 Java 编译后运行。SpotBugs Gradle 减少了 check 相干的工作依赖,所以简略的运行 ./gradlew check 也会执行生成的 SpotBugsTask。

参考 com.github.spotbugs 在 build.gradle 文件中减少以下内容来引入 SpotBugs Gradle 插件:

buildscript {
  repositories {
    maven {url "https://plugins.gradle.org/m2/"}
  }
  dependencies {classpath "com.github.spotbugs.snom:spotbugs-gradle-plugin:5.0.13"}
}

apply plugin: "com.github.spotbugs"

插件外面提供了很多可选的属性来配置或者扩大插件的行为。

上面展现了在 build.gradle 文件减少 spotbugs{} 相干属性的例子,详细信息能够参考 SpotBugsExtension,这外面有每个属性作用、可指定的值和其余相干信息。spotbugs{}中指定的大部分属性将作为生成的 SpotBugsTask 配置的默认值,意味着能够通过 spotbugs{} 给所有 SpotBugsTask 配置属性。

spotbugs {
    ignoreFailures = false
    showStackTraces = true
    showProgress = false
    reportLevel = 'default'
    effort = 'default'
    visitors = ['FindSqlInjection', 'SwitchFallthrough']
    omitVisitors = ['FindNonShortCircuit']
    reportsDir = file("$buildDir/reports/spotbugs")
    includeFilter = file('spotbugs-include.xml')
    excludeFilter = file('spotbugs-exclude.xml')
    onlyAnalyze = ['com.foobar.MyClass', 'com.foobar.mypkg.*']
    projectName = name
    release = version
    extraArgs = ['-nested:false']
    jvmArgs = ['-Duser.language=ja']
    maxHeapSize = '512m'
}

除了上述形式外,还可间接配置 SpotBugsTask,以设置某个工作特定的属性,比方上面为名为 spotbugsMain 的工作独自进行了设置,跟 spotbugs{} 同名的属性将被笼罩,详细信息参考 SpotBugsTask。

spotbugsMain {
    sourceDirs = sourceSets.main.allSource.srcDirs
    classDirs = sourceSets.main.output
    auxClassPaths = sourceSets.main.compileClasspath
      reports {
        html {
            required = true
            outputLocation = file("$buildDir/reports/spotbugs/main/spotbugs.html")
            stylesheet = 'fancy-hist.xsl'
        }
    }

    ignoreFailures = false
    showStackTraces = true
    showProgress = false
    reportLevel = 'default'
    effort = 'default'
    visitors = ['FindSqlInjection', 'SwitchFallthrough']
    omitVisitors = ['FindNonShortCircuit']
    reportsDir = file("$buildDir/reports/spotbugs")
    includeFilter = file('spotbugs-include.xml')
    excludeFilter = file('spotbugs-exclude.xml')
    baselineFile = file('spotbugs-baseline.xml')
    onlyAnalyze = ['com.foobar.MyClass', 'com.foobar.mypkg.*']
    projectName = name
    release = version
    extraArgs = ['-nested:false']
    jvmArgs = ['-Duser.language=ja']
    maxHeapSize = '512m'
}

前面将会有一段形容比拟罕用或者重要的属性。

com.github.spotbugs:spotbugs

因为 spotbugs-gradle-plugin 中曾经蕴含了spotbugs,所以个别状况下不须要再独自引入这个依赖。不过可能因为默认带的版本有 Bug、须要跟 IDEA 的 SpotBugs 插件应用雷同的版本,又或者新版本的检测器新加了实用的检测项等起因咱们会须要独自指定版本,上面提供了两种形式实现。

在配置 spotbugs-gradle-plugin 的时候通过 toolVersion 指定 spotbugs 版本。

spotbugs {toolVersion = '4.7.3'}

dependencies 增加依赖并指定依赖版本。

dependencies {spotbugs 'com.github.spotbugs:spotbugs:4.7.3'}

findsecbugs-plugin

findsecbugs-plugin是 Spotbugs 的安全漏洞检测插件。它在 spotbugs 的根底上减少了本人的规定集,专一于检测平安相干的问题,例如明码透露、SQL 注入、XSS 等,也就是 Bug descriptions#Security 中形容的相关检查项。

dependencies {spotbugsPlugins 'com.h3xstream.findsecbugs:findsecbugs-plugin:1.12.0'}

spotbugs-annotations

spotbugs-annotations是 Spotbugs 的一个扩大 / 辅助工具,外面提供了很多注解,咱们能够在编码时将它们加在被检测代码的相应地位(如属性、办法、形参、本地变量),让 Spotbugs 依照咱们的用意来查看代码,以便 SpotBugs 能够更失当地收回正告。在 Annotations 中列举了所有注解,须要留神外面有很多曾经标记为 Deprecated 表明曾经弃用了。

比方上面这段代码,只是输入 value 到终端,即便 test()传入 null 也不会导致空指针。Spotbugs 原本不会收回正告,然而因为咱们在办法上加了注解edu.umd.cs.findbugs.annotations.NonNull,Spotbugs 会依照咱们的用意进行入参不容许为 null 的校验,从而收回正告。

import edu.umd.cs.findbugs.annotations.NonNull;

public class SpotbugsAnnotationsSample {public static void main(String[] args) {test(null);
    }
    
    public static void test(@NonNull Integer value) {
        // 输入到终端
        System.out.println(value);
    }
}

上面是引入这个依赖的例子,参考 Refer the version in the build script,这外面还提到从spotbugs v4 版本开始,spotbugs.toolVersionString 变为 Provider<String>,所以请应用 get() 或其余形式援用理论版本。

dependencies {compileOnly "com.github.spotbugs:spotbugs-annotations:${spotbugs.toolVersion.get()}"
}

SpotBugs Gradle 扩大属性

includeFilter 和 excludeFilter

过滤器(Filter file)文件能够定义匹配检测项、被查看的类和被查看办法的一套匹配规定,让咱们能够为我的项目做定制化。文件配置好后,应用 SpotBugsTask 提供的属性指定过滤器文件的门路,实现蕴含(includeFilter)或者排除(excludeFilter)匹配的内容。

文件是 xml 格局的,每个匹配规定独自写在 <Match></Match> 中,应用 Types of Match clauses 形容的各种标签组合成匹配规定的内容,在 Examples 中给出了很多例子作为参考。

咱们重点关注下 <Bug> 这个标签,它能够通 category、code、pattern 来指定 Bug descriptions 中列出来的查看项,多个参数用逗号分隔,也能够应用正则表达式,以 ~ 字符结尾。

每个查看项有惟一的 pattern,比方下图中的XSS_REQUEST_PARAMETER_TO_SEND_ERRORXSS_REQUEST_PARAMETER_TO_SERVLET_WRITER;多个查看项可能属于同一个 code,比方下图中的XSS;多个不同的code 可能属于同一个category。这三者之间是一个清晰的层级关系,这样就能够按不同的粒度来配置。

上面给出了一个应用 <Bug> 简略的例子和形容,更多例子请参考 Examples。

<!-- 匹配指定的这两个查看项 -->
<Match>
  <Bug pattern="XSS_REQUEST_PARAMETER_TO_SEND_ERROR,XSS_REQUEST_PARAMETER_TO_SERVLET_WRITER" />
</Match>

<!-- 匹配所有 code 为 XSS 的查看项 -->
<Match>
  <Bug code="XSS" />
</Match>

<!-- 匹配 SECURITY 目录下所有查看项 -->
<Match>
  <Bug category="SECURITY" />
</Match>

visitors 和 omitVisitors

这两个属性能够别离指定应用(visitors)和禁用(omitVisitors)查看器,一个查看器会蕴含一个或者多个 Bug descriptions 中提到的查看项,所以这两个属性配置的目标和 includeFilterexcludeFilter是一样的,让咱们能够为我的项目做定制化,只是从查看器的维度进行配置。

在 Detectors 中将 Spotbugs 所有的查看器都列出来了,在不配置的状况下,Standard detectors 中列出来的默认应用,Disabled detectors 中列出来的默认禁用。

以查看器 SynchronizationOnSharedBuiltinConstant 为例,从下图咱们能够看到,查看器名称上面有段简短的形容,再往下将查看器蕴含的查看项都列出来了,能够看到上文提到的 patterncode,点击能够跳转到 Filter file 文档相应的地位。咱们配置 visitorsomitVisitors的时候填查看器的名字 SynchronizationOnSharedBuiltinConstant 即可。

effort

effort是配置代码检测预期的级别,级别由低到高别离为 min、less、more、max,级别越高计算成本越高,破费的工夫也就越长,在这个文档 Effort 外面有表格清晰的列出了这几个级别别离蕴含了哪些查看内容。effort 的默认值是default,等同于more

jvmArgs

jvmArgs用于设置 JVM 参数,因为 SpotBugs 是 Java 写的,天然要在 JVM 上运行。咱们在例子外面看到了-Duser.language=ja,这个意思是设置语言为日语,也就是代码剖析的后果展现的语音(输入到终端或者报告中)。总共有英语(en 默认),日语(ja),法语(fr)三种,在 GitHub 中能够看到相干展现文本的配置文件。

maxHeapSize

maxHeapSize是设置 JVM 最大堆内存大小,在 spotbugsextension#maxHeapSizea 中说了默认值为空,因而会应用 Gradle 的默认配置,所以个别不必管。

onlyAnalyze

onlyAnalyze指定哪些代码要被 SpotBugs 剖析,在大型项目外面用这个属性防止没必要的剖析,可能会大大减少运行剖析所需的工夫。能够指定类名,或者包名,指定包名的时候用 .*.-的作用一样,意思是剖析这个包下以及子包中的文件。

onlyAnalyze = ['com.foobar.MyClass', 'com.foobar.mypkg.*']

reportLevel

SpotBugs 按重大水平高到低将 Bug 分为了三个等级 P1、P2 和 P3,reportLevel 属性指明达到哪个等级的 Bug 才须要展现在报告外面,它可配置值对应有 HIGH、MEDIUM、LOWDEFAULT,它们定义在 Confidence.groovy 中。默认值是 DEFAULT 等同于MEDIUM,意思是要达到 P2 级别,那就意味着低级别的 P3 Bug 将被疏忽,

reports

reports配置报告的类型,有 html、xml、sarif 和 text 四种,配置在 SpotBugsTask 中(比方spotbugsMain {}),再往里一层可配置的属性不是很多,参考 SpotBugsReport。上面是 html 类型报告的配置示例:

spotbugsMain {
    reports {
        html {
            required = true
            outputLocation = file("$buildDir/reports/spotbugs/main/spotbugs.html")
            stylesheet = 'fancy-hist.xsl'
        }
    }
}

最佳实际

配置

在根目录 gradle.properties 中配置版本。

spotbugsGradlePluginVersion=5.0.13
findsecbugsPluginVersion=1.12.0

在根目录 build.gradle 中减少以下配置:

buildscript {
    repositories {mavenLocal()
        maven {url "https://plugins.gradle.org/m2/"}
        mavenCentral()}
    dependencies {classpath "com.github.spotbugs.snom:spotbugs-gradle-plugin:${spotbugsGradlePlugin}"
    }
}

apply plugin: "com.github.spotbugs"

repositories {mavenLocal()
    maven {url "https://plugins.gradle.org/m2/"}
    mavenCentral()}

dependencies {compileOnly "com.github.spotbugs:spotbugs-annotations:${spotbugs.toolVersion.get()}"
    spotbugsPlugins "com.h3xstream.findsecbugs:findsecbugs-plugin:${findsecbugsPluginVersion}"
}

spotbugs {
    ignoreFailures = false
    showStackTraces = false
    showProgress = false
    excludeFilter = file("${project.rootDir}/code-analysis/spotbugs/exclude-filter.xml")
    extraArgs = ['-nested:false']
}

spotbugsMain {
    reports {
        html {
            required = true
            stylesheet = 'fancy-hist.xsl'
        }
    }
}

如果是多模块我的项目按这种形式配置:

buildscript {
    repositories {mavenLocal()
        maven {url "https://plugins.gradle.org/m2/"}
        mavenCentral()}
    dependencies {classpath "com.github.spotbugs.snom:spotbugs-gradle-plugin:${spotbugsGradlePlugin}"
    }
}

allprojects {
        apply plugin: "com.github.spotbugs"
        
    repositories {mavenLocal()
        maven {url "https://plugins.gradle.org/m2/"}
        mavenCentral()}

    dependencies {compileOnly "com.github.spotbugs:spotbugs-annotations:${spotbugs.toolVersion.get()}"
        spotbugsPlugins "com.h3xstream.findsecbugs:findsecbugs-plugin:${findsecbugsPluginVersion}"
    }

    spotbugs {
        ignoreFailures = false
        showStackTraces = false
        showProgress = false
        excludeFilter = file("${project.rootDir}/code-analysis/spotbugs/exclude-filter.xml")
        extraArgs = ['-nested:false']
    }

    spotbugsMain {
        reports {
            html {
                required = true
                stylesheet = 'fancy-hist.xsl'
            }
        }
    }
}

在我的项目根目录下创立排除查看项的文件/code-analysis/spotbugs/exclude-filter.xml,前期再依据须要配置。

<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter
        xmlns="https://github.com/spotbugs/filter/3.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="https://github.com/spotbugs/filter/3.0.0 https://raw.githubusercontent.com/spotbugs/spotbugs/3.1.0/spotbugs/etc/findbugsfilter.xsd">

</FindBugsFilter>

应用

在 Gradle 窗口中双击运行 spotbugsMain 工作即可,检测实现会在 Run 窗口中打印出报告地址。

也能够在 Terminal 窗口中执行 ./gradlew spotbugsMain,可能须要先执行chmod +x gradlew 给 gradlew 文件执行权限。

如果执行过程中产生相似上面这种异样能够不必管,只是有局部相干的剖析会无奈执行,并不会中断整个过程,其余不相干的局部都会失常查看实现。在 GitHub 上 issues#527 很多人反映了这个问题,然而临时没有完满的解决。

The following classes needed for analysis were missing:
apply
test
accept

解读报告

报告概要(Summary)按包和 BUG 等级统计了数量。

按目录浏览(Browse by Categories)将发现的缺点代码按查看项中的 category、code、pattern 分层展现。

按包名浏览(Browse by Packages)就是换了个角度,依照包名 > 类名 > 查看项 pattern 分层展现。

最初一个窗口 Info 展现剖析的代码文件(Analyzed Files)、剖析的源文件夹(Source Files)应用的依赖(Used Libraries)、应用到的 SpotBugs 插件(Plugins)和剖析中产生的异样(Analysis Errors)。

过滤配置

通过 includeFilterexcludeFilter指定过滤器(Filter file)文件是最灵便的,根本什么维度都能够管制。然而倡议这外面只配置要蕴含或者排除哪些查看项,应用 category、code、pattern 配置不同的粒度。

从整体放大被查看的代码范畴应用onlyAnalyze,如果有必要的话。如果要疏忽不查看具体的类或者办法能够应用注解 @SuppressFBWarnings 来标记,它来自spotbugs-annotations

其余技巧

SpotBugs Links 外面列出了跟 SpotBugs 集成或者类似的工具,有须要能够理解下。

参考

SpotBugs- 官网

SpotBugs- 官网文档

SpotBugs-SpotBugsTask

SpotBugs-SpotBugsExtension

Gradle-spotbugs

GitHub-SpotBugs

GitHub-FindBugs

GitHub-spotbugs-gradle-plugin

FindBugs- 官网

退出移动版