1. 程式人生 > >把"重試"抽象出來做個工具類吧

把"重試"抽象出來做個工具類吧

背景介紹

我們在工作中難免會寫一些重複性的程式碼,所以需要我們具備一定的抽象能力,比如把共同的邏輯抽取到抽象類中,也可以通過一些工具類來避免冗餘程式碼

今天這篇文章就是把一個呼叫服務的重試功能抽取出一個工具類,以備複用。這裡為了方便介紹,把呼叫服務簡化成方法的呼叫,被呼叫的 foo 方法如下:

public static List<String> foo() {// 沒有顯示丟擲異常
    System.out.println("呼叫方法");
        // 模擬丟擲異常
    System.out.println(1/0);
    List<String> list = new ArrayList<>();
    list.add("1");
    return list;
}

呼叫方和重試邏輯如下:

List<String> result = null;
// 重試次數
int retryCount = 0;
// 呼叫服務的開關,預設開啟
boolean callSwitch = true;
// 只要呼叫服務開關開著,並且重試次數不大於最大的重試次數,就呼叫服務
while (callSwitch && retryCount <= 3) {
    try {
        // 呼叫服務
        result = foo();
        // 省略了對結果的校驗,假設到了這裡就說明沒有問題,把呼叫服務開關關掉
        callSwitch = false;
    } catch (Exception e) {
        // 發生了異常(比如超時,就需要重試了)
        // 呼叫服務的開關開啟
        callSwitch = true;
        retryCount++;
    }
}
// 後面對 result 進行處理

其實上面的程式碼就已經解決了,服務重試的邏輯,測試沒有問題後,可以提交程式碼讓同事幫忙進行 CR 了,可是小朋同學看到這個程式碼後,給了建議:

可以抽象一層,提出一個 retry 的工具類,這樣大家都可以簡單複用你的 retry 邏輯

抽象思考過程

白牙心想,也對哈,那就提出一個工具類吧,可是發了會兒呆,竟然沒有頭緒(反映出了抽象能力的薄弱,平時要多注意抽象能力的培養)。小朋見狀,給了一點提示,白牙立馬在鍵盤上噼裡啪啦敲擊了起來,就有了下面的工具類

主要依賴函式式介面 Supplier 和 BiFunction

public class RetryUtil {
    public static <T> T retry(int maxRetryCount, Supplier<T> supplier, BiFunction<T, Exception, Boolean> consumer) {
        T result = null;
        Exception exception = null;

        int retryCount = 0;
        boolean callMethod = true;
        while (callMethod && retryCount <= maxRetryCount) {
            try {
                // 獲取呼叫服務的結果
                result = supplier.get();
            } catch (Exception e) {
                // 如果重試次數不小於最大重試次數,就丟擲異常,我們把對異常的處理交給業務方
                if (retryCount >= maxRetryCount) {
                    throw e;
                }
                exception = e;  
            }
            // 對結果進行判斷
            callMethod = consumer.apply(result, exception);
            if (callMethod) {
                retryCount++;
            }
        }
        return result;
    }
}

業務呼叫方的程式碼如下:

List<String> result1 = retry(3,// 最大重試次數
                ()-> foo(),// 呼叫服務
                (list, e) -> e != null || list == null || list.isEmpty());// 對結果處理

自測沒有問題後,又提交程式碼讓小朋給 CR 一下,小朋凝視了會兒,就有了下面的對話

小朋:“retry 方法沒有丟擲異常”

白牙:“被呼叫的服務沒有顯示的丟擲異常,這裡也就沒有丟擲”

小朋:“那人如果有服務方顯示丟擲異常呢?”

白牙:“我再改一版”

服務方顯示丟擲了異常,這樣 retry 方法也得顯示丟擲異常,但呼叫方就會顯示會處理的異常,如下所示:

public static List<String> foo() throws Exception{// 顯示丟擲異常
    System.out.println("呼叫方法");
        // 模擬丟擲異常
    System.out.println(1/0);
    List<String> list = new ArrayList<>();
    list.add("1");
    return list;
}
public class RetryUtil {
    public static <T> T retry(int maxRetryCount, Supplier<T> supplier, BiFunction<T, Exception, Boolean> consumer) throws Exception{
        // 省略...
}

出現這種情況是因為 Supplier 的 get 方法沒有丟擲異常

@FunctionalInterface
public interface Supplier<T> {
    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}

既然你不支援,那就自己寫個唄,於是就有了下面的 DspSupplier,它和 Supplier 的主要區別就是在 get 方法中顯示丟擲了異常

@FunctionalInterface
interface DspSupplier<T> {
    /**
     * Gets a result.
     *
     * @return a result
     */
    T get() throws Exception;
}

於是 retry 方法就變成了下面這樣子

public class RetryUtil {
    public static <T> T retry(int maxRetryCount, DspSupplier<T> supplier, BiFunction<T, Exception, Boolean> consumer) throws Exception{
        // 省略...
}

使用了自定義的 Supplier 後,呼叫方就沒有 “Unhandled exception” 了

總結

我們平時再開發的過程中,可以嘗試去利用函式式介面去實現一些邏輯的抽取,做成一個工具類,供大家使用,簡化人力,也是對自己編碼能力的一個提升。

上面的案例比較簡單,但麻雀雖小,五臟俱全,也是一個不錯的體驗
歡迎關注公眾號 【每天晒白牙】,獲取最新文章,我們一起交流,共同進步!