代碼之醜(一)——讓判斷條件作真正的選擇java
if (0 == retCode) { SendMsg("000", "Process Success", outResult); } else { SendMsg("000", "Process Failure", outResult); }
看出來問題了嗎?通過仔細的對比,咱們發現,如此華麗的代碼,if/else的執行語句真正的差別只在於一個參數。第一段代碼,兩者的差別只是發送的消息,第二段代碼,差別在於最後那個參數。
看破這個差別以後,新的寫法就呼之欲出了,以第一段代碼爲例:
Java代碼 程序員
String msg = (0 == retCode ? "Process Success" : "Process Failure");
SendMsg("000", msg, outResult);
由這段代碼調整過程,咱們得出一個簡單的規則:
讓判斷條件作真正的選擇。
對於前面調整的代碼,判斷條件真正判斷的內容是消息的內容,而不是消息發送的過程。通過咱們的調整,獲取消息內容和發送消息的過程嚴格分離開來。
消除了代碼中的冗餘,代碼也更容易理解,同時,給將來留出了可擴展性。若是未來retCode還有更多的情形,咱們只要調整消息獲取的部分進行調整就行了。固然,封裝成函數是一個更好的選擇,這樣代碼就變成了:數組
SendMsg("000", msgFromRetCode(retCode),outResult);
代碼之醜(二)——長長的條件
這是一個長長的判斷條件:安全
Java代碼多線程
if (strcmp(type, 「DropGroup") == 0
|| strcmp(type, "CancelUserGroup") == 0
|| strcmp(type, "QFUserGroup") == 0
|| strcmp(type, "CancelQFUserGroup") == 0
|| strcmp(type, "QZUserGroup") == 0
|| strcmp(type, "CancelQZUserGroup") == 0
|| strcmp(type, "SQUserGroup") == 0
|| strcmp(type, "CancelSQUserGroup") == 0
|| strcmp(type, 「UseGroup") == 0
|| strcmp(type, "CancelGroup") == 0)
之因此注意到它,由於最後兩個條件是在最新修改裏面加入的,換句話說,這不是一次寫就的代碼。單就這一次而言,只改了兩行,這是能夠接受的。但這是遺留代碼,每次可能只改了一兩行,一般咱們會不僅一次踏入這片土地。經年累月,代碼成了這個樣子。函數
爲了讓這段代碼能夠接受一些,咱們不妨稍作封裝:spa
private boolean shouldExecute(String type) { return (strcmp(type, 「DropGroup") == 0 || strcmp(type, "CancelUserGroup") == 0 || strcmp(type, "QFUserGroup") == 0 || strcmp(type, "CancelQFUserGroup") == 0 || strcmp(type, "QZUserGroup") == 0 || strcmp(type, "CancelQZUserGroup") == 0 || strcmp(type, "SQUserGroup") == 0 || strcmp(type, "CancelSQUserGroup") == 0 || strcmp(type, 「UseGroup") == 0 || strcmp(type, "CancelGroup") == 0); }
Java代碼線程
if (shouldExecute(type)) { ... }
如今,雖然條件依然仍是不少,但比起原來龐大的函數,至少它已經被控制在一個相對較小的函數裏了。設計
雖然提取函數把這段代碼混亂的條件分離開來,它仍是能夠繼續改進的。好比,咱們把判斷的條件進一步提取:code
Java代碼
private boolean shouldExecute(String type) { String [] types= { "DropGroup", "CancelUserGroup", "QFUserGroup", "CancelQFUserGroup", "QZUserGroup", "CancelQZUserGroup", "SQUserGroup", "CancelSQUserGroup", "UseGroup", "CancelGroup" }; int size = types.size; for (int i = 0; i < size; i++) { if (strcmp(type, types) == 0) { return true; } } return false; }
這樣的話,若是之後要加一個新的type,只要在數組中增長一個新的元素便可。
代碼之醜(三)——不受歡迎的大心臟
Java代碼
ColdRule newRule = new ColdRule(); newRule.SetOID(oldRule.GetOID()); newRule.SetRegion(oldRule.GetRegion()); newRule.SetRebateRuleID(oldRule.GetRebateRuleID()); newRule.SetBeginCycle(oldRule.GetBeginCycle() + 1); newRule.SetEndCycle(oldRule.GetEndCycle()); newRule.SetMainAcctAmount(oldRule.GetMainAcctAmount()); newRule.SetGiftAcctAmount(oldRule.GetGiftAcctAmoun t()); newRule.SetValidDays(0); newRule.SetGiftAcct(oldRule.GetGiftAcct()); rules.Add(newRule);
就在我覺得這一片代碼就是完成給一個變量設值的時候,忽然,在那個不起眼的角落裏,這個變量獲得了應用:它被加到了rules裏面。什麼叫峯迴路轉,這就是。
既然它給了咱們這麼有趣的體驗,必然先殺後快。下面重構了這個函數:
Java代碼
ColdRule CreateNewRule(ColdRule& oldRule) { ColdRule newRule = new ColdRule(); newRule.SetOID(oldRule.GetOID()); newRule.SetRegion(oldRule.GetRegion()); newRule.SetRebateRuleID(oldRule.GetRebateRuleID()); newRule.SetBeginCycle(oldRule.GetBeginCycle() + 1); newRule.SetEndCycle(oldRule.GetEndCycle()); newRule.SetMainAcctAmount(oldRule.GetMainAcctAmount()); newRule.SetGiftAcctAmount(oldRule.GetGiftAcctAmount()); newRule.SetValidDays(0); newRule.SetGiftAcct(oldRule.GetGiftAcct()); return newRule; }
Java代碼
rules.Add(CreateNewRule(oldRule));
把這一堆設值操做提取了出來,整個函數看上去一會兒就清爽了。不是由於代碼變少了,而是由於代碼按照它職責進行了劃分:建立的歸建立,加入的歸加入。以前的代碼之因此讓我不安,多重職責是罪魁禍首。一旦把這個函數提取出來,作完這步操做,咱們就不難發現這個函數應該成爲CodeRule類的一部分。
代碼之醜(四)——退讓的縮進
for (int j = 0; j < attributes.size(); j++) { Attr *attr = attributes.get(j); if (attr == NULL ) { continue; } int IsCallFunc = -1; if(attr->status() == STATUS_NEW || attr->status() == STATUS_MODIFIED) { if(strcmp(attr->attrID(), "CallFunc") == 0) { if(0 == strcmp(attr->attrValue(), "1")) { IsCallFunc = 1; } else if(0 == strcmp(attr->attrValue(), "0")) { IsCallFunc = 0; } } } else if (attr->status() == STATUS_DELETED) { IsCallFunc = 0; } ... }
回到這段代碼上,能出現多層縮進,for循環功不可沒。出現這種循環,不少狀況下,都是對一個集合進行處理,而循環裏的內容,就是對集合裏的每個元素進行處理。這裏也不例外。因此,咱們先作一次提取:
Java代碼
for (int j = 0; j < attributes.size(); j++) { processAttr(attributes.get(j)); } void processAttr(Attr *attr) { if (attr == NULL ) { return; } int IsCallFunc = -1; if(attr->status() == STATUS_NEW || attr->status() == STATUS_MODIFIED) { if(strcmp(attr->attrID(), "CallFunc") == 0) { if(0 == strcmp(attr->attrValue(), "1")) { IsCallFunc = 1; } else if(0 == strcmp(attr->attrValue(), "0")) { IsCallFunc = 0; } } } else if (attr->status() == STATUS_DELETED) { IsCallFunc = 0; } ... }
至此,咱們去掉了一層縮進,並且由於這個提取,語義也變得很清晰:這個新函數只是處理集合裏的一個元素。
接下來,這個函數裏面長長的代碼是對IsCallFunc進行設值,後面省略的部分會根據這裏求出的結果進行處理。因此,這裏把processAttr進一步分拆:
Java代碼
void processAttr(Attr *attr) { if (attr == NULL ) { return; } int IsCallFunc = isCallFunc(attr); ...... } int isCallFunc(Attr *attr) { if(attr->status() == STATUS_NEW || attr->status() == STATUS_MODIFIED) { if(strcmp(attr->attrID(), "CallFunc") == 0) { if(0 == strcmp(attr->attrValue(), "1")) { return 1; } else if(0 == strcmp(attr->attrValue(), "0")) { return 0; } } } else if (attr->status() == STATUS_DELETED) { return 0; } return -1; }
代碼之醜(五)--無狀態方法
諸位Java程序員,想必你們對SimpleDateFormat並不陌生。不過,你是否知道,SimpleDateFormat不是線程安全的(thread safe)。這意味着,下面的代碼是錯誤的:
Java代碼
class Sample { private static final DateFormat format = new SimpleDateFormat("yyyy.MM.dd"); public String getCurrentDateText() { return format.format(new Date()); } }
從功能的角度上看,單獨執行這段代碼是沒有問題的,但放到多線程環境下,由於SimpleDateFormat不是線程安全的,這段代碼就會出錯。因此,要想讓這段代碼正確,咱們只要稍作微調:
public class Sample { public String getCurrentDateText() { return new SimpleDateFormat("yyyy.MM.dd").format(new Date()); } }
不知你是否注意到,這裏的調整隻是由原來的共享format這個變量,變成了每次調用這個方法時建立出一個新的SimpleDateFormat變量。
做爲一個專業程序員,咱們固然知道,相比於共享一個變量的開銷要比每次建立小。之因此咱們必須這麼作,是由於SimpleDateFormat不是線程安全的。但從SimpleDateFormat提供給咱們的接口上來看,實在讓人看不出它與線程安全有和相干。那接下來,咱們就要打開JDK的源碼,看一下其中的代碼之醜。
若是你手頭沒有JDK的源碼,這裏是個不錯的參考。
在format方法裏,有這樣一段代碼:
calendar.setTime(date);
其中,calendar是DateFormat的protected字段。這條語句改變了calendar,稍後,calendar還會用到(在subFormat方法裏),而這就是引起問題的根源。 想象一下,在一個多線程環境下,有兩個線程持有了同一個SimpleDateFormat的實例,分別調用format方法: 線程1調用format方法,改變了calendar這個字段。 中斷來了。 線程2開始執行,它也改變了calendar。 又中斷了。 線程1回來了,此時,calendar已然不是它所設的值,而是走上了線程2設計的道路。 BANG!!! 稍微花點時間分析一下format的實現,咱們便不難發現,用到calendar,惟一的好處,就是在調用subFormat時,少了一個參數,卻帶來了這許多的問題。其實,只要在這裏用一個局部變量,一路傳遞下去,全部問題都將迎刃而解。 這個問題背後隱藏着一個更爲重要的問題:無狀態。 無狀態方法的好處之一,就是它在各類環境下,均可以安全的調用。衡量一個方法是不是有狀態的,就看它是否改動了其它的東西,好比全局變量,好比實例的字段。format方法在運行過程當中改動了SimpleDateFormat的calendar字段,因此,它是有狀態的。 寫程序,咱們要儘可能編寫無狀態方法。