太原中軟分享:Java開發常用規範技巧總結

太原中軟分享:Java開發常用規範技巧總結

1、類的命名使用駝峰式命名的規範。

例如:UserService,但是以下情景例外:DO / BO / PO / DTO / VO。

例如說:UserPO,StudentPO(PO,VO,DTO,等這類名詞需要全大寫)

@Data
@Builder
public class CustomBodyDTO {

 private String name;

 private String idCode;

 private String status;
}

2、如果在模塊或者接口,類,方法中使用了設計模式,那麼請在命名的時候體現出來。

例如說:TokenFactory,LoginProxy等。

public class TokenFactory {

 public TokenDTO buildToken(LoginInfo loginInfo) {
 String token = UUID.randomUUID().toString();
 TokenDTO tokenDTO = TokenDTO.builder()
 .token(token)
 .createTime(LocalDateTime.now())
 .build();
 String redisKey = RedisKeyBuilder.buildTokenKey(token);
 redisService.setObject(redisKey, loginInfo, Timeout.ONE_DAY * 30 * 2);
 log.info("創建token成功|loginInfo={}", loginInfo.toString());
 return tokenDTO;
 }
}

3、Object 的 equals 方法容易拋空指針異常。

從源碼來進行分析equals方法是屬於Object類的,如果調用方為null,那麼自然在運行的時候會拋出空指針異常的情況。

object類中的源碼:

 public boolean equals(Object obj) {
 return (this == obj);
 }

為了避免這種現況出現,在比對的時候儘量將常量或者有確定值的對象置前。

例如說:

正確:“test”.equals(object);
錯誤:object.equals(“test”);

4、對於所有相同類型的包裝類進行比較的時候,都是用equal來進行操作。

對於Integer類來說,當相應的變量數值範圍在-128到127之間的時候,該對象會被存儲在IntegerCache.cache裡面,因此會有對象複用的情況發生。

所以對於包裝類進行比較的時候,最好統一使用equal方法。

 private static class IntegerCache {
 static final int low = -128;
 static final int high;
 static final Integer cache[];

 static {
 // high value may be configured by property
 int h = 127;
 String integerCacheHighPropValue =
 sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
 if (integerCacheHighPropValue != null) {
 try {
 int i = parseInt(integerCacheHighPropValue);
 i = Math.max(i, 127);
 // Maximum array size is Integer.MAX_VALUE
 h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
 } catch( NumberFormatException nfe) {
 // If the property cannot be parsed into an int, ignore it.
 }
 }
 high = h;

 cache = new Integer[(high - low) + 1];
 int j = low;
 for(int k = 0; k < cache.length; k++)
 cache[k] = new Integer(j++);

 // range [-128, 127] must be interned (JLS7 5.1.7)
 assert IntegerCache.high >= 127;
 }

 private IntegerCache() {}
 }

 public static Integer valueOf(int i) {
 if (i >= IntegerCache.low && i <= IntegerCache.high)
 return IntegerCache.cache[i + (-IntegerCache.low)];
 return new Integer(i);
 }

5、所有的pojo類中的屬性最好統一使用包裝類屬性類型數據。RPC方法的返回值和參數都統一使用包裝類數據。局部變量中使用基本的數據類型。

對於實際的應用場景來說,例如說一個學生類,當我們設置裡面的成績字段為int類型的時候,如果學生沒有考試,那麼這個成績字段應該為空,但是int默認會賦值為0,那麼這個時候使用基本數據類型就容易產生誤區,到底是考了0分,還是說沒有參加考試。

如果換成使用包裝類Integer類型的話,就可以通過null值來進行區分了。

6、當pojo類在進行編寫的時候要重寫相應的toString方法,如果該pojo中繼承了另外的一個pojo類,那麼請在相應的tostring函數中加入super.toString()方法。

通過重寫toString方法有利於在日誌輸出的時候查看相應對象的屬性內容進行逐一分析,對於一些有繼承關係的對象而言,加入了super.toString方法更加有助於對該對象的理解和分析。

7、在pojo的getter和setter方法裡面,不要增加業務邏輯的代碼編寫,這樣會增加問題排查的難度。

正確做法:

public class User {
 private Integer id;

 private String username;

 public Integer getId() {
 return id;
 }

 public User setId(Integer id) {
 this.id = id;
 return this;
 }

 public String getUsername() {
 return username;
 }

 public User setUsername(String username) {
 this.username = username;
 return this;
 }
}
錯誤做法:
public class User {
 private Integer id;

 private String username;

 public Integer getId() {
 return id;
 }

 public User setId(Integer id) {
 this.id = id;
 return this;
 }

 public String getUsername() {
 return "key-prefix-"+username;
 }

 public User setUsername(String username) {
 this.username = "key-prefix-"+username;
 return this;
 }
}

8、final 可以聲明類、成員變量、方法、以及本地變量。

下列情況使用 final 關鍵字:

  • 不允許被繼承的類,如:String 類。
  • 不允許修改引用的域對象,如:POJO 類的域變量。
  • 不允許被重寫的方法,如:POJO 類的 setter 方法。
  • 不允許運行過程中重新賦值的局部變量。
  • 避免上下文重複使用一個變量,使用 final 描述可以強制重新定義一個變量,方便更好地進行重構。

9、對於任何類而言,只要重寫了equals就必須重寫hashcode。

舉例說明:

1)HashSet在存儲數據的時候是存儲不重複對象的,這些對象在進行判斷的時候需要依賴hashcode和equals方法,因此需要重寫。

2)在自定義對象作為key鍵時,需要重寫hashcode和equals方法,例如說String類就比較適合用於做key來使用。

10、不要在 foreach 循環裡進行元素的 remove/add 操作。

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

Iterator iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (刪除元素的條件) {
iterator.remove();
}
}

11、使用HashMap的時候,可以指定集合的初始化大小。

例如說,HashMap裡面需要存放10000個元素,但是由於沒有進行初始化大小操作,所以在添加元素的時候,hashmap的內部會一直在進行擴容操作,影響性能。

那麼為了減少擴容操作,可以在初始化的時候將hashmap的大小設置為:已知需要存儲的大小/負載因子(0.75)+1

 HashMap hashMap=new HashMap<>(13334);

12、Map類集合中,K/V對於null類型存儲的情況:

集合名稱keyvalue說明HashMap允許為null允許為null線程不安全TreeMap不允許為null允許為null線程不安全HashTable不允許為null不允許為null線程安全ConcurrentHashMap不允許為null不允許為null線程安全

13、可以利用 Set 元素唯一的特性,可以快速對一個集合進行去重操作,避免使用 List 的contains 方法進行遍歷、對比、去重操作。

通關觀察可以發現,HashSet底層通過將傳入的值再傳入到一個HashMap裡面去進行操作,進入到HashMap裡面之後,會先通過調用該對象的hashcode來判斷是否有重複的值,如果有再進行equals判斷,如果沒有相同元素則插入處理。

 public boolean add(E e) {
 return map.put(e, PRESENT)==null;
 } 

14、線程池不允許使用 Executors 去創建,而是通過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。

錯誤做法:

ExecutorService executors = Executors.newSingleThreadExecutor();
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);

對於線程池的參數需要有深入的理解後,結合實際的機器參數來進行參數設置,從而防止在使用中出現異常。

 ExecutorService fixedExecutorService = new ThreadPoolExecutor(
 1,
 2, 
 60,
 TimeUnit.SECONDS,
 linkedBlockingQueue, 
 new MyThreadFactory(),
 new ThreadPoolExecutor.AbortPolicy()
 );

ps:使用Executors.new方式創建線程池的缺點:

對於FixedThreadPool 和 SingleThreadPool而言

允許的請求隊列長度為 Integer.MAX_VALUE,可能會堆積大量的請求,從而導致 OOM。

對於CachedThreadPool 和 ScheduledThreadPool而言

允許的創建線程數量為 Integer.MAX_VALUE,可能會創建大量的線程,從而導致 OOM。

15、使用一些日期類的時候,推薦使用LocalDateTime來替代Calendar類,或者說使用Instant來替代掉Date類。

16、儘量避免在for循環裡面執行try-catch操作,可以選擇將try-catch操作放在循環體外部使用。

正確做法:

try {
 for (int i = 0; i < 100; i++) {
 doSomeThing();
 }
 }catch (Exception e){
 e.printStackTrace();
 }

不推薦做法:
for (int i = 0; i < 100; i++) {
 try {
 doSomeThing();
 } catch (Exception e) {
 e.printStackTrace();
 }
 }

17、對於大段的代碼進行try-catch操作,這是一種不負責任的行為,將穩定的代碼也都包圍在了try-catch語句塊裡面沒能很好的分清代碼的穩定性範圍。

通常我們稱在運行中不會出錯的代碼塊為穩定性代碼,可能會有異常出錯的部分為非穩定性代碼塊,後者才是try-catch重點需要關注的對象。

18、在jdk7之後,對於流這類需要關閉連接釋放資源的對象,可以使用try-with-resource處理機制來應對。

例如下方代碼:

 File file = new File("*****");
 try (FileInputStream fin = new FileInputStream(file)) {
 //執行相關操作
 } catch (Exception e) {
 //異常捕獲操作
 }

19、使用ArrayList的時候,如果清楚它的指定大小的話,可以儘量在初始化的時候進行大小指定,因為隨著arraylist不斷添加新的元素之後,鏈表的體積會不斷增大擴容。

 private void grow(int minCapacity) {
 // overflow-conscious code
 int oldCapacity = elementData.length;
 int newCapacity = oldCapacity + (oldCapacity >> 1);
 if (newCapacity - minCapacity < 0)
 newCapacity = minCapacity;
 if (newCapacity - MAX_ARRAY_SIZE > 0)
 newCapacity = hugeCapacity(minCapacity);
 // minCapacity is usually close to size, so this is a win:
 elementData = Arrays.copyOf(elementData, newCapacity);
 }

20、對於一些短信,郵件,電話,下單,支付等應用場景而言,開發的時候需要設置相關的防重複功能限制,防止出現某些惡意刷單,濫刷這類型情況。

21、對於敏感詞彙發表的時候,需要考慮一些文本過濾的策略。

這一塊的功能可以考慮直接接入市面上已有的成熟的UGC監控服務,或者使用公司內部自研的ugc過濾工具,防止用戶發表惡意評論等情況出現。

22、在建立索引的時候,對於索引的命名需要遵循一定的規範:

索引類型命名規則案例主鍵索引pk_字段名,pk是指primary keypk_order_id唯一索引uk_字段名,uk是指 unique keyuk_order_id普通索引idx_字段名,idx是指 indexidx_order_id

23、當我們需要存儲一段文本信息的時候,需要先考慮存儲文本的長度。

如果文本的長度超過了5000,則不建議再選擇使用varchar類型來進行存儲,可以考慮使用text類型進行數據存儲,這個時候可以考慮單獨用一張表來進行存儲數據,並且通過一個額外的主鍵id來對應,從而避免影響其他字段的查詢。

24、在進行數據庫命名的時候儘量保證數據庫的名稱和項目工程的名稱一致。

25、在進行表結構設計的時候,只要具有唯一性質的字段都需要建立唯一索引。

這樣有助於後期進行查詢的時候提高查詢的效率,沒有唯一索引這一層的保障,即使在業務層加入了攔截,但是依然容易造成線上髒數據的產生。

26、在進行order by這類型sql查詢的時候,需要注意查詢索引的有序性。

關於索引的建立,可以去了解一下索引的星級評定,例如三星索引。但是個人認為索引沒有所謂的最優性,需要結合實際的業務場景來設計。

27、在MySQL中,使用count(*)會統計值為 NULL 的行,而 count(列名)不會統計此列為 NULL 值的行。

28、在進行數據庫存儲引擎選擇的時候,需要結合相關的應用場景來選擇,如果是需要應用在select操作較多的情況下,可以選擇使用MyIsAM存儲引擎,如果是對於數據的insert,update,這類修改操作較多的業務場景,則優先推薦使用innodb存儲引擎。目前普遍互聯網公司都推薦使用innodb較多。


分享到:


相關文章: