今天一個(gè)曾經(jīng)共事的同行問(wèn)我:“要從編碼轉(zhuǎn)為設(shè)計(jì),大概需要多長(zhǎng)時(shí)間?”
我的回答是:“編碼本身就是一種設(shè)計(jì),你可以設(shè)計(jì)你的代碼。”
其實(shí)正如概要設(shè)計(jì)與詳細(xì)設(shè)計(jì),系統(tǒng)設(shè)計(jì)與架構(gòu)設(shè)計(jì)一樣,編碼與設(shè)計(jì)也是沒(méi)有明顯的邊界,每個(gè)正確成長(zhǎng)的程序員,都必須從編碼開(kāi)始,慢慢鍛煉抽象思維、邏輯思維、面向?qū)ο笏季S,然后慢慢的過(guò)渡到系統(tǒng)設(shè)計(jì),再隨著經(jīng)驗(yàn)和知識(shí)的積累,慢慢過(guò)渡到架構(gòu)設(shè)計(jì)。下面我將會(huì)以最近的一個(gè)手頭的編碼任務(wù),簡(jiǎn)單介紹一下如何“設(shè)計(jì)”你的代碼。
任務(wù)是這樣的,某銀行支付系統(tǒng)的客戶(hù)端接收銀行用戶(hù)錄入的轉(zhuǎn)賬數(shù)據(jù),當(dāng)轉(zhuǎn)賬數(shù)據(jù)被審批通過(guò)后,狀態(tài)轉(zhuǎn)變?yōu)?#8220;transfer”,同時(shí),該客戶(hù)端需要通過(guò)JMS以異步的方式向支付系統(tǒng)后臺(tái)發(fā)送一條帶有轉(zhuǎn)賬記錄(Instruction)的消息,后端在接收到信息之后,需要根據(jù)Instruction的一些相關(guān)信息,首先確定這筆轉(zhuǎn)賬數(shù)據(jù)是直接發(fā)送給真正進(jìn)行轉(zhuǎn)賬的清算(Clearing)銀行系統(tǒng),還是停留在后端系統(tǒng),等待后端系統(tǒng)中需要執(zhí)行的工作流程(work flow)。而后端系統(tǒng)需要對(duì)Instruction執(zhí)行的工作流程有兩個(gè),同時(shí)需要根據(jù)Instruction的一些相關(guān)信息進(jìn)行選擇。
為了簡(jiǎn)化復(fù)雜度,我這里假設(shè)系統(tǒng)有一個(gè)InstructionHandleMsgDrivenBean,該bean有一個(gè)onMessage()方法,所有業(yè)務(wù)邏輯需要在該方法中實(shí)現(xiàn)。
同時(shí)解釋一下詳細(xì)的業(yè)務(wù)細(xì)節(jié):
public void onMessage(InstructionInfo instructionInfo) {
if(xx && yy && zz) { // 停留在后端等待執(zhí)行指定的工作流程
// 根據(jù)每種組合進(jìn)行條件判斷,走哪個(gè)流程
if(a==true && b==true && c==true && d==true {
...
}
else if(...) {...}
else if(...) {...}
...
else(...) {...}
}
}
這種做法是最為開(kāi)發(fā)人員歡迎的,因?yàn)樗?jiǎn)單、直接,但這種做法也恰恰反映了開(kāi)發(fā)人員的通病——使用Java編寫(xiě)純面向過(guò)程的代碼。
好了,說(shuō)了一大堆,如何“設(shè)計(jì)”你的代碼呢?答案是:使用面向?qū)ο笏季S:
我們拿到需求之后,可以分析,這個(gè)需求大體上分為兩部分:
有了這個(gè)前提,我可以設(shè)計(jì)出兩個(gè)職責(zé)單一的對(duì)象了:
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";
}
}
可以看到,我先按職責(zé)劃分了類(lèi),再按職責(zé)抽取了私有方法,“框架”設(shè)計(jì)好 ,為了讓編譯通過(guò),我上面完整的填寫(xiě)了代碼的,然后加上TODO標(biāo)識(shí),然后,我可以編寫(xiě)我的onMessage方法了:
public void onMessage(InstructionInfo instructionInfo) {
if( InstructionHandleDecisionMaker.isHandledByBackEnd(...) ) {
WorkFlow wf =InstructionWorkFlowSelector.getWorkFlow(...);
//TODO Implment the logic
}
}
到目前為止,我已經(jīng)用純面向?qū)ο蟮乃季S方式“設(shè)計(jì)”好我的代碼了,這時(shí),我思維非常清晰,因而代碼結(jié)構(gòu)也非常清晰,職責(zé)單一,內(nèi)聚高,耦合低,最后,我可以根據(jù)需求文檔的細(xì)節(jié)(沒(méi)有描述)慢慢的編寫(xiě)我的實(shí)現(xiàn)了。
復(fù)雜的事物總是由一些較簡(jiǎn)單的事物組成,而這些較簡(jiǎn)單的事物也是由更簡(jiǎn)單的事物組成,如此類(lèi)推。因此,在編寫(xiě)代碼的時(shí)候,先用面向?qū)ο蟮乃季S把復(fù)雜的問(wèn)題分解,再進(jìn)一步分解,最后把簡(jiǎn)單的問(wèn)題各個(gè)擊破,這就是一種設(shè)計(jì)。開(kāi)發(fā)人員只要養(yǎng)成這種習(xí)慣,即使你每天都只是做最底層的編碼工作,其實(shí)你已經(jīng)在參與設(shè)計(jì)工作了,隨著知識(shí)和經(jīng)驗(yàn)的累積,慢慢的,你從設(shè)計(jì)代碼開(kāi)始,上升為設(shè)計(jì)類(lèi)、方法,進(jìn)而是設(shè)計(jì)模塊,進(jìn)而設(shè)計(jì)子系統(tǒng),進(jìn)而設(shè)計(jì)系統(tǒng)……,最終,一步一步成為一個(gè)優(yōu)秀的架構(gòu)師。
最后,有一個(gè)真理奉獻(xiàn)給浮躁的程序員:
優(yōu)秀的架構(gòu)師、設(shè)計(jì)師,必定是優(yōu)秀的程序員,不要因?yàn)槟愕穆毼簧仙?,就放棄編碼。
補(bǔ)充說(shuō)明:本博文純粹是討論一種思維習(xí)慣,不要把其做法生搬硬套,不管實(shí)際情況,直接在編碼的時(shí)候這樣做,不見(jiàn)得是最好的選擇。在實(shí)際編碼中,有如下問(wèn)題你必須考慮:
我的回答是:“編碼本身就是一種設(shè)計(jì),你可以設(shè)計(jì)你的代碼。”
其實(shí)正如概要設(shè)計(jì)與詳細(xì)設(shè)計(jì),系統(tǒng)設(shè)計(jì)與架構(gòu)設(shè)計(jì)一樣,編碼與設(shè)計(jì)也是沒(méi)有明顯的邊界,每個(gè)正確成長(zhǎng)的程序員,都必須從編碼開(kāi)始,慢慢鍛煉抽象思維、邏輯思維、面向?qū)ο笏季S,然后慢慢的過(guò)渡到系統(tǒng)設(shè)計(jì),再隨著經(jīng)驗(yàn)和知識(shí)的積累,慢慢過(guò)渡到架構(gòu)設(shè)計(jì)。下面我將會(huì)以最近的一個(gè)手頭的編碼任務(wù),簡(jiǎn)單介紹一下如何“設(shè)計(jì)”你的代碼。
任務(wù)是這樣的,某銀行支付系統(tǒng)的客戶(hù)端接收銀行用戶(hù)錄入的轉(zhuǎn)賬數(shù)據(jù),當(dāng)轉(zhuǎn)賬數(shù)據(jù)被審批通過(guò)后,狀態(tài)轉(zhuǎn)變?yōu)?#8220;transfer”,同時(shí),該客戶(hù)端需要通過(guò)JMS以異步的方式向支付系統(tǒng)后臺(tái)發(fā)送一條帶有轉(zhuǎn)賬記錄(Instruction)的消息,后端在接收到信息之后,需要根據(jù)Instruction的一些相關(guān)信息,首先確定這筆轉(zhuǎn)賬數(shù)據(jù)是直接發(fā)送給真正進(jìn)行轉(zhuǎn)賬的清算(Clearing)銀行系統(tǒng),還是停留在后端系統(tǒng),等待后端系統(tǒng)中需要執(zhí)行的工作流程(work flow)。而后端系統(tǒng)需要對(duì)Instruction執(zhí)行的工作流程有兩個(gè),同時(shí)需要根據(jù)Instruction的一些相關(guān)信息進(jìn)行選擇。
為了簡(jiǎn)化復(fù)雜度,我這里假設(shè)系統(tǒng)有一個(gè)InstructionHandleMsgDrivenBean,該bean有一個(gè)onMessage()方法,所有業(yè)務(wù)邏輯需要在該方法中實(shí)現(xiàn)。
同時(shí)解釋一下詳細(xì)的業(yè)務(wù)細(xì)節(jié):
- 判斷Instruction是否需要停留在后端等待執(zhí)行指定的工作流程有三個(gè)條件:xx、yy、zz,當(dāng)三個(gè)條件都為true時(shí),停留。
- 判斷Instruction需要走A流程還是B流程,由4個(gè)因素的組合確定,如果用“Y”代表true,“N”代表false,那么由這個(gè)四個(gè)因素組成的“XXXX”一共有16種組合,不同的組合分別走A和B流程,如:YYNN、YYNY to A,NNYY、NNNY to B,……不累贅。
public void onMessage(InstructionInfo instructionInfo) {
if(xx && yy && zz) { // 停留在后端等待執(zhí)行指定的工作流程
// 根據(jù)每種組合進(jìn)行條件判斷,走哪個(gè)流程
if(a==true && b==true && c==true && d==true {
...
}
else if(...) {...}
else if(...) {...}
...
else(...) {...}
}
}
這種做法是最為開(kāi)發(fā)人員歡迎的,因?yàn)樗?jiǎn)單、直接,但這種做法也恰恰反映了開(kāi)發(fā)人員的通病——使用Java編寫(xiě)純面向過(guò)程的代碼。
好了,說(shuō)了一大堆,如何“設(shè)計(jì)”你的代碼呢?答案是:使用面向?qū)ο笏季S:
我們拿到需求之后,可以分析,這個(gè)需求大體上分為兩部分:
- 判斷是否需要停留在后端等待執(zhí)行指定的工作流程的部分
- 選擇走哪個(gè)工作流程的部分
有了這個(gè)前提,我可以設(shè)計(jì)出兩個(gè)職責(zé)單一的對(duì)象了:
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";
}
}
可以看到,我先按職責(zé)劃分了類(lèi),再按職責(zé)抽取了私有方法,“框架”設(shè)計(jì)好 ,為了讓編譯通過(guò),我上面完整的填寫(xiě)了代碼的,然后加上TODO標(biāo)識(shí),然后,我可以編寫(xiě)我的onMessage方法了:
public void onMessage(InstructionInfo instructionInfo) {
if( InstructionHandleDecisionMaker.isHandledByBackEnd(...) ) {
WorkFlow wf =InstructionWorkFlowSelector.getWorkFlow(...);
//TODO Implment the logic
}
}
到目前為止,我已經(jīng)用純面向?qū)ο蟮乃季S方式“設(shè)計(jì)”好我的代碼了,這時(shí),我思維非常清晰,因而代碼結(jié)構(gòu)也非常清晰,職責(zé)單一,內(nèi)聚高,耦合低,最后,我可以根據(jù)需求文檔的細(xì)節(jié)(沒(méi)有描述)慢慢的編寫(xiě)我的實(shí)現(xiàn)了。
復(fù)雜的事物總是由一些較簡(jiǎn)單的事物組成,而這些較簡(jiǎn)單的事物也是由更簡(jiǎn)單的事物組成,如此類(lèi)推。因此,在編寫(xiě)代碼的時(shí)候,先用面向?qū)ο蟮乃季S把復(fù)雜的問(wèn)題分解,再進(jìn)一步分解,最后把簡(jiǎn)單的問(wèn)題各個(gè)擊破,這就是一種設(shè)計(jì)。開(kāi)發(fā)人員只要養(yǎng)成這種習(xí)慣,即使你每天都只是做最底層的編碼工作,其實(shí)你已經(jīng)在參與設(shè)計(jì)工作了,隨著知識(shí)和經(jīng)驗(yàn)的累積,慢慢的,你從設(shè)計(jì)代碼開(kāi)始,上升為設(shè)計(jì)類(lèi)、方法,進(jìn)而是設(shè)計(jì)模塊,進(jìn)而設(shè)計(jì)子系統(tǒng),進(jìn)而設(shè)計(jì)系統(tǒng)……,最終,一步一步成為一個(gè)優(yōu)秀的架構(gòu)師。
最后,有一個(gè)真理奉獻(xiàn)給浮躁的程序員:
優(yōu)秀的架構(gòu)師、設(shè)計(jì)師,必定是優(yōu)秀的程序員,不要因?yàn)槟愕穆毼簧仙?,就放棄編碼。
補(bǔ)充說(shuō)明:本博文純粹是討論一種思維習(xí)慣,不要把其做法生搬硬套,不管實(shí)際情況,直接在編碼的時(shí)候這樣做,不見(jiàn)得是最好的選擇。在實(shí)際編碼中,有如下問(wèn)題你必須考慮:
- 你需要考慮業(yè)務(wù)邏輯的可重用性和復(fù)雜程度,是否有必要設(shè)計(jì)出新的類(lèi)或抽取新的私有方法來(lái)封裝邏輯,或者直接在原方法上編碼(如果足夠簡(jiǎn)單)。
- 新的業(yè)務(wù)邏輯,是否在某些地方已經(jīng)存在,可以復(fù)用,即使不存在,這些邏輯是應(yīng)該封裝到新的類(lèi)中,還是應(yīng)該放置到現(xiàn)有的類(lèi)中,這需要進(jìn)行清晰的職責(zé)劃分。
- 需要在設(shè)計(jì)和性能上作出權(quán)衡。
- 如果在現(xiàn)成的系統(tǒng)中增加新的功能,而現(xiàn)成系統(tǒng)的編碼風(fēng)格與你想要的相差很遠(yuǎn),但你又沒(méi)有足夠的時(shí)間成本來(lái)進(jìn)行重構(gòu),那么還是應(yīng)該讓你的代碼與現(xiàn)成系統(tǒng)保持一致的風(fēng)格。
posted on 2010-04-28 00:51 Johnny.Liang 閱讀(4954) 評(píng)論(8) 編輯 收藏 所屬分類(lèi): 編程技巧 、系統(tǒng)設(shè)計(jì)