代码扫描常见问题盘点-风格/格式/OOP

作者:FIN技术铺

文章分类:研发技术工具/SONAR扫描    投稿日期: 2024-7-18

SONAR系统基于项目配置的扫描规则识别交付代码当中存在的问题。本文针对日常扫描环节经常出现的问题进行梳理,盘点日常用户支持过程中发现典型案例,向用户解释为什么会被识别以及识别之后如何修改的问题。文章从风格&格式类、OOP类、集合类等典型场景进行归类,盘点各类场景下的典型问题,针对被规则命中的问题进行解释、基于示例说明原因并且给出解决办法。在与用户进行讨论过程当中,有意识就问题的根本原因进行分析记录,并摘选成文,供编码参考。

01风格&格式类

1. 类名使用UpperCamelCase风格(AO/BO/DO/DTO/VO/PO/UID等专有类除外)

【解释】:驼峰命令格式在各种规范当中多次强调,不再重复赘述,介绍一下规范当中提到的专有类的命名规范及用途:

  1. DO( Data Object):与数据库表结构直接对应的数据对象。通常使用ORM(Object-Relational Mapping)工具如Hibernate、MyBatis等进行数据库操作。
  2. DTO( Data Transfer Object):用于在应用程序的不同层之间传输数据的对象。它通常用于封装从数据库检索的数据,然后将其发送到前端或其他服务。。
  3. BO( Business Object):封装业务逻辑的对象。它通常包含与特定业务功能相关的数据和操作。
  4. VO( View Object):用于在前端显示的对象。它通常包含前端模板所需的所有数据。
  5. AO (Application Object) : 在Web层和Service层之间传递的对象,用于抽象和复用与展示层紧密相关的数据模型。
  6. POJO:普通的Java对象,不包含特定的规则或约束。在上述上下文中,POJO可以泛指DO、DTO、BO、VO等。
  • 业务方法禁止使用getXxx,setXxx命名,容易和字段的getter,setter混淆,同时一些JSON框架在将对象转换成字符串时,会将getXxx()方法误输出成JSON字段

【解释】

  1. 混淆问题:Java 和很多其他面向对象编程语言中,getXxx 和 setXxx 通常是用于表示属性的 getter 和 setter 方法的。如果你的业务方法也使用这样的命名方式,那么其他开发者在阅读或维护代码时可能会混淆,错误地认为这些方法只是简单的 getter 或 setter,而不是具有实际业务逻辑的方法。
  2. JSON 序列化问题:当使用某些 JSON 框架(如 Jackson、Gson 等)将对象转换为 JSON 字符串时,这些框架通常会查找并使用对象的 getter 方法来获取属性值。如果业务方法被命名为 getXxx,那么 JSON 框架可能会错误地将其识别为 getter 方法,并在序列化过程中调用它。这不仅可能导致不必要的开销,还可能导致输出的 JSON 数据与预期不符。

2. POJO 类中布尔类型变量都不要加 is 前缀,否则部分框架解析会引起序列化错误

【解释】:在Java编程中,POJO(Plain Old Java Object)是一个简单的Java类,用于表示数据结构。在使用POJO类时,确实需要注意布尔类型(boolean)变量的命名。许多Java开发者和框架都遵循一种命名约定,即布尔类型的getter方法通常以"is"开头。但是,如果布尔类型变量的字段名也以"is"开头,这可能会导致一些问题,尤其是在序列化和反序列化过程中。

【问题原因】

        当使用某些框架(如Jackson、Gson等)进行对象序列化或反序列化时,这些框架会依赖getter和setter方法来访问对象的属性。如果布尔类型变量的字段名以"is"开头,并且getter方法也遵循标准的"is"前缀,这可能会导致混淆。框架可能会错误地认为getter方法对应的字段名不包含"is"前缀,从而在序列化和反序列化过程中出现错误。

【示例】

考虑以下POJO类:

public class User {

private boolean isAdmin;


public boolean isAdmin() {

return isAdmin;

}


public void setAdmin(boolean isAdmin) {

this.isAdmin = isAdmin;

}

}

在这个例子中,isAdmin字段和它的getter方法isAdmin()都以"is"开头。如果使用某些框架来序列化和反序列化这个类的对象,可能会出现问题。例如,使用Jackson库将User对象转换为JSON时,可能会得到以下结果:

{

"admin": false

}

注意,JSON中的键是"admin"而不是"isAdmin"。这是因为Jackson在处理布尔类型的getter方法时,会去掉"is"前缀来推断字段名。由于字段名本身也以"is"开头,这导致推断出的字段名不正确。

【解决办法】

为了避免这种问题,可以采取以下措施:

  • 不要给布尔类型的变量字段名加"is"前缀。例如,可以将isAdmin改为admin。
  • 如果字段名已经包含"is"前缀,可以在getter方法中显式使用@JsonProperty注解来指定序列化时使用的名称。例如:
import com.fasterxml.jackson.annotation.JsonProperty;


public class User {

private boolean isAdmin;


@JsonProperty("isAdmin")

public boolean isAdmin() {

return isAdmin;

}


public void setAdmin(boolean isAdmin) {

this.isAdmin = isAdmin;

}

}

这样,无论字段名如何,序列化和反序列化过程都会按照@JsonProperty注解中指定的名称进行。

02 OOP

1. 避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问即可

【解释】:

  1. 增加JVM成本:每创建一个对象实例,JVM都需要在堆内存中为其分配空间。如果通过对象实例来访问静态变量或方法(这些静态成员是属于类的,而不是属于具体对象的),就会无谓地增加内存消耗。
  2. 检索效率:当通过对象实例访问静态成员时,JVM需要先去堆内存中检索这个对象实例,然后再通过这个实例去访问静态成员。而直接使用类名访问静态成员时,JVM只需要去方法区(Metaspace)中查找这个类,然后直接访问其静态成员,效率更高。

2. 比较两个对象的值是否相等用equals方法,禁止使用==

【解释】:在Java中,==操作符和equals()方法都可以用来比较两个对象,但它们的行为是不同的。

1). ==操作符

==操作符用于比较两个对象的引用是否相等,即它们是否指向内存中的同一个对象。

如果两个引用指向同一个对象,==将返回true;否则返回false。

2). equals()方法

equals()方法是Object类的一个方法,可以被所有Java对象继承和使用。

equals()方法的默认实现与==操作符相同,即比较两个引用是否指向同一个对象。

但是,许多类(如String, Integer, ArrayList等)都重写了equals()方法,以便比较对象的“值”是否相等,而不是比较它们的引用是否相等。

3. 浮点数之间的等值判断,基本数据类型(如:float)不能用==来比较,包装数据类型(如:Float)不能用equals来判断

【解释】:为什么基本数据类型(如:float)不能用==来比较?

计算机内部表示浮点数时使用的是IEEE 754标准,该标准规定了浮点数的表示方式。简单来说,一个浮点数被表示为三部分:符号位、指数部分和尾数部分。这种表示方法会导致某些十进制小数不能被精确表示。例如:

float a = 0.1f; 

float b = 0.1f * 0.1f * 10; 

System.out.println(a == b); // 输出:false

在上面的例子中,尽管从数学上看a和b应该是相等的,但由于浮点数的精度问题,它们在计算机内部的表示并不完全相同,因此a == b的结果为false。

【解释】为什么包装数据类型(如:Float)不能用equals来判断?

对于包装类(如Float、Double),其equals()方法实际上是调用了其对应的基本数据类型的==操作符。所以,对于Float的实例来说,使用equals()方法和使用==操作符具有相同的问题。例如:

Float f1 = new Float(0.1f); 

Float f2 = new Float(0.1f * 0.1f * 10); 

System.out.println(f1.equals(f2)); // 输出:false

在上面的例子中,由于浮点数的精度问题,f1和f2的值并不相等。

4. 为了防止精度损失,禁止使用构造方法 BigDecimal(double)的方式把 double 值转化为 BigDecimal 对象

【解释】:BigDecimal(double)存在精度损失风险,在精确计算或值比较的场景中可能会导致业务逻辑异常。

如:BigDecimal g = new BigDecimal(0.1f); 实际的存储值为:0.10000000149,BigDecimal(0.1)这货实际上等于0.1000000000000000055511151231257827021181583404541015625,因为准确的来说0.1本身不能算是一个double(其实0.1不能代表任何一个定长二进制分数)。

5. 循环拼接字符串请使用StringBuilder的append方法,不要使用字符串相加

解释】:减少对象的构建过程,builder用的是char[]实现。减少堆中不必要对象的创建过程。

下例中,反编译出的字节码文件显示每次循环都会 new 出一个 StringBuilder 对象,然后进行 append 操作,最后通过 toString 方法返回 String 对象,造成内存资源浪费。

例如:

String str = "start";

for (int i = 0; i < 100; i++) {

str = str + "hello";

}

5. 使用lombok插件,子类必须显式声明@EqualsAndHashCode(callSuper = true)

解释】:当使用 Lombok 的 @EqualsAndHashCode 注解时,它会自动为你的类生成 equals() 和 hashCode() 方法。但是,在继承体系中,子类如何与超类(父类)的 equals() 和 hashCode() 方法互动是一个需要特别注意的问题。

默认情况下,如果子类只使用 @EqualsAndHashCode 注解而不进行任何配置,那么生成的 equals() 和 hashCode() 方法只会考虑子类的字段,而不会考虑超类的字段。这可能会导致一些不期望的行为。

例如,考虑以下类定义:
 

@EqualsAndHashCode 

class Parent { 

    private int parentField; 

} 


@EqualsAndHashCode 

class Child extends Parent { 

    private int childField; 

}

在这种情况下,Child 类的 equals() 和 hashCode() 方法只会基于 childField 字段。这意味着,即使两个 Child 对象的 parentField 值不同,它们仍然可能被视为相等。这显然是不正确的。

为了避免这种情况,Lombok 要求在子类中显式声明 @EqualsAndHashCode(callSuper = true)。这将确保在生成 equals() 和 hashCode() 方法时,子类会同时考虑自己的字段和超类的字段。

修正后的代码示例如下:

@EqualsAndHashCode 

class Parent { 

    private int parentField; 

} 

@EqualsAndHashCode(callSuper = true) 

class Child extends Parent { 

    private int childField; 

}

在这个修正后的示例中,Child 类的 equals() 和 hashCode() 方法会同时考虑 parentField 和 childField 字段,从而得到正确的相等性检查。

03 结语

限于篇幅原因,问题盘点计划按照多个批次内容进行输出,针对Sonar用户日常扫描过程当中识别的5大类场景常见的问题进行盘点,针对问题的原因进行分析总结,结合具体实现案例帮忙大家理解并且在日常的工作中规避这些问题。

  • 31
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值