搬磚頭

          Knocking on Heaven's Door
          posts - 34, comments - 6, trackbacks - 0, articles - 0
          原文出處:http://www.aygfsteel.com/chords/archive/2006/12/14/87591.html
          ------------------------
          Five Habits of Highly Profitable Software Developers

          by Robert J. Miller
          08/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)境中開發(fā)出穩(wěn)定質(zhì)量的軟件開發(fā)人員。在團(tuán)隊(duì)開發(fā)的環(huán)境中,開發(fā)者面對的挑戰(zhàn)就是讀懂別的開發(fā)者寫的軟件。本文將文章盡力幫助軟件開發(fā)團(tuán)隊(duì)來克服這樣的困難。

          本文為了闡明了五個(gè)讓開發(fā)團(tuán)隊(duì)變得比以往更加高效的好習(xí)慣,首先將介紹公司業(yè)務(wù)對開發(fā)團(tuán)隊(duì)以及他們開發(fā)出軟件的需求,接下來會(huì)解釋狀態(tài)改變邏輯和行為邏輯之間重要的區(qū)別,最后會(huì)通過顧客賬號(hào)這么一個(gè)案例來闡述這五個(gè)習(xí)慣。

          業(yè)務(wù)帶給開發(fā)人員的需求

          公司業(yè)務(wù)團(tuán)隊(duì)的工作就是在決定將哪些對公司業(yè)務(wù)最有利的新價(jià)值可以被加到軟件中。這里的“新價(jià)值”指的是新產(chǎn)品或者是對現(xiàn)有產(chǎn)品的強(qiáng)化。換句話說就是,業(yè)務(wù)團(tuán)隊(duì)決定什么將給公司帶來最多的錢。決定了下個(gè)新價(jià)值是什么的關(guān)鍵因素是實(shí)現(xiàn)它的成本。如果實(shí)現(xiàn)的成本超過了潛在收益,那么這個(gè)新價(jià)值就不會(huì)被加到軟件中來。

          業(yè)務(wù)團(tuán)隊(duì)要求開發(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à)值是什么。

          軟件開發(fā)團(tuán)隊(duì)通過創(chuàng)造出容易理解的軟件來滿足商業(yè)團(tuán)隊(duì)的需求。難以理解的軟件帶來的后果就是整個(gè)開發(fā)過程的低效率。低效率會(huì)造成軟件開發(fā)成本的增加,引起一些預(yù)料不到的現(xiàn)有價(jià)值的損失,開發(fā)周期滾雪球般越拖越長以及交付錯(cuò)誤的軟件文檔。通過改變業(yè)務(wù)團(tuán)隊(duì)的需求,甚至將復(fù)雜的軟件轉(zhuǎn)變成簡單、容易理解的軟件,就可以提高開發(fā)過程的效率。


          介紹關(guān)鍵概念:狀態(tài)和行為

          開發(fā)容易理解的軟件可以從創(chuàng)建有狀態(tài)和行為的對象開始。“狀態(tài)”是對象在調(diào)用方法前后所保存的數(shù)據(jù)。一個(gè)JAVA對象的實(shí)例變量可以暫時(shí)的保持自己的狀態(tài),并且可以隨時(shí)存放到數(shù)據(jù)存儲(chǔ)器里。這里,永久數(shù)據(jù)存儲(chǔ)器可以是數(shù)據(jù)庫或者是Web服務(wù)。“狀態(tài)變更方法”主要管理一個(gè)對象的數(shù)據(jù)存取。“行為”則是一個(gè)對象基于狀態(tài)回答問題的能力。“行文方法”回答問題永遠(yuǎn)不會(huì)修改狀態(tài),并且這些方法往往跟一個(gè)應(yīng)用的商業(yè)邏輯有關(guān)。


          案例研究:CustomerAccount對象

          下面這個(gè)ICustomerAccount接口定義了管理一個(gè)客戶賬號(hào)對象必須實(shí)現(xiàn)的功能。這個(gè)接口定義了可以創(chuàng)建一個(gè)新的賬號(hào),加載一個(gè)已經(jīng)存在的賬號(hào)的信息,驗(yàn)證某個(gè)賬號(hào)的用戶名和密碼,驗(yàn)證購買時(shí)這個(gè)賬號(hào)是否是激活的。

          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)造器僅僅用來接受參數(shù)給實(shí)例變量加載數(shù)據(jù)。下面一個(gè)例子,讓構(gòu)造器做盡可能少的事情會(huì)讓這個(gè)類使用起來比較簡單,因?yàn)闃?gòu)造器只是簡單的給類中的實(shí)例變量賦值。

          public?class?CustomerAccount?implements?ICustomerAccount{
          ??
          //Instance?variables.
          ??private?String?username;
          ??
          private?String?password;
          ??
          protected?String?accountStatus;
          ??
          ??
          //Constructor?that?performs?minimal?work.
          ??public?CustomerAccount(String?username,?String?password)?{
          ????
          this.password?=?password;
          ????
          this.username?=?username;
          ??}

          }


          構(gòu)造器是用來創(chuàng)建一個(gè)類的實(shí)例。構(gòu)造器的名字永遠(yuǎn)是跟這個(gè)類的名字是一樣的。既然構(gòu)造器的名字無法改變,那么它就不能表達(dá)出它做的事情的含義。所以,最好是盡可能的讓構(gòu)造器少做點(diǎn)事。另一個(gè)方面,狀態(tài)變更方法和行為方法會(huì)通過自己的名字來表達(dá)出自己復(fù)雜的工作,在“習(xí)慣二:方法名要清晰的表現(xiàn)意圖”中會(huì)詳細(xì)講到。下一個(gè)例子表明,很大程度上是因?yàn)闃?gòu)造器十分的簡單,更多的讓狀態(tài)變更和行為方法來完成其他的部分,使得一個(gè)軟件具有很高的可讀性。


          注:例子中“...”部分僅僅是真實(shí)情景中必須的部分,跟本文要闡述的問題沒有關(guān)系。

          String?username?=?"robertmiller";
          String?password?
          =?"java.net";
          ICustomerAccount?ca?
          =?new?CustomerAccount(username,?password);
          if(ca.isRequestedUsernameValid()?&&?ca.isRequestedPasswordValid())?{
          ???
          ???ca.createNewActiveAccount();
          ???
          }

          相反的,如果構(gòu)造器除了給實(shí)例變量賦值以外的事情,將會(huì)使代碼很難讓人理解,并且有可能被誤用,因?yàn)闃?gòu)造器的名字沒有說明要做的意圖。例如,下面的代碼將調(diào)用數(shù)據(jù)庫或者Web服務(wù)來預(yù)加載賬號(hào)的狀態(tài):

          //Constructor?that?performs?too?much?work!
          public?CustomerAccount(String?username,?String?password)?
          ??????????????????
          throws?CustomerAccountsSystemOutageException?{

          ??
          this.password?=?password;
          ??
          this.username?=?username;
          ??
          this.loadAccountStatus();//unnecessary?work.
          }

          //Remote?call?to?the?database?or?web?service.
          public?void?loadAccountStatus()?
          ??????????????????
          throws?CustomerAccountsSystemOutageException?{
          ??
          }

          別人可能在不知道會(huì)使用遠(yuǎn)程調(diào)的情況下使用這個(gè)構(gòu)造器,從而導(dǎo)致了以下個(gè)遠(yuǎn)程調(diào)用:

          String?username?=?"robertmiller";
          String?password?
          =?"java.net";?
          try?{
          ??
          //makes?a?remote?call
          ??ICustomerAccount?ca?=?new?CustomerAccount(username,?password);
          ??
          //makes?a?second?remote?call
          ??ca.loadAccountStatus();
          }
          ?catch?(CustomerAccountsSystemOutageException?e)?{
          ??
          }

          或者使開發(fā)人員重用這個(gè)構(gòu)造器來驗(yàn)證用戶名和密碼,并且被強(qiáng)制的進(jìn)行了遠(yuǎn)程調(diào)用,然而這些行為方法(isRequestedUsernameValid(), isRequestedPasswordValid())根本不需要賬戶的狀態(tài):

          String?username?=?"robertmiller";
          String?password?
          =?"java.net";
          try?{
          ??
          //makes?unnecessary?remote?call
          ??ICustomerAccount?ca?=?new?CustomerAccount(username,?password);
          ??
          if(ca.isRequestedUsernameValid()?&&?ca.isRequestedPasswordValid())?{
          ????
          ????ca.createNewActiveAccount();
          ????
          ??}

          }
          ?catch?(CustomerAccountsSystemOutageException?e){
          ??
          }


          習(xí)慣二:方法名要清晰的表現(xiàn)意圖

          第二個(gè)習(xí)慣就是要讓所有的方法名字清晰的表現(xiàn)本方法要做什么的意圖。例如isRequestedUsernameValid()讓開發(fā)人員知道這個(gè)方法時(shí)用來驗(yàn)證用戶名是否正確的。相反的,isGoodUser() 可能有很多用途:用來驗(yàn)證賬號(hào)是否是激活的,用來驗(yàn)證用戶名或者密碼是否正確,或者是用來搞清楚用戶是不是個(gè)好人。方法名表意不清,這就很難讓開發(fā)者明白這個(gè)方法到底是用來干什么的。簡單的說,長一點(diǎn)并且表意清晰的方法名要比又短又表意不明的方法名好。

          表意清晰的長名字會(huì)幫助開發(fā)團(tuán)隊(duì)快速的理解他們軟件的功能意圖。更大的優(yōu)點(diǎn)在于,給測試方法也起個(gè)好名字會(huì)讓軟件現(xiàn)有的要求更加的清晰。例如,本軟件要求驗(yàn)證請求的用戶名和用戶密碼是不同的。使用名為testRequestedPasswordIsNotValidBecauseItMustBeDifferentThanTheUsername()的方法清晰的表達(dá)出了方法的意圖,也就是軟件要達(dá)到的要求。

          import?junit.framework.TestCase;

          public?class?CustomerAccountTest?extends?TestCase{
          ??
          public?void?testRequestedPasswordIsNotValid
          ????????BecauseItMustBeDifferentThanTheUsername()
          {
          ????String?username?
          =?"robertmiller";
          ????String?password?
          =?"robertmiller";
          ????ICustomerAccount?ca?
          =?new?CustomerAccount(username,?password);
          ????assertFalse(ca.isRequestedPasswordValid());
          ??}

          }


          這個(gè)方法簡單的被命名為testRequestedPasswordIsNotValid(),或者更糟的是testBadPassword()。這兩個(gè)名字都讓人很難搞清楚這個(gè)方法是用來測試什么的。不清楚或者是模棱兩可的測試方法名會(huì)帶來生產(chǎn)力的損失。從而導(dǎo)致花費(fèi)來越多的時(shí)間來理解測試,不必要的重復(fù)測試,或者是破壞了被測試的類。

          最后,明了的方法名還能減少文檔和注釋的工作量。


          習(xí)慣三:一個(gè)對象只進(jìn)行一類服務(wù)。

          第三個(gè)喜歡就是對象只關(guān)心處理一小類獨(dú)立的服務(wù)。一個(gè)對象只處理一小部分事情將使得代碼更好讀好用,因?yàn)槊總€(gè)對象代碼量很少。更糟糕的是,重復(fù)的邏輯將花費(fèi)很多時(shí)間和成本去維護(hù)。設(shè)想一下,業(yè)務(wù)部門將來要求升級(jí)一下isRequestedPasswordValid()里的邏輯,然而有兩個(gè)不同的對象卻有著功能完全一樣但是名字不一樣的方法。這種情況下,開發(fā)團(tuán)隊(duì)要花費(fèi)更多的時(shí)間去更新兩個(gè)對象,而不是一個(gè)。

          這個(gè)案例表明了CustomerAccount類的目的就是管理一個(gè)客戶的帳號(hào)。它首先創(chuàng)建了一個(gè)帳號(hào),然后嚴(yán)整這個(gè)帳號(hào)能否用來購買產(chǎn)品。假設(shè)軟件要給所有購買過10件物品以上的客戶打折。再創(chuàng)建一個(gè)接口叫ICustomerTransactions和一個(gè)叫CustomerTransactions的類,這樣會(huì)讓代碼更加易懂,并且實(shí)現(xiàn)目標(biāo)。

          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)建起來十分簡單,方便測試以及穩(wěn)定,因?yàn)樗鼘W⑿倪@一個(gè)目標(biāo)。一個(gè)低效率的方法是如同下面的例子一樣在ICustomerAccount接口和CustomerAccount類加上新的方法:

          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();
          }

          ?

          就像是上面所看到的一樣,這樣使得類具有太多職責(zé),難以讀懂,甚至更容被易誤解。代碼被誤解的后果就是降低生產(chǎn)力,費(fèi)時(shí)費(fèi)力。總的來說,最好讓一個(gè)對象和它的方法集中處理一個(gè)小的工作單元。


          習(xí)慣四:狀態(tài)變更方法少含有行為邏輯

          第四個(gè)習(xí)慣是讓狀態(tài)變更方法少含有行為邏輯。混合了狀態(tài)變更邏輯和行為邏輯的代碼讓人很難理解,因?yàn)樵谝粋€(gè)地方處理了太多的事情。狀態(tài)變更方法涉及到遠(yuǎn)程調(diào)用來存儲(chǔ)數(shù)據(jù)的話很容易產(chǎn)生系統(tǒng)問題。如果遠(yuǎn)程方法是相對獨(dú)立的,并且方法本身沒有行為邏輯,這樣診斷起狀態(tài)改變方法就會(huì)十分容易。另外一個(gè)問題是,混合了行為邏輯的狀態(tài)代碼很難進(jìn)行單元測試。例如,getPostLogonMessage() 是一個(gè)依靠accountStatus的值的行為:

          public?String?getPostLogonMessage()?{
          ??
          if("A".equals(this.accountStatus)){
          ????
          return?"Your?purchasing?account?is?active.";
          ??}
          ?else?if("E".equals(this.accountStatus))?{
          ????
          return?"Your?purchasing?account?has?"?+
          ???????????
          "expired?due?to?a?lack?of?activity.";
          ??}
          ?else?{
          ????
          return?"Your?purchasing?account?cannot?be?"?+
          ???????????
          "found,?please?call?customer?service?"+
          ???????????
          "for?assistance.";
          ??}

          }


          loadAccountStatus()是一個(gè)使用遠(yuǎn)程調(diào)用來加載 accountStatus值的狀態(tài)改變方法。

          public?void?loadAccountStatus()?
          ??????????????????
          throws?CustomerAccountsSystemOutageException?{
          ??Connection?c?
          =?null;
          ??
          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();
          ????
          if?(rs.next())?{
          ??????
          this.accountStatus=rs.getString("status");
          ????}

          ????rs.close();
          ????ps.close();
          ????c.close();??
          ??}
          ?catch?(SQLException?e)?{
          ????
          throw?new?CustomerAccountsSystemOutageException(e);
          ??}
          ?finally?{
          ????
          if?(c?!=?null)?{
          ??????
          try?{
          ????????c.close();
          ???????}
          ?catch?(SQLException?e)?{}
          ????}

          ??}

          }


          單元測試 getPostLogonMessage()? 方法十分簡單,只用loadAccountStatus()方法就行了。每個(gè)場景都可以在使用遠(yuǎn)程調(diào)用連接數(shù)據(jù)庫的情況下進(jìn)行測試。例如,如果 accountStatus 的值是E,代表過期,則getPostLogonMessage() 會(huì)如下代碼顯示一樣返回 "Your purchasing account has expired due to a lack of activity"

          public?void?testPostLogonMessageWhenStatusIsExpired(){
          ??String?username?
          =?"robertmiller";
          ??String?password?
          =?"java.net";
          ?
          ??
          class?CustomerAccountMock?extends?CustomerAccount{
          ????????
          ????
          public?void?loadAccountStatus()?{
          ??????
          this.accountStatus?=?"E";
          ????}

          ??}

          ??ICustomerAccount?ca?
          =?new?CustomerAccountMock(username,?password);
          ??
          try?{
          ????ca.loadAccountStatus();
          ??}
          ?
          ??
          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)該這么做:

          public?String?getPostLogonMessage()?{
          ??
          return?this.postLogonMessage;
          }

          public?void?loadAccountStatus()?
          ??????????????????
          throws?CustomerAccountsSystemOutageException?{
          ??Connection?c?
          =?null;
          ??
          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();
          ????
          if?(rs.next())?{
          ??????
          this.accountStatus=rs.getString("status");
          ????}

          ????rs.close();
          ????ps.close();
          ????c.close();??
          ??}
          ?catch?(SQLException?e)?{
          ????
          throw?new?CustomerAccountsSystemOutageException(e);
          ??}
          ?finally?{
          ????
          if?(c?!=?null)?{
          ??????
          try?{
          ????????c.close();
          ???????}
          ?catch?(SQLException?e)?{}
          ????}

          ??}

          ??
          if("A".equals(this.accountStatus)){
          ????
          this.postLogonMessage?=?"Your?purchasing?account?is?active.";
          ??}
          ?else?if("E".equals(this.accountStatus))?{
          ????
          this.postLogonMessage?=?"Your?purchasing?account?has?"?+
          ????????????????????????????
          "expired?due?to?a?lack?of?activity.";
          ??}
          ?else?{
          ????
          this.postLogonMessage?=?"Your?purchasing?account?cannot?be?"?+
          ????????????????????????????
          "found,?please?call?customer?service?"+
          ????????????????????????????
          "for?assistance.";
          ??}

          }

          這個(gè)實(shí)現(xiàn)了一個(gè)沒有包含任何行為邏輯的getPostLogonMessage()行為方法,并且簡單的返回一個(gè)實(shí)例變量this.postLogonMessage。這么做有三個(gè)問題:第一,很難讓人明白"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ù)庫的CustomerAccount對象,并且將賬號(hào)狀態(tài)設(shè)置成E才能進(jìn)行對getPostLogonMessage()邏輯的單元測試。結(jié)果式這個(gè)測試要進(jìn)行遠(yuǎn)程調(diào)用。這會(huì)導(dǎo)致測試的很慢,而且在改變數(shù)據(jù)庫內(nèi)容的時(shí)候很容易出意想不到的問題。由于 loadAccountStatus()方法包含了行為邏輯,測試必須進(jìn)行遠(yuǎn)程調(diào)用。如果行為邏輯測試失敗了,測的只是那個(gè)失敗的對象行為,而不是真正的對象的行為。


          習(xí)慣五:可以任意次序調(diào)用行為方法

          第五個(gè)習(xí)慣是要保證每個(gè)行為方法之間保持著獨(dú)立。換句話說,一個(gè)對象的行為方法可以被重復(fù)或任何次序來調(diào)用。這個(gè)習(xí)慣能讓對象實(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();
          if(ca.isActiveForPurchasing()){?
          ??
          //go?to?"begin?purchasing"?display
          ??
          ??
          //show?post?logon?message.
          ??ca.getPostLogonMessage();
          }
          ?else?{
          ??
          //go?to?"activate?account"?display??
          ??
          ??
          //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?

          //show?post?logon?message.
          ca.getPostLogonMessage();


          如果要求調(diào)用getPostLogonMessage()之前必須調(diào)用isActiveForPurchasing()方法,CustomerAccount 對象將無法支持第二個(gè)情景。如果兩個(gè)方法使用了 postLogonMessage 實(shí)例變量來存放兩個(gè)方法所需要的值,那么這將支持第一個(gè)情景,但不支持第二個(gè):

          public?boolean?isActiveForPurchasing()?{
          ??
          boolean?returnValue?=?false;
          ??
          if("A".equals(this.accountStatus)){
          ????
          this.postLogonMessage?=?"Your?purchasing?account?is?active.";
          ????returnValue?
          =?true;
          ??}
          ?else?if("E".equals(this.accountStatus))?{
          ????
          this.postLogonMessage?=?"Your?purchasing?account?has?"?+
          ????????????????????????????
          "expired?due?to?a?lack?of?activity.";
          ????returnValue?
          =?false;

          ??}
          ?else?{
          ????
          this.postLogonMessage?=?"Your?purchasing?account?cannot?be?"?+
          ????????????????????????????
          "found,?please?call?customer?service?"+
          ????????????????????????????
          "for?assistance.";
          ????returnValue?
          =?false;
          ??}

          ??
          return?returnValue;
          }

          public?String?getPostLogonMessage()?{
          ??
          return?this.postLogonMessage;
          }

          然而,如果兩個(gè)方法的邏輯推理是相互獨(dú)立的,那么就可以支持第二個(gè)情景了。在下面的一個(gè)例子中,postLogonMessage是getPostLogonMessage()創(chuàng)建的一個(gè)局部變量。

          public?boolean?isActiveForPurchasing()?{
          ??
          return?this.accountStatus?!=?null?&&?this.accountStatus.equals("A");
          }

          public?String?getPostLogonMessage()?{
          ??
          if("A".equals(this.accountStatus)){
          ????
          return?"Your?purchasing?account?is?active.";
          ??}
          ?else?if("E".equals(this.accountStatus))?{
          ????
          return?"Your?purchasing?account?has?"?+
          ???????????
          "expired?due?to?a?lack?of?activity.";
          ??}
          ?else?{
          ????
          return?"Your?purchasing?account?cannot?be?"?+
          ???????????
          "found,?please?call?customer?service?"+
          ???????????
          "for?assistance.";
          ??}

          }

          讓這兩個(gè)方法之間相互獨(dú)立的另一個(gè)好處是更容易理解。例如,isActiveForPurchasing()如果只是用來回答如“能否購買”的問題會(huì)顯得可讀性更佳,如果是用來解決“顯示登陸消息”就不那么好了。另一個(gè)好處就是測試是獨(dú)立的,讓測試更加簡單和容易理解:

          public?class?CustomerAccountTest?extends?TestCase{
          ??
          public?void?testAccountIsActiveForPurchasing(){
          ????String?username?
          =?"robertmiller";
          ????String?password?
          =?"java.net";

          ????
          class?CustomerAccountMock?extends?CustomerAccount{
          ??????
          ??????
          public?void?loadAccountStatus()?{
          ????????
          this.accountStatus?=?"A";
          ??????}

          ????}

          ????ICustomerAccount?ca?
          =?new?CustomerAccountMock(username,?password);
          ????
          try?{
          ??????ca.loadAccountStatus();
          ????}
          ?catch?(CustomerAccountsSystemOutageException?e)?{
          ??????fail(
          ""+e);
          ????}

          ????assertTrue(ca.isActiveForPurchasing());?
          ??}
          ?
          ??
          ??
          public?void?testGetPostLogonMessageWhenAccountIsActiveForPurchasing(){
          ????String?username?
          =?"robertmiller";
          ????String?password?
          =?"java.net";

          ????
          class?CustomerAccountMock?extends?CustomerAccount{
          ??????
          ??????
          public?void?loadAccountStatus()?{
          ????????
          this.accountStatus?=?"A";
          ??????}

          ????}

          ????ICustomerAccount?ca?
          =?new?CustomerAccountMock(username,?password);
          ????
          try?{
          ??????ca.loadAccountStatus();
          ????}
          ?catch?(CustomerAccountsSystemOutageException?e)?{
          ??????fail(
          ""+e);
          ????}

          ????assertEquals(
          "Your?purchasing?account?is?active.",
          ??????????????????????????????ca.getPostLogonMessage());
          ??}

          }


          總結(jié)

          上述的五種習(xí)慣會(huì)幫助開發(fā)團(tuán)隊(duì)創(chuàng)造出方便閱讀、理解和修改的軟件。如果開發(fā)團(tuán)隊(duì)僅僅是想快速的創(chuàng)造價(jià)值而不考慮將來的規(guī)劃,他們軟件的實(shí)現(xiàn)將會(huì)耗費(fèi)越來越多的成本。當(dāng)這些開發(fā)團(tuán)隊(duì)要審查軟件來理解和修改時(shí),不可避免的會(huì)遭到自己寫的壞代碼的報(bào)復(fù)。如果軟件十分難以理解,在增加新價(jià)值的時(shí)候會(huì)花費(fèi)巨大的代價(jià)。然而,一旦開發(fā)團(tuán)隊(duì)將良好的習(xí)慣運(yùn)用到開發(fā)實(shí)踐中,他們會(huì)以最低的成本為業(yè)務(wù)提供新價(jià)值。

          只有注冊用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 葫芦岛市| 桓台县| 徐州市| 泸定县| 安庆市| 华池县| 永寿县| 泰宁县| 安远县| 沅陵县| 政和县| 焦作市| 基隆市| 买车| 常宁市| 琼结县| 庆安县| 五寨县| 平昌县| 青浦区| 宣威市| 阜南县| 新营市| 福清市| 昆明市| 台东市| 松江区| 兖州市| 丰台区| 常山县| 兴国县| 获嘉县| 常宁市| 远安县| 遂宁市| 始兴县| 阳新县| 通辽市| 泾源县| 耒阳市| 闽侯县|