在寫方法(函式)的過程中,總結了如下原則:
【問題1】、有些方法,內容都差不多,只是引數的個數不一樣。此時,到底是寫多個方法呢,還是寫一個方法涵蓋所有功能(根據傳入的標識去執行不同的步驟)。
A、寫多個方法,每個方法引數不同
優點:每個方法執行一件事情,過程清晰明瞭,呼叫也簡單。
缺點:多個方法,有很多程式碼是冗餘的,而且要改起來很麻煩。
B、寫一個方法,涵蓋所有引數
優點:只需要編寫和維護一套程式碼。
缺點:呼叫時需要明確指定所有引數。需要做很多判斷,效能稍微低一點(可以忽略)。
》》》舉例:
改變字串編碼的方法:
按照A原則,可以寫兩個方法:
changeCode(String orgStr);
changeCode(String orgStr, String charSet);
但是按照B原則,則只需要寫一個方法:
changeCode(String orgStr, String charSet);
因為方法二的功能已經包括了方法一的功能。
如上所述,其實二者各有優劣,但是我更傾向於第二種方法。
因為:
對於呼叫者而言,只有一個方法供選擇,而且他必須顯式的指定第二個引數。對呼叫者而言,是透明的。
對於寫方法的人而言,注意到,他應該檢查引數的合法性,按照A原則,如果呼叫了第二個方法,但是第二個引數為空,那這個時候怎麼辦?
應該呼叫方法1呢,還是說直接報錯?那既然反正都要檢查這個charSet的引數,那何不制定一個規則:呼叫第2個方法,如果charSet為空,則執行第1個方法的流程。
總結:通常情況下,優先採用B方案,採用A方案的情景為:
1)如果某些引數的方法使用非常頻繁,則可以單獨提出來,做成一個獨立的方法。
2)引數個數相同,但是引數型別不同(但是也可以用模糊型別的引數,在方法內再去判斷引數的型別)。
【問題2】、一個方法執行時走到了錯誤的流程,應該返回一個標識呢,還是throw一個錯誤?
A、返回一個標識,例如,執行失敗,則返回null、-1等。
優點:執行失敗時,返回null或者-1,呼叫者根據返回的標識即可判斷執行是否有錯。不需要try-catch。
缺點:通常,呼叫者不會去判斷返回的結果是什麼,它也不知道返回值代表的標識是什麼,所以當返回null、-1時,可能導致後期無法預知的錯誤。
B、throw一個錯誤,終止程式。
優點:無需定義錯誤標識,而且throw錯誤時,可以給出錯誤資訊。
缺點:呼叫者,需要用try-catch去捕獲錯誤,比較麻煩。
總結:對於那種公用的方法,還是採用方案B較好,可以約定,一律採用方案B。
【問題3】、什麼樣的錯誤往外拋,什麼樣的錯誤內部處理?還有,錯誤日誌的記錄 應該在哪一層做處理(裡層還是外層)?
引入自定義 Runtime Exception: ZtyRuntimeException
和自定義 Checked Exception: MedialException。
前者,自動中斷程式執行,預設情況下將堆疊資訊列印到控制檯,通過過載Thread.setDefaultUncaughtExceptionHandler方法,可以改變其策略,將執行時異常處理後再寫入日誌中。
後者必須在外層程式中捕捉進行處理。最佳的流程是:如果不需要後續處理,則直接丟擲Runtime Exception,否則通過MedialException傳遞Exception到最外層,由最外層將錯誤程式碼和錯誤資訊展示或者傳給第三方系統。
日誌的處理一定要定位準確,所以通常情況下,哪裡出錯,就在哪裡記錄日誌,不要在外層寫日誌,外層只負責處理錯誤後的程式邏輯,不負責寫日誌的工作。
一個例子:
D程式呼叫C程式,C程式呼叫B程式,B程式呼叫A程式,現在A程式出錯了。
情景一:
D程式需要得到執行是否正確的回撥資訊,然後做相應處理。
那麼,錯誤處理應該為:A丟擲MedialException(errorCode, errorMsg),B、C和A一樣,D捕獲MedialException異常,將errorCode和errorMsg轉發到檢視層或者呼叫方。
情景二:
D、C、B程式不需要回撥資訊,執行不成功只需要終止就行了。無論在哪裡終止都可以,框架會捕獲一個全域性異常資訊,然後顯示全域性錯誤頁面。
情景三:
D、C、B、A流程環環相扣,聯絡緊密,但是都沒有需要顯示捕獲的異常,正常情況下,是不會出錯的。只有當出現未捕獲的RuntimeException時,才會導致程式異常終止,
這種終止是未曾預料到的,可能導致嚴重後果。
情景二分析:直接丟擲自定義的執行時異常ZtyRuntimeException,全域性捕獲到時,列印錯誤資訊就可以了。
情景三分析:首先我們應該分析,哪些步驟是關鍵性且不允許出現異常的(例如一個操作具有“要麼全部成功、要麼全部不成功”的特點時),需要儘量預料哪些地方容易出錯,進而捕獲錯誤。
其次,無論我們怎麼努力,都有一些無法預料的錯誤出現,那麼這個時候,我們只能考慮是否要將這些錯誤資訊記錄下來?如果要記錄下來,一種方式,是通過全域性的錯誤處理,另一種是針對性
的處理(例如:全域性處理是列印堆疊資訊到控制檯,但是如果某個功能出錯時你想要記錄日誌)。針對性處理,可以在入口處捕獲整個方法,然後做專門處理。
結合情景一、二、三,一個功能最齊全的處理流程是這樣的:
A丟擲MedialException錯誤資訊給B,同時A呼叫了兩個工具類,可能出現自定義的執行時異常ZtyRuntimeException和未捕獲的執行時異常RuntimeException(及其子類)
B丟擲MedialException錯誤資訊給C,
C丟擲MedialException錯誤資訊給D,D收到MedialException異常後,將errorCode和errorMsg傳送到頁面上。
由於D過程十分重要,不允許出現未知異常,但是未知異常是無法避免的,為了捕獲D在執行過程中出現未知異常,我們在D的整個方法上,加上了如下程式碼:
try{
// ..... 整個D的方法
}catch (TreRuntimeException e) {
LOG.error( e.getMessage() );
}catch (RuntimeException e) {
LOG.error( e );
}
這樣,即使D出現了未知錯誤,也可以記錄日誌,便於我們分析。其中對TreRuntimeException錯誤進行了區別對待,因為TreRuntimeException的錯誤資訊已經經過了加工,
它的errorMsg裡面自帶了一手堆疊資訊,故不需要像未知RuntimeException那樣記錄堆疊資訊了。
總結:對於那種公用的方法,還是採用方案B較好,可以約定,一律採用方案B。