SonarQube质量报告提供项目代码质量的总体信息,通过点击报告可查看详情,包括代码质量是否合格及各项指标。文中还列举了SonarQube扫描的常见Bug和漏洞修复建议,如空指针异常处理、整数运算溢出解决、内存泄露防止、资源正确关闭、条件判断优化、类型比较修正、代码冗余删除、日志记录改进等,旨在提升代码质量和安全性。
SonarQube 质量报告基本概念
进入SonarQube质量首页,看到项目的代码质量总体信息,如下图
点击报告,可进入查看详情,
左侧是代码质量(是否合格),如果不合格会列出明细
右侧是各个指标,这些指标和上图一致,不再赘述,需要注意有两个标签,一个是“新增代码”一个是“全量代码”
SonarQube扫描常见Bug、漏洞修复
1、A "NullPointerException" could be thrown; "sra" is nullable here.
这种提示是指可能存在空指针异常,需要增加空值检测。 说明:未做非空校验,可能产生空指针 解决方案:加上非空校验 解决方式:先判断或者先实例化,再访问里面的属性或者成员。
2、Cast one of the operands of this multiplication operation to a "long"
说明:int数运算最终再把结果转为long将有可能产生溢出
解决方案:转换为long型预算 举例: long bigNum = Integer.MAX_VALUE + 2; // Noncompliant. Yields -2147483647 换为 long bigNum = Integer.MAX_VALUE + 2L;
3、Call "remove()" on "requestContainer".
说明:防止内存泄露溢出,ThreadLocal字段【requestContainer】应该至少调用一次remove()方法。
// 解决方案:定义删除方法 public void removeRequest() { requestContainer.remove(); }
4、Use try-with-resources or close this "FileInputStream" in a "finally" clause.
说明:使用try-with-resources或在 “finally” 子句中关闭此 “BufferedOutputStream”。
// 解决方案1:使用try-with-resources BufferedOutputStream out = null; try(BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(new File("C://test"))) { out.write(file.getBytes()); out.flush(); return Result.success("上传成功!", null); } catch (IOException e) { return Result.error("上传失败!"); }
// 解决方案2:“finally” 子句中关闭流 BufferedOutputStream out = null; try { out = new BufferedOutputStream(new FileOutputStream(new File("C://test")); out.write(file.getBytes()); out.flush(); return Result.success("上传成功!", null); } catch (IOException e) { return Result.error("上传失败!"); } finally { CloseIoUtils.closeAll(out); }
5、Change this condition so that it does not always evaluate to "false"
说明:checkInsertParam方法没有返回false的情况一直是true,所以条件判断后一直返回的结果为false。需要改条件判断或者添加方案中异常false情况返回。
6、Use the "equals" method if value comparison was intended.
说明:使用引用等式==或!=,比较java.lang.String或装箱类型(如java.lang.Integer)的两个实例几乎总是一个错误,因为它不是比较实际值,而是比较内存中的位置
解决:将 “==” 换成 equals 比较
7、Do something with the "boolean" value returned by "delete".
//解决方案:增加false判断 if (!csvFile.delete()) { log.error("文件删除失败"); }
8、Either re-interrupt this method or rethrow the "InterruptedException" that can be caught here.
//解决方案 try(){ //业务代码... }catch (InterruptedException e){ //抛出InterruptedException 异常需要重新清除线程的中断状态,添加如下 Thread.currentThread().interrupt(); }
9、Use "BigDecimal.valueOf" instead.
说明:由于浮点不精确,您不太可能从BigDecimal(double)构造函数中获得预期的值。
//解决方案 BigDecimal.valueOf((double) (Float) r)
10、Remove this "break" statement or make it conditional.
说明:移除break;或者把它放在条件中
11、Remove this conditional structure or edit its code blocks so that they're not all the same.
说明:写if else的时候,卡条件卡得离散了一点,本身可以合成一个的,结果写成了多级if,增加了程序的复杂度。
解决:去掉if else语句
12、Use try-with-resources or close this "XSSFWorkbook" in a "finally" clause.
问题分析:
连接、流、文件和其他类在使用后需要关闭。此外,必须在finally块中进行关闭调用,否则异常可能会阻止调用。最好是,当类实现自动关闭时,应该使用“try with resources”模式创建资源,并将自动关闭。
未能正确关闭资源将导致资源泄漏,这可能首先导致应用程序崩溃,然后可能导致应用程序崩溃。
解决:
在finally中关闭XSSFWorkbook
workbook.close();
13、Cast one of the operands of this addition operation to a "double".
问题分析:
当对整数执行算术运算时,结果总是整数。您可以通过自动类型转换将该结果指定给long、double或float,但如果以int或long开头,则结果可能不是您期望的结果。
例如,如果int除法的结果被赋给浮点变量,则在赋值之前精度将丢失。同样,如果乘法结果被分配给long,则它可能在分配之前已经溢出。
在这两种情况下,结果都不是预期的结果。相反,在操作发生之前,至少应将一个操作数强制转换或升级为最终类型。
解决:
转换为指定类型setCellValue需要的是Double类型的、将int计算后的转Double
//第一次修改,并没有通过、还增加了一个Bug cell.setCellValue(Double.valueOf(i + 1));//第二次修改 cell.setCellValue(Double.valueOf(i)+ 1);
14、Use the "equals" method if value comparison was intended.
问题分析:
使用引用等式==或!=,比较java.lang.String或装箱类型(如java.lang.Integer)的两个实例几乎总是一个错误,因为它不是比较实际值,而是比较内存中的位置。。
解决:
String类型判断空字符串可以使用.isEmpty()
pageKey.isEmpty()
15、Remove the boxing to "Double"; The argument is already of the same type.
问题分析:
装箱是将原语值放入类似对象的过程,例如创建一个整数来保存int值。取消装箱是从这样一个对象中检索原语值的过程。
由于原始值在装箱和取消装箱期间保持不变,因此在不需要时也没有必要这样做。这也适用于自动装箱和自动取消装箱(当Java隐式地为您处理原语/对象转换时)。
解决:
Boolean.valueOf //装箱 (Boolean) //装箱 Boolean.valueOf((Boolean) val) //两次装箱,多此一举 case "Double" : fields[i].set(t, (Double) cellVal);break;case "Float" : fields[i].set(t, (Float) cellVal);break;case "Boolean" : fields[i].set(t,(Boolean) cellVal);
16、Verify this is the key that was intended; it was already set before
问题分析:
当为键或索引保存值,然后无条件覆盖时,这是非常可疑的。这种替换很可能是错误的。
出现这个问题一般都是往集合中多次设置相同key的值,这会导致之前传入的被覆盖。
解决:
去掉一个put、set.....等
17、A "NullPointerException" could be thrown; "fwqxxMap" is nullable here.
问题分析:
没有判断当前map是否为空,直接get会导致NullPointerException。
解决:
先判断map是否为空。
if(fwqxxMap!=null){....}
18、Use an "instanceof" comparison instead.
问题分析:
不应该按照名称来比较类类型。
解决:
改为Class.isAssignableFrom()比较
clazz.isAssignableFrom(java.lang.Object.class)
19、Change this condition so that it does not always evaluate to "false"
问题分析:
始终为真或假的条件表达式可能导致死代码。这样的代码总是有缺陷的,不应该在生产中使用。inputStream在这里始终不为空、则
if(inputStream == null){//代码始终没有执行 }
解决:
去掉inputStream或者判断inputStream之前File是否为空。
if (f == null) {}
20、 Complete the task associated to this TODO comment.
问题分析:
TODO标记通常用于标记需要更多代码但开发人员希望稍后实现的地方。
有时开发人员没有时间,或者只是忘记返回到该标记。
此规则旨在跟踪这些标记,并确保它们不会被忽略。
解决:
将TODO的内容完善、说明类、方法是干什么的。
21、Provide the parametrized type for this generic.
问题分析:
泛型类型不应在变量声明或返回值中使用原始类型(没有类型参数)。这样做会绕过泛型类型检查,并将不安全代码的捕获延迟到运行时。
常见的有 List list;Map map;
解决:
提供泛型,例如List<String>,Map<String,Object>。
22、Rename "body" which hides the field declared at line 50.
问题分析:
重写或隐藏在外部作用域中声明的变量会严重影响代码的可读性,从而影响代码的可维护性。此外,这可能会导致维护人员引入bug,因为他们认为自己在使用一个变量,但实际上在使用另一个变量。
简单理解就是说这个变量有在别的地方定义、在这个局部又定义了一次,代码可读性差。
解决:
去掉局部声明的变量。
23、Replace charset name argument with StandardCharsets.UTF_8
问题分析:
JDK7引入了类java.nio.charset.StandardCharsets。它为保证在Java平台的每个实现上可用的所有字符集提供常量。
解决:
将"UTF-8"替换为StandardCharsets.UTF_8
24、Add a nested comment explaining why this method is empty, throw an UnsupportedOperationException or complete the implementation
问题分析:
方法没有方法体有几个原因:
这是无意的疏忽,应该加以纠正,以防止生产中出现意外行为。
它还没有或永远不会得到支持。在这种情况下,应抛出UnsupportedOperationException。
该方法是一个故意为空的重写。在这种情况下,嵌套注释应该解释空白覆盖的原因。
解决:
增加实现内容、抛出UnsupportedOperationException、嵌套注释应该解释空白覆盖的原因
25、Use the primitive boolean expression here.
问题分析:
当装箱类型java.lang.Boolean用作表达式时,如果值为null(如java语言规范§5.1.8取消装箱转换中所定义),它将抛出NullPointerException。完全避免这种转换并显式处理空值更安全。
解决:
Boolean.TRUE.equals(flag);
26、This block of commented-out lines of code should be removed.
问题分析:
程序员不应该注释掉代码,因为它会使程序膨胀并降低可读性。
应该删除未使用的代码,如果需要,可以从源代码管理历史记录中检索。
解决:
不需要、注释的代码需要删掉、如果实在需要保存将代码中每行最后的;去掉。
27、Define a constant instead of duplicating this literal "xxDm" 3 times.
问题分析:
重复的字符串文字使重构过程容易出错,因为必须确保更新所有出现的内容。
另一方面,常数可以从许多地方引用,但只需要在一个地方更新。
解决:
当一个类中存在重复的字符串,超过3次时、需要使用常量、或者枚举。
private final static String ZZJGDM = "zzjgDm"; params.put(ZZJGDM,getLoginOrganCode());
28、Refactor this method to reduce its Cognitive Complexity from 20 to the 15 allowed.
问题分析:
认知复杂性是一种衡量方法控制流理解难度的指标。认知复杂度高的方法将难以维护。
代码太复杂、for循环、if嵌套太多,难以理解。
解决:
抽象出方法、精简代码和逻辑。
29、Make this "public static CHARSET" field final
问题分析:
如果不将字段声明为“final”,则没有充分的理由将其声明为“public”和“static”。大多数情况下,这是在多个对象之间共享状态的一个难题。但通过这种方法,任何对象都可以对共享状态执行任何操作,例如将其设置为null。
解决:
这种“public static”字段应该成员变量应该是不变的,应该加上final修饰。
30、Remove this useless assignment to local variable "organslist".
问题分析:
当一个局部变量被分配了一个不被任何后续指令读取的值时,就会发生死区存储。计算或检索一个值,然后将其覆盖或丢弃,可能表明代码中存在严重错误。即使这不是一个错误,充其量也只是浪费资源。因此,应使用所有计算值。
解决:
移除声明的变量,因为该变量在上下文中并没有使用。
31、Reorder the modifiers to comply with the Java Language Specification.
问题分析:
Java语言规范建议按以下顺序列出修饰符:
Annotations
public
protected
private
abstract
static
final
transient
volatile
synchronized
native
strictfp
不遵循此约定没有技术影响,但会降低代码的可读性,因为大多数开发人员都习惯于标准顺序。
解决:
按照规范建议、对Java关键字重新排序。
32、Add a private constructor to hide the implicit public one.
问题分析:
实用程序类是静态成员的集合,不需要实例化。即使是可以扩展的抽象实用程序类也不应该有公共构造函数。
Java为每个类添加了一个隐式公共构造函数,而这些类至少没有显式定义一个。因此,应至少定义一个非公共构造函数。
解决:
定义一个私有的构造函数。
private ExcelImportUtil(){}
33、 Define and throw a dedicated exception instead of using a generic one.
问题分析:
使用诸如Error、RuntimeException、Throwable和Exception之类的通用异常可以防止调用方法以不同于应用程序生成错误的方式处理真实的系统生成异常。
通俗的讲就是不应该直接抛出Exception而是应该抛出具体的异常如IOE、NPE
解决:
抛出具体异常信息。
例如:
throws InstantiationException, IllegalAccessException, ParseException
34、Return an empty array instead of null.
问题分析:
返回null而不是实际的数组或集合会强制方法的调用方显式测试null性,从而使它们更复杂,可读性更低。
此外,在许多情况下,null被用作empty的同义词。
解决:
返回空的数组、Map、Set、List等不要直接返回null。
35、Iterate over the "entrySet" instead of the "keySet".
问题分析:
当循环中只需要映射中的键时,迭代键集是有意义的。但是,当同时需要键和值时,迭代entrySet更有效,它将提供对键和值的访问。
"entrySet()" should be iterated when both the key and value are needed;
通过查看源代码发现,调用方法keySetMap.keySet()会生成KeyIterator迭代器,其next方法只返回其key值,而调用entrySetMap.entrySet()方法会生成EntryIterator 迭代器,其next方法返回一个Entry对象的一个实例,其中包含key和value。
所以当我们用到key和value时,用entrySet迭代性能更高。
解决:
使用entrySet代替keySet。
36、Remove this unused private "getRequestParameters" method.
问题分析:
从不执行的私有方法是死代码:不必要的、不工作的代码,应该删除。清除死代码减少了维护的代码库的大小,使其更容易理解程序并防止引入bug。
请注意,此规则不考虑反射,这意味着仅使用反射API访问的私有方法会出现问题。
解决:
移除类中私有未被调用的方法。
37、Replace this use of System.out or System.err by a logger.
问题分析:
记录消息时,必须满足以下几个重要要求:
1.用户必须能够轻松检索日志
2.所有记录消息的格式必须统一,以允许用户轻松阅读日志
3.必须实际记录记录的数据
4.必须安全地记录敏感数据
如果程序直接写入标准输出,则绝对无法满足这些要求。这就是为什么强烈建议定义和使用专用记录器的原因。
解决:
移除System.out.println替换为log输出
System.out.println("My Message"); //改为 logger.log("My Message");
38、Invoke method(s) only conditionally.
问题分析:
解释晦涩难懂,好像map.toString存在性能问题。
解决:
直接移除掉logger.info。
39、Use isEmpty() to check whether the collection is empty or not.
问题分析:
使用Collection.size()测试空性是可行的,但使用Collection.isEmpty()可以使代码更可读,性能也更高。任何isEmpty()方法实现的时间复杂度都应该是O(1),而size()的一些实现可以是O(n)。
解决:
使用isEmpty()替换.size<=0。
40、Use static access with "org apache poi. ss.usermodel Cell" for "CELL TYPE STRING"
问题分析:
过期的。
替换一下。
解决:
使用currentCell.getCellTypeEnum() == CellType.STRING。
41、Move the array designator from the variable to the type.
问题分析:
数组指示符应始终位于类型上,以提高代码可读性。否则,开发人员必须同时查看类型和变量名,以了解变量是否为数组。
解决:
将[]从变量名后面放到变量名前面。
42、Replace the type specification in this constructor call with the diamond operator ("<>").
问题分析:
Java7引入了菱形操作符(<>),以减少泛型代码的冗长。例如,不必在列表的声明和构造函数中声明列表的类型,现在可以使用<>简化构造函数声明,编译器将推断类型。
请注意,当项目的sonar.java.source低于7时,此规则将自动禁用。
解决:
将后面泛型的内容去掉只留一个<>。
43、Immediately return this expression instead of assigning it to the temporary variable "swapStream".
问题分析:
声明一个变量只是为了立即返回或抛出它是一种不好的做法。
一些开发人员认为,这种做法提高了代码的可读性,因为它使他们能够显式地命名返回的内容。但是,此变量是一个内部实现细节,不会向方法的调用方公开。方法名称应足以让调用方确切知道将返回什么。
解决:
直接return new ByteArrayInputStream(baos.toByteArray());,而不是定义一个swapStream接收,然后返回swapStream。
return new ByteArrayInputStream(baos.toByteArray());
44、Replace this if-then-else statement by a single return statement.
问题分析:
将此if-then-else语句替换为单个return语句。
解决:
直接return 、不要定义变量接收再返回、不要使用三目再返回,本身>0结果就是true或false。
return customItemClassMapper.updateById(customItemClass) > 0;
45、This accessibility bypass should be removed
问题分析:
当反射用于更改类、方法或字段的可见性时,以及当反射用于直接更新字段值时,此规则会引发问题。更改或绕过类、方法或字段的可访问性违反了封装原则,并可能导致运行时错误。
解决:
使用 ReflectionUtils.setField替换。
fields[i].setAccessible(true); //使用 ReflectionUtils.makeAccessible(fields[i]); //替换//********************fields[i].set(t, str); //使用 ReflectionUtils.setField(fields[i],t,str); //替换
46、Add a default case to this switch.
问题分析:
switch只有case没有default。
解决:
给switch添加一个default:。
switch (name){ case "1" : "男";break;default: "未知" }
47、Remove this unnecessary cast to "Integer".
问题分析:
将此不必要的强制转换删除。
解决:
删除后合并代码。
case "Integer" :case "Double" :case "Float" :case "Boolean" :ReflectionUtils.setField(fields[i],t,cellVal);break;
48、Reduce the total number of break and continue statements in this loop to use at most one.
问题分析:
for循环中存在超过1个break或continue。
减少此循环中break和continue语句的总数,使其最多使用一个。
解决:
重构代码,进行判断、只保留一个break或continue。
推荐阅读