高質量軟件開發人員的五大習慣
ICustomerAccount
接口定義了一個對象必須實現的用來管理用戶帳戶的方法。它定義了產生一個活躍賬戶的能力,載入一個已經存在客戶賬戶狀態、校驗潛在用戶的用戶名和密碼、校驗一個賬戶是否爲活躍賬戶能夠購買產品等功能。
isRequestedUsernameValid()
, isRequestedPasswordValid()
)不需要賬戶狀態。
isRequestedUsernameValid()
讓開發人員知道這個方法確定請求的用戶名是否是合法的。與之相對照的是,isGoodUser()
可能有好幾種用途:它能決定一個用戶的賬戶是否是活躍的,決定是否請求的用戶名或密碼是正確的,或者決定用戶是否是一個好人。既然這個方法名的描述性不強,那麼它對於其他開發人員來說很難確定它的意圖是什麼。簡短地說,一個方法名使用長的和描述性的比使用短的和毫無意義的好。testRequestedPasswordIsNotValidBecauseItMustBeDifferentThanTheUsername()
就能傳遞測試的這個意圖,因此爲表達了軟件需求的意圖。
testRequestedPasswordIsNotValid()
,或者更差的testBadPassword()
,這兩個名稱都使得它很難確定測試的意圖。不清楚地或者說含糊不清的名稱將導致效率的損失。效率的損失導致需要增加額外的時間來理解測試、創建不需要的方法或屬性、重複的或者衝突的測試、或者銷燬了對象已經測試過的已經存在的功能。isRequestedPasswordValid()
的邏輯,如果兩個不同的對象都有執行相同動作的類似方法,在這種情況下,軟件開發人員將要比升級僅僅一個對象花費更多的時間來升級兩個對象。CustomerAccount
對象的目的是管理一個獨立的客戶的賬戶。它首先是創建一個賬戶,然後是驗證賬戶對於購買商品來說仍然是活躍的。假設在未來,軟件需要給那些購買了十件以上商品的客戶折扣。創建一個新的接口,ICustomerTransactions
,而且對象,CustomerTransactions
,來實現這些新的特性。這些都是開發“易於理解”軟件需要有目的進行的工作。
ICustomerAccount
接口和CustomerAccount
對象。如下所示:
getPostLogonMessage()
是一個基於accountStatus
的值的行爲方法:
loadAccountStatus()
是從遠程數據存儲設備載入accountStatus
的值的狀態改變方法:
getPostLogonMessage()
能夠通過模仿loadAccountStatus()
方法很容易地進行測試,不需要那種通過數據庫的遠程調用,每一個假設條件就能夠被測試到。例如,accountStatus
是“E”用來中止,那麼getPostLogonMessage()
將返回“Your purchasing account has expired due to a lack of activity,”,如下所示:
getPostLogonMessage()
的行爲邏輯和loadAccountStatus()
的狀態改變工作放到一個方法裏。下面的示例展示了這個錯誤的做法:
getPostLogonMessage()
沒有包含任何的行爲邏輯,而是簡單的返回實例變量this.postLogonMessage
。這個實現存在着三個問題。首先,這個實現使得我們很難理解“post logon message”的邏輯是怎麼工作的,因爲它被包含在一個執行兩個任務的方法裏。第二,getPostLogonMessage()
的重用是受限制的,因爲它永遠和loadAccountStatus()
相關聯。最後,在出現系統問題的情況下,CustomerAccountsSystemOutageException
將會被拋出,使得方法在設置this.postLogonMessage
的值之前就停止了。這個實現也對測試產生了一個負面的影響,因爲測試getPostLogonMessage()
邏輯的唯一方法是創建一個CustomerAccount
對象,這個對象有一個在數據庫裏有用戶名和密碼的用戶,而且這個用戶的accountStatus
被設置爲“E”,被用來停止。這將導致爲了這個測試必須給數據庫做一個遠程調用。這使得這個測試運行起來速度慢,而且由於數據庫發生的改變將導致測試意想不到的失敗。這個測試需要對數據庫做一個遠程調用,因爲loadAccountStatus()
方法也包含了行爲邏輯,如果行爲邏輯被模仿,那麼測試測試的是模擬對象的行爲,而不是實際對象的行爲。CustomerAccount
對象的isActiveForPurchasing()
和getPostLogonMessage()
行爲方法在它們的邏輯裏都使用accountStatus
的值。每一個方法對於其他的方法來說是功能獨立的。例如,一個場景要求isActiveForPurchasing()
被調用,接着調用getPostLogonMessage()
:
getPostLogonMessage()
,而不要求調用isActiveForPurchasing()
:
getPostLogonMessage()
要求isActiveForPurchasing()
首先被調用的話,CustomerAccount
對象將不支持第二個場景。例如,創建兩個方法來使用一個postLogonMessage
實例變量,這樣,它的值能夠在支持場景一的方法中間得到維護,但是在支持場景二的方法中卻不能:
postLogonMessage
是一個局域變量,由getPostLogonMessage()
方法自己創建:
isActiveForPurchasing()
更加具有可讀性,因爲它僅僅只回答“is active for purchasing”的問題,而不是相反的它還要設置“post logon message”。另外一個額外的好處是每一個方法都能被獨立的測試,這也使得測試容易被理解: