代碼質量管理工具:SonarQube常見的問題及正確解決方案
SonarQube 簡介
Sonar 是一個用於代碼質量管理的開放平台。通過插件機制,Sonar 可以集成不同的測試工具,代碼分析工具,以及持續集成工具。
與持續集成工具(例如 Hudson/Jenkins 等)不同,Sonar 並不是簡單地把不同的代碼檢查工具結果(例如 FindBugs,PMD 等)直接顯示在 Web 頁面上,而是通過不同的插件對這些結果進行再加工處理,通過量化的方式度量代碼質量的變化,從而可以方便地對不同規模和種類的工程進行代碼質量管理。
在對其他工具的支持方面,Sonar 不僅提供了對 IDE 的支持,可以在 Eclipse 和 IntelliJ IDEA 這些工具里聯機查看結果;同時 Sonar 還對大量的持續集成工具提供了接口支持,可以很方便地在持續集成中使用 Sonar。
此外,Sonar 的插件還可以對 Java 以外的其他編程語言提供支持,對國際化以及報告文檔化也有良好的支持

1."@RequestMapping" 方法應為“ public”
將調用具有@Controller注釋的類的@RequestMapping注釋部分的方法(直接或間接通過元注釋-Spring Boot的@RestController是一個很好的示例)來處理匹配的Web請求。 即使該方法是私有的,也會發生這種情況,因為Spring會通過反射調用此類方法,而不檢查可見性。
因此,將敏感方法標記為私有似乎是控制如何調用此類代碼的好方法。 不幸的是,並非所有的Spring框架都以這種方式忽略可見性。 例如,如果您試圖通過將其標記為@Secured來控制對敏感,私有@RequestMapping方法的Web訪問,則無論用戶是否被授權訪問它,它仍將被調用。 這是因為AOP代理不適用於非公開方法。
除了@RequestMapping之外,此規則還考慮了Spring Framework 4.3中引入的注釋:@ GetMapping,@ PostMapping,@ PutMapping,@ DeleteMapping,@ PatchMapping。
2.默認軟件包中不應使用“ @SpringBootApplication”和“ @ComponentScan”
@ComponentScan用於確定哪些Spring Bean在應用程序上下文中可用。 可以使用basePackageClasses或basePackages(或其別名值)參數來配置要掃描的軟件包。 如果未配置任何參數,則@ComponentScan將僅考慮帶有注釋的類的程序包。 在屬於默認包的類上使用@ComponentScan時,將掃描整個類路徑。
這將減慢應用程序的啟動速度,並且該應用程序可能無法啟動BeanDefinitionStoreException,因為您最終掃描了Spring Framework軟件包本身。
在以下情況下,此規則會引起問題:
@ ComponentScan,@ SpringBootApplication和@ServletComponentScan用於默認包的類
@ComponentScan已使用默認程序包顯式配置
不兼容代碼示例
import org.springframework.boot.SpringApplication;
@SpringBootApplication //不合規; RootBootApp在默認包中聲明
public class RootBootApp {
...
}
@ComponentScan("")
public class Application {
...
}
兼容解決方案
package hello;
import org.springframework.boot.SpringApplication;
@SpringBootApplication //合規 RootBootApp屬於“ hello”包
public class RootBootApp {
...
}
3.不應使用雙重檢查鎖定
雙重檢查鎖定是在輸入同步塊之前和之后檢查延遲初始化對象的狀態,以確定是否初始化該對象。
如果不對float或int以外的任何可變實例進行額外同步,則無法以獨立於平台的方式可靠地工作。使用延遲初始化的雙重檢查鎖定任何其他類型的原始或可變對象風險第二個線程使用未初始化或部分初始化成員第一個線程仍然是創建它時,程序崩潰。
有多種解決方法。最簡單的方法是根本不使用雙重檢查鎖定,而是同步整個方法。對於早期版本的JVM,出於性能原因,通常建議不要同步整個方法。但是,在新的JVM中,同步性能已大大提高,因此,現在這是首選的解決方案。如果您希望完全避免使用同步,則可以使用內部靜態類來保存引用。內部靜態類保證延遲加載。
不兼容代碼示例
@NotThreadSafe //線程不安全
public class DoubleCheckedLocking {
private static Resource resource;
public static Resource getInstance() {
if (resource == null) {//2
synchronized (DoubleCheckedLocking.class) {
if (resource == null)//1
resource = new Resource(); //第一個線程還沒創建完,只是分配了內存,指了引用,線程執行上面,會判斷resource != null,最終導致程序崩潰
}
}
return resource;
}
static class Resource {
}
}
兼容解決方案
@ThreadSafe
public class SafeLazyInitialization {
private static Resource resource;
public static synchronized Resource getInstance() {
if (resource == null)
resource = new Resource();
return resource;
}
static class Resource {
}
}
@ThreadSafe
public class ResourceFactory {
private static class ResourceHolder {
public static Resource resource = new Resource(); // This will be lazily initialised
}
public static Resource getResource() {
return ResourceFactory.ResourceHolder.resource;
}
static class Resource {
}
}
class ResourceFactory {
private volatile Resource resource;
public Resource getResource() {
Resource localResource = resource;
if (localResource == null) {
synchronized (this) {
localResource = resource;
if (localResource == null) {
resource = localResource = new Resource();
}
}
}
return localResource;
}
static class Resource {
}
}
4.資源應該關閉
在使用后,需要關閉實現Closeable接口或其超級接口AutoCloseable的連接,流,文件和其他類。 此外,必須在finally塊中進行關閉調用,否則異常可能使調用無法進行。 最好在類實現AutoCloseable時,應使用“ try-with-resources”模式創建資源並將其自動關閉。
無法正確關閉資源將導致資源泄漏,這可能首先導致應用程序崩潰,然后可能使應用程序崩潰。
不兼容代碼示例
private void readTheFile() throws IOException {
Path path = Paths.get(this.fileName);
BufferedReader reader = Files.newBufferedReader(path, this.charset);
// ...
reader.close(); // 不合規
// ...
Files.lines("input.txt").forEach(System.out::println); // 不合規:需要關閉流
}
private void doSomething() {
OutputStream stream = null;
try {
for (String property : propertyList) {
stream = new FileOutputStream("myfile.txt"); // 不合規
// ...
}
} catch (Exception e) {
// ...
} finally {
stream.close(); //打開了多個流。 僅最后一個關閉。
}
}
兼容解決方案
private void readTheFile(String fileName) throws IOException {
Path path = Paths.get(fileName);
try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
reader.readLine();
// ...
}
// ..
try (Stream<String> input = Files.lines("input.txt")) {
input.forEach(System.out::println);
}
}
private void doSomething() {
OutputStream stream = null;
try {
stream = new FileOutputStream("myfile.txt");
for (String property : propertyList) {
// ...
}
} catch (Exception e) {
// ...
} finally {
stream.close();
}
}
Java 7引入了try-with-resources語句,該語句隱式關閉Closeables。 在try-with-resources語句中打開的所有資源都被該規則忽略。
4.“Random”對象應重復使用
每次需要一個隨機值時,創建一個新的Random對象都是效率低下的,並且可能生成取決於JDK的非隨機數。 為了獲得更好的效率和隨機性,請創建一個隨機數,然后存儲並重新使用它。
Random()構造函數每次嘗試為種子設置一個不同的值。 但是,不能保證種子將是隨機的,甚至是均勻分布的。 一些JDK將當前時間用作種子,這使得生成的數字根本不是隨機的。
該規則查找每次調用方法並將其分配給局部隨機變量時都會創建新的Random的情況。
不兼容代碼示例
public void doSomethingCommon() {
Random rand = new Random(); // 不合規; 每次調用都創建一個新實例
int rValue = rand.nextInt();
//...
兼容解決方案
private Random rand = SecureRandom.getInstanceStrong(); // SecureRandom優先於Random
public void doSomethingCommon() {
int rValue = this.rand.nextInt();
//...
5.不再使用時應清除“ ThreadLocal”變量
一旦保持線程不再存在,就應該對ThreadLocal變量進行垃圾回收。 當重新使用保持線程時,可能會發生內存泄漏,在使用線程池的應用程序服務器上就是這種情況。
為避免此類問題,建議始終使用remove()方法清除ThreadLocal變量,以刪除ThreadLocal變量的當前線程值。
另外,調用set(null)刪除值可能會在映射中保留對該指針的引用,這在某些情況下可能導致內存泄漏。 使用remove可以更安全地避免此問題。
不兼容代碼示例
public class ThreadLocalUserSession implements UserSession {
private static final ThreadLocal<UserSession> DELEGATE = new ThreadLocal<>();
public UserSession get() {
UserSession session = DELEGATE.get();
if (session != null) {
return session;
}
throw new UnauthorizedException("User is not authenticated");
}
public void set(UserSession session) {
DELEGATE.set(session);
}
public void incorrectCleanup() {
DELEGATE.set(null); // //不合規
}
// some other methods without a call to DELEGATE.remove()
}
兼容解決方案
public class ThreadLocalUserSession implements UserSession {
private static final ThreadLocal<UserSession> DELEGATE = new ThreadLocal<>();
public UserSession get() {
UserSession session = DELEGATE.get();
if (session != null) {
return session;
}
throw new UnauthorizedException("User is not authenticated");
}
public void set(UserSession session) {
DELEGATE.set(session);
}
public void unload() {
DELEGATE.remove(); // 合規
}
// ...
}
6.字符串和包裝類型應使用“equals()"進行比較
使用引用相等==或!=比較java.lang.String或包裝類型(如java.lang.Integer)的兩個實例幾乎總是一個錯誤,因為它不是在比較實際值,而是在內存中的位置。
不兼容代碼示例
String firstName = getFirstName(); // String overrides equals
String lastName = getLastName();
if (firstName == lastName) { ... }; //不合規; 即使字符串具有相同的值,也為false
兼容解決方案
String firstName = getFirstName();
String lastName = getLastName();
if (firstName != null && firstName.equals(lastName)) { ... };
在Java 中包裝類型與基本數據類型存儲位置不同。
Java 基本數據類型存放位置
- 方法參數、局部變量存放在棧內存中的棧楨中的局部變量表
- 常量存放在常量池中
包裝類型如Integer存放位置
- 常量池
- 堆內存
Integer 存儲在常量池中時可以使用對比,但當在堆內存中時,使用對比,實際對比的是兩個內存地址而非值。
根據Integer源碼,
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-nUMu95xG-1588123019546)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1583733976376.png)]
可以看出數值在-128-127時,會使用cache中的數據,其實也就是常量池。超過范圍后新創建Integer,此時數據就無法使用==。
本項規則,主要就是為了避免對比內存地址而引發的錯誤判斷。
7.“ compareTo”不應重載
在實現Comparable
當實現Comparable
不兼容代碼示例
public class Foo {
static class Bar implements Comparable<Bar> {
public int compareTo(Bar rhs) {
return -1;
}
}
static class FooBar extends Bar {
public int compareTo(FooBar rhs) { //不合規參數類型必須為Bar
return 0;
}
}
}
兼容解決方案
public class Foo {
static class Bar implements Comparable<Bar> {
public int compareTo(Bar rhs) {
return -1;
}
}
static class FooBar extends Bar {
public int compareTo(Bar rhs) {
return 0;
}
}
}
8.周年("YYYY")不應用於日期格式
當使用SimpleDateFormat格式化和解析日期時,很少有開發人員會意識到“周年”的Y和“年”的y之間的區別。 這很可能是因為對於大多數日期而言,“周年”和“年”是相同的,因此在除該年的第一周或最后一周之外的任何時間進行測試,都會得到y和Y相同的值。但是在12月的最后一周和 一月的第一周,您可能會得到意想不到的結果。
不兼容代碼示例
Date date = new SimpleDateFormat("yyyy/MM/dd").parse("2015/12/31");
String result = new SimpleDateFormat("YYYY/MM/dd").format(date); //Noncompliant; yields '2016/12/31'
兼容解決方案
Date date = new SimpleDateFormat("yyyy/MM/dd").parse("2015/12/31");
String result = new SimpleDateFormat("yyyy/MM/dd").format(date); //Yields '2015/12/31' as expected
異常
Date date = new SimpleDateFormat("yyyy/MM/dd").parse("2015/12/31");
String result = new SimpleDateFormat("YYYY-ww").format(date); //compliant, 'Week year' is used along with 'W
9.裝箱和拆箱不應連續操作
裝箱是將原始值放入類似對象的過程,例如創建一個Integer來保存一個int值。 拆箱是從此類對象中檢索原始值的過程。
由於在裝箱和拆箱期間原始值保持不變,因此在不需要時進行任何操作都是沒有意義的。 這也適用於自動裝箱和自動拆箱(當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.Boxed "Boolean" should be avoided in boolean expressions
在布爾表達式中應避免使用裝箱的“布爾”
如果將裝箱的類型java.lang.Boolean用作表達式,則如Java語言規范§5.1.8取消裝箱轉換中所定義的,如果該值為null,則它將拋出NullPointerException。
完全避免這種轉換並顯式處理null值是更安全的。
不兼容代碼示例
Boolean b = getBoolean();
if (b) { // Noncompliant, 當b為null,回拋NPE
foo();
} else {
bar();
}
兼容解決方案
Boolean b = getBoolean();
if (Boolean.TRUE.equals(b)) { //注意這塊寫法
foo();
} else {
bar(); // will be invoked for both b == false and b == null
}
11.Math should not be performed on floats
對於較小的數字,浮點數學具有足夠的精度來產生期望值,但對於較大的數字則不能。 BigDecimal是最好的選擇,但是如果需要基元,則使用double。
不兼容代碼示例
float a = 16777216.0f;
float b = 1.0f;
float c = a + b; // Noncompliant; yields 1.6777216E7 not 1.6777217E7
double d = a + b; // Noncompliant; addition is still between 2 floats
兼容解決方案
float a = 16777216.0f;
float b = 1.0f;
BigDecimal c = BigDecimal.valueOf(a).add(BigDecimal.valueOf(b));
double d = (double)a + (double)b;
12.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();
線程不安全的類型設置為靜態后,對於靜態變量來說,類在加載的時候會占用同一個存儲區,而每個線程都是公用這個存儲區的,因此存在線程安全的問題。
在多並發的過程中容易產生問題,而且問題原因不易跟蹤。
13.“ java.time”類應用於日期和時間
年代久遠的Date和Calendar類一直令人困惑,難以正確使用,尤其是在多線程環境中。 長期以來,JodaTime一直是一種流行的選擇,但現在內置了一個更好的選擇。 Java 8的JSR 310實現為以下提供了特定的類:
| Class | Use for |
|---|---|
| LocalDate | a date, without time of day, offset, or zone |
| LocalTime | the time of day, without date, offset, or zone |
| LocalDateTime | the date and time, without offset, or zone |
| OffsetDate | a date with an offset such as +02:00, without time of day, or zone |
| OffsetTime | the time of day with an offset such as +02:00, without date, or zone |
| OffsetDateTime | the date and time with an offset such as +02:00, without a zone |
| ZonedDateTime | the date and time with a time zone and offset |
| YearMonth | a year and month |
| MonthDay | month and day |
| Year/MonthOfDay/DayOfWeek/... | classes for the important fields |
| DateTimeFields | stores a map of field-value pairs which may be invalid |
| Calendrical | access to the low-level API |
| Period | a descriptive amount of time, such as "2 months and 3 days" |
不兼容代碼示例
Date now = new Date(); // Noncompliant
DateFormat df = new SimpleDateFormat("dd.MM.yyyy");
Calendar christmas = Calendar.getInstance(); // Noncompliant
christmas.setTime(df.parse("25.12.2020"));
兼容解決方案
LocalDate now = LocalDate.now(); // 獲取日歷日期。 沒有時間成分
LocalTime now2 = LocalTime.now(); // 獲取當前時間。 沒有日期成分
LocalDate christmas = LocalDate.of(2020,12,25);
14."static" members should be accessed statically(“靜態”成員應靜態訪問)
盡管可以從類實例訪問靜態成員,但是這種形式很差,並且大多數人認為這具有誤導性,因為它向您的代碼讀者暗示每個類實例都有一個成員實例。
不兼容代碼示例
public class A {
public static int counter = 0;
}
public class B {
private A first = new A();
private A second = new A();
public void runUpTheCount() {
first.counter ++; // Noncompliant
second.counter ++; // Noncompliant. A.counter is now 2, which is perhaps contrary to expectations
}
}
兼容解決方案
public class A {
public static int counter = 0;
}
public class B {
private A first = new A();
private A second = new A();
public void runUpTheCount() {
A.counter ++; // Compliant
A.counter ++; // Compliant
}
}
15."InterruptedException" should not be ignored
絕不應該在代碼中忽略InterruptedExceptions,在這種情況下,只需將異常計數記錄為“忽略”即可。拋出InterruptedException會清除Thread的中斷狀態,因此,如果未正確處理該異常,則該線程被中斷的事實將丟失。相反,應該立即或在清除方法狀態后重新拋出InterruptedExceptions-或應該通過調用Thread.interrupt()重新中斷線程,即使這應該是單線程應用程序也是如此。任何其他措施可能會導致線程關閉延遲,並丟失該線程被中斷的信息-可能未完成其任務。
不兼容代碼示例
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();
}
}
16.“ getClass”不應用於同步
getClass不應該用於非final類的同步,因為子類將在不同於父類或其他類的對象上進行同步,從而允許多個線程同時進入代碼塊,盡管synchronized關鍵字是這樣的。
相反,硬編碼要同步或使類成為final的類的名稱。
不兼容代碼示例
public class MyClass {
public void doSomethingSynchronized(){
synchronized (this.getClass()) { //不合規
// ...
}
}
兼容解決方案
public class MyClass {
public void doSomethingSynchronized(){
synchronized (MyClass.class) { //合規
// ...
}
}
17.Constructor injection should be used instead of field injection(應該使用構造函數注入而不是字段注入)
字段注入似乎是一種讓類完成它們的工作所需的整潔方法,但它實際上是一個等待發生的NullPointerException,除非所有的類構造函數都是私有的。這是因為由調用者構造的任何類實例,而不是由符合JSR-330 (Spring, Guice,…)的依賴注入框架實例化的,將不能執行字段注入。
相反,應該將@Inject移動到構造函數中,並將所需的字段作為構造函數參數。
當具有非私有構造函數(包括默認構造函數)的類使用字段注入時,此規則會引發問題。
不兼容代碼示例
class MyComponent { // 任何人都可以調用默認構造函數
@Inject MyCollaborator collaborator; // Noncompliant
public void myBusinessMethod() {
collaborator.doSomething(); // 這將在調用者新創建的類中失敗
}
}
兼容解決方案
class MyComponent {
private final MyCollaborator collaborator;
@Inject
public MyComponent(MyCollaborator collaborator) {
Assert.notNull(collaborator, "MyCollaborator must not be null!");
this.collaborator = collaborator;
}
public void myBusinessMethod() {
collaborator.doSomething();
}
}
18.“ volatile”變量不應與復合運算符一起使用
在原始字段上使用復合運算符以及遞增和遞減(在布爾值的情況下進行切換)不是原子操作。 也就是說,它們不會一步一步發生。 例如,當易失性原語字段遞增或遞減時,如果線程在更新步驟中交織,則存在數據丟失的風險。 而是使用保證原子的類(例如AtomicInteger)或同步訪問。volatile只能保證可見性,不行保證原子性
不兼容代碼示例
private volatile int count = 0;
private volatile boolean boo = false;
public void incrementCount() {
count++; // Noncompliant
}
public void toggleBoo(){
boo = !boo; // Noncompliant
}
兼容解決方案
private AtomicInteger count = 0;
private boolean boo = false;
public void incrementCount() {
count.incrementAndGet();
}
public synchronized void toggleBoo() {
boo = !boo;
}
19.".equals()" should not be used to test the values of "Atomic" classes
AtomicInteger和AtomicLong擴展Number,但它們與Integer和Long不同,因此應以不同的方式處理。 AtomicInteger和AtomicLong旨在支持對單個變量進行無鎖的線程安全編程。 這樣,AtomicInteger將永遠只與自身“相等”。 相反,您應該.get()值並對其進行比較。
這適用於所有原子的,看似原始的包裝器類:AtomicInteger,AtomicLong和AtomicBoolean。
不兼容代碼示例
AtomicInteger aInt1 = new AtomicInteger(0);
AtomicInteger aInt2 = new AtomicInteger(0);
if (aInt1.equals(aInt2)) { ... } // Noncompliant
兼容解決方案
AtomicInteger aInt1 = new AtomicInteger(0);
AtomicInteger aInt2 = new AtomicInteger(0);
if (aInt1.get() == aInt2.get()) { ... }
20."toArray"應傳遞適當類型的數組
如果沒有給出任何參數,Collections.toArray方法將返回一個Object [],如果您嘗試將其強制轉換為適當類的數組,則它將在運行時導致ClassCastException。 而是將正確類型的數組傳遞給調用。
不兼容代碼示例
public String [] getStringArray(List<String> strings) {
return (String []) strings.toArray(); // Noncompliant; ClassCastException thrown
}
兼容解決方案
public String [] getStringArray(List<String> strings) {
return strings.toArray(new String[0]);
}
21.JEE applications should not "getClassLoader"
使用標准的getClassLoader()可能無法在JEE上下文中返回正確的類加載器。 相反,請通過currentThread。
不兼容代碼示例
ClassLoader cl = this.getClass().getClassLoader(); // Noncompliant
兼容解決方案
ClassLoader cl = Thread.currentThread().getContextClassLoader();
22.“ StringBuilder”和“ StringBuffer”不應使用字符實例化
用字符實例化StringBuilder或StringBuffer會產生誤導,因為大多數Java開發人員都希望字符是StringBuffer的初始值。
實際發生的是,字符的int表示用於確定StringBuffer的初始大小。
不兼容代碼示例
StringBuffer foo = new StringBuffer('x'); //相當於StringBuffer foo = new StringBuffer(120);
兼容解決方案
StringBuffer foo = new StringBuffer("x");
23.Blocks should be synchronized on "private final" fields
對類字段進行同步不是對字段本身進行同步,而是對分配給它的對象進行同步。因此,在非final字段上進行同步可以使字段的值在線程處於與舊值同步的塊中時發生更改。這將允許在新值上同步的第二個線程同時進入該塊。
在參數同步方面,情況非常類似;兩個並行運行該方法的不同線程可以將兩個不同的對象實例作為參數傳遞給該方法,這完全破壞了同步。
不兼容代碼示例
private String color = "red";
private void doSomething(){
synchronized(color) { // 不合規; 鎖實際上在由顏色變量引用的對象實例“紅色”上
//...
color = "green"; // 現在允許其他線程進入該塊
// ...
}
synchronized(new Object()) { // 不合規,這是禁止操作。
// ...
}
}
兼容解決方案
private String color = "red";
private final Object lockObj = new Object();//重點
private void doSomething(){
synchronized(lockObj) {
//...
color = "green";
// ...
}
}
總結
SonarQube 進行代碼質量檢查,不僅可以分析當前代碼已存在問題。也可以通過問題進行分析,把錯誤的代碼習慣,改正。
長期使用SonarQube,可以培養開發者寫優秀代碼。降低bug率
微信公眾號

