在Java中,在JDK 1.8裏也引入了類JavaScript的玩法 —— CompletableFuture
。這個類提供了大量的異步編程中Promise的各類方式。下面例舉幾個。java
鏈式處理:編程
CompletableFuture.supplyAsync(this::findReceiver) .thenApply(this::sendMsg) .thenAccept(this::notify);
上面的這個鏈式處理和JavaScript中的then()
方法很像,其中的 supplyAsync()
表示執行一個異步方法,而 thenApply()
表示執行成功後再串聯另一個異步方法,最後是 thenAccept()
來處理最終結果。app
下面這個例子是,合併兩個異步函數的結果成一個的示例:異步
String result = CompletableFuture.supplyAsync(() -> { return "hello"; }).thenCombine(CompletableFuture.supplyAsync(() -> { return "world"; }), (s1, s2) -> s1 + " " + s2).join()); System.out.println(result);
接下來,咱們再來看一下,Java這個類相關的異常處理:分佈式
CompletableFuture.supplyAsync(Integer::parseInt) //輸入: "ILLEGAL" .thenApply(r -> r * 2 * Math.PI) .thenApply(s -> "apply>> " + s) .exceptionally(ex -> "Error: " + ex.getMessage());
咱們要注意到上面代碼裏的 exceptionally()
方法,這個和JavaScript Promise中的 catch()
方法類似。異步編程
運行上面的代碼,會出現以下輸出:函數
Error: java.lang.NumberFormatException: For input string: "ILLEGAL"
也能夠這樣:this
CompletableFuture.supplyAsync(Integer::parseInt) // 輸入: "ILLEGAL" .thenApply(r -> r * 2 * Math.PI) .thenApply(s -> "apply>> " + s) .handle((result, ex) -> { if (result != null) { return result; } else { return "Error handling: " + ex.getMessage(); } });
上面代碼中,你能夠看到,其使用了 handle()
方法來處理最終的結果,其中包含了異步函數中的錯誤處理。spa
下面是我我的總結的幾個錯誤處理的最佳實踐。若是你知道更好的,請必定告訴我。調試
統一分類的錯誤字典。不管你是使用錯誤碼仍是異常捕捉,都須要認真並統一地作好錯誤的分類。最好是在一個地方定義相關的錯誤。好比,HTTP的4XX表示客戶端有問題,5XX則表示服務端有問題。也就是說,你要創建一個錯誤字典。
同類錯誤的定義最好是能夠擴展的。這一點很是重要,而對於這一點,經過面向對象的繼承或是像Go語言那樣的接口多態能夠很好地作到。這樣能夠方便地重用已有的代碼。
定義錯誤的嚴重程度。好比,Fatal表示重大錯誤,Error表示資源或需求得不到知足,Warning表示並不必定是個錯誤但仍是須要引發注意,Info表示不是錯誤只是一個信息,Debug表示這是給內部開發人員用於調試程序的。
錯誤日誌的輸出最好使用錯誤碼,而不是錯誤信息。打印錯誤日誌的時候,除了要用統一的格式,最好不要用錯誤信息,而使用相應的錯誤碼,錯誤碼不必定是數字,也能夠是一個能從錯誤字典裏找到的一個惟一的可讓人讀懂的關鍵字。這樣,會很是有利於日誌分析軟件進行自動化監控,而不是要從錯誤信息中作語義分析。好比:HTTP的日誌中就會有HTTP的返回碼,如:404
。但我更推薦使用像PageNotFound
這樣的標識,這樣人和機器都很容易處理。
忽略錯誤最好有日誌。否則會給維護帶來很大的麻煩。
對於同一個地方不停的報錯,最好不要都打到日誌裏。否則這樣會致使其它日誌被淹沒了,也會致使日誌文件太大,最好的實踐是,打出一個錯誤以及出現的次數。
不要用錯誤處理邏輯來處理業務邏輯。也就是說,不要使用異常捕捉這樣的方式來處理業務邏輯,而是應該用條件判斷。若是一個邏輯控制能夠用if - else清楚地表達,很是不建議使用異常方式處理。異常捕捉是用來處理不指望發生的事的,而錯誤碼則用來處理可能會發生的事。
對於同類的錯誤處理,用同樣的模式。好比,對於null
對象的錯誤,要麼都用返回null,加上條件檢查的模式,要麼都用拋NullPointerException的方式處理。不要混用,這樣有助於代碼規範。
儘量在錯誤發生的地方處理錯誤。由於這樣會讓調用者變得更簡單。
向上儘量地返回原始的錯誤。若是必定要把錯誤返回到更高層去處理,那麼,應該返回原始的錯誤,而不是從新發明一個錯誤。
處理錯誤時,老是要清理已分配的資源。這點很是關鍵,使用RAII技術,或是try-catch-finally,或是Go的defer均可以容易地作到。
不推薦在循環體裏處理錯誤。這裏說的更多的狀況是對於try-catch這種狀況,對於絕大多數的狀況你不須要這樣作。最好把整個循環體外放在try語句塊內,而在外面作catch。
不要把大量的代碼都放在一個try語句塊內。一個try語句塊內的語句應該是完成一個簡單單一的事情。
爲你的錯誤定義提供清楚的文檔以及每種錯誤的代碼示例。若是你是作RESTful API方面的,使用Swagger會幫你很容易搞定這個事。
對於異步的方式,推薦使用Promise模式處理錯誤。對於這一點,JavaScript中有很好的實踐。
對於分佈式的系統,推薦使用APM相關的軟件。尤爲是使用Zipkin這樣的服務調用跟蹤的分析來關聯錯誤。