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

SonarQube 簡介

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

與持續集成工具(例如 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形式開始時,結果可能不會達到您的指望。app

例如,若是將int除法的結果分配給浮點變量,則在分配以前將失去精度。一樣,若是將乘法結果分配給long,則在分配以前它可能已經溢出。編程語言

不合規代碼ide

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
}

合規代碼工具

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;
}

 

分析oop

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

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

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

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

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

不合格的代碼

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

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

合規的代碼

String firstName = getFirstName();
String lastName = getLastName();

if (firstName != null && firstName.equals(lastName)) { ... };

分析

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

Java 基本數據類型存放位置

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

  • 常量存放在常量池中

包裝類型如Integer存放位置

  • 常量池

  • 堆內存

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

根據Integer源碼,

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

 

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

本項規則,主要就是爲了不對比內存地址而引起的錯誤判斷。

Boxing and unboxing should not be immediately reversed

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

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


不合規代碼
 

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
}

合規代碼

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);
}

分析

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

 

Intermediate Stream methods should not be left unused

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

不合規

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

合規

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();


Loops with at most one iteration should be refactored

循環執行一次應該重構。

不合規

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;
  }
}

不合規

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);
  }
}

Non-thread-safe fields should not be static

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

不合規

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


合規

public class MyClass {
  private SimpleDateFormat format = new SimpleDateFormat("HH-mm-ss");
  private Calendar calendar = Calendar.getInstance();

分析

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

在多併發的過程當中容易產生問題,並且問題緣由不易跟蹤。


"InterruptedException" should not be ignored

毫不應該在代碼中忽略InterruptedExceptions,在這種狀況下,只需將異常計數記錄爲「忽略」便可。拋出InterruptedException會清除Thread的中斷狀態,所以,若是未正確處理該異常,則該線程被中斷的事實將丟失。相反,應該當即或在清除方法狀態後從新拋出InterruptedExceptions-或應該經過調用Thread.interrupt()從新中斷線程,即便這應該是單線程應用程序也是如此。任何其餘措施可能會致使線程關閉延遲,並丟失該線程被中斷的信息-可能未完成其任務。

 

不合規代碼

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);
  }
}

 

合規

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

總結

sonarqube 進行代碼質量檢查,不只能夠分析當前代碼已存在問題。也能夠經過問題進行分析,把錯誤的代碼習慣,改正。

長期使用sonarqube,能夠培養開發者寫優秀代碼。下降bug率。

頭條號通常是首發。

頭條號:

公衆號

相關文章
相關標籤/搜索