代碼質量管理工具:SonarQube常見的問題及正確解決方案

SonarQube 簡介

Sonar 是一個用於代碼質量管理的開放平臺。通過插件機制,Sonar 可以集成不同的測試工具,代碼分析工具,以及持續集成工具。

與持續集成工具(例如 Hudson/Jenkins 等)不同,Sonar 並不是簡單地把不同的代碼檢查工具結果(例如 FindBugs,PMD 等)直接顯示在 Web 頁面上,而是通過不同的插件對這些結果進行再加工處理,通過量化的方式度量代碼質量的變化,從而可以方便地對不同規模和種類的工程進行代碼質量管理。

在對其他工具的支持方面,Sonar 不僅提供了對 IDE 的支持,可以在 Eclipse 和 IntelliJ IDEA 這些工具裡聯機查看結果;同時 Sonar 還對大量的持續集成工具提供了接口支持,可以很方便地在持續集成中使用 Sonar。

此外,Sonar 的插件還可以對 Java 以外的其他編程語言提供支持,對國際化以及報告文檔化也有良好的支持


代碼質量管理工具:SonarQube常見的問題及正確解決方案


Math operands should be cast before assignment-數字操作在操作或賦值前要分配

對整數執行算術運算時,結果將始終是整數。您可以通過自動類型轉換將該結果分配給long,double或float類型,但是以int或long形式開始時,結果可能不會達到您的期望。

例如,如果將int除法的結果分配給浮點變量,則在分配之前將失去精度。同樣,如果將乘法結果分配給long,則在分配之前它可能已經溢出。

不合規代碼

<code>float twoThirds = 2/3; // Noncompliant; int division. Yields 0.0
long millisInYear = 1_000*3_600*24*365; // Noncompliant; int multiplication. Yields 1471228928
long bigNum = Integer.MAX_VALUE + 2; // Noncompliant. Yields -2147483647
long bigNegNum = Integer.MIN_VALUE-1; //Noncompliant, gives a positive result instead of a negative one.
Date myDate = new Date(seconds * 1_000); //Noncompliant, won't produce the expected result if seconds > 2_147_483
...
public long compute(int factor){
return factor * 10_000; //Noncompliant, won't produce the expected result if factor > 214_748
}

public float compute2(long factor){
return factor / 123; //Noncompliant, will be rounded to closest long integer
}/<code>

合規代碼

<code>float twoThirds = 2f/3; // 2 promoted to float. Yields 0.6666667
long millisInYear = 1_000L*3_600*24*365; // 1000 promoted to long. Yields 31_536_000_000
long bigNum = Integer.MAX_VALUE + 2L; // 2 promoted to long. Yields 2_147_483_649

long bigNegNum = Integer.MIN_VALUE-1L; // Yields -2_147_483_649
Date myDate = new Date(seconds * 1_000L);
...
public long compute(int factor){
return factor * 10_000L;
}

public float compute2(long factor){
return factor / 123f;
}

或者是
float twoThirds = (float)2/3; // 2 cast to float
long millisInYear = (long)1_000*3_600*24*365; // 1_000 cast to long
long bigNum = (long)Integer.MAX_VALUE + 2;
long bigNegNum = (long)Integer.MIN_VALUE-1;
Date myDate = new Date((long)seconds * 1_000);
...
public long compute(long factor){
return factor * 10_000;
}

public float compute2(float factor){
return factor / 123;
}/<code>


分析

本項sonar 規則,主要是Java 中 小類型可以向大的類型轉換,如int 可以自動向long 轉換。避免這種自動轉換引發的問題,就是本規則的初衷。

讓開發者可以明確的清楚當前數據的類型。

Strings and Boxed types should be compared using "equals()"

字符串和包裝類型對比時應該使用equals方法。

使用引用相等==或!=比較java.lang.String或包裝類型(如java.lang.Integer)的兩個實例幾乎總是false,因為它不是在比較實際值,而是在內存中的位置。

不合格的代碼

<code>String firstName = getFirstName(); // String overrides equals
String lastName = getLastName();

if (firstName == lastName) { ... }; // Non-compliant; false even if the strings have the same value/<code>

合規的代碼

<code>String firstName = getFirstName();
String lastName = getLastName();

if (firstName != null && firstName.equals(lastName)) { ... };/<code>

分析

在Java 中包裝類型與基本數據類型存儲位置不同。

Java 基本數據類型存放位置

  • 方法參數、局部變量存放在棧內存中的棧楨中的局部變量表
  • 常量存放在常量池中

包裝類型如Integer存放位置

  • 常量池
  • 堆內存

Integer 存儲在常量池中時可以使用==對比,但當在堆內存中時,使用==對比,實際對比的是兩個內存地址而非值。

根據Integer源碼,

代碼質量管理工具:SonarQube常見的問題及正確解決方案

可以看出數值在-128-127時,會使用cache中的數據,其實也就是常量池。超過範圍後新創建Integer,此時數據就無法使用==。

本項規則,主要就是為了避免對比內存地址而引發的錯誤判斷。

Boxing and unboxing should not be immediately reversed

裝箱(創建int/Integer類型值的對象)和拆箱(將對象中原始值解出來)不應連續操作。

由於在裝箱和拆箱期間原始值保持不變,因此在不需要時進行任何操作都是沒有意義的。這也適用於自動裝箱和自動拆箱(當Java為您隱式處理原始/對象轉換時)。


不合規代碼

<code>public void examineInt(int a) {
//...
}

public void examineInteger(Integer a) {
// ...
}

public void func() {
int i = 0;
Integer iger1 = Integer.valueOf(0);

double d = 1.0;

int dIntValue = new Double(d).intValue(); // Noncompliant

examineInt(new Integer(i).intValue()); // Noncompliant; explicit box/unbox
examineInt(Integer.valueOf(i)); // Noncompliant; boxed int will be auto-unboxed

examineInteger(i); // Compliant; value is boxed but not then unboxed
examineInteger(iger1.intValue()); // Noncompliant; unboxed int will be autoboxed

Integer iger2 = new Integer(iger1); // Noncompliant; unnecessary unboxing, value can be reused
}/<code>

合規代碼

<code>public void examineInt(int a) {
//...
}

public void examineInteger(Integer a) {
// ...
}

public void func() {
int i = 0;
Integer iger1 = Integer.valueOf(0);
double d = 1.0;

int dIntValue = (int) d;

examineInt(i);

examineInteger(i);
examineInteger(iger1);
}/<code>

分析

拆箱,與裝箱數值沒有發生變化,但在大數據量前提下是極其浪費時間。以下實例中,兩者耗時相差10倍。此項目檢查主要是提高性能。

<code> /<code>

Intermediate Stream methods should not be left unused

中間流方法不應該閒置,應該提供對應的終端操作(流操作有兩種類型:中間操作(返回另一個流)和終端操作(返回比流更多的內容)。中間操作是惰性的,如果中間流操作的結果沒有提供給終端操作,那麼它就沒有任何作用)

不合規

<code>widgets.stream().filter(b -> b.getColor() == RED); // Noncompliant
/<code>

合規

<code>int sum = widgets.stream()
.filter(b -> b.getColor() == RED)
.mapToInt(b -> b.getWeight())
.sum();
Stream<widget> pipeline = widgets.stream()
.filter(b -> b.getColor() == GREEN)
.mapToInt(b -> b.getWeight());
sum = pipeline.sum();/<widget>/<code>


Loops with at most one iteration should be refactored

循環執行一次應該重構。

不合規

<code>for (int i = 0; i < 10; i++) { // noncompliant, loop only executes once
printf("i is %d", i);

break;
}
...
for (int i = 0; i < 10; i++) { // noncompliant, loop only executes once
if(i == x) {
break;
} else {
printf("i is %d", i);
return;
}
}/<code>

不合規

<code>for (int i = 0; i < 10; i++) {
printf("i is %d", i);
}
...
for (int i = 0; i < 10; i++) {
if(i == x) {
break;
} else {
printf("i is %d", i);
}
}/<code>

Non-thread-safe fields should not be static

非線程安全的屬性不能設置為靜態

不合規

<code>public class MyClass {
private static SimpleDateFormat format = new SimpleDateFormat("HH-mm-ss"); // Noncompliant
private static Calendar calendar = Calendar.getInstance(); // Noncompliant/<code>


合規

<code>public class MyClass {
private SimpleDateFormat format = new SimpleDateFormat("HH-mm-ss");

private Calendar calendar = Calendar.getInstance();/<code>

分析

線程不安全的類型設置為靜態後,對於靜態變量來說,類在加載的時候會佔用同一個存儲區,而每個線程都是公用這個存儲區的,因此存在線程安全的問題。

在多併發的過程中容易產生問題,而且問題原因不易跟蹤。


"InterruptedException" should not be ignored

絕不應該在代碼中忽略InterruptedExceptions,在這種情況下,只需將異常計數記錄為“忽略”即可。拋出InterruptedException會清除Thread的中斷狀態,因此,如果未正確處理該異常,則該線程被中斷的事實將丟失。相反,應該立即或在清除方法狀態後重新拋出InterruptedExceptions-或應該通過調用Thread.interrupt()重新中斷線程,即使這應該是單線程應用程序也是如此。任何其他措施可能會導致線程關閉延遲,並丟失該線程被中斷的信息-可能未完成其任務。


合規代碼

<code>InterruptedExceptions should never be ignored in the code, and simply logging the exception counts in this case as "ignoring". The throwing of the InterruptedException clears the interrupted state of the Thread, so if the exception is not handled properly the fact that the thread was interrupted will be lost. Instead, InterruptedExceptions should either be rethrown - immediately or after cleaning up the method's state - or the thread should be re-interrupted by calling Thread.interrupt() even if this is supposed to be a single-threaded application. Any other course of action risks delaying thread shutdown and loses the information that the thread was interrupted - probably without finishing its task.

public void run () {
try {
while (true) {
// do stuff
}
}catch (InterruptedException e) { // Noncompliant; logging is not enough
LOGGER.log(Level.WARN, "Interrupted!", e);
}
}/<code>


不合規

<code>public void run () {
try {
while (true) {
// do stuff
}
}catch (InterruptedException e) {
LOGGER.log(Level.WARN, "Interrupted!", e);
// Restore interrupted state...
Thread.currentThread().interrupt();
}
}/<code>

總結

sonarqube 進行代碼質量檢查,不僅可以分析當前代碼已存在問題。也可以通過問題進行分析,把錯誤的代碼習慣,改正。

長期使用sonarqube,可以培養開發者寫優秀代碼。降低bug率。


分享到:


相關文章: