一致性和Use Right Tool For Right Job哪个重要?

这个争执发生在测试的时候。
背景是这样的:
一个接口有很多乱七八糟的业务相关的方法,其中有这么四个方法:
[code]
interface TaxLawBuckets {
double getRemaining401k();
double getRemaining403g();
void apply401k(double amount);
void apply403g(double amount);
}
[/code]
当然,这个设计有点不是很好。更好的也许是:
[code]
interface TaxLawBuckets {
TaxLawBucket get401k();
TaxLawBucket get403g();
}
interface TaxLawBucket {
double getRemaining();
void apply(double amount);
}
[/code]
不过,因为种种原因,无法采用这个设计(主要是这么一改,改动会比较大,这个系统里面有一个wsdl代码生成器,这个生成器对接口长的样子有变态的要求)。所以就凑合着把。


现在要制作一个PerAccountTaxLawBuckets类,这个类会对401k和403g有额外的逻辑处理:
[code]
interface TaxLawBuckets {
private double bucket_401k;
private double bucket_403g;
private TaxLawBuckets globalBuckets;

public double getRemaining401k(){
return Math.min(bucket_401k, globalBuckets.getRemaining401k());
}
public double getRemaining403g() {
return Math.min(bucket_403g, globalBuckets.getRemaining403g());
}
public void apply401k(double amount) {
bucket_401k -= amount;
if(bucket_401k<0) throw new SystemException(...);
globalBuckets.apply401k(amount);
}
public void apply403g(double amount) {
bucket_403g -= amount;
if(bucket_403g<0) throw new SystemException(...);
globalBuckets.apply403g(amount);
}
//所有其他的函数都直接委托给globalBuckets。
}
[/code]
恩。有些代码重复。主要还是因为这个接口设计的不好。好在重复不多,凑合吧。

下面要写测试代码了。先看怎么写针对401k的代码。pair用的是jmock。
[code]
public class PerAccountTaxLawBucketsTest extends MockObjectTestCase {
Mock bucketsMock = mock(TaxLawBuckets.class);
TaxLawBuckets globalBuckets = bucketMock.proxy();
PerAccountTaxLawBuckets perAccount = new PerAccountTaxLawBuckets(100, 100, globalBuckets);
public void testGetRemaining401kReturnsTheMinimal() {
bucketsMock.expects(once()).method("getRemaining401k").will(return(200));
assertEquals(100, perAccount.getRemaining401k());
}
...
}
[/code]
其它的测试也都是通过jmock设置expectation,然后从perAccount对象调用对应的函数。

下面注意到401k和403g的逻辑几乎一模一样,只有方法名不同。所以为了避免代码重复,我和pair决定用一个abstract class来抽出共性。然后用两个子类来代表不同之处。pair的代码是这样:

[code]
public abstract class AbstractPerAccountTaxLawBucketsTest extends MockObjectTestCase {
Mock bucketsMock = mock(TaxLawBuckets.class);
TaxLawBuckets globalBuckets = bucketMock.proxy();
PerAccountTaxLawBuckets perAccount = new PerAccountTaxLawBuckets(100, 100, globalBuckets);
public void testGetRemainingReturnsTheMinimal() {
bucketsMock.expects(once()).method(getRemainingName()).will(return(200));
assertEquals(100, getRemaining(perAccount));
}
...
abstract String getRemainingName();
abstract double getRemaining(TaxLawBuckets buckets);
abstract String getApplyName();
abstract void apply(TaxLawBuckets buckets, double amount);
...
}

public class PerAccount401kTestCase extends AbstractPerAccountTaxLawBucketsTest {
String getRemainingName() {
return "getRemaining401k";
}
double getRemaining(TaxLawBuckets buckets) {
return buckets.getRemaining401k();
}
String getApplyName() {
return "apply401k";
}
void apply(TaxLawBuckets buckets, double amount) {
buckets.apply401k(amount);
}
}

public class PerAccount403gTestCase extends AbstractPerAccountTaxLawBucketsTest {
String getRemainingName() {
return "getRemaining403g";
}
double getRemaining(TaxLawBuckets buckets) {
return buckets.getRemaining403g();
}
String getApplyName() {
return "apply403g";
}
void apply(TaxLawBuckets buckets, double amount) {
buckets.apply403g(amount);
}
}
[/code]
而我则不太喜欢getRemainingName()和getRemaining的重复。所以我建议用easymock这样写:
[code]

public abstract class AbstractPerAccountTaxLawBucketsTest extends TestCase {
MockControl bucketsMock = MockControl.createControl(TaxLawBuckets.class);
TaxLawBuckets globalBuckets = bucketMock.getMock();
PerAccountTaxLawBuckets perAccount = new PerAccountTaxLawBuckets(100, 100, globalBuckets);
public void testGetRemainingReturnsTheMinimal() {
bucketsMock.expectsAndReturn(getRemaining(globalBuckets), 200);
bucketsMock.replay();
assertEquals(100, getRemaining(perAccount));
bucketsMock.verify();
}
...
abstract double getRemaining(TaxLawBuckets buckets);
abstract void apply(TaxLawBuckets buckets, double amount);
...
}

public class PerAccount401kTestCase extends AbstractPerAccountTaxLawBucketsTest {
double getRemaining(TaxLawBuckets buckets) {
return buckets.getRemaining401k();
}
void apply(TaxLawBuckets buckets, double amount) {
buckets.apply401k(amount);
}
}

public class PerAccount403gTestCase extends AbstractPerAccountTaxLawBucketsTest {
double getRemaining(TaxLawBuckets buckets) {
return buckets.getRemaining403g();
}
void apply(TaxLawBuckets buckets, double amount) {
buckets.apply403g(amount);
}
}
[/code]
这样,减少了重复,代码感觉更干净。

其实,这个例子简化了问题。实际的那个程序,除了getRemaining(), apply()之外,还有restore()和其他几个方法,也是401k和403g除了方法名字不同其他都一样。

但是,pair一听说用easymock直接就把这个方案枪毙了。pair的观点:
为了保持一致性,我们应该都用jmock。使用easymock会给公司里别的程序员带来理解上的困难。(我很心虚地想,我的几个test case都是用的easymock亚。)


对这个说法,我有点不知道如何说了。我一直以来的观点,都是use the right tool for the right job。
如果工具甲能够做功能1,2,3,4,工具乙能够做功能3,4,5,6。那么我不会为了保持一致性而强迫只用甲或者只用乙。而如果一个公司标准的ComplexTool能够做功能1,2,3,4,5,6,一个业界标准的SimpleTool(比如java.util.HashMap)能做功能1,而我又不需要功能2,3,4,5,6,那么我会选择SimpleTool。

何况,在一个比较强调技术的公司,担心大家不会用easymock或者jmock真的必要么?毕竟不管jmock还是easymock应该都是半个小时就能掌握的冬冬把?

其实,这种关于强调一致性的问题我已经和同事有若干次分歧了。另外一次是:公司里大多用一个内部做的MyPropertyFactory framework来读取class path里面的property文件内容。而我有一次独立开发的一个模块直接使用了ClassLoader.loadResourceAsInputStream()。于是,同事以一致性为理由要求我更改代码来使用MyPropertyFactory。具体细节在下一次disagree里面会解释。


那么,一致性和use the right tool真的是矛盾的么?你怎么在两者之间取得平衡呢?
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值