原文出處:http://www.tkk7.com/chords/archive/2006/12/14/87591.html
------------------------
Five Habits of Highly Profitable Software Developers
by
Robert J. Miller08/24/2006
原文地址:
http://today.java.net/pub/a/today/2006/08/24/five-habits-of-highly-profitable-developers.html
當(dāng)今技術(shù)引領(lǐng)經(jīng)濟(jì)社會(huì)大量的需要能夠在團(tuán)隊(duì)環(huán)境中開(kāi)發(fā)出穩(wěn)定質(zhì)量的軟件開(kāi)發(fā)人員。在團(tuán)隊(duì)開(kāi)發(fā)的環(huán)境中,開(kāi)發(fā)者面對(duì)的挑戰(zhàn)就是讀懂別的開(kāi)發(fā)者寫(xiě)的軟件。本文將文章盡力幫助軟件開(kāi)發(fā)團(tuán)隊(duì)來(lái)克服這樣的困難。
本文為了闡明了五個(gè)讓開(kāi)發(fā)團(tuán)隊(duì)變得比以往更加高效的好習(xí)慣,首先將介紹公司業(yè)務(wù)對(duì)開(kāi)發(fā)團(tuán)隊(duì)以及他們開(kāi)發(fā)出軟件的需求,接下來(lái)會(huì)解釋狀態(tài)改變邏輯和行為邏輯之間重要的區(qū)別,最后會(huì)通過(guò)顧客賬號(hào)這么一個(gè)案例來(lái)闡述這五個(gè)習(xí)慣。
業(yè)務(wù)帶給開(kāi)發(fā)人員的需求
公司業(yè)務(wù)團(tuán)隊(duì)的工作就是在決定將哪些對(duì)公司業(yè)務(wù)最有利的新價(jià)值可以被加到軟件中。這里的“新價(jià)值”指的是新產(chǎn)品或者是對(duì)現(xiàn)有產(chǎn)品的強(qiáng)化。換句話說(shuō)就是,業(yè)務(wù)團(tuán)隊(duì)決定什么將給公司帶來(lái)最多的錢。決定了下個(gè)新價(jià)值是什么的關(guān)鍵因素是實(shí)現(xiàn)它的成本。如果實(shí)現(xiàn)的成本超過(guò)了潛在收益,那么這個(gè)新價(jià)值就不會(huì)被加到軟件中來(lái)。
業(yè)務(wù)團(tuán)隊(duì)要求開(kāi)發(fā)團(tuán)隊(duì)能夠盡可能低成本的,并且是在規(guī)定時(shí)間內(nèi)以及在不失去原有價(jià)值的情況下創(chuàng)造新價(jià)值。當(dāng)軟件增加了一定價(jià)值后,業(yè)務(wù)團(tuán)隊(duì)會(huì)要求一份描述現(xiàn)有軟件所能提供的價(jià)值的文檔。這個(gè)文檔將幫助他們決定下一個(gè)新價(jià)值是什么。
軟件開(kāi)發(fā)團(tuán)隊(duì)通過(guò)創(chuàng)造出容易理解的軟件來(lái)滿足商業(yè)團(tuán)隊(duì)的需求。難以理解的軟件帶來(lái)的后果就是整個(gè)開(kāi)發(fā)過(guò)程的低效率。低效率會(huì)造成軟件開(kāi)發(fā)成本的增加,引起一些預(yù)料不到的現(xiàn)有價(jià)值的損失,開(kāi)發(fā)周期滾雪球般越拖越長(zhǎng)以及交付錯(cuò)誤的軟件文檔。通過(guò)改變業(yè)務(wù)團(tuán)隊(duì)的需求,甚至將復(fù)雜的軟件轉(zhuǎn)變成簡(jiǎn)單、容易理解的軟件,就可以提高開(kāi)發(fā)過(guò)程的效率。
介紹關(guān)鍵概念:狀態(tài)和行為
開(kāi)發(fā)容易理解的軟件可以從創(chuàng)建有狀態(tài)和行為的對(duì)象開(kāi)始。“狀態(tài)”是對(duì)象在調(diào)用方法前后所保存的數(shù)據(jù)。一個(gè)JAVA對(duì)象的實(shí)例變量可以暫時(shí)的保持自己的狀態(tài),并且可以隨時(shí)存放到數(shù)據(jù)存儲(chǔ)器里。這里,永久數(shù)據(jù)存儲(chǔ)器可以是數(shù)據(jù)庫(kù)或者是Web服務(wù)。“狀態(tài)變更方法”主要管理一個(gè)對(duì)象的數(shù)據(jù)存取。“行為”則是一個(gè)對(duì)象基于狀態(tài)回答問(wèn)題的能力。“行文方法”回答問(wèn)題永遠(yuǎn)不會(huì)修改狀態(tài),并且這些方法往往跟一個(gè)應(yīng)用的商業(yè)邏輯有關(guān)。
案例研究:CustomerAccount對(duì)象
下面這個(gè)ICustomerAccount接口定義了管理一個(gè)客戶賬號(hào)對(duì)象必須實(shí)現(xiàn)的功能。這個(gè)接口定義了可以創(chuàng)建一個(gè)新的賬號(hào),加載一個(gè)已經(jīng)存在的賬號(hào)的信息,驗(yàn)證某個(gè)賬號(hào)的用戶名和密碼,驗(yàn)證購(gòu)買時(shí)這個(gè)賬號(hào)是否是激活的。
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
public?interface?ICustomerAccount?
{
??//State-changing?methods
??public?void?createNewActiveAccount()
???????????????????throws?CustomerAccountsSystemOutageException;
??public?void?loadAccountStatus()?
???????????????????throws?CustomerAccountsSystemOutageException;
??//Behavior?methods
??public?boolean?isRequestedUsernameValid();
??public?boolean?isRequestedPasswordValid();
??public?boolean?isActiveForPurchasing();
??public?String?getPostLogonMessage();
}
習(xí)慣一:構(gòu)造器盡量少做事
第一個(gè)應(yīng)該養(yǎng)成的喜歡就是讓類的構(gòu)造器盡量的少做些事情。理想的情況就是構(gòu)造器僅僅用來(lái)接受參數(shù)給實(shí)例變量加載數(shù)據(jù)。下面一個(gè)例子,讓構(gòu)造器做盡可能少的事情會(huì)讓這個(gè)類使用起來(lái)比較簡(jiǎn)單,因?yàn)闃?gòu)造器只是簡(jiǎn)單的給類中的實(shí)例變量賦值。
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
public?class?CustomerAccount?implements?ICustomerAccount
{
??//Instance?variables.
??private?String?username;
??private?String?password;
??protected?String?accountStatus;
??
??//Constructor?that?performs?minimal?work.
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
??public?CustomerAccount(String?username,?String?password)?
{
????this.password?=?password;
????this.username?=?username;
??}
}
構(gòu)造器是用來(lái)創(chuàng)建一個(gè)類的實(shí)例。構(gòu)造器的名字永遠(yuǎn)是跟這個(gè)類的名字是一樣的。既然構(gòu)造器的名字無(wú)法改變,那么它就不能表達(dá)出它做的事情的含義。所以,最好是盡可能的讓構(gòu)造器少做點(diǎn)事。另一個(gè)方面,狀態(tài)變更方法和行為方法會(huì)通過(guò)自己的名字來(lái)表達(dá)出自己復(fù)雜的工作,在“習(xí)慣二:方法名要清晰的表現(xiàn)意圖”中會(huì)詳細(xì)講到。下一個(gè)例子表明,很大程度上是因?yàn)闃?gòu)造器十分的簡(jiǎn)單,更多的讓狀態(tài)變更和行為方法來(lái)完成其他的部分,使得一個(gè)軟件具有很高的可讀性。
注:例子中“...”部分僅僅是真實(shí)情景中必須的部分,跟本文要闡述的問(wèn)題沒(méi)有關(guān)系。
String?username?=?"robertmiller";
String?password?=?"java.net";
ICustomerAccount?ca?=?new?CustomerAccount(username,?password);
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
if(ca.isRequestedUsernameValid()?&&?ca.isRequestedPasswordValid())?
{
???秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/dot.gif)
???ca.createNewActiveAccount();
???秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/dot.gif)
}相反的,如果構(gòu)造器除了給實(shí)例變量賦值以外的事情,將會(huì)使代碼很難讓人理解,并且有可能被誤用,因?yàn)闃?gòu)造器的名字沒(méi)有說(shuō)明要做的意圖。例如,下面的代碼將調(diào)用數(shù)據(jù)庫(kù)或者Web服務(wù)來(lái)預(yù)加載賬號(hào)的狀態(tài):
//Constructor?that?performs?too?much?work!
public?CustomerAccount(String?username,?String?password)?
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
??????????????????throws?CustomerAccountsSystemOutageException?
{
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/InBlock.gif)
??this.password?=?password;
??this.username?=?username;
??this.loadAccountStatus();//unnecessary?work.
}
//Remote?call?to?the?database?or?web?service.
public?void?loadAccountStatus()?
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
??????????????????throws?CustomerAccountsSystemOutageException?
{
??秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/dot.gif)
}別人可能在不知道會(huì)使用遠(yuǎn)程調(diào)的情況下使用這個(gè)構(gòu)造器,從而導(dǎo)致了以下個(gè)遠(yuǎn)程調(diào)用:
String?username?=?"robertmiller";
String?password?=?"java.net";?
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
try?
{
??//makes?a?remote?call
??ICustomerAccount?ca?=?new?CustomerAccount(username,?password);
??//makes?a?second?remote?call
??ca.loadAccountStatus();
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
}?catch?(CustomerAccountsSystemOutageException?e)?
{
??秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/dot.gif)
}或者使開(kāi)發(fā)人員重用這個(gè)構(gòu)造器來(lái)驗(yàn)證用戶名和密碼,并且被強(qiáng)制的進(jìn)行了遠(yuǎn)程調(diào)用,然而這些行為方法(isRequestedUsernameValid(), isRequestedPasswordValid())根本不需要賬戶的狀態(tài):
String?username?=?"robertmiller";
String?password?=?"java.net";
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
try?
{
??//makes?unnecessary?remote?call
??ICustomerAccount?ca?=?new?CustomerAccount(username,?password);
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
??if(ca.isRequestedUsernameValid()?&&?ca.isRequestedPasswordValid())?
{
????秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/dot.gif)
????ca.createNewActiveAccount();
????秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/dot.gif)
??}
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
}?catch?(CustomerAccountsSystemOutageException?e)
{
??秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/dot.gif)
}
習(xí)慣二:方法名要清晰的表現(xiàn)意圖
第二個(gè)習(xí)慣就是要讓所有的方法名字清晰的表現(xiàn)本方法要做什么的意圖。例如isRequestedUsernameValid()讓開(kāi)發(fā)人員知道這個(gè)方法時(shí)用來(lái)驗(yàn)證用戶名是否正確的。相反的,isGoodUser() 可能有很多用途:用來(lái)驗(yàn)證賬號(hào)是否是激活的,用來(lái)驗(yàn)證用戶名或者密碼是否正確,或者是用來(lái)搞清楚用戶是不是個(gè)好人。方法名表意不清,這就很難讓開(kāi)發(fā)者明白這個(gè)方法到底是用來(lái)干什么的。簡(jiǎn)單的說(shuō),長(zhǎng)一點(diǎn)并且表意清晰的方法名要比又短又表意不明的方法名好。
表意清晰的長(zhǎng)名字會(huì)幫助開(kāi)發(fā)團(tuán)隊(duì)快速的理解他們軟件的功能意圖。更大的優(yōu)點(diǎn)在于,給測(cè)試方法也起個(gè)好名字會(huì)讓軟件現(xiàn)有的要求更加的清晰。例如,本軟件要求驗(yàn)證請(qǐng)求的用戶名和用戶密碼是不同的。使用名為testRequestedPasswordIsNotValidBecauseItMustBeDifferentThanTheUsername()的方法清晰的表達(dá)出了方法的意圖,也就是軟件要達(dá)到的要求。
import?junit.framework.TestCase;
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/None.gif)
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
public?class?CustomerAccountTest?extends?TestCase
{
??public?void?testRequestedPasswordIsNotValid
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
????????BecauseItMustBeDifferentThanTheUsername()
{
????String?username?=?"robertmiller";
????String?password?=?"robertmiller";
????ICustomerAccount?ca?=?new?CustomerAccount(username,?password);
????assertFalse(ca.isRequestedPasswordValid());
??}
}
這個(gè)方法簡(jiǎn)單的被命名為testRequestedPasswordIsNotValid(),或者更糟的是testBadPassword()。這兩個(gè)名字都讓人很難搞清楚這個(gè)方法是用來(lái)測(cè)試什么的。不清楚或者是模棱兩可的測(cè)試方法名會(huì)帶來(lái)生產(chǎn)力的損失。從而導(dǎo)致花費(fèi)來(lái)越多的時(shí)間來(lái)理解測(cè)試,不必要的重復(fù)測(cè)試,或者是破壞了被測(cè)試的類。
最后,明了的方法名還能減少文檔和注釋的工作量。
習(xí)慣三:一個(gè)對(duì)象只進(jìn)行一類服務(wù)。
第三個(gè)喜歡就是對(duì)象只關(guān)心處理一小類獨(dú)立的服務(wù)。一個(gè)對(duì)象只處理一小部分事情將使得代碼更好讀好用,因?yàn)槊總€(gè)對(duì)象代碼量很少。更糟糕的是,重復(fù)的邏輯將花費(fèi)很多時(shí)間和成本去維護(hù)。設(shè)想一下,業(yè)務(wù)部門(mén)將來(lái)要求升級(jí)一下isRequestedPasswordValid()里的邏輯,然而有兩個(gè)不同的對(duì)象卻有著功能完全一樣但是名字不一樣的方法。這種情況下,開(kāi)發(fā)團(tuán)隊(duì)要花費(fèi)更多的時(shí)間去更新兩個(gè)對(duì)象,而不是一個(gè)。
這個(gè)案例表明了CustomerAccount類的目的就是管理一個(gè)客戶的帳號(hào)。它首先創(chuàng)建了一個(gè)帳號(hào),然后嚴(yán)整這個(gè)帳號(hào)能否用來(lái)購(gòu)買產(chǎn)品。假設(shè)軟件要給所有購(gòu)買過(guò)10件物品以上的客戶打折。再創(chuàng)建一個(gè)接口叫ICustomerTransactions和一個(gè)叫CustomerTransactions的類,這樣會(huì)讓代碼更加易懂,并且實(shí)現(xiàn)目標(biāo)。
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
public?interface?ICustomerTransactions?
{
??//State-changing?methods
??public?void?createPurchaseRecordForProduct(Long?productId)
?????????????????????throws?CustomerTransactionsSystemException;
??public?void?loadAllPurchaseRecords()
?????????????????????throws?CustomerTransactionsSystemException;
??//Behavior?method
??public?void?isCustomerEligibleForDiscount();
}這個(gè)新的類里面有狀態(tài)變更和行為方法,可以儲(chǔ)存客戶的交易并且判斷是否能夠打折。這個(gè)類創(chuàng)建起來(lái)十分簡(jiǎn)單,方便測(cè)試以及穩(wěn)定,因?yàn)樗鼘W⑿倪@一個(gè)目標(biāo)。一個(gè)低效率的方法是如同下面的例子一樣在ICustomerAccount接口和CustomerAccount類加上新的方法:
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
public?interface?ICustomerAccount?
{
??//State-changing?methods
??public?void?createNewActiveAccount()
???????????????????throws?CustomerAccountsSystemOutageException;
??public?void?loadAccountStatus()
???????????????????throws?CustomerAccountsSystemOutageException;
??public?void?createPurchaseRecordForProduct(Long?productId)
???????????????????throws?CustomerAccountsSystemOutageException;
??public?void?loadAllPurchaseRecords()
???????????????????throws?CustomerAccountsSystemOutageException;
??//Behavior?methods
??public?boolean?isRequestedUsernameValid();
??public?boolean?isRequestedPasswordValid();
??public?boolean?isActiveForPurchasing();
??public?String?getPostLogonMessage();
??public?void?isCustomerEligibleForDiscount();
}
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/None.gif)
?
就像是上面所看到的一樣,這樣使得類具有太多職責(zé),難以讀懂,甚至更容被易誤解。代碼被誤解的后果就是降低生產(chǎn)力,費(fèi)時(shí)費(fèi)力。總的來(lái)說(shuō),最好讓一個(gè)對(duì)象和它的方法集中處理一個(gè)小的工作單元。
習(xí)慣四:狀態(tài)變更方法少含有行為邏輯
第四個(gè)習(xí)慣是讓狀態(tài)變更方法少含有行為邏輯。混合了狀態(tài)變更邏輯和行為邏輯的代碼讓人很難理解,因?yàn)樵谝粋€(gè)地方處理了太多的事情。狀態(tài)變更方法涉及到遠(yuǎn)程調(diào)用來(lái)存儲(chǔ)數(shù)據(jù)的話很容易產(chǎn)生系統(tǒng)問(wèn)題。如果遠(yuǎn)程方法是相對(duì)獨(dú)立的,并且方法本身沒(méi)有行為邏輯,這樣診斷起狀態(tài)改變方法就會(huì)十分容易。另外一個(gè)問(wèn)題是,混合了行為邏輯的狀態(tài)代碼很難進(jìn)行單元測(cè)試。例如,getPostLogonMessage() 是一個(gè)依靠accountStatus的值的行為:
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
public?String?getPostLogonMessage()?
{
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
??if("A".equals(this.accountStatus))
{
????return?"Your?purchasing?account?is?active.";
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
??}?else?if("E".equals(this.accountStatus))?
{
????return?"Your?purchasing?account?has?"?+
???????????"expired?due?to?a?lack?of?activity.";
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
??}?else?
{
????return?"Your?purchasing?account?cannot?be?"?+
???????????"found,?please?call?customer?service?"+
???????????"for?assistance.";
??}
}
loadAccountStatus()是一個(gè)使用遠(yuǎn)程調(diào)用來(lái)加載 accountStatus值的狀態(tài)改變方法。
public?void?loadAccountStatus()?
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
??????????????????throws?CustomerAccountsSystemOutageException?
{
??Connection?c?=?null;
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
??try?
{
????c?=?DriverManager.getConnection("databaseUrl",?"databaseUser",?
????????????????????????????????????"databasePassword");
????PreparedStatement?ps?=?c.prepareStatement(
??????????????"SELECT?status?FROM?customer_account?"
????????????+?"WHERE?username?=???AND?password?=???");
????ps.setString(1,?this.username);
????ps.setString(2,?this.password);
????ResultSet?rs?=?ps.executeQuery();
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
????if?(rs.next())?
{
??????this.accountStatus=rs.getString("status");
????}
????rs.close();
????ps.close();
????c.close();??
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
??}?catch?(SQLException?e)?
{
????throw?new?CustomerAccountsSystemOutageException(e);
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
??}?finally?
{
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
????if?(c?!=?null)?
{
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
??????try?
{
????????c.close();
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
???????}?catch?(SQLException?e)?
{}
????}
??}
}
單元測(cè)試 getPostLogonMessage()? 方法十分簡(jiǎn)單,只用loadAccountStatus()方法就行了。每個(gè)場(chǎng)景都可以在使用遠(yuǎn)程調(diào)用連接數(shù)據(jù)庫(kù)的情況下進(jìn)行測(cè)試。例如,如果 accountStatus 的值是E,代表過(guò)期,則getPostLogonMessage() 會(huì)如下代碼顯示一樣返回 "Your purchasing account has expired due to a lack of activity"
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
public?void?testPostLogonMessageWhenStatusIsExpired()
{
??String?username?=?"robertmiller";
??String?password?=?"java.net";
?
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
??class?CustomerAccountMock?extends?CustomerAccount
{
????
????
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
????public?void?loadAccountStatus()?
{
??????this.accountStatus?=?"E";
????}
??}
??ICustomerAccount?ca?=?new?CustomerAccountMock(username,?password);
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
??try?
{
????ca.loadAccountStatus();
??}?
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
??catch?(CustomerAccountsSystemOutageException?e)
{
????fail(""+e);
??}
??assertEquals("Your?purchasing?account?has?"?+
?????????????????????"expired?due?to?a?lack?of?activity.",
?????????????????????ca.getPostLogonMessage());
}下面這個(gè)反例將
getPostLogonMessage() 的行為邏輯和
loadAccountStatus()的狀態(tài)轉(zhuǎn)變都放到了一個(gè)方法里,我們不應(yīng)該這么做:
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
public?String?getPostLogonMessage()?
{
??return?this.postLogonMessage;
}
public?void?loadAccountStatus()?
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
??????????????????throws?CustomerAccountsSystemOutageException?
{
??Connection?c?=?null;
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
??try?
{
????c?=?DriverManager.getConnection("databaseUrl",?"databaseUser",?
????????????????????????????????????"databasePassword");
????PreparedStatement?ps?=?c.prepareStatement(
??????????"SELECT?status?FROM?customer_account?"
????????+?"WHERE?username?=???AND?password?=???");
????ps.setString(1,?this.username);
????ps.setString(2,?this.password);
????ResultSet?rs?=?ps.executeQuery();
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
????if?(rs.next())?
{
??????this.accountStatus=rs.getString("status");
????}
????rs.close();
????ps.close();
????c.close();??
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
??}?catch?(SQLException?e)?
{
????throw?new?CustomerAccountsSystemOutageException(e);
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
??}?finally?
{
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
????if?(c?!=?null)?
{
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
??????try?
{
????????c.close();
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
???????}?catch?(SQLException?e)?
{}
????}
??}
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
??if("A".equals(this.accountStatus))
{
????this.postLogonMessage?=?"Your?purchasing?account?is?active.";
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
??}?else?if("E".equals(this.accountStatus))?
{
????this.postLogonMessage?=?"Your?purchasing?account?has?"?+
????????????????????????????"expired?due?to?a?lack?of?activity.";
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
??}?else?
{
????this.postLogonMessage?=?"Your?purchasing?account?cannot?be?"?+
????????????????????????????"found,?please?call?customer?service?"+
????????????????????????????"for?assistance.";
??}
}這個(gè)實(shí)現(xiàn)了一個(gè)沒(méi)有包含任何行為邏輯的getPostLogonMessage()行為方法,并且簡(jiǎn)單的返回一個(gè)實(shí)例變量this.postLogonMessage。這么做有三個(gè)問(wèn)題:第一,很難讓人明白"post logon message"這個(gè)嵌入到一個(gè)方法中的邏輯式怎么完成兩個(gè)任務(wù)的。第二,getPostLogonMessage()方法很難被重用,因?yàn)樗偸呛蚻oadAccountStatus()方法相關(guān)聯(lián)。最后,CustomerAccountsSystemOutageException異常將會(huì)拋出,導(dǎo)致了在給this.postLogonMessage賦值前就退出方法了。
這個(gè)實(shí)現(xiàn)同樣創(chuàng)造了負(fù)面效應(yīng),因?yàn)橹挥袆?chuàng)建一個(gè)存在于數(shù)據(jù)庫(kù)的CustomerAccount對(duì)象,并且將賬號(hào)狀態(tài)設(shè)置成E才能進(jìn)行對(duì)getPostLogonMessage()邏輯的單元測(cè)試。結(jié)果式這個(gè)測(cè)試要進(jìn)行遠(yuǎn)程調(diào)用。這會(huì)導(dǎo)致測(cè)試的很慢,而且在改變數(shù)據(jù)庫(kù)內(nèi)容的時(shí)候很容易出意想不到的問(wèn)題。由于 loadAccountStatus()方法包含了行為邏輯,測(cè)試必須進(jìn)行遠(yuǎn)程調(diào)用。如果行為邏輯測(cè)試失敗了,測(cè)的只是那個(gè)失敗的對(duì)象行為,而不是真正的對(duì)象的行為。
習(xí)慣五:可以任意次序調(diào)用行為方法第五個(gè)習(xí)慣是要保證每個(gè)行為方法之間保持著獨(dú)立。換句話說(shuō),一個(gè)對(duì)象的行為方法可以被重復(fù)或任何次序來(lái)調(diào)用。這個(gè)習(xí)慣能讓對(duì)象實(shí)現(xiàn)穩(wěn)定的行為。比如,CustomerAccount's isActiveForPurchasing()和getPostLogonMessage() 行為方法都要用到accountStatus的值。這兩個(gè)方法必須在功能上相互獨(dú)立。例如,有一個(gè)情景要求調(diào)用isActiveForPurchasing(),接著又調(diào)用了getPostLogonMessage():
ICustomerAccount?ca?=?new?CustomerAccount(username,?password);
ca.loadAccountStatus();
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
if(ca.isActiveForPurchasing())
{?
??//go?to?"begin?purchasing"?display
??秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/dot.gif)
??//show?post?logon?message.
??ca.getPostLogonMessage();
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
}?else?
{
??//go?to?"activate?account"?display??
??秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/dot.gif)
??//show?post?logon?message.
??ca.getPostLogonMessage();?????
}
?一個(gè)發(fā)送的情節(jié)會(huì)要求調(diào)用getPostLogonMessage()之前不必調(diào)用isActiveForPurchasing():
ICustomerAccount?ca?=?new?CustomerAccount(username,?password);
ca.loadAccountStatus();
//go?to?"welcome?back"?display?
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/None.gif)
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/dot.gif)
//show?post?logon?message.
ca.getPostLogonMessage();
如果要求調(diào)用getPostLogonMessage()之前必須調(diào)用isActiveForPurchasing()方法,CustomerAccount 對(duì)象將無(wú)法支持第二個(gè)情景。如果兩個(gè)方法使用了 postLogonMessage 實(shí)例變量來(lái)存放兩個(gè)方法所需要的值,那么這將支持第一個(gè)情景,但不支持第二個(gè):
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
public?boolean?isActiveForPurchasing()?
{
??boolean?returnValue?=?false;
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
??if("A".equals(this.accountStatus))
{
????this.postLogonMessage?=?"Your?purchasing?account?is?active.";
????returnValue?=?true;
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
??}?else?if("E".equals(this.accountStatus))?
{
????this.postLogonMessage?=?"Your?purchasing?account?has?"?+
????????????????????????????"expired?due?to?a?lack?of?activity.";
????returnValue?=?false;
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/InBlock.gif)
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
??}?else?
{
????this.postLogonMessage?=?"Your?purchasing?account?cannot?be?"?+
????????????????????????????"found,?please?call?customer?service?"+
????????????????????????????"for?assistance.";
????returnValue?=?false;
??}
??return?returnValue;
}
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
public?String?getPostLogonMessage()?
{
??return?this.postLogonMessage;
}然而,如果兩個(gè)方法的邏輯推理是相互獨(dú)立的,那么就可以支持第二個(gè)情景了。在下面的一個(gè)例子中,postLogonMessage是getPostLogonMessage()創(chuàng)建的一個(gè)局部變量。
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
public?boolean?isActiveForPurchasing()?
{
??return?this.accountStatus?!=?null?&&?this.accountStatus.equals("A");
}
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
public?String?getPostLogonMessage()?
{
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
??if("A".equals(this.accountStatus))
{
????return?"Your?purchasing?account?is?active.";
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
??}?else?if("E".equals(this.accountStatus))?
{
????return?"Your?purchasing?account?has?"?+
???????????"expired?due?to?a?lack?of?activity.";
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
??}?else?
{
????return?"Your?purchasing?account?cannot?be?"?+
???????????"found,?please?call?customer?service?"+
???????????"for?assistance.";
??}
}讓這兩個(gè)方法之間相互獨(dú)立的另一個(gè)好處是更容易理解。例如,isActiveForPurchasing()如果只是用來(lái)回答如“能否購(gòu)買”的問(wèn)題會(huì)顯得可讀性更佳,如果是用來(lái)解決“顯示登陸消息”就不那么好了。另一個(gè)好處就是測(cè)試是獨(dú)立的,讓測(cè)試更加簡(jiǎn)單和容易理解:
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
public?class?CustomerAccountTest?extends?TestCase
{
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
??public?void?testAccountIsActiveForPurchasing()
{
????String?username?=?"robertmiller";
????String?password?=?"java.net";
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/InBlock.gif)
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
????class?CustomerAccountMock?extends?CustomerAccount
{
??????秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/dot.gif)
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
??????public?void?loadAccountStatus()?
{
????????this.accountStatus?=?"A";
??????}
????}
????ICustomerAccount?ca?=?new?CustomerAccountMock(username,?password);
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
????try?
{
??????ca.loadAccountStatus();
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
????}?catch?(CustomerAccountsSystemOutageException?e)?
{
??????fail(""+e);
????}
????assertTrue(ca.isActiveForPurchasing());?
??}?
??
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
??public?void?testGetPostLogonMessageWhenAccountIsActiveForPurchasing()
{
????String?username?=?"robertmiller";
????String?password?=?"java.net";
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/InBlock.gif)
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
????class?CustomerAccountMock?extends?CustomerAccount
{
??????秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/dot.gif)
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
??????public?void?loadAccountStatus()?
{
????????this.accountStatus?=?"A";
??????}
????}
????ICustomerAccount?ca?=?new?CustomerAccountMock(username,?password);
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
????try?
{
??????ca.loadAccountStatus();
秀軟件開(kāi)發(fā)人員的五個(gè)好習(xí)慣.mht!http://www.tkk7.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
????}?catch?(CustomerAccountsSystemOutageException?e)?
{
??????fail(""+e);
????}
????assertEquals("Your?purchasing?account?is?active.",
??????????????????????????????ca.getPostLogonMessage());
??}
}
總結(jié)
上述的五種習(xí)慣會(huì)幫助開(kāi)發(fā)團(tuán)隊(duì)創(chuàng)造出方便閱讀、理解和修改的軟件。如果開(kāi)發(fā)團(tuán)隊(duì)僅僅是想快速的創(chuàng)造價(jià)值而不考慮將來(lái)的規(guī)劃,他們軟件的實(shí)現(xiàn)將會(huì)耗費(fèi)越來(lái)越多的成本。當(dāng)這些開(kāi)發(fā)團(tuán)隊(duì)要審查軟件來(lái)理解和修改時(shí),不可避免的會(huì)遭到自己寫(xiě)的壞代碼的報(bào)復(fù)。如果軟件十分難以理解,在增加新價(jià)值的時(shí)候會(huì)花費(fèi)巨大的代價(jià)。然而,一旦開(kāi)發(fā)團(tuán)隊(duì)將良好的習(xí)慣運(yùn)用到開(kāi)發(fā)實(shí)踐中,他們會(huì)以最低的成本為業(yè)務(wù)提供新價(jià)值。