IDEA 阿里巴巴 P3C Java開發手冊 [MD]


博文地址

我的GitHub 我的博客 我的微信 我的郵箱
baiqiantao baiqiantao bqt20094 baiqiantao@sina.com

目錄

Java開發手冊

P3C是世界知名的反潛機,專門對付水下潛水艇,項目組使用此名稱寓意是掃描出所有潛在的代碼隱患

規則

Forty-nine rules are realized based on PMD, Four rules are implemented within IDE plugins (IDEA and Eclipse) as follows:

  • [Mandatory] Using a deprecated class or method is prohibited 禁止.
  • [Mandatory] An overridden method from an interface or abstract class must be marked with @Override annotation.
  • [Mandatory] A static field or method should be directly referred by its class name instead of its corresponding object name.
  • [Mandatory] The usage of hashCode and equals should follow:
    • Override hashCode if equals is overridden.
      • 如果重寫了equals,必須重寫hashCode
      • 如果equals為true,則hashCode一定相同
      • 既然equals為false,hashCode也有可能相同
      • 如果hashCode不相同,那么equals一定為false
    • These two methods must be overridden for Set since they are used to ensure that no duplicate 重復 object will be inserted in Set.
      • 因為Set存儲的是不重復的對象,依據 hashCode equals 進行判斷,所以Set存儲的對象必須重寫這兩個方法。
    • These two methods must be overridden if self-defined 自定義 object is used as the key of Map. Note: String can be used as the key of Map since these two methods have been rewritten.
      • 如果自定義對象做為Map的鍵,那么必須重寫 hashCode equals

Mandatory:必須

IDEA 插件

目前插件實現了開發手冊中的的53條規則,大部分基於PMD實現,其中有4條規則基於IDEA實現,並且基於Inspection實現了實時檢測功能。部分規則實現了Quick Fix功能。

目前插件檢測有兩種模式:實時檢測、手動觸發。

實時檢測

實時檢測功能會在開發過程中對當前文件進行檢測,並以高亮的形式提示出來,同時也可以支持Quick Fix,該功能默認開啟,可以通過配置關閉。

  • 檢測結果高亮提示,並且鼠標放上去會彈出提示信息。
  • Alt+Enter鍵可呼出Intention菜單,不同的規則會提示不同信息的Quick Fix按鈕
  • 如果我們不希望對代碼提示違規信息,可以通過Inspection的設置關閉實時檢測功能。
    • 通過菜單【關閉/打開實時檢測功能】關閉/打開所有規則的實時檢測功能
    • 通過File -> Settings -> Editor -> Inspections -> Ali-Check對每一個規則進行自由設置,比如可以關閉某條規則的實時檢測功能或者修改提示級別。

PS:我覺得里面最惡心的一條規則是:不能在行尾添加注釋!

提示內容:方法內部單行注釋,在被注釋語句上方另起一行,使用//注釋。方法內部多行注釋使用/* */注釋。注意與代碼對齊。

代碼掃描

可以通過右鍵菜單、Toolbar按鈕、快捷鍵三種方式手動觸發代碼檢測。同時結果面板中可以對部分實現了QuickFix功能的規則進行快速修復。

觸發方式

  • 在當前編輯的文件中點擊右鍵,可以在彈出的菜單中觸發對該文件的檢測。
  • 在左側的Project目錄樹種點擊右鍵,可以觸發對整個工程或者選擇的某個目錄、文件進行檢測。
  • 也可以通過Toolbar中的按鈕來觸發檢測,目前Toolbar的按鈕觸發的檢測范圍與您IDE當時的焦點有關,如當前編輯的文件或者是Project目錄樹選中的項。
  • 使用快捷鍵觸發彈出窗口,會讓你選擇檢測范圍。

掃描結果

  • 檢測結果直接使用IDEA Run Inspection By Name功能的結果界面,插件的檢測結果分級為Blocker、Critical、Major,默認按等級分組。
    • Blocker(崩潰):阻礙開發或測試工作的問題
    • Critical(嚴重):系統主要功能部分喪失、數據庫保存調用錯誤、用戶數據丟失
    • Major(一般):功能沒有完全實現但是不影響使用,功能菜單存在缺陷但不會影響系統穩定性
  • 默認情況需要雙擊具體違規項才能打開對應的源文件,開啟Autoscroll To Source選項后單擊面板中的文件名、或者是具體的違規項的時候就可自動打開對應的源文件。
  • 對於實現Quick Fix的規則,在結果面板中可以直接一鍵修復。

其他說明

版本要求:

  • 最低支持IDEA版本為14.1(buildNumber 141.0)
  • 最低支持JDK版本為1.7

設置入口:

  • 頂部工具欄圖標
  • 主菜單 Tools -> 阿里編碼規約
  • 右鍵菜單(在最下面)
  • 搜索Action【阿里】

代碼提交時檢測

  • 在提交代碼框勾選Alibaba Code Guidelines選項
  • 如果有違反手冊的地方會提示是否繼續提交,選擇取消后會自動對修改的代碼進行掃描

中文亂碼解決方法

  • 將字體設置成中文字體,如微軟雅黑(microsoft yahei light)
  • 主菜單 Tools -> 阿里編碼規約 -> 切換語言

P3C-PMD

插件詳細信息可以在以下路徑查閱:File -> Settings -> Editor -> Inspections -> Ali-Check

檢測內容簡要說明

Ali-Check 全部檢測內容:

  • 【】ArrayList的subList結果不可強轉成ArrayList,否則會拋出ClassCastException異常。
  • iBATIS自帶的queryForList(String statementName,int start,int size)不推薦使用
  • 【】long或者Long初始賦值時,必須使用大寫的L,不能是小寫的l,小寫容易跟數字1混淆,造成誤解。
  • 【-】Map/Set的key為自定義對象時,必須重寫 hashCode equals。
  • 【】Object的equals方法容易拋空指針異常,應使用常量或確定有值的對象來調用 equals。
  • 【-】POJO類中的任何布爾類型的變量,都不要加is,否則部分框架解析會引起序列化錯誤
  • 【-】POJO類必須寫toString()方法。如果繼承了另一個POJO類,注意在前面加一下super.toString()
  • 【-】SimpleDateFormat 是線程不安全的類,一般不要定義為static變量,如果定義為static,必須加鎖,或者使用DateUtils工具類。
    • 如果是JDK8的應用,可以使用Instant代替Date,LocalDateTime代替Calendar,DateTimeFormatter代替SimpleDateFormat
    • 官方給出的解釋:simple beautiful strong immutable thread-safe
  • 【】不允許任何魔法值(即未經定義的常量)直接出現在代碼中。
  • 【】不能使用過時的類或方法。
    • 接口提供方既然明確是過時接口,那么有義務同時提供新的接口
    • 作為調用方來說,有義務去考證過時方法的新實現是什么
  • 【-】不能在finally塊中使用return,finally塊中的return返回后方法結束執行,不會再執行try塊中的return語句
  • 【】不要在foreach循環里進行元素的remove/add操作,remove元素請使用Iterator方式。
  • 【】中括號是數組類型的一部分,數組定義如下:String[] args
  • 事務場景中,拋出異常被catch后,如果需要回滾,一定要手動回滾事務。
  • 【--】使用CountDownLatch進行異步轉同步操作,每個線程退出前必須調用countDown()方法,線程執行代碼注意catch異常,確保countDown()方法可以執行,避免主線程無法執行至await方法,直到超時才返回結果。
    • 注意,子線程拋出異常堆棧,不能在主線程try-catch到
  • 【】使用工具類Arrays.asList()把數組轉換成集合時,不能使用其修改集合相關的方法,它的add/remove/clear方法會拋出UnsupportedOperationException異常。
  • 【-】使用集合轉數組的方法,必須使用集合的toArray(T[] array),傳入的是類型完全一樣的數組,大小就是list.size()
    • 反例:Integer[] a = (Integer[]) list.toArray();
    • 正例:Integer[] b = (Integer[]) list.toArray(new Integer[list.size()]);
  • 【-】基本數據類型與包裝數據類型的使用標准:所有的POJO類屬性必須使用包裝數據類型;RPC方法的返回值和參數必須使用包裝數據類型;所有的局部變量推薦使用基本數據類型。
    • POJO類屬性沒有初值是提醒使用者在需要使用時,必須自己顯式地進行賦值,任何NPE問題,或者入庫檢查,都由使用者來保證。
  • 【-】創建線程或線程池時請指定有意義的線程名稱,方便出錯時回溯。創建線程池的時候請使用帶ThreadFactory的構造函數,並且提供自定義ThreadFactory實現或者使用第三方實現。
  • 【】包名統一使用小寫,點分隔符之間有且僅有一個自然語義的英語單詞。包名統一使用單數形式,但是類名如果有復數含義,類名可以使用復數形式
  • 【】單個方法的總行數不超過80行。除注釋之外的方法簽名、結束右大括號、方法內代碼、空行、回車及任何不可見字符的總行數不超過80行。
  • 【】及時清理不再使用的代碼段或配置信息。對於垃圾代碼或過時配置,堅決清理干凈,避免程序過度臃腫,代碼冗余。
  • 后台輸送給頁面的變量必須加感嘆號,${var}——中間加感嘆號!。如果var=null或者不存在,那么${var}會直接顯示在頁面上。
  • 【】在if/else/for/while/do語句中必須使用大括號,即使只有一行代碼,避免使用下面的形式:if (condition) statements;
  • 【-】在subList場景中,高度注意對原列表的修改,會導致子列表的遍歷、增加、刪除均產生ConcurrentModificationException異常。
  • 【】在一個switch塊內,每個case要么通過break/return等來終止,要么注釋說明程序將繼續執行到哪一個case為止;在一個switch塊內,都必須包含一個default語句並且放在最后,即使它什么代碼也沒有。
  • 【】在使用正則表達式時,利用好其預編譯功能,可以有效加快正則匹配速度。
    • 不要在方法體內定義:Pattern pattern = Pattern.compile(規則);,應該定義為類屬性
  • 【-】在使用阻塞等待獲取鎖的方式中,必須在try代碼塊之外,並且在lock方法與try代碼塊之間沒有任何可能拋出異常的方法調用,避免加鎖成功后,在finally中無法解鎖。
    • 說明一:如果在lock方法與try代碼塊之間的方法調用拋出異常,那么無法解鎖(因為沒有try就不會執行finally),造成其它線程無法成功獲取鎖。
    • 說明二:如果lock方法在try代碼塊之內,可能由於其它方法拋出異常,導致在finally代碼塊中,執行unlock時對未加鎖的對象解鎖,拋出IllegalMonitorStateException異常。
    • 說明三:如果lock方法在try代碼塊之內,由於Lock對象的lock方法實現中可能拋出unchecked異常,產生與說明二相同的問題。
  • 【-】多線程並行處理定時任務時,Timer運行多個TimeTask時,只要其中之一沒有捕獲拋出的異常,其它任務便會自動終止運行,使用ScheduledExecutorService則沒有這個問題。
  • 【-】定義DO/DTO/VO等POJO類時,不要加任何屬性默認值
  • 【】對於Service和DAO類,基於SOA的理念,暴露出來的服務一定是接口,內部的實現類用Impl的后綴與接口區別
  • 【】常量命名應該全部大寫,單詞間用下划線隔開,力求語義表達完整清楚,不要嫌名字長
  • 【】異常類命名使用Exception結尾
  • 【】循環體內,字符串的聯接方式,使用StringBuilderappend方法進行擴展。
    • 反編譯出的字節碼文件顯示每次循環都會new出一個StringBuilder對象,然后進行append操作,最后通過toString方法返回String對象,造成內存資源浪費。
  • 【-】必須回收自定義的ThreadLocal變量,尤其在線程池場景下,線程經常會被復用,如果不清理自定義的 ThreadLocal 變量,可能會影響后續業務邏輯和造成內存泄露等問題。
  • 【】所有的包裝類對象之間值的比較,全部使用equals方法比較。
    • 對於Integer在[-128,128)之間的賦值,Integer對象是在IntegerCache.cache產生,會復用已有對象,這個區間內的Integer值可以直接使用==進行判斷
    • 但是[-128,128)這個區間之外的所有數據,都會在上產生,並不會復用已有對象,這是一個大坑,推薦使用equals方法進行判斷。
  • 【】所有的抽象方法(包括接口中的方法)必須要用javadoc注釋,除了返回值、參數、異常說明外,還必須指出該方法做什么事情,實現什么功能。
  • 【】所有的枚舉類型字段必須要有注釋,說明每個數據項的用途。
  • 【】所有的類都必須添加創建者信息。在設置模板時,注意IDEA的@author${USER},而eclipse為${user},大小寫有區別,而日期的設置統一為yyyy/MM/dd的格式。
  • 【】所有的覆寫方法,必須加@Override注解。加@Override可以准確判斷是否覆蓋成功。另外,如果在抽象類中對方法簽名進行修改,其實現類會馬上編譯報錯。
  • 【】所有編程相關的命名均不能以下划線或美元符號開始
  • 【】抽象類命名使用AbstractBase開頭
  • 【】方法內部單行注釋,在被注釋語句上方另起一行,使用//注釋。方法內部多行注釋使用/* */注釋。注意與代碼對齊。
  • 【】方法名、參數名、成員變量、局部變量都統一使用lowerCamelCase,必須遵從駝峰形式
  • 【】日期格式化字符串[%s]使用錯誤,應注意使用小寫y表示當天所在的年,大寫Y代表week in which year。
    • 日期格式化時,yyyy表示當天所在的年,而大寫的YYYY代表是week in which year(JDK7之后引入的概念),意思是當天所在的周屬於的年份,一周從周日開始,周六結束,只要本周跨年,返回的YYYY就是下一年。
  • 【】注意 Math.random() 這個方法返回是double類型,注意取值的范圍[0,1),如果想獲取整數類型的隨機數,不要將x放大10的若干倍然后取整,直接使用Random對象的nextInt或者nextLong方法。
  • 【】測試類命名以它要測試的類的名稱開始,以Test結尾
  • 【-】浮點數之間的等值判斷,基本數據類型不能用==來比較,包裝數據類型不能用equals來判斷。
    • 浮點數采用尾數+階碼的編碼方式,類似於科學計數法的有效數字+指數的表示方式,二進制無法精確表示大部分的十進制小數
  • 【-】禁止使用構造方法BigDecimal(double)的方式把double值轉化為BigDecimal對象
    • 反編譯出的字節碼文件顯示每次循環都會new出一個StringBuilder對象,然后進行append操作,最后通過toString方法返回String對象,造成內存資源浪費。
    • 反例:BigDecimal bad = new BigDecimal(0.1);
    • 正例:BigDecimal good = new BigDecimal("0.1");BigDecimal good = BigDecimal.valueOf(0.1);
  • 【】類、類屬性、類方法的注釋必須使用javadoc規范,使用/**內容*/格式,不得使用//xxx方式和/*xxx*/方式。
  • 【】類名使用UpperCamelCase風格,必須遵從駝峰形式,但以下情形例外:(領域模型的相關命名)DO / BO / DTO / VO / DAO
  • 【-】線程池不允許使用Executors去創建,而是通過ThreadPoolExecutor的方式,這樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。
    • FixedThreadPool和SingleThreadPool:由於允許的請求隊列長度為Integer.MAX_VALUE,可能會堆積大量的請求,從而導致OOM。
    • CachedThreadPool:由於允許的創建線程數量為Integer.MAX_VALUE,可能會創建大量的線程,從而導致OOM。
  • 【】線程資源必須通過線程池提供,不允許在應用中自行顯式創建線程。
    • 使用線程池的好處是減少在創建和銷毀線程上所花的時間以及系統資源的開銷,解決資源不足的問題。
    • 如果不使用線程池,有可能造成系統創建大量同類線程而導致消耗完內存或者過度切換的問題。
  • 【】獲取當前毫秒數:System.currentTimeMillis();而不是new Date().getTime();。如果想獲取更加精確的納秒級時間值,用System.nanoTime
  • 【】返回類型為基本數據類型,return包裝數據類型的對象時,(return null時)自動拆箱有可能產生NPE
  • 【】避免Random實例被多線程使用,雖然共享該實例是線程安全的,但會因競爭同一seed導致的性能下降。說明:Random實例包括java.util.Random的實例或者Math.random()的方式。
  • 避免用Apache Beanutils進行屬性的copy,因為性能較差。可以使用其他方案比如Spring BeanUtils, Cglib BeanCopier。
  • 【】避免通過一個類的對象引用訪問此類的靜態變量或靜態方法,無謂增加編譯器解析成本,直接用類名來訪問即可。
  • 【】避免采用取反邏輯運算符。取反邏輯不利於快速理解,並且取反邏輯寫法必然存在對應的正向邏輯寫法。
  • 【】除常用方法(如getXxx/isXxx)等外,不要在條件判斷中執行復雜的語句,將復雜邏輯判斷的結果賦值給一個有意義的布爾變量,以提高可讀性。
  • 【】集合初始化時,指定集合初始值大小。如果暫時無法確定集合大小,那么指定默認值16即可。

對一些規則的解釋

SimpleDateFormat

public class Test {
    private static final String FORMAT = "yyyy-MM-dd HH:mm:ss";
    private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    private static final ThreadLocal<DateFormat> DATE_FORMATTER = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

    public static void main(String[] args) {
        System.out.println(new SimpleDateFormat(FORMAT).format(new Date()));
        synchronized (SIMPLE_DATE_FORMAT) {
            System.out.println(SIMPLE_DATE_FORMAT.format(new Date()));
        }
        System.out.println(DATE_FORMATTER.get().format(new Date()));
    }
}

CountDownLatch

public void operate(CountDownLatch countDownLatch) {
    try {
        System.out.println("business logic");
    } catch (RuntimeException e) {
        // do something
    } finally {
        countDownLatch.countDown();
    }
}

ThreadFactory

ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build();
ExecutorService singleThreadPool = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
    new LinkedBlockingQueue<>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());

singleThreadPool.execute(() -> System.out.println(Thread.currentThread().getName()));
singleThreadPool.shutdown();

subList

List<String> originList = new ArrayList<>();
originList.add("22");
List<String> subList = originList.subList(0, 1);
originList.add("23");
System.out.println(subList.size()); //java.util.ConcurrentModificationException

lock 方法獲取鎖

正例

Lock lock = new XxxLock();
// ...
lock.lock(); // 如果在lock方法與try代碼塊之間的方法調用拋出異常,那么無法解鎖,造成其它線程無法成功獲取鎖
try {
	//doSomething
	//doOthers
} finally {
	lock.unlock();
}

反例

Lock lock = new XxxLock();
// ...
try {
	// If an exception is thrown here, the finally block is executed directly
	//doSomething
	lock.lock(); //如果lock方法在try之內,可能由於其它方法拋出異常,導致在finally中執行unlock時拋出異常
	//doOthers
} finally {
	// The finally block executes regardless of whether the lock is successful or not
	lock.unlock();
}

ThreadLocal

public class Test {
	public static void main(String[] args) {
		UserHolder.set(new User());
		User user = UserHolder.get();
		UserHolder.remove();
	}
}

class UserHolder {
	private static final ThreadLocal<User> userThreadLocal = new ThreadLocal<>();
	
	public static void set(User user) {
		userThreadLocal.set(user);
	}
	
	public static User get() {
		return userThreadLocal.get();
	}
	
	public static void remove() {
		userThreadLocal.remove();
	}
}

class User {
}

浮點數之間的等值判斷

方法一:指定一個誤差范圍,兩個浮點數的差值在此范圍之內,則認為是相等的

float a = 1.0f - 0.9f;
float b = 0.9f - 0.8f;
float diff = 1e-6f;

if (Math.abs(a - b) < diff) {
	System.out.println("true");
}

方法二:使用BigDecimal來定義值,再進行浮點數的運算操作

BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
BigDecimal c = new BigDecimal("0.8");

BigDecimal x = a.subtract(b);
BigDecimal y = b.subtract(c);

if (x.equals(y)) {
	System.out.println("true");
}

線程池

//org.apache.commons.lang3.concurrent.BasicThreadFactory
BasicThreadFactory factory = new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build();
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1, factory);
TimeUnit unit = TimeUnit.MILLISECONDS;
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(1024);
ThreadFactory factory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build();
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();

//Common Thread Pool
ExecutorService pool = new ThreadPoolExecutor(5, 200, 0L, unit, workQueue, factory, handler);
pool.execute(() -> System.out.println(Thread.currentThread().getName()));
pool.shutdown();//gracefully shutdown

Random

class RandomInThread extends Thread {
	//private final Random random = new Random();
	private final Random random = ThreadLocalRandom.current();
	
	@Override
	public void run() {
		System.out.println(random.nextLong());
	}
}

2020-05-28


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM