高质量软件开发人员的五大习惯
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”。另外一个额外的好处是每一个方法都能被独立的测试,这也使得测试容易被理解: