您好,欢迎访问三七文档
1多币种资金我们就从Ward在WyCash系统中创建的多币种资金(multi-currencymoney)这个对象(参见“导言”)开始谈起吧。假设我们有这样的一个报表:票据股份股价合计为了使其变成一个多币种的报表,我们需要加上币种单位:票据股份股价合计当然,我们还需要为此指定汇率(exchangerate):源币种兑换币种汇率当瑞士法郎与美元的兑换率为2:1的时候,5美元+10瑞士法郎=10美元5美元*2=10美元我们要怎么做才能产生上面经过修订的报表呢?或者说,哪些测试一旦通过,就能够说明目前我们信赖的这些代码可以正确地计算出报表了呢?在假设已经给定汇率的情况下,要能对两种不同币种的金额相加,并将结果转换为某一种币种;要能将某一金额(每股股价)与某一个数(股数)相乘,并得到一个总金额。为此,我们将建立一个计划清单(to-dolist)以提醒我们需要做哪些事情,它将使我们始终保持注意力集中,同时它也可以告诉我们什么时候可以完工。当我们开始某一项工作时,我们用粗体来表示它,就像这样。当我们完成某项工作时,我们将其划去,就像这样。如果我们想起其他要做的测试,就将其加入清单。正如前面的计划清单所讲的一样,我们就从实现乘法这个功能开始。那么,我们首先需要建立什么对象呢?什么对象也不需要。记住,我们不是从建立对象开始,而是从测试开始。(我一直都在提醒自己注意这个问题,希望你也能时刻记住提醒自己。)既然如此,那么我们首先应该进行什么测试呢?清单中的第一个测试看起来很复杂,我们需要从比较简单的开始。第二个测试不过是实现乘法功能而已,能难到哪儿去呢?我们就IBM10002525000GE40010040000合计65000IBM100025美元25000美元Novartis400150瑞士法郎60000瑞士法郎合计65000美元瑞士法郎美元1.53从它开始吧。在编写测试的时候,我们总是为我们的操作设想最完美的接口(interface)。我们总是告诉自己这些操作在外界看来应该是个什么样子,尽管很多时候我们的设想并不能化为现实,最好是从一种尽可能优秀的应用编程接口(applicationprograminterface,API)开始,然后再倒着进行设计,这要比从一开始就把一切都搞得很复杂、拙劣而“现实”好。下面是一个关于乘法功能的简单实例:[我知道,我知道!这段代码有很多问题:公共域问题,副作用问题,货币金额用整数来表示的问题,等等。别急,一步一步来。我们将这些毛病记录下来,然后继续前进。显然,测试没有通过,但是我们希望测试能够尽快到达可运行状态(greenbar)。①]当瑞士法郎与美元的兑换率为2:1的时候,5美元+10瑞士法郎=10美元5美元*2=10美元将“amount”定义为私有Dollar类有副作用吗?钱数必须为整数?我们刚才键入的测试程序甚至还不能通过编译。[我会在后边讲测试框架(testingframework)JUnit的时候解释在什么地方键入以及怎样将其键入。]修改这样的测试非常简单。即便是编译后也无法运行,但为了使其能够编译通过,我们至少要做哪些工作呢?我们存在以下四个编译错误:没有Dollar类没有构造函数没有times(int)方法没有amount域让我们逐一改正(我总是在寻找某种度量进度的数值化方法)。我们可以通过定义Dollar类来去掉一个错误:一个错误已经解决,还有三个。现在我们需要一个构造函数,但是单单为了让测试能够编译通过,它不必实现任何功能:还有两个错误。我们需要times()的存根实现(stubimplementation)。同样仅做可以使测试程序通过的最少的工作:仅剩下一个错误。最后,我们需要一个amount域:①Junit测试工具运行测试时,如果测试全部运行通过,那么状态条是绿色的;如果存在没有通过的测试,那么状态条就是红色的。本书作者大量使用包含green或red的字句,我们以后统一将其译作测试运行通过或没有通过。——译者注好了,现在我们可以运行测试程序,结果如图1-1所示,失败了。图1-1虽然测试失败,但有进步!可以看出测试程序没有运行通过(redbar)。我们在测试框架(在该例中为JUnit)中运行了这个作为开篇所编写的一小段代码,可以发现,尽管我们希望结果是“10”,事实上却很不幸,我们看到的结果是“0”。没有关系,失败也是一种进步。我们已经对这次失败有了一个具体的衡量,这要比只是模模糊糊地知道自己要失败的好。我们要解决的编程问题已经由原来的“实现多币种”转化为“让这个测试程序能够工作,然后让剩下的测试程序也能够工作”。问题已经比以前简单多了,要考虑问题的范围也小了很多。而且,我们完全可以让这个测试程序工作起来。你也许不喜欢这个解决方案,但是现在的目的不是获得最完美的解决方案,而是让这个测试程序可以运行。我们将在做出理想的产品之前做出点牺牲。下面是我所能想到的可以让测试程序通过的最小改动:图1-2显示了测试程序再次运行后的结果。现在测试程序运行通过,可喜可贺!不过不要高兴得太早,致力于电脑编程的男孩女孩们,这一轮的工作还没完成呢!世界上恐怕很难找到几个输入可以让这个功能有限、风格很差、近乎弱智的测试程序运行通过。所以,我们在继续前进之前要把它一般化。记注,这一轮工作由下列的环节组成:图1-2测试程序运行(1)新增一个测试。(2)运行所有的测试程序并失败。(3)做一些小小的改动。(4)运行所有的测试程序,并且全部通过。(5)重构代码以消除重复设计,优化设计结构。▼▼依赖关系(dependency)与重复设计(duplication)SteveFreeman指出:测试程序与代码所存在的问题不在于重复设计(关于重复设计的概念我们还没有提到过,但我将在这段闲话说完后尽快向你解释),而在于代码与测试程序之间的依赖关系——你不可能只改动其中一个而不改动另外一个。我们的目标是编写另外一个对我们有用的测试而不必改动代码,而这对于当前实现而言是不可能的。依赖关系是各种规模的软件开发中的关键问题。如果你让一家厂商的SQL具体实现散布在整个产品代码中,而又决定换成另一家厂商,那么就会发现,你的代码依赖于某家数据库厂商,你在不修改代码的情况下无法改变数据库。如果问题出在依赖关系上,那么其表现就是重复设计。重复设计通常表现为逻辑上的重复设计——相同的表达式在代码的多个地方出现。利用各种对象可以很好地抽象出逻辑上的重复设计。与现实生活中的大多数问题不同,在现实生活中,消除某种症状往往使问题以其他更恶劣的形式重新表现出来,消除程序中的重复设计就是消除依赖关系,这就是测试驱动开发第二条规则的由来。只有在编写下一个测试之前消除现有的重复设计,通过一处且仅仅一处改动即可让下一个测试运行通过的可能性才最大。▲▲(1)~(4)项我们都已运行过了。那么什么地方有重复设计呢?通常重复设计存在于两段代码之间,但是在这儿重复设计却存在于测试中的数据与代码中的数据之间。你看到了吗?如果我们这样写会怎样呢:这个10必然有它的来历,我们只顾在大脑中快速地做乘法以至于将这点忽略了。在这儿的5与2处于两个不同的地方,所以依照规则,在我们继续之前必须毫不留情地消除重复。我们无法只通过一步就消除5和2。既然如此,可以不在对象初始化时给amount赋值,而将这个过程移至times()方法中。测试仍然通过,测试程序保持在可运行状态。这样的软件开发步伐对于你来说是否太小了?记住,测试驱动开发并非一定要采取这样一小步一小步的开发过程,而是要培养你将软件开发化为这样的一小步一小步的开发任务的能力。我日复一日都以这样小的步伐进行开发吗?当然不是。但是当情况变得有些棘手时,我很高兴我有这样的能力。选择一个简单的例子一步一步来尝试,来学习。如果你可以将软件开发分成一个个粒度比较小的开发任务,那么你自然可以将它分得大小适当。但是如果你仅仅采用较大的步伐进行开发,那么你根本不会知道较小的步伐是否合适。言归正传。我们刚才谈到哪儿了?对,谈到如何消除测试代码和工作代码之间的重复。我们可以从哪儿得到一个5呢?这是传给构造函数的值,所以我们用amount变量来保存它:然后我们就可以在times()函数中使用它:参数“multiplier”的值是2,所以我们可以用这个参数来代替这个常量:为了证明我们精通Java的语法,我们将使用*=操作符(这确实削减了重复内容):当瑞士法郎与美元的兑换率为2:1的时候,5美元+10瑞士法郎=10美元5美元*2=10美元将“amount”定义为私有Dollar类有副作用吗?钱数必须为整数?现在可以说第一个测试已经完成了,下一步我们将解决那些奇怪的副作用问题。在此之前,让我们回顾一下,我们做了以下的工作:创建一个清单,列出我们所知道的需要让其运行通过的测试通过一小段代码说明我们希望看到怎样的一种操作暂时忽略JUnit的一些细节问题通过建立存根(stub)来让测试程序通过编译通过一些另类的做法来让测试运行通过逐渐使工作代码一般化,用变量代替常量将新工作逐步加入计划清单,而不是一次全部提出
本文标题:多币种资金
链接地址:https://www.777doc.com/doc-1208704 .html