浏览 3890 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2007-02-05
背景是这样的: 一个接口有很多乱七八糟的业务相关的方法,其中有这么四个方法: 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真的是矛盾的么?你怎么在两者之间取得平衡呢? 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间: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] 待补充…… |
|
返回顶楼 | |
发表时间:2007-02-05
求同存异.
在共同目标可以保证的情况下,尽可能的达成一致意见. 在这种情况下,不是采纳他的方案就是采纳你的方案, 如果让我来选择的话,我一般会采纳他的方案,虽然我觉得jmock,easymock可以共存,无甚影响。 但是他的“一致性”的要求也不是不无道理,所以我会选择“自己采纳别人的意见,然后用实际的行动来赢得一种团队的共识”。 我觉得这是很有必要的。 技术的氛围,确实争论非常多,但是“一致性”的要求,有时还是很重要的,一致性的提出,也是在一种团队合作的意识形态上达成的一种产物。 这种产物是在共同目标下,经过积极充分的讨论,“求同存异”,再“求同存异”而形成的,如果团队规模比较大,这个“一致性的”要求无论如何强调都不过分,当然,它 和 “正确的方法和思路” 两者 没有必然的冲突。 后者是前者的目标。 “是否正确?”这个问题有时候更多的是一种折中,在一定范围内的折中。 就拿这个例子来说, jmock,easyMock共存 是一种我心中正确的方法, 尽可能一致的使用easyMock是pair的心中正确的做法。 如何解决这种“异‘呢? 首先,应该把这种“异”上升到团队的讨论议程上,比如召唤技术经理 ,,拍手让大家停下手头的工作,然后一起讨论这个“异”,大家各抒已见。 最后肯定能够达成一个共识。 在这个共识的前提下,然后大家一起认同这个共识,一起按照这个共识开展工作。 或许,这个共识和 我心中的想法有很大的区别,但是因为我不能说服别人,而且共识也已经通过了,那么我就愉快的采纳这个共识。 过了几天,很不幸,我心中的预想终于出现了,因为一些特殊的mock必须使用到easyMock,jmock不能够提供这样的功能,然后,继续重复前面的步骤,大家再次讨论,这个时候,我的想法可能就比较有说服力了,然后大家都觉得确实有必要“部分采纳easyMock" , 大家再愉快的达成一种共识, 然后,”修改了“前几天的共识,继续我们的工作。 这也是一个共识的”求同存异“的迭代的工程,推动这个过程的关键要素是” 1) 无论如何充分都不过分的讨论 (范围从pair-->team) 2) 彼此的尊重 3)主动积极 |
|
返回顶楼 | |
发表时间:2007-02-06
我认为Use Right Tool For Right Job远比一致性重要,数量级的差别。如果一个东西明明很臭,难道也要因为一致性而将就吗?那也太讲"政治“了吧。问题是什么是Right也是见仁见智的事情。
就是只从一致性考虑问题,那也是Java API 〉Common Well Known API 〉Home Made API 〉Individual Made API。理由是它们有更完全的测试,它们被更多的程序员所理解。 |
|
返回顶楼 | |
发表时间:2007-08-15
一致性是一个非常重要的原则,在团队合作中。
关键是在什么范围内一致?为什么是公司范围内?我们知道现在的趋势是整个世界趋向于融合、杂交,公司这个边界还真的能够对整个公司有利益吗? 正确的工具当然很重要,但这无疑是一句没有营养的话。对于工作,基本的解决思路是:1、这是不是真的工作?,说实话,我们很多时候都是自己给自己添麻烦而折腾出来的工作。如果万一不行真的是,那么,2、我必须解决这个问题吗?,大多数情况下,我们都有迂回或者腾挪或者偷懒的余地的。这样折腾下来,能够剩下来的真正的问题其实并不很多了。 |
|
返回顶楼 | |