java代碼重構的經驗總結分享

轉載: http://www.cnblogs.com/jun-ma/p/4967839.html
相關文章: https://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/
java代碼重構的經驗總結分享
java代碼重構的經驗總結分享

幾天前的壹次上線,腦殘手抖不小心寫了bug,雖然組裏的老大沒有說什麽,但心裏面很是難過。同事說我之所以寫蟲子是因為我討厭if/else,這個習慣不好。的確,if/else可以幫助我們很方便的寫出流程控制代碼,簡潔明了,這個條件做什麽,那個條件做什麽,說得很清楚。說真的,我從來不反對if/else,從經驗上看,越復雜的業務場景下,代碼寫的越簡單單壹,通常越不容易出錯。以結果為導向的現代項目管理方式,這是壹種很有效實踐經驗。

同事說的沒錯,我的確很討厭if/else。這個習慣很大程度是受Thoughtworks壹位咨詢師朋友影響,他經常在我耳邊嶗刀,寫代碼要幹凈,要簡潔,要靈活多變,不要固守城規,不要動不動就if/else,switch/case。初入it領域,我壹直把這句話奉為經典。在以後的學習工作中也時刻提醒自己要讓自己的代碼盡可能的看起來簡潔,不失靈活。不喜歡if/else並不意味著拒絕它,該使用的時候必要使用,比如函數接口入參check,處理異常分支邏輯流程等。通常能不用分支語句,我盡量不會使用,因為我覺得if/else很醜,每每看到if/else代碼,總會以挑剔的眼光看待它,想想能不能重構的更好。大多數時候,關於什麽好的代碼,大家的意見往往分歧很大,每個人都有各自的想法,審查妳代碼的人可能會選擇另壹種實現方式,這並不能說明誰對誰錯。

OO設計遵循SOLID(單壹功能、開閉原則、裏氏替換、接口隔離以及依賴反轉)原則,使用這個原則去審視if/else,可能會發現很多問題,比如不符合單壹原則,它本身就像壹團漿糊,融合了各種作料,黏糊糊的很不幹凈;比如不符合開閉原則,每新增壹種場景,就需要修改源文件增加壹條分支語句,業務邏輯復雜些若有1000種場景就得有1000個分支流,這種情況下代碼不僅僅惡心問題了,效率上也存在很大問題。由此可見,if/else雖然簡單方便,但不恰當的使用會給編碼代碼帶來非常痛苦的體驗。針對這種惡心的if/else分支,我們當然首先想到的去重構它–在不改變代碼外部功能特征的前提下對代碼內部邏輯進行調整和優化,但,如何做呢?前段時間在項目中正好遇到壹個惡心的if/else例子,想在這篇博客裏和大家分享壹下去除if/else重構的歷程。

java代碼重構的經驗總結分享

if/else的惡瘤

有句話說的好–好文章是改出來,同樣,好的代碼也肯定是重構出來的,因為沒有哪個軟件工程師能夠拍著胸脯保證在項目之初代碼設計這塊,就考慮到了所有需求變化可能性的擴展。隨著項目的不斷成長,業務邏輯變的越來越復雜,代碼也開始變的越來越多,原有的設計可能不再滿足需求,那麽此時必須要重構。就系統整體架構而言,重構可能需要很大的改動,可能在架構流程上需要評審;就功能內代碼層次而言,這種重構在我們編碼過程中隨時可以進行,類似於if/else,swicth/case這種代碼的重構也屬於這種類型。今天我們要重構的if/else源碼如下所示,針對不同的status code,CountRecoder對象會執行不同的set方法,為不同內部屬性賦值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public CountRecoder getCountRecoder(List countEntries) {
CountRecoder countRecoder = new CountRecoder();
for (CountEntry countEntry : countEntries) {
if (1 == countEntry.getCode()) {
countRecoder.setCountOfFirstStage(countEntry.getCount());
} else if (2 == countEntry.getCode()) {
countRecoder.setCountOfSecondStage(countEntry.getCount());
} else if (3 == countEntry.getCode()) {
countRecoder.setCountOfThirdtage(countEntry.getCount());
} else if (4 == countEntry.getCode()) {
countRecoder.setCountOfForthtage(countEntry.getCount());
} else if (5 == countEntry.getCode()) {
countRecoder.setCountOfFirthStage(countEntry.getCount());
} else if (6 == countEntry.getCode()) {
countRecoder.setCountOfSixthStage(countEntry.getCount());
}
}
return countRecoder;
}

CountRecoder對象是壹個簡單的Java Bean,用於保存壹天之中六種狀態分別對應的數據條目,提供了get和set方法。CountEntry是對應數據庫中每種狀態的數據條目記錄,包含狀態code和以及count兩個字段, 我們可以使用mybatis實現數據庫記錄和java對象之間的轉換。上面getCountRecoder的方法實現了將list轉換為CountRecoder的功能。

看到這段代碼,想必已經有很多人要呵呵了,像壹坨啥啥啥,長得這麽醜,真不知道它”爸媽”怎麽想的,怎麽敢”生”出來。啥都不說了,直接回爐重構吧。重構是門藝術,Martin flow曾寫過壹本書《重構改變代碼之道》,裏面詳細的記錄了重構的方法論,感興趣的朋友可以閱讀壹下。說到重構,通常我們在重構中會遇到壹個問題,那就是如何能夠保證重構的代碼不改變原有的外部功能特征 ?經過TDD訓練的朋友應該知道答案,那就是單元測試,重構之前要寫單元測試,準確的來說應該是補單元測試,畢竟TDD的核心理念是測試驅動開發。對於今天博客中分享的例子,因為代碼邏輯比較簡單,所以偷了懶,省卻了單元測試的歷程。

重構初體驗–反射

要重構上面的代碼,對設計模式精通的人可以立馬可以看出來這是使用策略模式/狀態模式的絕佳場景,將策略模式稍微變換,工廠模式應該也是ok的,當然也有些人會選擇使用反射。對於這些方法,這裏不壹壹列出,主要想講壹下使用反射和工廠模式如何解決消除if/else問題,那先說反射吧,代碼如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private static Map methodsMap = new HashMap<>();

static {
methodsMap.put(1, "setCountOfFirstStage");
methodsMap.put(2, "setCountOfSecondStage");
methodsMap.put(3, "setCountOfThirdtage");
methodsMap.put(4, "setCountOfForthtage");
methodsMap.put(5, "setCountOfFirthStage");
methodsMap.put(6, "setCountOfSixthStage");
}

public CountRecoder getCountRecoderByReflect(List countEntries) {
CountRecoder countRecoder = new CountRecoder();
countEntries.stream().forEach(countEntry -> fillCount(countRecoder, countEntry));
return countRecoder;
}

private void fillCount(CountRecoder shippingOrderCountDto, CountEntry countEntry) {
String name = methodsMap.get(countEntry.getCode());
try {
Method declaredMethod = CountRecoder.class.getMethod(name, Integer.class);
declaredMethod.invoke(shippingOrderCountDto, countEntry.getCount());
} catch (Exception e) {
System.out.println(e);
}
}

重構初體驗–所謂模式

使用反射去掉if/else的原理很簡單,使用HashMap建立狀態碼和需要調用的方法的方法名之間的映射關系,對於每個CountEntry,首先取出狀態碼,然後根據狀態碼獲得相應的要調用方法的方法名,然後使用java的反射機制就可以實現對應方法的調用了。本例中使用反射的確可以幫助我們完美的去掉if/else的身影,但是,眾所周知,反射效率很低,在高並發的條件下,反射絕對不是壹個良好的選擇。除去反射這種方法,能想到的就剩下使用策略模式或者與其類似的狀態模式,以及工廠模式了,我們以工廠模式為例,經典的架構UML架構圖通常由三個組成要素:

抽象產品角色:通常是壹個抽象類或者接口,裏面定義了抽象方法
具體產品角色:具體產品的實現類,繼承或是實現抽象策略類,通常由壹個或多個組成類組成。
工廠角色:持有抽象產品類的引用,負責動態運行時產品的選擇和構建
策略模式的架構圖和工廠模式非常類似,不過在策略模式裏執行的對象不叫產品,叫策略。在本例中,這裏的產品是虛擬產品,它是服務類性質的接口或者實現。Ok,按照工廠模式的思路重構我們的代碼,我們首先定義壹個抽象產品接口FillCountService,裏面定義產品的行為方法fillCount,代碼如下所示:

1
2
3
public interface FillCountService {
void fillCount(CountRecoder countRecoder, int count);
}

接著我們需要分別實現這六種服務類型的產品,在每種產品中封裝不同的服務算法,具體的代碼如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class FirstStageService implements FillCountService {
@Override
public void fillCount(CountRecoder countRecoder, int count) {
countRecoder.setCountOfFirstStage(count);
}
}

class SecondStageService implements FillCountService {
@Override
public void fillCount(CountRecoder countRecoder, int count) {
countRecoder.setCountOfSecondStage(count);
}
}

class ThirdStageService implements FillCountService {
@Override
public void fillCount(CountRecoder countRecoder, int count) {
countRecoder.setCountOfThirdtage(count);
}
}

class ForthStageService implements FillCountService {
@Override
public void fillCount(CountRecoder countRecoder, int count) {
countRecoder.setCountOfForthtage(count);
}
}

class FirthStageService implements FillCountService {
@Override
public void fillCount(CountRecoder countRecoder, int count) {
countRecoder.setCountOfFirthStage(count);
}
}

class SixthStageService implements FillCountService {
@Override
public void fillCount(CountRecoder countRecoder, int count) {
countRecoder.setCountOfSixthStage(count);
}
}

緊接著,我們需要是實現工廠角色,在工廠內需要實現產品的動態選擇算法,使用HashMap維護狀態code和具體產品的對象之間的映射關系,
就可以非常容易的實現這壹點,具體代碼如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class FillCountServieFactory {

private static Map fillCountServiceMap = new HashMap<>();

static {
fillCountServiceMap.put(1, new FirstStageService());
fillCountServiceMap.put(2, new SecondStageService());
fillCountServiceMap.put(3, new ThirdStageService());
fillCountServiceMap.put(4, new ForthStageService());
fillCountServiceMap.put(5, new FirthStageService());
fillCountServiceMap.put(6, new SixthStageService());
}

public static FillCountService getFillCountStrategy(int statusCode) {
return fillCountServiceMap.get(statusCode);
}
}

客戶端在具體使用的時候就變的很簡單,那getCountRecoder方法就可以用下面的代碼實現:

1
2
3
4
5
6
7
public CountRecoder getCountRecoder(List countEntries) {
CountRecoder countRecoder = new CountRecoder();
countEntries.stream().forEach(countEntry ->
FillCountServieFactory.getFillCountStrategy(countEntry.getCode())
.fillCount(countRecoder, countEntry.getCount()));
return countRecoder;
}

重構初體驗–Java8對模式設計的精簡

和反射壹樣使用設計模式也同樣完美的去除了if/else,但是不得不引入大量的具體服務實現類,同時程序中出現大量的模板代碼,使得我們程序看起來很不幹凈,幸好Java 8之後引入了Functional Interface,我們可以使用lambda表達式來去除這些模板代碼。將壹個接口變為Functional interface,可以通過在接口上添加FunctionalInterface註解實現,代碼如下所示:

1
2
3
4
@FunctionalInterface
public interface FillCountService {
void fillCount(CountRecoder countRecoder, int count);
}

那麽具體的服務實現類就可以使用壹個簡單的lambda表達式代替,原先的FirstStageService類對象就可以使用下面的表達式代替:

(countRecoder, count) -> countRecoder.setCountOfFirstStage(count)
那麽工廠類中的代碼就可以變為:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
`public class FillCountServieFactory {`

private static Map<Integer, FillCountService> fillCountServiceMap = new HashMap<>();

static {
fillCountServiceMap.put(1, (countRecoder, count) -> countRecoder.setCountOfFirstStage(count));
fillCountServiceMap.put(2, (countRecoder, count) -> countRecoder.setCountOfSecondStage(count));
fillCountServiceMap.put(3, (countRecoder, count) -> countRecoder.setCountOfThirdtage(count));
fillCountServiceMap.put(4, (countRecoder, count) -> countRecoder.setCountOfForthtage(count));
fillCountServiceMap.put(5, (countRecoder, count) -> countRecoder.setCountOfFirthStage(count));
fillCountServiceMap.put(6, (countRecoder, count) -> countRecoder.setCountOfSixthStage(count));
}

public static FillCountService getFillCountStrategy(int statusCode) {
return fillCountServiceMap.get(statusCode);
}
`}`

這樣我們的代碼就重構完畢了,當然了還是有些不完美,程序中的魔法數字不利於閱讀理解,可以使用易讀的常量標識它們,在這裏就不做過多說明了。

總結

Craig Larman曾經說過軟件開發最重要的設計工具不是什麽技術,而是壹顆在設計原則方面訓練有素的頭腦。重構的最終結果不壹定會讓代碼變少,相反還有可能增加程序的復雜度和抽象性,就本例中的if/else而言,確實如此。我非常贊同我的壹位朋友說的話,做技術要有追求,沒錯if/else可以在代碼中工作的挺好,也可以很容易的被接替者所理解,但是我們可以有更好的選擇,因為簡單的代碼也可以變得很精彩。多勤多思,也許有壹天真的就可以達到Craig所說的在設計原則方面擁有訓練有素的頭腦,誰說不是這樣呢?加油吧。

# java
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×