1. 程式人生 > 程式設計 >java開發時各類工具的使用規範

java開發時各類工具的使用規範

工具類規範

一個專案不可能沒有工具類,工具類的初衷是良好的,程式碼重用,但到了後面工具類越來越亂,有些專案工具類有幾十個,看的眼花繚亂,還有不少重複。如何編寫出好的工具類,我有幾點建議:

隱藏實現

就是要定義自己的工具類,儘量不要在業務程式碼裡面直接呼叫第三方的工具類。這也是解耦的一種體現。如果我們不定義自己的工具類而是直接使用第三方的工具類有2個不好的地方:

  1. 不同的人會使用不同的第三方工具庫,會比較亂。
  2. 將來萬一要修改工具類的實現邏輯會很痛苦。

以最簡單的字串判空為例,很多工具庫都有 StringUtils工具類,如果我們使用commons的工具類,一開始我們直接使用StringUtils.isEmpty,字串為空或者空串的時候會返回為true,後面業務改動,需要改成如果全部是空格的時候也會返回true,怎麼辦?我們可以改成使用StringUtils.isBlank。看上去很簡單,對吧? 如果你有幾十個檔案都呼叫了,那我們要改幾十個檔案,是不是有點噁心?再後面發現,不只是英文空格,如果是全形的空格,也要返回為true,怎麼辦?StringUtils上的方法已經不能滿足我們的需求了,真不好改了。。。

所以我的建議是,一開始就自己定義一個自己專案的StringUtil,裡面如果不想自己寫實現,可以直接呼叫commons的方法,如下:

public static boolean isEmpty(String str) {
 return org.apache.commons.lang3.StringUtils.isEmpty(str);
}

後面全部空格也返回true的時候,我們只需要把isEmpty改成isBlank;再後面全部全形空格的時候也返回true的話,我們增加自己的邏輯即可。我們只需要改動和測試一個地方。

在舉一個真實一點的例子,如複製物件的屬性方法。

一開始,如果我們自己不定義工具類方法,那麼我們可以使用org.springframework.beans.BeanUtils.copyProperties(source,dest)這個工具類來實現,就一行程式碼,和呼叫自己的工具類沒有什麼區別。看上去很OK,對吧?

隨著業務發展,我們發現這個方式的效能或者某些特性不符合我們要求,我們需要修改改成commons-beanutils包裡面的方法,org.apache.commons.beanutils.BeanUtils.copyProperties(dest,source),這個時候問題來了,第一個問題,它的方法的引數順序和之前spring的工具類是相反的,改起來非常容易出錯!第二個問題,這個方法有異常丟擲,必須宣告,這個改起來可要命了!結果你發現,一個看上去很小的改動,改了幾十個檔案,每個改動還得測試一次,風險不是那麼得小。有一點小奔潰了,是不是?

等你改完之後測試完了,突然有一天需要改成,複製引數的時候,有些特殊欄位需要保留(如物件id)或者需要過濾掉(如密碼)不復制,怎麼辦?這個時候我估計你要崩潰了吧?不要覺得我是憑空想象,程式設計活久見,你總會遇到的一天!

所以,我們需要定義自己的工具類函式,一開始我定義成這樣子。

public void copyAttribute(Object source,Object dest) {

 org.springframework.beans.BeanUtils.copyProperties(source,dest);

}

後面需要修改為commons-beanutis的時候,我們改成這樣即可,把引數順序掉過來,然後處理了一下異常,我使用的是Lombok的SneakyThrows來處理異常,你也可以捕獲掉丟擲執行時異常,個人喜好。

@SneakyThrows
public void copyAttribute(Object source,Object dest) {
 org.apache.commons.beanutils.BeanUtils.copyProperties(dest,source);
}

再後面,複製屬性的時候需要保留某些欄位或者過濾掉某些欄位,我們自己參考其他庫實現一次即可,只改動價格和測試一個檔案一個方法,風險非常可控。

還記得我之前的帖子裡說的需求變更嗎?你可以認為這算需求變更,但同樣的需求變更,我一個小時改完測試,沒有任何風險輕輕鬆鬆上線,你可能滿頭大汗加班加點還擔心出問題。。。

使用父類/介面

上面那點隱藏實現,說到底是封裝/解耦的思想,而現在說的這點是抽象的思想,做好了這點,我們就能編寫出看上去很專業的工具類。這點很好理解,但是我們容易忽略。

舉例,假設我們寫了一個判斷arraylist是否為空的函式,一開始是這樣的。

public static boolean isEmpty(ArrayList<?> list) {
 return list == null || list.size() == 0;
}

這個時候,我們需要思考一下引數的型別能不能使用父類。我們看到我們只用了size方法,我們可以知道size方法再list介面上有,於是我們修改成這樣。

public static boolean isEmpty(List<?> list) {
 return list == null || list.size() == 0;
}

後面發現,size方法再list的父類/介面Collection上也有,那麼我們可以修改為最終這樣。

public static boolean isEmpty(Collection<?> list) {
 return list == null || list.size() == 0;
}

到了這部,Collection沒有父類/介面有size方法了,修改就結束了。最後我們需要把引數名字改一下,不要再使用list。改完後,所有實現了Collection都物件都可以用,最終版本如下:

public static boolean isEmpty(Collection<?> collection) {
 return collection == null || collection.size() == 0;
}

是不是看上去通用多了 ,看上去也專業多了?上面的string相關的工具類方法,使用相同的思路,我們最終修改一下,把引數類型別由String修改為CharSequence,引數名str修改為cs。如下:

public static boolean isEmpty(CharSequence cs) {
 return org.apache.commons.lang3.StringUtils.isEmpty(cs);
}

思路和方法很簡單,但效果很好,寫出來的工具類也顯得很專業!總結一下,思路是抽象的思想,主要是修改引數型別,方法就是往上找父類/介面,一直找到頂為止,記得修改引數名。

使用過載編寫衍生函式組

開發過的兄弟都知道,有一些工具庫,有一堆的過載函式,呼叫起來非常方便,經常能直接呼叫,不需要做引數轉換。這些是怎麼樣編寫出來的呢?我們舉例說明。

現在需要編寫一個方法,輸入是一個utf-8格式的檔案的檔名,把裡面內容輸出到一個list。我們剛剛開始編寫的時候,是這個樣子的

public static List<String> readFile2List(String filename) throws IOException {
 List<String> list = new ArrayList<String>();

 File file = new File(filename);

 FileInputStream fileInputStream = new FileInputStream(file);

 BufferedReader br = new BufferedReader(new InputStreamReader(fileInputStream,"UTF-8"));

 // XXX操作

 return list;
}

我們先實現,實現完之後我們做第一個修改,很明顯,utf-8格式是很可能要改的,所以我們先把它做為引數提取出去,方法一拆為二,就變成這樣。

public static List<String> readFile2List(String filename) throws IOException {
 return readFile2List(filename,"UTF-8");
}

public static List<String> readFile2List(String filename,String charset) 
 throws IOException {
 List<String> list = new ArrayList<String>();

 File file = new File(filename);
 FileInputStream fileInputStream = new FileInputStream(file);

 BufferedReader br = new BufferedReader(new InputStreamReader(fileInputStream,charset));

 // XXX操作

 return list;
}

多了一個方法,直接呼叫之前的方法主體,主要的程式碼還是隻有一份,之前的呼叫地方不需要做任何修改!可以放心修改。

然後我們在看裡面的實現,下面這2行程式碼裡面,String型別的filename會變化為File型別,然後在變化為FileInputStream 型別之後才使用。

File file = new File(filename);
FileInputStream fileInputStream = new FileInputStream(file);

這裡我們就應該想到,使用者可能直接傳如File型別,也可能直接傳入FileInputStream型別,我們應該都需要支援,而不需要使用者自己做型別的處理!在結合上一點的使用父類,把FileInputStream改成父類InputStream,我們最終的方法組如下:

package plm.common.utils;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.io.IOUtils;

/**
 * 工具類編寫範例,使用過載編寫不同引數型別的函式組
 * 
 * @author 曉風輕 https://github.com/xwjie/PLMCodeTemplate
 *
 */
public class FileUtil {

 private static final String DEFAULT_CHARSET = "UTF-8";

 public static List<String> readFile2List(String filename) throws IOException {
 return readFile2List(filename,DEFAULT_CHARSET);
 }

 public static List<String> readFile2List(String filename,String charset)
 throws IOException {
 FileInputStream fileInputStream = new FileInputStream(filename);
 return readFile2List(fileInputStream,charset);
 }

 public static List<String> readFile2List(File file) throws IOException {
 return readFile2List(file,DEFAULT_CHARSET);
 }

 public static List<String> readFile2List(File file,String charset)
 throws IOException {
 FileInputStream fileInputStream = new FileInputStream(file);
 return readFile2List(fileInputStream,charset);
 }

 public static List<String> readFile2List(InputStream fileInputStream)
 throws IOException {
 return readFile2List(fileInputStream,DEFAULT_CHARSET);
 }

 public static List<String> readFile2List(InputStream inputStream,String charset)
 throws IOException {
 List<String> list = new ArrayList<String>();

 BufferedReader br = null;
 try {
 br = new BufferedReader(new InputStreamReader(inputStream,charset));

 String s = null;
 while ((s = br.readLine()) != null) {
 list.add(s);
 }
 } finally {
 IOUtils.closeQuietly(br);
 }

 return list;
 }

}

怎麼樣?6個方法,實際上程式碼主體只有一份,但提供各種型別的入參,呼叫起來很方便。開發組長編寫的時候,多費一點點時間,就能寫來看上去很專業呼叫起來很方便的程式碼。如果開發組長不寫好,開發人員發現現有的方法只能傳String,她要傳的是InputStream,她又不敢改原來的程式碼,就會copy一份然後修改一下,就多了一份重複程式碼。程式碼就是這樣爛下去了。。。

關鍵點,多想一步,根據引數變化編寫各種型別的入參函式,需要保證函式主要程式碼只有一份。

使用靜態引入

工具類的一個問題就是容易氾濫,主要原因是開發人員找不到自己要用的方法,就自己寫一個,開發人員很難記住類名,你也不可能天天程式碼評審。

所以要讓開發人員容易找到,我們可以使用靜態引入,在Eclipse裡面這樣匯入:

java開發時各類工具的使用規範

物理上獨立存放

這點是我的習慣,我習慣把和業務無關的程式碼放到獨立的工程或者目錄,在物理上要分開,專人維護。不是所有人都有能力寫工具類,獨立存放專門維護,專門的許可權控制有助於保證程式碼的純潔和質量。這樣普通的開發人員就不會隨意修改。

例如我的範例工程裡面,專門建立了一個source目錄存放框架程式碼,工具類也在裡面,這裡的程式碼,只有我一個人會去修改:

總結

幾乎所有人都知道面向物件的思想有抽象封裝,但幾個人真正能做到,其實有心的話,處處都能體現出這些思想。編寫工具類的時候需要注意引數的優化,而且大型專案裡面不要在業務程式碼裡面直接呼叫第三方的工具類,然後就是多想一步多走一步,考慮各種型別的入參,這樣你也能編寫出專業靈活的工具類!

到此這篇關於java開發時各類工具的使用規範的文章就介紹到這了,更多相關java編碼時工具的使用規範內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!