今天一個曾經共事的同行問我:“要從編碼轉為設計,大概需要多長時間?”
我的回答是:“編碼本身就是一種設計,你可以設計你的代碼。”
其實正如概要設計與詳細設計,系統設計與架構設計一樣,編碼與設計也是沒有明顯的邊界,每個正確成長的程序員,都必須從編碼開始,慢慢鍛煉抽象思維、邏輯思維、面向對象思維,然后慢慢的過渡到系統設計,再隨著經驗和知識的積累,慢慢過渡到架構設計。下面我將會以最近的一個手頭的編碼任務,簡單介紹一下如何“設計”你的代碼。
任務是這樣的,某銀行支付系統的客戶端接收銀行用戶錄入的轉賬數據,當轉賬數據被審批通過后,狀態轉變為“transfer”,同時,該客戶端需要通過JMS以異步的方式向支付系統后臺發送一條帶有轉賬記錄(Instruction)的消息,后端在接收到信息之后,需要根據Instruction的一些相關信息,首先確定這筆轉賬數據是直接發送給真正進行轉賬的清算(Clearing)銀行系統,還是停留在后端系統,等待后端系統中需要執行的工作流程(work flow)。而后端系統需要對Instruction執行的工作流程有兩個,同時需要根據Instruction的一些相關信息進行選擇。
為了簡化復雜度,我這里假設系統有一個InstructionHandleMsgDrivenBean,該bean有一個onMessage()方法,所有業務邏輯需要在該方法中實現。
同時解釋一下詳細的業務細節:
- 判斷Instruction是否需要停留在后端等待執行指定的工作流程有三個條件:xx、yy、zz,當三個條件都為true時,停留。
- 判斷Instruction需要走A流程還是B流程,由4個因素的組合確定,如果用“Y”代表true,“N”代表false,那么由這個四個因素組成的“XXXX”一共有16種組合,不同的組合分別走A和B流程,如:YYNN、YYNY to A,NNYY、NNNY to B,……不累贅。
好了,對于一個純編程人員來說,拿到這樣的需求,感覺邏輯很簡單,可以直接編碼了,于是,他開始一行一行的編寫代碼(偽代碼):
public void onMessage(InstructionInfo instructionInfo) {
if(xx && yy && zz) { // 停留在后端等待執行指定的工作流程
// 根據每種組合進行條件判斷,走哪個流程
if(a==true && b==true && c==true && d==true {
...
}
else if(...) {...}
else if(...) {...}
...
else(...) {...}
}
}
這種做法是最為開發人員歡迎的,因為它簡單、直接,但這種做法也恰恰反映了開發人員的通病——使用Java編寫純面向過程的代碼。
好了,說了一大堆,如何“設計”你的代碼呢?答案是:使用面向對象思維:
我們拿到需求之后,可以分析,這個需求大體上分為兩部分:
- 判斷是否需要停留在后端等待執行指定的工作流程的部分
- 選擇走哪個工作流程的部分
有了這個前提,我可以設計出兩個職責單一的對象了:
public class InstructionHandleDecisionMaker {
public static boolean isHandledByBackEnd(InstructionInfo info) {
return (isXX(...) && isYY(...) && isZZ(...));
}
private booolean isXX(...) {
//TODO Implement the logic
return false;
}
private booolean isYY(...) {
//TODO Implement the logic
return false;
}
private booolean isZZ(...) {
//TODO Implement the logic
return false;
}
}
public class InstructionWorkFlowSelector {
private static Map mapping = new HashMap();
static {
mapping.input("YYNN",WorkFlow.A);
mapping.input("NNYY",WorkFlow.B);
...
}
public static WorkFlow getWorkFlow(Instruction info) {
StringBuilder result = new StringBuilder();
result.append(isA(...)).append(isB(...));
result.append(isC(...)).append(isD(...));
return mapping.get(result.toString());
}
private static String isA(...) {
//TODO Implment the logic
return "N";
}
private static String isB(...) {
//TODO Implment the logic
return "N";
}
private static String isC(...) {
//TODO Implment the logic
return "N";
}
private static String isD(...) {
//TODO Implment the logic
return "N";
}
}
可以看到,我先按職責劃分了類,再按職責抽取了私有方法,“框架”設計好 ,為了讓編譯通過,我上面完整的填寫了代碼的,然后加上TODO標識,然后,我可以編寫我的onMessage方法了:
public void onMessage(InstructionInfo instructionInfo) {
if( InstructionHandleDecisionMaker.isHandledByBackEnd(...) ) {
WorkFlow wf =InstructionWorkFlowSelector.getWorkFlow(...);
//TODO Implment the logic
}
}
到目前為止,我已經用純面向對象的思維方式“設計”好我的代碼了,這時,我思維非常清晰,因而代碼結構也非常清晰,職責單一,內聚高,耦合低,最后,我可以根據需求文檔的細節(沒有描述)慢慢的編寫我的實現了。
復雜的事物總是由一些較簡單的事物組成,而這些較簡單的事物也是由更簡單的事物組成,如此類推。因此,在編寫代碼的時候,先用面向對象的思維把復雜的問題分解,再進一步分解,最后把簡單的問題各個擊破,這就是一種設計。開發人員只要養成這種習慣,即使你每天都只是做最底層的編碼工作,其實你已經在參與設計工作了,隨著知識和經驗的累積,慢慢的,你從設計代碼開始,上升為設計類、方法,進而是設計模塊,進而設計子系統,進而設計系統……,最終,一步一步成為一個優秀的架構師。
最后,有一個真理奉獻給浮躁的程序員:
優秀的架構師、設計師,必定是優秀的程序員,不要因為你的職位上升了,就放棄編碼。
補充說明:本博文純粹是討論一種思維習慣,不要把其做法生搬硬套,不管實際情況,直接在編碼的時候這樣做,不見得是最好的選擇。在實際編碼中,有如下問題你必須考慮:
- 你需要考慮業務邏輯的可重用性和復雜程度,是否有必要設計出新的類或抽取新的私有方法來封裝邏輯,或者直接在原方法上編碼(如果足夠簡單)。
- 新的業務邏輯,是否在某些地方已經存在,可以復用,即使不存在,這些邏輯是應該封裝到新的類中,還是應該放置到現有的類中,這需要進行清晰的職責劃分。
- 需要在設計和性能上作出權衡。
- 如果在現成的系統中增加新的功能,而現成系統的編碼風格與你想要的相差很遠,但你又沒有足夠的時間成本來進行重構,那么還是應該讓你的代碼與現成系統保持一致的風格。