`
ajoo
  • 浏览: 452703 次
社区版块
存档分类
最新评论

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

阅读更多
这个争执发生在测试的时候。
背景是这样的:
一个接口有很多乱七八糟的业务相关的方法,其中有这么四个方法:
interface TaxLawBuckets {
  double getRemaining401k();
  double getRemaining403g();
  void apply401k(double amount);
  void apply403g(double amount);
}

当然,这个设计有点不是很好。更好的也许是:
interface TaxLawBuckets {
  TaxLawBucket get401k();
  TaxLawBucket get403g();
}
interface TaxLawBucket {
  double getRemaining();
  void apply(double amount);
}

不过,因为种种原因,无法采用这个设计(主要是这么一改,改动会比较大,这个系统里面有一个wsdl代码生成器,这个生成器对接口长的样子有变态的要求)。所以就凑合着把。


现在要制作一个PerAccountTaxLawBuckets类,这个类会对401k和403g有额外的逻辑处理:
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。
}

恩。有些代码重复。主要还是因为这个接口设计的不好。好在重复不多,凑合吧。

下面要写测试代码了。先看怎么写针对401k的代码。pair用的是jmock。
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());
  }
  ...
}

其它的测试也都是通过jmock设置expectation,然后从perAccount对象调用对应的函数。

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

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

而我则不太喜欢getRemainingName()和getRemaining的重复。所以我建议用easymock这样写:

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

这样,减少了重复,代码感觉更干净。

其实,这个例子简化了问题。实际的那个程序,除了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真的是矛盾的么?你怎么在两者之间取得平衡呢?
分享到:
评论
4 楼 fixopen 2007-08-15  
一致性是一个非常重要的原则,在团队合作中。
关键是在什么范围内一致?为什么是公司范围内?我们知道现在的趋势是整个世界趋向于融合、杂交,公司这个边界还真的能够对整个公司有利益吗?

正确的工具当然很重要,但这无疑是一句没有营养的话。对于工作,基本的解决思路是:1、这是不是真的工作?,说实话,我们很多时候都是自己给自己添麻烦而折腾出来的工作。如果万一不行真的是,那么,2、我必须解决这个问题吗?,大多数情况下,我们都有迂回或者腾挪或者偷懒的余地的。这样折腾下来,能够剩下来的真正的问题其实并不很多了。
3 楼 pojo 2007-02-06  
我认为Use Right Tool For Right Job远比一致性重要,数量级的差别。如果一个东西明明很臭,难道也要因为一致性而将就吗?那也太讲"政治“了吧。问题是什么是Right也是见仁见智的事情。

就是只从一致性考虑问题,那也是Java API 〉Common Well Known API 〉Home Made API 〉Individual Made API。理由是它们有更完全的测试,它们被更多的程序员所理解。
2 楼 firebody 2007-02-05  
求同存异.

在共同目标可以保证的情况下,尽可能的达成一致意见.

在这种情况下,不是采纳他的方案就是采纳你的方案, 如果让我来选择的话,我一般会采纳他的方案,虽然我觉得jmock,easymock可以共存,无甚影响。 但是他的“一致性”的要求也不是不无道理,所以我会选择“自己采纳别人的意见,然后用实际的行动来赢得一种团队的共识”。 我觉得这是很有必要的。

技术的氛围,确实争论非常多,但是“一致性”的要求,有时还是很重要的,一致性的提出,也是在一种团队合作的意识形态上达成的一种产物。  这种产物是在共同目标下,经过积极充分的讨论,“求同存异”,再“求同存异”而形成的,如果团队规模比较大,这个“一致性的”要求无论如何强调都不过分,当然,它 和 “正确的方法和思路” 两者 没有必然的冲突。  后者是前者的目标。 “是否正确?”这个问题有时候更多的是一种折中,在一定范围内的折中。 就拿这个例子来说, jmock,easyMock共存 是一种我心中正确的方法, 尽可能一致的使用easyMock是pair的心中正确的做法。 如何解决这种“异‘呢?

首先,应该把这种“异”上升到团队的讨论议程上,比如召唤技术经理 ,,拍手让大家停下手头的工作,然后一起讨论这个“异”,大家各抒已见。 最后肯定能够达成一个共识。


在这个共识的前提下,然后大家一起认同这个共识,一起按照这个共识开展工作。


或许,这个共识和 我心中的想法有很大的区别,但是因为我不能说服别人,而且共识也已经通过了,那么我就愉快的采纳这个共识。


过了几天,很不幸,我心中的预想终于出现了,因为一些特殊的mock必须使用到easyMock,jmock不能够提供这样的功能,然后,继续重复前面的步骤,大家再次讨论,这个时候,我的想法可能就比较有说服力了,然后大家都觉得确实有必要“部分采纳easyMock" , 大家再愉快的达成一种共识,

然后,”修改了“前几天的共识,继续我们的工作。



这也是一个共识的”求同存异“的迭代的工程,推动这个过程的关键要素是”

1) 无论如何充分都不过分的讨论 (范围从pair-->team)

2) 彼此的尊重

3)主动积极

1 楼 Allen 2007-02-05  
通常来说,在一个各方代码处理能力参差不齐的人员组成的环境中——当需要接触(甚至理解)这段代码的人数超过4的时候,“一致性”所带来的效率稳定性会随着人数的增长体现出越来越高于“use the right tool”的趋势。
引用
毕竟不管jmock还是easymock应该都是半个小时就能掌握的冬冬把?
这句话说的是没错,一个具备合格素养的Java开发者确实能够做到这些。但是这不仅要求你能够迅速准确地向信息攸关各方传达这个意念;同时还需要他们能够及时正确地做出反应。为完成这一点而需要花费的开销恐怕不是很小……

况且就算各方协调得很好,所有程序阅读者也都能做到“半个小时就能掌握”,那么n位参与者大概也要耗费(最佳推演下)n*30minutes的时间来让所有人能够具备理解这个的基础知识。

要在这个开发环境内部推行"use the right tool" in every special circumstance,我个人觉得至少应该具备以下几点:
1] 各成员之间的信任程度很高,且均具备良好的专业素养;
2] 此类信息能够流畅的传达和接受(不是接收),利益攸关方也不算太多;
3] 在推动此方案试运行的过程中已经证实是有利于集体的;
4] 待补充……

相关推荐

Global site tag (gtag.js) - Google Analytics