本文是對阿里插件中規約的詳細解釋二,關於插件使用,請參考這里
- 及時清理不再使用的代碼段或配置信息。 說明:對於垃圾代碼或過時配置,堅決清理干凈,避免程序過度臃腫,代碼冗余
Positive example: For codes which are temporarily removed and likely to be reused, use /// to add a reasonable note. public static void hello() { /// Business is stopped temporarily by the owner. // Business business = new Business(); // business.active(); System.out.println("it's finished"); }
- 后台輸送給頁面的變量必須加感嘆號,${var}——中間加感嘆號!。 說明:如果var=null或者不存在,那么${var}會直接顯示在頁面上
<input type="text" name="email" value="$!email"/> <input type="text" name="email" value="$!{email}"/>
- 在if/else/for/while/do語句中必須使用大括號,即使只有一行代碼,避免使用下面的形式:if (condition) statements;
- 在subList場景中,高度注意對原列表的修改,會導致子列表的遍歷、增加、刪除均產生ConcurrentModificationException異常;
注:原因從源碼可以看出,當子list添加刪除元素時,也會相應添加刪除本list。但反之就會出現異常。至於Java為何要這樣設計,不得而知。
Negative example: List<String> originList = new ArrayList<String>(); originList.add("22"); List<String> subList = originList.subList(0, 1); //warn originList.add("22");
-
在一個switch塊內,每個case要么通過break/return等來終止,要么注釋說明程序將繼續執行到哪一個case為止;在一個switch塊內,都必須包含一個default語句並且放在最后,即使它什么代碼也沒有。
- 在使用正則表達式時,利用好其預編譯功能,可以有效加快正則匹配速度。 說明:不要在方法體內定義:Pattern pattern = Pattern.compile(規則);
注:為代碼效率考慮
public class XxxClass { // Use precompile private static Pattern NUMBER_PATTERN = Pattern.compile("[0-9]+"); public Pattern getNumberPattern() { // Avoid use Pattern.compile in method body. Pattern localPattern = Pattern.compile("[0-9]+"); return localPattern; } }
-
多線程並行處理定時任務時,Timer運行多個TimeTask時,只要其中之一沒有捕獲拋出的異常,其它任務便會自動終止運行,使用ScheduledExecutorService則沒有這個問題。
注:當處理多個並行任務時,如果需要線程異常不互相影響,用ScheduledExecutorService代替Timer//org.apache.commons.lang3.concurrent.BasicThreadFactory ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1, new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build()); executorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { //do something } },initialDelay,period, TimeUnit.HOURS);
- 定義DO/DTO/VO等POJO類時,不要加任何屬性默認值。(注:跟前面的用包裝類一脈相承)
- 對於Service和DAO類,基於SOA的理念,暴露出來的服務一定是接口,內部的實現類用Impl的后綴與接口區別
注:SOA:面向服務架構(Service-Oriented Architecture) - 常量命名應該全部大寫,單詞間用下划線隔開,力求語義表達完整清楚,不要嫌名字長
public class ConstantNameDemo { /** * max stock count */ public static final Long MAX_STOCK_COUNT = 50000L;
- 異常類命名使用Exception結尾
- 循環體內,字符串的聯接方式,使用StringBuilder的append方法進行擴展。
-
必須回收自定義的ThreadLocal變量,尤其在線程池場景下,線程經常會被復用,如果不清理自定義的 ThreadLocal變量,可能會影響后續業務邏輯和造成內存泄露等問題。盡量在代理中使用try-finally塊進行回收。
- 所有的包裝類對象之間值的比較,全部使用equals方法比較。
說明:對於Integer var=?在-128至127之間的賦值,Integer對象是在IntegerCache.cache產生,會復用已有對象,這個區間內的Integer值可以直接使用==進行判斷,
但是這個區間之外的所有數據,都會在堆上產生,並不會復用已有對象,這是一個大坑,推薦使用equals方法進行判斷。 Integer a = 235; Integer b = 235; if (a.equals(b)) { //相等 } - 所有的抽象方法(包括接口中的方法)必須要用javadoc注釋、除了返回值、參數、異常說明外,還必須指出該方法做什么事情,實現什么功能。 說明:如有實現和調用注意事項,請一並說明。
/** * fetch data by rule id * * @param ruleId rule id * @param page page number * @param jsonContext json format context * @return Result<XxxxDO> */ Result<XxxxDO> fetchDataByRuleId(Long ruleId, Integer page, String jsonContext);
- 所有的枚舉類型字段必須要有注釋,說明每個數據項的用途。
- 所有的類都必須添加創建者信息。
- 所有的覆寫方法,必須加@Override注解。(注:可以讓編譯器提前發現錯誤)
- 所有編程相關的命名均不能以下划線或美元符號開始
- 抽象類命名使用Abstract或Base開頭
- 方法內部單行注釋,在被注釋語句上方另起一行,使用//注釋。方法內部多行注釋使用/* */注釋。注意與代碼對齊。
- 方法名、參數名、成員變量、局部變量都統一使用lowerCamelCase,必須遵從駝峰形式
- 注意 Math.random() 這個方法返回是double類型,注意取值的范圍[0,1)(能夠取到零值,注意除零異常),如果想獲取整數類型的隨機數,不要將x放大10的若干倍然后取整,直接使用Random對象的nextInt或者nextLong方法。
Negative example: Long randomLong =(long) (Math.random() * 10); Positive example: Long randomLong = new Random().nextLong();
- 測試類命名以它要測試的類的名稱開始,以Test結尾
- 類、類屬性、類方法的注釋必須使用javadoc規范,使用/**內容*/格式,不得使用//xxx方式和/*xxx*/方式。
說明:在IDE編輯窗口中,javadoc方式會提示相關注釋,生成javadoc可以正確輸出相應注釋;在IDE中,工程調用方法時,不進入方法即可懸浮提示方法、參數、返回值的意義,提高閱讀效率。 /** * * XXX class function description. * */ public class XxClass implements Serializable { private static final long serialVersionUID = 113323427779853001L; /** * id */ private Long id; /** * title */ private String title; /** * find by id * * @param ruleId rule id * @param page start from 1 * @return Result<Xxxx> */ public Result<Xxxx> funcA(Long ruleId, Integer page) { return null; } }
- 類名使用UpperCamelCase風格,必須遵從駝峰形式,但以下情形例外:(領域模型的相關命名)DO / BO / DTO / VO / DAO
- 線程池不允許使用Executors去創建,而是通過ThreadPoolExecutor的方式,這樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。
說明:Executors各個方法的弊端: 1)newFixedThreadPool和newSingleThreadExecutor: 主要問題是堆積的請求處理隊列可能會耗費非常大的內存,甚至OOM。 2)newCachedThreadPool和newScheduledThreadPool: 主要問題是線程數最大數是Integer.MAX_VALUE,可能會創建數量非常多的線程,甚至OOM。 Positive example 1: //org.apache.commons.lang3.concurrent.BasicThreadFactory ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1, new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build()); Positive example 2: ThreadFactory namedThreadFactory = new ThreadFactoryBuilder() .setNameFormat("demo-pool-%d").build(); //Common Thread Pool ExecutorService pool = new ThreadPoolExecutor(5, 200, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy()); pool.execute(()-> System.out.println(Thread.currentThread().getName())); pool.shutdown();//gracefully shutdown Positive example 3: <bean id="userThreadPool" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"> <property name="corePoolSize" value="10" /> <property name="maxPoolSize" value="100" /> <property name="queueCapacity" value="2000" /> <property name="threadFactory" value= threadFactory /> <property name="rejectedExecutionHandler"> <ref local="rejectedExecutionHandler" /> </property> </bean> //in code userThreadPool.execute(thread);
- 獲取當前毫秒數:System.currentTimeMillis(); 而不是new Date().getTime();
注:這是從效率考慮,不用生成冗余的DAT對象 -
說明:如果想獲取更加精確的納秒級時間值,用System.nanoTime。在JDK8中,針對統計時間等場景,推薦使用Instant類。 public class TimeMillisDemo { public static void main(String args[]) { // Positive example: long a = System.currentTimeMillis(); // Negative example: long b = new Date().getTime(); System.out.println(a); System.out.println(b); } }
返回類型為基本數據類型,return包裝數據類型的對象時,自動拆箱有可能產生NPE
public int method() { Integer a = null; return a; }
- 避免Random實例被多線程使用,雖然共享該實例是線程安全的,但會因競爭同一seed 導致的性能下降。說明:Random實例包括java.util.Random 的實例或者 Math.random()的方式。
說明:使用線程池的好處是減少在創建和銷毀線程上所花的時間以及系統資源的開銷,解決資源不足的問題。如果不使用線程池,有可能造成系統創建大量同類線程而導致消耗完內存或者“過度切換”的問題。 ThreadFactory namedThreadFactory = new ThreadFactoryBuilder() .setNameFormat("demo-pool-%d").build(); ExecutorService singleThreadPool = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy()); singleThreadPool.execute(()-> System.out.println(Thread.currentThread().getName())); singleThreadPool.shutdown();
-
避免用Apache Beanutils進行屬性的copy。
說明:Apache BeanUtils性能較差,可以使用其他方案比如Spring BeanUtils, Cglib BeanCopier。 TestObject a = new TestObject(); TestObject b = new TestObject(); a.setX(b.getX()); a.setY(b.getY());
- 避免通過一個類的對象引用訪問此類的靜態變量或靜態方法,無謂增加編譯器解析成本,直接用類名來訪問即可。(注:需要了解JVM機制)
- 除常用方法(如getXxx/isXxx)等外,不要在條件判斷中執行復雜的語句,將復雜邏輯判斷的結果賦值給一個有意義的布爾變量,以提高可讀性。
說明:很多if語句內的邏輯相當復雜,閱讀者需要分析條件表達式的最終結果,才能明確什么樣的條件執行什么樣的語句,那么,如果閱讀者分析邏輯表達式錯誤呢? Negative example: if ((file.open(fileName, "w") != null) && (...) || (...)) { ... } Positive example: boolean existed = (file.open(fileName, "w") != null) && (...) || (...); if (existed) { ... }
- 集合初始化時,指定集合初始值大小。
說明:HashMap使用如下構造方法進行初始化,如果暫時無法確定集合大小,那么指定默認值(16)即可。 Negative example: Map<String, String> map = new HashMap<String, String>(); Positive example: Map<String, String> map = new HashMap<String, String>(16);