前言
只有光頭才能變強
前一陣子一直在學redis,結果在黃金段位被虐了,暫時升不了段位了,每天都拿不到首勝(好煩)。
趁著學校校運會,合理地給自己放了一個小長假,然后就回家了。回到家才發現當時618買了一堆書,這堆書還有沒撕包裝的呢....于是我翻出了最薄的一本《阿里巴巴 java開發手冊》
這本書一共就90多頁,一天就可以通讀完了,看完之后我又來水博文了。
注意:
- 書上很多的規范是可以用ide來避免的,也有很多之前已經知道的了。
- 所以,這篇文章只記錄我認為比較重要,或者說是我之前開發時沒有注意到的一些規范(知識點)。
- 該文章的內容肯定沒有書上寫得那么全的,如果感興趣的同學可以去買一本來讀一下~
pdf官方地址:https://github.com/alibaba/p3c
一、java相關
1.pojo是do/dto/bo/vo的統稱,禁止命名為xxxpojo
2.獲取多個對象的方法中list作為前綴
3.獲取統計值的方法用count作為前綴
4.pojo類中的布爾類型(boolean)的變量都不要加is前綴,否則部分框架解析會引起序列化錯誤
- 如果你的變量名帶is的話,比如isactive,框架解析的時候可能就當成active了。
5.如果是形容能力的接口名稱,取對應的形容詞為接口名(通常是-able的形式)
6.不允許任何魔法值(未經預先定義的常量)直接出現在代碼中
7.object的euqals方法容易拋出空指針異常,應使用常量或者有值的對象來調用equals。推薦使用java.util.object#equals工具類
8.所有pojo類的屬性全部使用包裝數據類型,rpc的返回值和參數必須使用包裝數據類型,所有的局部變量都使用基本數據類型。定義vo/dto/do等pojo類時,不要設定任何屬性的默認值
- 如果你的類屬性使用int這樣的基本數據類型,默認值是0。一般情況下該變量沒有賦值,一般想表達的是不存在(null),而不是0。
9.構造方法禁止加入任何的業務邏輯,如果初始化邏輯可以放在init方法中。set/get方法也不要增加業務邏輯。 •如果set/get方法放入業務邏輯,有時候排查問題就變得很麻煩了
10.工具類arrays.aslist()把數組轉成list時,不能使用其修改集合的相關方法。比如說add、clear、remove
11.在jdk7以及以上版本中,comparator要滿足三個條件,不然調用arrays.sort()或者collections.sort()會報異常。 •x,y 的比較結果和 y,x 的比較結果相反
- 傳遞性:x>y并且y>z,那么x一定大于z
- 對稱性:x=y,則 x,z 比較結果和y,z比較結果相同
12.使用entryset遍歷map類集合k/v,而不是用keyset方式遍歷 •keyset遍歷了兩次,一次是轉成iterator對象,一次是從hashmap中取出key所對應的value,如果jdk8可以使用map.foreach方法
13.線程資源必須由線程池提供,不允許在應用中自行顯示創建線程。線程池不允許用executors創建,通過threadpoolexecutor的方式創建,這樣的處理方式能夠讓編寫代碼的工程師更加明確線程池的運行規則,規避資源耗盡的風險。
14.simpledateformat是線程不安全的類,一般不要定義為static變量,如果定義為static,必須加鎖,或者使用dateutils工具類
- 如果是jdk8應用,可以使用instant(針對時間統計等場景)代替date,localdatetime代替calendar,datetimeformatter代替simpledateformat
15.避免random實例被多線程使用,雖然共享該實例是線程安全的,但會因競爭同一seed導致性能下降 •在jdk7之后,可以直接使用api threadlocalrandom,而在jdk7 之前,需要編碼保證每個線程持有一個實例。
16.類、類屬性、類方法的注釋必須使用 javadoc 規范,使用 /**內容*/ 格式,不得使用 //xxx 方式
17.所有的抽象方法(包括接口中的方法)必須要用 javadoc 注釋,除了返回值、參數、異常說明外,還必須指出該方法做什么事情,實現什么功能。所有的類都必須添加創建者和創建日期。
18.對于暫時被注釋掉,后續可能恢復使用的代碼片斷,在注釋代碼的上方,使用三個斜杠///來說明注釋代碼的理由
19.保證單元測試的獨立性。為了保證單元測試穩定可靠且便于維護,單元測試之間不能互相調用,也不能依賴執行的先后順序。
20.高并發服務器建議調小tcp協議的time_await超時時間,調大最大事件句柄數(fd),
1.1值得說明的點
一、不允許任何魔法值(未經預先定義的常量)直接出現在代碼中
例子:
1
2
3
4
5
6
7
8
9
10
11
|
negative example: //magic values, except for predefined, are forbidden in coding. if (key.equals( "關注公眾號:java3y" )) { //... } positive example: string key_pre = "關注公眾號:java3y" ; if (key_pre.equals(key)) { //... } |
ps:我猜是把先常量定義出來,后續引用/修改的時候就很方便了。
二、object的euqals方法容易拋出空指針異常,應使用常量或者有值的對象來調用equals。推薦使用java.util.object#equals
工具類
java.util.object#equals的源碼(已經判斷null的情況了)
1
2
3
|
public static boolean equals(object a, object b) { return (a == b) || (a != null && a.equals(b)); } |
三、工具類arrays.aslist()把數組轉成list時,不能使用其修改集合的相關方法。
因為返回的arraylist是一個內部類,并沒有實現集合的修改方法。后臺的數據仍是數組,這里體現的是適配器模式。
四、在jdk7以及以上版本中,comparator要滿足自反性,傳遞性,對稱性,不然調用arrays.sort()或者collections.sort()
會報異常。
the implementor must ensure that sgn(compare(x, y)) == -sgn(compare(y, x)) for all x and y. (this implies that compare(x, y) must throw an exception if and only if compare(y, x) throws an exception.)
the implementor must also ensure that the relation is transitive: ((compare(x, y)>0) && (compare(y, z)>0)) implies compare(x, z)>0.
finally, the implementor must ensure that compare(x, y)==0 implies that sgn(compare(x, z))==sgn(compare(y, z)) for all z.
1) x,y 的比較結果和 y,x 的比較結果相反。
2) 傳遞性:x>y,y>z,則 x>z。
3) 對稱性:x=y,則 x,z 比較結果和 y,z 比較結果相同。
反例:下例中沒有處理相等的情況,實際使用中可能會出現異常:
1
2
3
4
5
6
|
new comparator<student>() { @override public int compare(student o1, student o2) { return o1.getid() > o2.getid() ? 1 : - 1 ; } } |
使用entryset遍歷map類集合k/v,而不是用keyset方式遍歷
首先我們來看一下使用keyset是如何遍歷hashmap的:
1
2
3
4
5
6
|
new comparator<student>() { @override public int compare(student o1, student o2) { return o1.getid() > o2.getid() ? 1 : - 1 ; } } |
再來看一下源碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
// 1. 得到keyset,如果不存在,則創建 public set<k> keyset() { set<k> ks = keyset; if (ks == null ) { ks = new keyset(); keyset = ks; } return ks; } // 2.初始化ks (實際上就是set集合[hashmap的內部類],在初始化時需要順便初始化iterator) ks = new abstractset<k>() { public iterator<k> iterator() { return new iterator<k>() { private iterator<entry<k,v>> i = entryset().iterator(); public boolean hasnext() { return i.hasnext(); } public k next() { return i.next().getkey(); } public void remove() { i.remove(); } }; } }; |
再來看一下entryset,可以直接拿到key和value,不用再使用get方法來得到value,所以比keyset更加推薦使用!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public static void main(string[] args) throws interruptedexception { hashmap<string, string> hashmap = new hashmap<>(); hashmap.put( "關注公眾號:" , "java3y" ); hashmap.put( "堅持原創" , "java3y" ); hashmap.put( "點贊" , "關注,轉發,分享" ); // 得到entryset,遍歷entryset得到結果 set<map.entry<string, string>> entryset = hashmap.entryset(); iterator<map.entry<string, string>> iterator = entryset.iterator(); while (iterator.hasnext()) { map.entry<string, string> entry = iterator.next(); system.out.println( "key = " + entry.getkey() + ", value = " + entry.getvalue()); } } |
如果是jdk8的話,推薦直接使用map.foreach()
就好了,我們也來看看用法:
1
2
3
4
5
6
7
8
9
10
11
|
public static void main(string[] args) throws interruptedexception { hashmap<string, string> hashmap = new hashmap<>(); hashmap.put( "關注公眾號:" , "java3y" ); hashmap.put( "堅持原創" , "java3y" ); hashmap.put( "點贊" , "關注,轉發,分享" ); // foreach用法 hashmap.foreach((key, value) -> system.out.println( "key = " + key + ", value = " + value)); } |
其實在源碼里邊我們可以發現,foreach實際上就是封裝了entryset,提供foreach給我們可以更加方便地遍歷map集合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
// foreach源碼 default void foreach(biconsumer<? super k, ? super v> action) { objects.requirenonnull(action); for (map.entry<k, v> entry : entryset()) { k k; v v; try { k = entry.getkey(); v = entry.getvalue(); } catch (illegalstateexception ise) { // this usually means the entry is no longer in the map. throw new concurrentmodificationexception(ise); } action.accept(k, v); } } |
五、simpledateformat是線程不安全的類,一般不要定義為static變量,如果定義為static,必須加鎖,或者使用dateutils工具類。
有以下的例子可以正確使用simpledateformat:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
// 1. 在方法內部使用,沒有線程安全問題 private static final string format = "yyyy-mm-dd hh:mm:ss" ; public string getformat(date date){ simpledateformat dateformat = new simpledateformat(format); return dateformat.format(date); } // 2. 每次使用的時候加鎖 private static final simpledateformat simple_date_format = new simpledateformat( "yyyy-mm-dd hh:mm:ss" ); public void getformat(){ synchronized (simple_date_format){ simple_date_format.format( new date()); ….; } // 3. 使用threadlocal,每個線程都有自己的simpledateformat對象,互不干擾 private static final threadlocal<dateformat> date_formatter = new threadlocal<dateformat>() { @override protected dateformat initialvalue() { return new simpledateformat( "yyyy-mm-dd" ); } }; // 4. 使用datetimeformatter(this class is immutable and thread-safe.) datetimeformatter timeformatter = datetimeformatter.ofpattern( "yyyy-mm-dd hh:mm:ss" ); system.out.println(timeformatter.format(localdatetime.now())); |
如果是jdk8應用,可以使用instant代替date,localdatetime代替calendar,datetimeformatter代替simpledateformat。
二、數據庫相關
1.表達是否概念的字段,必須使用isxxx的方式命名,數據類型是unsigned tinyint(1表示是,0表示否)
2.小數類型用decimal,禁止使用float和double。
3.varchar是可變字符串,不預選分配存儲空間的話,長度不要超過5000個字符。如果超過則用text,獨立一張表,用主鍵對應,避免影響到其他字段的索引效率。
4.表必備的三個字段:id(類型是unsigned bigint),gmt_create(創建時間),gme_modified(修改時間)
5.字段允許適當冗余,以提高查詢性能,但必須考慮數據一致性。冗余的字段必須不是頻繁修改的字段,不是varhar超長字段(更不能是text字段)。
6.單表行數超過500萬行或者單表容量超過2gb才推薦進行分庫分表(如果預計三年都達不到這個數據量,不要在創建表的時候就分庫分表!)
7.超過三個表禁止使用join,需要join的字段,數據類型必須保持一致,當多表關聯查詢時,保證被關聯的字段需要有索引!
8.在varchar字段上建立索引時,必須指定索引長度,沒必要對全字段建立索引,頁面搜索嚴禁左模糊或者全模糊,如果需要則通過搜索引擎來解決。
- 充分利用好最左前綴匹配特性!
9.利用延遲關聯或者子查詢優化超多也分場景。
10.如果有全球化需要,均以utf-8編碼。如果需要存儲表情,選擇utf8mb4進行存儲。
2.1值得說明的點
一、利用延遲關聯或者子查詢優化超多也分場景。
mysql并不是跳過 offset行,而是取 offset+n行,然后返回放棄前offset行,返回n行,那當 offset特別大的時候,效率就非常的低下,要么控制返回的總頁數,要么對超過特定閾值的頁數進行sql改寫。
例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
// 優化前 select id, cu_id, name, info, biz_type , gmt_create, gmt_modified, start_time, end_time, market_type , back_leaf_category, item_status, picuture_url from relation where biz_type = '0' and end_time >= '2014-05-29' order by id asc limit 149420 , 20 ; // 優化后 select a.* from relation a, ( select id from relation where biz_type = '0' and end_time >= '2014-05-29' order by id asc limit 149420 , 20 ) b where a.id = b.id |
解釋:其實這里就是通過使用覆蓋索引查詢返回需要的主鍵,再根據主鍵關聯原表獲得需要的數據。這樣就是充分利用了索引!
三、未解決的問題
在看《手冊》的時候還有一些知識點沒看過、沒實踐過、涉及到的知識點比較多的,在這里先mark一下,后續再遇到或者有空的時候再回來補坑~
- 使用countdownlatch進行異步轉同步操作,每個線程退出前必須調用 countdown方法,線程執行代碼注意 catch 異常,確保 countdown 方法被執行到,避免主線程無法執行至 await 方法,直到超時才返回結果。說明: 注意,子線程拋出異常堆棧,不能在主線程 try-catch 到。
-
對于一寫多讀,是可以解決變量同步問題, 但是如果多寫,同樣無法解決線程安全問題。如果是 count++操作,使用如下類實現:
atomicinteger count = new atomicinteger(); count.addandget(1);
如果是 jdk8,推薦使用 longadder 對象,比 atomiclong 性能更好(減少樂觀鎖的重試次數)。 - 使用jdk8的optional類來防止npe問題。
當然了,如果你有比較好的資料閱讀,也可以在評論區告訴我。我也會mark住好好看看。
比如說:“3y,我發現optional類有篇文章寫得很不錯,url是xxxx(書籍的名稱是xxx)
由于現在沒有一定的經驗積累,所以以下的章節得回頭看:
- 《手冊》中的“日志規約”,“工程結構”、“設計規范”
最后
看我上面寫的內容就知道,除了一些規范外,還有很多實用的小技巧,這些對我們開發是有幫助的。我這個階段也有一些沒怎么接觸過的("日志","設計","二方庫"),這些都需要我在成長中不斷的回看才行。
ps:我會回來補坑的。
引用書上的一句話:很多編程方式客觀上沒有對錯之分,一致性很重要,可讀性很重要,團隊溝通效率很重要。程序員天生需要團隊協作,而協作的正能量要放在問題的有效溝通上。個性化應盡量表現在系統架構和算法效率的提升上,而不是在合作規范上進行糾纏不休的討論、爭論,最后沒有結論。
作者(孤盡)在知乎回答的一句話:翻完了不代表記住了,記住了不代表理解了,理解了不代表能夠應用上去,真正的知識是實踐,實踐,實踐。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:https://www.cnblogs.com/Java3y/p/9969760.html