優雅編程之阿里巴巴開發規范分享及擴展學習(三十八)

開心一笑

【小明的前女友開了家小賓館,小明進去吃碗牛肉面,吃完就付錢,她前女友說什么都不肯收,于是小明把錢放在桌子上就走了,沒想到她前女友追出來把錢塞給了小明。小明就跟他說:“做生意不容易啊,你這樣我以后不來了”。她前女友說:“我不收你錢,就是讓你以后別來了!” 這尼瑪就尷尬了......】

提出問題

關于阿里巴巴Java開發手冊公開版學習和自己加入的一些擴展???

解決問題

元旦快樂

同事發了一篇很好的文章《阿里巴巴Java開發手冊公開版》,針對里面的幾點,在之前文章沒寫到的,分享和擴充下:

0.謙虛,謙虛,再謙虛

這是作為程序員的第一道關卡,大神都很謙卑

這是我自己總結的。

1.不要嫌名字長

無論是方法,變量,還是函數的取名,不要嫌棄名稱太長,只要能夠表示清楚含義就可以了。

2.String[] args而不是String args[]

中括號是數組類型的一部分,數組定義如下:String[] args;

在《Thinking in Java》這邊書里面,是這么解釋的:

  • 大部分開發人員,習慣前一種寫法。
  • 前一種寫法符合我們的口語化,我們口語通常都說:定義一個字符串數組(String 代碼字符,[]代表數組)
3.POJO 類中的任何布爾類型的變量,都不要加is

POJO類中的任何布爾類型的變量,都不要加 is,否則部分框架解析會引起序列化錯
誤。

大部分經典的書籍都有提到這一點,老話題了。

4.各層命名規約

A) Service/DAO 層方法命名規約
1) 獲取單個對象的方法用 get 做前綴。
2) 獲取多個對象的方法用 list 做前綴。
3) 獲取統計值的方法用 count 做前綴。
4) 插入的方法用 save(推薦)或 insert 做前綴。
5) 刪除的方法用 remove(推薦)或 delete 做前綴。
6) 修改的方法用 update 做前綴。

這一點確實很重要,有時候自己都做不到,規范大于編碼

5.不允許出現任何魔法值(即未經定義的常量)
String key="Id#taobao_"+tradeId;
cache.put(key, value);

魔法數值:是指在代碼中直接出現的數值,而只有在這個數值記述的那部分代碼中才能明確了解其含義。

在這里進行擴充下:

魔法數字的例子

  int priceTable[] = new int[16]; //ERROR:這個16究竟有何含義呢?

使用了帶名字的數值的例子

解決方法:

static final int PRICE_TABLE_MAX = 16; //OK:帶名字
int price Table[] = new int [PRICE_TABLE_MAX]; //OK:名字的含義是很清楚的
6.多個參數逗號后邊必須加空格

有時候做項目,心急如焚時,就會忘記

下例中實參的"a",后邊必須要有一個空格

method("a", "b", "c"); 
7.開發工具編碼設置

IDE 的 text file encoding 設置為 UTF-8; IDE 中文件的換行符使用 Unix 格式,不
要使用 windows 格式

8.所有的覆寫方法,必須加@Override注解

如果在抽象類中對方法簽名進行修改,其實現類會馬上編
譯報錯

《Effective Java》這本書已經說的清清楚楚了,大家可以去啃啃這一節的內容。

9.提倡盡量不用可變參數編程

相同參數類型,相同業務含義,才可以使用Java的可變參數,避免使用 Object。可變參數必須放置在參數列表的最后

public User getUsers(String type, Integer... ids);
10.基本數據類型與包裝數據類型的使用標準

1) 所有的POJO類屬性必須使用包裝數據類型。
2) RPC方法的返回值和參數必須使用包裝數據類型。
3) 所有的局部變量推薦使用基本數據類型。
說明:POJO類屬性沒有初值是提醒使用者在需要使用時,必須自己顯式地進行賦值,任何
NPE 問題,或者入庫檢查,都由使用者來保證。
正例:數據庫的查詢結果可能是 null,因為自動拆箱,用基本數據類型接收有 NPE 風險。
反例:某業務的交易報表上顯示成交總額漲跌情況,即正負 x%,x 為基本數據類型,調用的
RPC 服務,調用不成功時,返回的是默認值,頁面顯示:0%,這是不合理的,應該顯示成中劃
線-。所以包裝數據類型的 null 值,能夠表示額外的信息,如:遠程調用失敗,異常退出

11.序列化類新增屬性時,請不要修改serialVersionUID字段

避免反序列失敗

12.構造方法里面禁止加入任何業務邏輯

如果有初始化邏輯,請放在init方法中

其實這是符合一個方法只做一件事的原則

13.POJO 類必須寫 toString 方法

使用工具類 source> generate toString 時,如果繼承了另一個 POJO 類,注意在前面加一下 super.toString。說明:在方法執行拋出異常時,可以直接調用 POJO 的 toString()方法打印其屬性值,便于排查問題。

貌似我們項目框架中,這一點沒有做到

14.同名的或者相似的方法放置在一起
15.類內方法定義順序

類內方法定義順序依次是:公有方法或保護方法 > 私有方法 > getter/setter 方法。

說明: 公有方法是類的調用者和維護者最關心的方法,首屏展示最好;保護方法雖然只是子類
關心,也可能是“模板設計模式”下的核心方法;而私有方法外部一般不需要特別關心,是一
個黑盒實現;因為方法信息價值較低,所有 Service 和 DAO 的 getter/setter 方法放在類體最
后。

16.final可提高程序響應效率

final可提高程序響應效率,聲明成final的情況:
1) 不需要重新賦值的變量,包括類屬性、局部變量。
2) 對象參數前加final,表示不允許修改引用的指向。
3) 類方法確定不允許被重寫

17.Map/Set的key為自定義對象時,必須重寫hashCode和equals

String 重寫了 hashCode 和 equals 方法,所以我們可以非常愉快地使用String對象作
為 key 來使用。

18.使用集合轉數組的方法,必須使用集合的 toArray(T[] array)

傳入的是類型完全一樣的數組,大小就是 list.size()。

反例:直接使用 toArray 無參方法存在問題,此方法返回值只能是 Object[]類,若強轉其它
類型數組將出現 ClassCastException 錯誤。

正例:

List<String> list = new ArrayList<String>(2); 
list.add("guan"); 
list.add("bao"); 
String[] array = new String[list.size()]; 
array = list.toArray(array);  

說明:使用 toArray 帶參方法,入參分配的數組空間不夠大時,toArray 方法內部將重新分配
內存空間,并返回新數組地址;如果數組元素大于實際所需,下標為[ list.size() ]的數組
元素將被置為 null,其它數組元素保持原值,因此最好將方法入參數組大小定義與集合元素
個數一致

19.Arrays.asList()把數組轉換成集合時,不能使用其修改集合相關的方法

它的 add/remove/clear 方法會拋出 UnsupportedOperationException 異常。
說明: asList 的返回對象是一個 Arrays 內部類,并沒有實現集合的修改方法。 Arrays.asList
體現的是適配器模式,只是轉換接口,后臺的數據仍是數組。

String[] str = new String[] { "a", "b" };
List  list = Arrays.asList(str);
第一種情況:list.add("c"); 運行時異常。
第二種情況:str[0]= "gujin"; 那么 list.get(0)也會隨之修改。
20.不要在foreach循環里進行元素的remove/add操作

remove元素請使用Iterator方式,如果并發操作,需要對 Iterator 對象加鎖。

反例:

List<String> a = new ArrayList<String>(); 
a.add("1");   
a.add("2"); 
for (String temp : a) { 
    if("1".equals(temp)){ 
        a.remove(temp); 
    } 
} 
21.集合初始化時,盡量指定集合初始值大小

ArrayList 盡量使用 ArrayList(int initialCapacity) 初始化。

22.使用entrySet遍歷Map類集合KV,而不是keySet

keySet 其實是遍歷了 2 次,一次是轉為 Iterator 對象,另一次是從 hashMap 中取出 key
所對應的 value。而 entrySet 只是遍歷了一次就把 key 和 value 都放到了 entry 中,效率更
高。如果是 JDK8,使用Map.foreach 方法。

這一點可以參考《Thinking in Java》第17章容器深入研究這一章

23.利用Set元素唯一的特性,可以快速對另一個集合進行去重操作

避免使用List的contains 方法進行遍歷去重操作。

24.數據庫合適的字符存儲長度

不但節約數據庫表空間、節約索引存儲,更重要的是提升檢索速度。

25.在表查詢中,一律不要使用 * 作為查詢的字段列表

需要哪些字段必須明確寫明。

說明:1)增加查詢分析器解析成本。2)增減字段容易與resultMap配置不一致。

26.in操作能避免則避免

若實在避免不了,需要仔細評估 in 后邊的集合元素數量,控制在 1000 個之內。

27.異常信息應該包括兩類信息

異常信息應該包括兩類信息:案發現場信息和異常堆棧信息。如果不處理,那么往上
拋。

logger.error(各類參數或者對象 toString + "_" + e.getMessage(), e);
28.if/else/for/while/do 語句中必須使用大括號,即使只有一行代碼

這一點我有點不支持,比如下面的代碼:

//阿里巴巴建議的寫法
if(a > 0){
    System.out.println(".....");
} 

//對于if邏輯中只有一句話,我習慣下面這么些
if(a > 0) System.out.println(".....");

那個比較好自己對比吧!!!

29.禁止使用存儲過程

存儲過程難以調試和擴展,更沒有移植性

反正我們公司沒用。

30.推薦盡量少用 else, if-else的方式可以改寫成
if(condition){ 
    …
    return obj; 
} 

事實上這個是《重構-改善既有代碼設計》這本書所提倡的,多些衛語句,具體什么是衛語句,我在下面參考文章中有一篇鏈接,大家可以去看。

31.不要在條件判斷中執行復雜的語句

正例:

//偽代碼如下 
InputStream stream = file.open(fileName, "w");  

if (stream != null) {
    …
}

反例:

if (file.open(fileName, "w") != null)) { 
    …
} 
32.線程安全相關的類使用

SimpleDateFormat 是線程不安全的類,一般不要定義為 static 變量,如果定義為static,必須加鎖,或者使用 DateUtils 工具類。

正例:注意線程安全,使用 DateUtils。亦推薦如下處理:

private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() { 
    @Override 
    protected DateFormat initialValue() { 
        return new SimpleDateFormat("yyyy-MM-dd"); 
    } 
};

說明:如果是 JDK8 的應用,可以使用 instant 代替 Date,Localdatetime 代替 Calendar,
Datetimeformatter 代替 Simpledateformatter,官方給出的解釋:simple beautiful strong
immutable thread-safe。

避免 Random 實例被多線程使用,雖然共享該實例是線程安全的,但會因競爭同一 seed
導致的性能下降。
說明:Random 實例包括 java.util.Random 的實例或者 Math.random()實例。
正例:在 JDK7 之后,可以直接使用 API ThreadLocalRandom,在 JDK7 之前,可以做到每個線
程一個實例。

33.類、類屬性、類方法的注釋必須使用 javadoc 規范

類、類屬性、類方法的注釋必須使用 javadoc 規范,使用/*內容/格式,不得使用//xxx 方式。

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

可以參考《代碼整潔之道》這本書中的注釋這一章,里面內容更加纖細。

34.刪除被注釋掉的沒用的東西

對于“明確停止使用的代碼和配置”,如方法、變量、類、配置文件、動態配置屬性
等要堅決從程序中清理出去,避免造成過多垃圾。清理這類垃圾代碼是技術氣場,不要有這樣
的觀念:“不做不錯,多做多錯”。

35.獲取當前毫秒數:System.currentTimeMillis()

而不是 new Date().getTime();

說明:如果想獲取更加精確的納秒級時間值,用 System.nanoTime。在 JDK8 中,針對統計時
間等場景,推薦使用 Instant 類。

【強制】線程資源必須通過線程池提供,不允許在應用中自行顯式創建線程。
說明: 使用線程池的好處是減少在創建和銷毀線程上所花的時間以及系統資源的開銷,解決資
源不足的問題。如果不使用線程池,有可能造成系統創建大量同類線程而導致消耗完內存或者
“過度切換”的問題。

36.所有的類都必須添加創建者信息。

事實上,我個人建議,每個方法都要寫上創建者姓名,這樣出現問題比較好排查

出現問題,分分鐘弄死它。

37.代碼修改的同時,注釋也要進行相應的修改

尤其是參數、返回值、異常、核心邏輯等的修改。

38.任何數據結構的使用都應限制大小

說明:這點很難完全做到,但很多次的故障都是因為數據結構自增長,結果造成內存被吃光。

39.日志文件推薦至少保存 15 天

因為有些異常具備以“周”為頻次發生的特點。

40.對trace/debug/info級別的日志輸出,必須使用條件輸出形式

或者使用占位符的方式。

說明:logger.debug("Processing trade with id: " + id + " symbol: " + symbol); 如果
日志級別是 warn,上述日志不會打印,但是會執行字符串拼接操作,如果 symbol 是對象,會
執行 toString()方法,浪費了系統資源,執行了上述操作,最終日志卻沒有打印。

正例:(條件)

if (logger.isDebugEnabled()) { 
logger.debug("Processing trade with id: " + id + " symbol: " + symbol); 
} 

正例:(占位符)

logger.debug("Processing trade with id: {} and symbol : {} ", id, symbol);
41.特殊注釋標記,請注明標記人與標記時間

注意及時處理這些標記,通過標記掃描,經常清理此類標記。線上故障有時候就是來源于這些標記處的代碼。

  • 1) 待辦事宜(TODO):( 標記人,標記時間,[預計處理時間])
    表示需要實現,但目前還未實現的功能。這實際上是一個 javadoc 的標簽,目前的
    javadoc 還沒有實現,但已經被廣泛使用。 只能應用于類,接口和方法(因為它是一個 javadoc
    標簽)。
  • 2) 錯誤,不能工作(FIXME):(標記人,標記時間,[預計處理時間])
    在注釋中用 FIXME 標記某代碼是錯誤的,而且不能工作,需要及時糾正的情況。
42.避免用Apache Beanutils 進行屬性的 copy

說明:Apache BeanUtils 性能較差,可以使用其他方案比如 Spring BeanUtils, Cglib
BeanCopier。

以后會專門寫一篇關于這方面的文章,敬請期待

43.注意HashMap的擴容死鏈

導致 CPU 飆升的問題

44.應用中的擴展日志(如打點、臨時監控、訪問日志等)命名方式

appName_logType_logName.log。logType:日志類型,推薦分類有 stats/desc/monitor/visit
等;logName:日志描述。這種命名的好處:通過文件名就可知道日志文件屬于什么應用,什么類型,什么目的,也有利于歸類查找。

45.高并發時,同步調用應該去考量鎖的性能損耗

能用無鎖數據結構,就不要用鎖;能鎖區塊,就不要鎖整個方法體;能用對象鎖,就不要用類鎖。

46在代碼中使用“拋異常”還是“返回錯誤碼”

對于公司外的 http/api 開放接口必須使用“錯誤碼”;而應用內部推薦異常拋出;跨應用間 RPC 調用優先考慮使用 Result 方式,封裝 isSuccess、“錯誤碼”、“錯誤簡短信息”。

說明:關于 RPC 方法返回方式使用 Result 方式的理由:
1)使用拋異常返回方式,調用方如果沒有捕獲到就會產生運行時錯誤。
2)如果不加棧信息,只是 new 自定義異常,加入自己的理解的 error message,對于調用
端解決問題的幫助不會太多。如果加了棧信息,在頻繁調用出錯的情況下,數據序列化和傳輸
的性能損耗也是問題。

47.謹慎地記錄日志。生產環境禁止輸出 debug 日志

有選擇地輸出 info 日志;如果使用 warn 來記錄剛上線時的業務行為信息,一定要注意日志輸出量的問題,避免把服務器磁盤撐爆,并記得及時刪除這些觀察日志。

說明:大量地輸出無效日志,不利于系統性能提升,也不利于快速定位錯誤點。紀錄日志時請
思考:這些日志真的有人看嗎?看到這條日志你能做什么?能不能給問題排查帶來好處?

48.單表行數超過 500 萬行或者單表容量超過 2GB,才推薦進行分庫分表。

讀書感悟

來自 尼爾.唐納徳.沃爾什《與神對話》

  • 最高的思維永遠是那包含著喜悅的思維,最清楚的話語永遠是那些包含著真理的話語,最崇高的感受,就是你們稱為愛的那種感受
  • 如果你愿意聽,我將會與你說話。如果你愿意邀請我,我將會來找你。到時我將會讓你看到,我一直都在。以各種各樣的方式。
  • 尷尬是仍然在意別人如何看待自己的人才會有的反應
  • 別妒忌成功,別憐憫失敗,因為你不知道在靈魂的權衡中,什么算成功,什么算失敗。遇事別稱其災難或歡樂,除非你已確定或見證它的用途。因為,如果死拯救了數以千計的生命,它還能被稱為災難嗎?如果生只帶來悲哀,它還能被稱為歡樂嗎?然而就算這個你也別去判斷,你永遠走你的路,同時允許別人走他們的路就可以了。
  • 完美人生的三大核心: 1.清醒 2.誠信 3.負責

經典故事

【一只小豬、一只綿羊和一頭乳牛,被關在同一個畜欄里。有一次,牧人捉住小豬,他大聲號叫,猛烈地抗拒。綿羊和乳牛討厭它的號叫,便說:“他常常捉我們,我們并不大呼小叫。”小豬聽了回答道:“捉你們和捉我完全是兩回事,他捉你們,只是要你們的毛和乳汁,但是捉住我,是要我的命的 ? ”
立場不同、所處環境不同的人,很難了解對方的感受;因此對別人的失意、挫折、傷痛 ,不宜幸災樂禍,而應要有關懷、了解的心情。要有寬容的心!】

元旦快樂

參考文章

【1】java中的魔法數
【2】阿里巴巴Java開發手冊公開版
【3】SQL Server-聚焦EXISTS AND IN性能分析(十六)
【4】衛語句
【5】《Thinking in Java》書籍(Java編程思想)
【6】《Effective Java第二版》書籍
【7】《代碼整潔之道》書籍
【8】《重構-改善既有代碼設計》書籍
【9】數據脫敏技術

其他

如果有帶給你一絲絲小快樂,就讓快樂繼續傳遞下去,歡迎點贊、頂、歡迎留下寶貴的意見、多謝支持!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容