??xml version="1.0" encoding="utf-8" standalone="yes"?>
人月话的作者Brooks新出了一本书叫做The Design of designQ本文不是这本书的书评。写q一我已经想了很久了,内容不会太多Q但是却很慎重?nbsp;
Design是个听v来特别高雅的词,很多Z事所谓的设计行业。比如徏{设计,机械设计Q艺术设计,以及我所从事的Y件设计?nbsp;
英国戏剧家作家DorothySayers在《The Mind of Makr》里Q将创造过E分Z个阶D,x惻IIdeaQ,_QEnergyQ(或实玎ͼImplementationQ)以及交互QInteractionQ。这代表着Q?nbsp;
1Q概忉|构想的形成?nbsp;
2Q在真实的媒体中实现
3Q?在真实的体验中与用户交互?nbsp;
我特别赞同这一说法。这表明Q设计最重要的是Idea?nbsp;
Brooks又讲qC莫扎特的故事Q其父亲询问其歌剧进度,他回{:一切都谱好了,只是q没写下来而已?nbsp;
我看q莫扎特传那部电(sh)影,因此我理解这D话会有所不同Q在莫扎特头脑中的谱子实际上已经h实现的部分,而不单单是Idea或者叫构思。因Zq样形容他快速而绝无修改的作曲q程Q就好像把头脑中谱好的曲子誊写在U怸一栗?nbsp;
正文
1 设计是一创造性活动,而实现则是另外一Ҏ(gu)动?nbsp;
2 DesignQ常怼用一USignQ符P来表CIdeaQSign不是设计Q而其表示的东西的才是设计?nbsp;
3 Zl常提及建筑行业Q因为其设计和实玎ͼ建造)q程划分最为清晰。另外大型机械制造的设计也类伹{?nbsp;
4 设计q程与实现过E以及跟用户的交互的q程会@环多ơ,其他q程使设计变得有用,q且能够作ؓ反馈促设计改进。但是它们都不是设计?nbsp;
5 Z能够设计和实现q程分离Q需要有一U全面的展现形式交给实现人员Q比如设计图U,乐谱{?nbsp;
6 对于设计软g而言Q其实是有两个分c,一个是面向用户使用的Y件设计,一个是软g自nl构的设计。项目经理,产品l理Q销售主等往往只看到第一UY件设计,而架构师往往才同时关注两者?nbsp;
7 在Y件设计中Q必L认源代码才是最l设计交付的形式Q但是ƈ不应该局限于C或者Java写出来文本文件才是源代码Q如果编译系l能够直接将UML囄译成机器指o或者虚拟机指oQ那么她们也是源代码。如果不能,其他的文档,图表只是设计的不同粒度,以及所设计的物品的不同部分的表现Ş式,他们代表设计Q但不是全部。(q点与Jack W.Reeves的《What is Software Design》中的观点略有不同)
8 在设计Y件时Q程序员会不断地完成实现q程Q然后看到Y件运行时的效果,q与之交互,q行最基本的创造过E@环。这个过E最好要l常q行Q快速进行?nbsp;
9 对于艺术设计而言Q比如设计一个LogoQ想个Logo是什么样子,配色Q以及其内涵是一个设计过E,而将其用PhotoshopQAI或其他工L(fng)地l制出来Q只是一个施工过E?nbsp;
10 ׃不同的实现方式,对同L(fng)设计实现隑ֺ不同Q成本不同,因此好的设计师应该考虑q点?nbsp;
11 因此好的设计师应该对实施工具了解?nbsp;
12 _NPSQAIQ或者JavaQƈ不等同于好的设计师,因ؓ好的设计在于你有怎样的想法?nbsp;
13 对于软g设计而言Q一U编E语a可能比另外一U更能表现设计意图?nbsp;
14 另外对于软g设计而言Q具体的一D微观的代码Q或怸是Y件设计,而是法设计。不要一概而论?nbsp;
15 ׃有时所要设计的对象非常庞大Q比如设计故宫,或者航I天YӞ那么需要考虑程化设计过E,对设计进行管理。对设计元素q行l织。这有利于创造更好的设计Q但不是设计本n?nbsp;
16 一个优U的设计师Qƈ不一定精通你所认ؓ那个设计工具Q那个工兯么是建造工P要么是ؓ了进行设计交,而不是设计本w?nbsp;
17 同样Q一个好的设计,q不在乎你用了多么复杂的工P利用了多奇技淫yQ有时它很简单,比如Nike的标志?nbsp;
18 设计跟Q何其他的发明创造工作一P需要承和学习?nbsp;
19 M事情都有规律Q做M事情Q都有成功模式和最?jng)_c设计也一栗?nbsp;
20 我们应该q泛涉猎所有知识,以启发我们的设计?nbsp;
设计是一U思想Q而不是工兗?nbsp;
天赋Q博学,以及表达Q才能成Z个好的设计师?/span>
]]>
]]>
雉其蒙原创 转蝲h?/span>
试驱动开发不是什么噱_而是真正有用的开发实c今天派l我一个Q务,让我解决一下退休提醒功能的BugQ我没看出原来的代码有何错误Q不q觉得设计思\不十分的好:数据库中所有的员工都取出来Q然后再{选应该被提醒的员工。而我觉得应该直接到数据库中去做筛选,q回大量无用的数据是资源的巨大浪费(我们在甲方公叔R面开发,其网l之差o人发指)?/span>
正好Q我在研I?/span>TDDQ本文有时指的是试驱动开发,有时指的是测试驱动设计,因ؓ两者都是存在的Q,x何不来一ơ彻底的实践Q真正的、完整的来一?/span>TDDQ体味一下其中的乐趣?/span>
׃我们的开发工hDelphiQ因此自动测试工兯然而然p使用DUnit了。尽有的h?/span>TDD不一定非得用自动试框架Q我也在使用VBq行OOpȝ开发时Q用自制的测试程序进行测试,不过觉得那样都有一U不爽的感觉。因为总需要去l护复杂的测试代码,不能全力投入到测试驱动设计中?/span>
首先介绍一下业务:
很简单,是在一个界面上昄卛_退休的人员Q具体提前多天昄是从数据库中d的参数?/span>
然后配置DUnit环境Q网上有n多教E,然后安装了一?/span>DUnit plug-in插gQ方便开发,|上也有讲解?/span>
?/span>Stop on Delphi Exception前的对号取消Q这样就不会在出现异常时跛_了?/span>
本文是我q行TDD的实践记录,当然光的思考要比这个多一些,不过M部分基本都包含了Q而且l对写实。本文不?/span>TDD的颂歌,我也提出了自己在实践中遇到的困难和疑惑。希望能l读者带来启C?/span>
创徏工程文gHR.dprQ然后?/span>DUnit plug-inQ?/span>New ProjectQ就自动?/span>HR.dpr所在文件夹Z一?/span>dunit文g夹,新徏的测试工E默认名?/span>HRTestsQ这是很好的规范Q默认即可。然?/span>New TestModuleQ徏立一个测试单元?/span>
接下来的工作是在这两个同时开着的工E中开始工作了Q一会我会切换到HRTests~写试用例Q一会我会在HR下编写品代码,然后再回?/span>HRTests下运?/span>DunitQ进行测试?/span>
首先构徏领域层,领域概念是退休(RetiredQ,退休h员(EmployeeRetiredQ了?/span>
先创两个c,不少文章说先建立试用例Q然后测试时肯定昄U条Q因试的类q没有徏立,我觉得没建立的话q编译都q不了,怎么q行DUnit啊?
然后可以开始根据想象编写测试用例了。思考对象的责Q和工作方式,然后切换C品工E添加这些责仅R(有点像一边画序图一边画cdq行责Q分配Q?/span>
首先Q我创徏c?/span>TRetire
l?/span>TRetirecd配一个责任:查找退休提醒参敎ͼ
{ 作者:雉其蒙
创徏旉Q?/span>
Blog:blog.csdn.net/sslaowan
m.tkk7.com/sslaowan
}
function TRetire.getretireAwokeParaList: TObjectList;
var paraList:TObjectList;
begin
end;
q是一个空壻I没有实际的内容,然后切换?/span>HRTest工程Q会出现下面的对话框?/span>
?/span>HR工程做了M改动Q保存后Q都会在HRTest中有提醒?/span>
可能很多Z来没见过试用例长什么样子,下面qZ个完整的例子?/span>
{ 作者:雉其蒙
创徏旉Q?/span>
Blog:blog.csdn.net/sslaowan
m.tkk7.com/sslaowan
}
unit HRTestsTests;
interface
uses
TestFrameWork,
URetire,
Contnrs;
type
TTestRetire=class(TTestCase)
private
retire:TRetire;
retirePara:TRetirePara;
protected
procedure SetUp; override;
procedure TearDown; override;
published
procedure testGetretireAwokeParaList;
end;
implementation
function UnitTests: ITestSuite;
var
ATestSuite: TTestSuite;
begin
ATestSuite := TTestSuite.Create('Retire tests');
ATestSuite.AddTests(TTestRetire);
Result := ATestSuite;
end;
{ TTestRetire }
procedure TTestRetire.SetUp;
begin
inherited;
retire:=TRetire.Create;
retirePara:=TRetirePara.Create;
end;
procedure TTestRetire.TearDown;
begin
inherited;
retire.Free;
retirePara.Free;
end;
procedure TTestRetire.testGetretireAwokeParaList;
var paraList:TObjectList;
begin
paraList:=retire.getretireAwokeParaList;
retirePara:=TRetirePara(paraList.Items[0]);
check(retirePara._emp_type='理?/span>?/span>');
check(retirePara._sex='?span>');
check(retirePara._retireage='60');
check(retirePara._uptime='90');
retirePara:=TRetirePara(paraList.Items[1]);
check(retirePara._emp_type='理?/span>?/span>');
check(retirePara._sex='?span>');
check(retirePara._retireage='55');
check(retirePara._uptime='90');
retirePara:=TRetirePara(paraList.Items[2]);
check(retirePara._emp_type='工h');
check(retirePara._sex='?span>');
check(retirePara._retireage='60');
check(retirePara._uptime='90');
retirePara:=TRetirePara(paraList.Items[3]);
check(retirePara._emp_type='工h');
check(retirePara._sex='?span>');
check(retirePara._retireage='50');
check(retirePara._uptime='90');
end;
initialization
RegisterTest('Retire test',UnitTests);
end.
在编写测试用例时Q我发现q回的不是一个一l的表,而是二维的,我还是用了对象来报存另一l的数据Q又创徏了一个名?/span>TRetirePara的类。其属性如上面的代码所C?/span>
然后~译q行HRTestQ出?/span>DUnitQ点ȝ色的RUN按钮Q出现红条。错误是EAccessViolation?/span>
q是因ؓ没有创徏TObjectlist的实?/span>paraListQ而在testGetretireAwokeParaList中访问了它,q时切换?/span>HR工程Q在getretireAwokeParaList中添加如下代码?/span>
{ 作者:雉其蒙
创徏旉Q?/span>
Blog:blog.csdn.net/sslaowan
m.tkk7.com/sslaowan
}
function TRetire.getretireAwokeParaList: TObjectList;
var paraList:TObjectList;
begin
paraList:=TObjectList.Create;
getretireAwokeParaList:=paraList;
end;
再次试?/span>
依然昄U色Q错误是List index out of boundsQ这是因为在getretireAwokeParaListҎ(gu)中ƈ没有?/span>paraList中添加Q何对象,q时需要再?/span>getretireAwokeParaListq行重构?/span>
当有?#8220;试驱动依赖?#8221;后,想?/span>DUnit帮我思考一些内容,比如下一步应该编写什么。通过试Q我不断的清楚了自己下一步的d。但是或怸需要如此小步的前进Q如果已l有了很好的思\Q可以一下子把刚才的E序都编完,甚至把整?/span>getretireAwokeParaListҎ(gu)都完成?/span>
然而由于没有太多的planQ想一点编一点的Q难免就会采取这样小的步伐,其实q样也很好,因ؓ不至于写了一大堆Q错了都不知道哪一句是|魁R。经常看到有人在面对一堆不知道在哪个地方出错的代码Ӟ采用了删除所有,然后一句一句还原,发现到哪句错误就改哪句。这U做法一般都被用于没有调试器的环境,比如HTML面。如果有了调试器Q传l的做法当然是设|断点,然后利用调试器进行跟t。关掉调试器Q以试代替调试的一个支撑点是,不大可能会出现大D늚需要你去跟t错误的代码Q因为很的一步重构进行之后,开始测试了Q哪里有错误一目了然。当然调试器的作用ƈ不只是跟t某个变量在q行时的值的变化Q还有理解代码在汇编一U上是如何工作的Q这更加有利于你调错。但是,总而言之,调试器肯定是帮助你调试错误的Q小步前q的单元试可以帮助你在不用调试器的情况下Q找出错误?/span>
或许看到q篇文章的?zhn)Q有更好的方法来完成q样的测试,h告诉我,因ؓ我也是?/span>Dunitq行TDD的新手,希望分n(zhn)的宝贵l验?/span>
我们使用的是ODAC控gq接ORACLE数据库。在产品代码Q?/span>HR.dprQ中Q通常我们都是直接加蝲数据模块?/span>TOraSessionQ来q接数据库,再用TOraQuery与之相连?/span>getretireAwokeParaList的主要操作是从数据库中获取记录,产品代码如下Q?/span>
{ 作者:雉其蒙
创徏旉Q?/span>
Blog:blog.csdn.net/sslaowan
m.tkk7.com/sslaowan
}
function TRetire.getretireAwokeParaList: TObjectList;
var paraList:TObjectList;
retirePara:TRetirePara;
begin
paraList:=TObjectList.Create;
_qry.Close;
_qry.SQL.Clear;
_qry.SQL.Text:='select * from HR1_RETIREPARAMETER';
_qry.Open;
if _qry.RecordCount>0 then
begin
_qry.First;
while not _qry.Eof do
begin
retirePara:=TRetirePara.Create;
retirePara._emp_type:=_qry.FieldByName('EMPLOYEE_TYPE').AsString;
retirePara._sex:=_qry.FieldByName('SEX').AsString;
retirePara._retireage:=_qry.FieldByName('RETIRE_AGE').AsString;
retirePara._uptime:=_qry.FieldByName('UPTIME_DAYS').AsString;
paraList.Add(retirePara) ;
_qry.Next;
end;
end;
getretireAwokeParaList:=paraList;
end;
q比你在上一节看到的代码又丰富了许多?/span>_qry是用到的TOraQuerycd的变量,它接?/span>TOraQuery实例。由于进行单元测试时Q?/span>HR.dpr是不启动的,因此DMҎ(gu)׃会被创徏?/span>
考虑再三Q我在测试项?/span>HRTest.dpr中加入了数据库连接代码,q将创徏?/span>TOraQuery实例赋值给TRetire的属性(propertyQ?/span>qry。代码如下:
{ 作者:雉其蒙
创徏旉Q?/span>
Blog:blog.csdn.net/sslaowan
m.tkk7.com/sslaowan
}
procedure TTestRetire.SetUp;
begin
inherited;
_qry:=TOraQuery.Create(nil);
{ _session:=TOraSession.Create(nil);
_session.Server:=';
_session.ConnectString:='';
_session.Username:='';
_session.Password:='';
_session.ConnectPrompt:=false;
_session.Connect; }
_dmhr:=TDMHR.Create(nil);
_session:=TOraSession.Create(nil);
_session:= _dmhr.HRSession ;
_qry.Session:=_session;
retire:=TRetire.Create;
retire.qry:=_qry;
end;
起初Q我创徏?span>TOraSession对象Q可是不知道Z么说驱动器有错误Q于是就创徏了一?span>DMQ数据模块)Q然后获得其中的HRSession。注释掉的代码有何错误还请高人指炏V之后我又写了个试q接是否成功的方法?/span>
{ 作者:雉其蒙
创徏旉Q?/span>
Blog:blog.csdn.net/sslaowan
m.tkk7.com/sslaowan
}
procedure TTestRetire.testConnect;
begin
check(_session.Connected=true);
end;
之后再进行测试,l条l于出现了!
我在之前q犯了错误,L出现U条。后来才发现原来对象创徏没搞清楚。这个错误在我编?/span>JavaE序时也犯过Q看来一定要注意ѝ?/span>
{ 作者:雉其蒙
创徏旉Q?/span>
Blog:blog.csdn.net/sslaowan
m.tkk7.com/sslaowan
}
while not _qry.Eof do
begin
retirePara:=TRetirePara.Create;
retirePara._emp_type:=_qry.FieldByName('EMPLOYEE_TYPE').AsString;
//省略若干?/span>
paraList.Add(retirePara) ;
_qry.Next;
end;
end;
原来?/span>retirePara:=TRetirePara.Create;q句写在循环之外了,l果试Ӟ只有最后一条(paraList.Item[3]对应的结果)是正的。这个错误大家一看就知道了,每@环一ơ都需要创Z?span>retirePara对象Q要不然?span>paraListd的其实都是一个对象,?span>paraList的每个元素都是指向同一个对象引用,赋g后,当然每个对象的属性值都是一L(fng)啦?/span>
q有Q在试用例中进行比较时Q我刚开始图省事Q都使用checkQ结果错了后没有M提示Q后来到TestFramework中查CCheckEqualsҎ(gu)Q这个方法很好,如果出错了,会告诉你期望值是什么,实际值是什么?/span>
TDD韵律操是Q编写单元测试——〉测试,U条——〉编写品代码——〉绿条——〉编写单元测试——〉测试,U条——〉编写品代码——〉绿条,~写产品代码q不能L一气呵成,因此׃在编写部分品代码——〉绿条——〉重构——〉测试,l条/U条——〉重构——〉测试,l条/U条
下面开始编?/span>function retireAwoke(qry:TOraQuery):TObjectList;Ҏ(gu)。还是先写测试用例?/span>
q里面有个问题,q个Ҏ(gu)的作用是查询q返回所有的W合退休条件的员工Q每天的人可能都不一P那么该怎样写测试用例呢Q这个或许应该从DUnit的测试装备中dQ如?span>DUnit有的话,我也不知道有没有Q,也可以从一个文本文件或?span>Excel中读取,q样或许好些。但是这依然不是一个可回归试?/span>
最l想的办法是通过SQL语句先查询一下,看看有哪些记录,而且q个SQL语句与程序中的不相同。主要是计退休者生日的E序写在?/span>SQL中还是写在程序中的区别?/span>
使用如下?/span>SQL语句q行查询Q?/span>
select employeeid,employeename,dptname,birthday,sex,EMPLOYEETYPE
from HR1_EMPLOYEE left join hr1_workdept on hr1_employee.workdeptid=hr1_workdept.dptid
where sex='?/span>' and EMPLOYEETYPE='理人员'
and BIRTHDAY between '
from dual) order by dptid asc;
用于q回60岁退休的h管理h员(提前90天提醒)。以下是SQL Plus的查询结果?/span>
EMPLOYEEID EMPLOYEENA DPTNAME BIRTHDAY SE EMPLOYEETY
---------- ---------- ------------------- ---------- -- ----------
YG000043 张三?/span> 人力资源?/span>
已选择 1 ?/span>
q需要说明的是,有四U情况需要测试,但是Z快速实玎ͼ我只是写了其中一U情况,即男Q管理h员。然后我写了测试程序?/span>
{ 作者:雉其蒙
创徏旉Q?/span>
Blog:blog.csdn.net/sslaowan
m.tkk7.com/sslaowan
}
procedure TTestRetire.testRetireAwoke;
var employeeRetiredList:TObjectList;
i:integer;
begin
employeeRetiredList:=TObjectList.Create;
_employeeRetired:=TEmployeeRetired(employeeRetiredList.Items[0]);
CheckEquals(' YG000043',_employeeRetired._ID);
CheckEquals('张三?/span>',_employeeRetired._Name);
end;
产品代码只是d了参数列表的W一U情c然后嵌套进SQL语句中进行查询?/span>
{ 作者:雉其蒙
创徏旉Q?/span>
Blog:blog.csdn.net/sslaowan
m.tkk7.com/sslaowan
}
function TRetire.retireAwoke(qry:TOraQuery): TObjectList;
var employeeRetired:TEmployeeRetired;
paraList,employeesRetired:TObjectList;
retirePara:TRetirePara;
strSQL,strBirthdayUp, strBirthdayDown:string;
begin
_qry:= qry;
employeesRetired:=TObjectList.Create;
paraList:=getretireAwokeParaList;
retirePara:=TRetirePara(paraList.Items[0]);
//满条g的男理人员
dtBirthday:=EncodeDate(Yearof(date)-StrToInt(retirePara._retireage),monthof(date),DayOf(date));
strBirthdayDown:=formatdatetime('yyyy-mm-dd',dtBirthday);
strBirthdayUp:=formatdatetime('yyyy-mm-dd',dtBirthday+strtoint(retirePara._uptime));
_qry.Close;
_qry.SQL.Clear;
strSQL:='select employeeid,employeename,dptname,birthday,sex,EMPLOYEETYPE';
strSQL:=strSQL +' from HR1_EMPLOYEE left join hr1_workdept on hr1_employee.workdeptid=hr1_workdept.dptid ';
strSQL:=strSQL +' where sex=:sex';
strSQL:=strSQL +' and EMPLOYEETYPE=:EMPLOYEETYPE and BIRTHDAY between '+''''+strBirthdayDown+''''+' and '+''''+strBirthdayUp+''''; strSQL:=strSQL+'order by dptid asc';
_qry.SQL.Text :=strSQL;
_qry.ParamByName('EMPLOYEETYPE').AsString :=retirePara._emp_type;
_qry.ParamByName('SEX').AsString :=retirePara._sex;
_qry.Open;
if _qry.RecordCount>0 then
begin
while not _qry.Eof do
begin
employeeRetired:=TEmployeeRetired.Create;
employeeRetired._ID:=_qry.FieldByName('employeeid').AsString;
//以下省略若干代码
employeesRetired.Add(employeeRetired);
end;
end;
retireAwoke:=employeesRetired;
end;
很高_试通过了!不过实话实说Q也q一ơ就能做成功的,其中SQL语句写错了,查了半天,DUnit报错?/span>missing expression。我是一个这样马虎的人,很难把程序一下子写对Q有DUnit做保证,步前进Q以免陷入绝境,?/span>Bug丛生的密林中LQ当是非常痛苦的事情了?/span>
对于XPQ我觉得单设计?/span>TDD、重构、持l集成、小规模发布是连在一L(fng)实践。简单设计而没有重构,变成了Code and Fix。只有重构,而没有单元测试的保证Q无异于徒手I越原始林Q没有安全保证。单元测试,l而持l集成、小规模发布Q才能实现工作的软g。再加之l对~程Q以提高代码质量Q完全客L(fng)场,以捕h真实的需求和得到最真实的反馈,以最快速最真实的态度响应变化Q集体代码所有制Q以减少人员?gu)动的风险,和提高复用?/span>40时工作日,以避免篏d创徏更优质的代码。这是我体会到的XP实践的好处,随着实践和思考的增多Q我觉得自己来认?/span>XP的观点了?/span>
闲言叙Ql编E。l完成其余三U情况,目前而言Q就只有四种cd的员工?/span>
| 理人员 | 工h
?/span>| |
?/span>| |
从注释(//满条g的男理人员Q开始,下面每一cd都要重复一ơ代码,当然Q可以直接在其中写@环语句,但是看到Too Long Method恐惧症,大量的查询代码在这个方法,让我觉得很别扭,于是我觉得将它们重构出来Q采?/span>Extract Method?/span>
首先新徏getretireAwokeListҎ(gu)Q将从数据库中取出需要被提醒的退休h员的代码Extract到其中。然后整理局部变量?/span>
不断的编译,来帮助我查错误,是不是某些局部变量没有迁U过来,有没有变量重名等。经q一番折腾,~译通过Q运行测试?/span>
q行试Q?zhn)剧发生了Q在昄l条后死ZQ我惛_能是内存释放有问题了?/span>
l果果然如此Q下面这D늨序结束后没有释放employeeRetiredList?/span>
{ 作者:雉其蒙
创徏旉Q?/span>
Blog:blog.csdn.net/sslaowan
m.tkk7.com/sslaowan
}
procedure TTestRetire.testRetireAwoke;
var employeeRetiredList:TObjectList;
i:integer;
begin
employeeRetiredList:=TObjectList.Create;
_employeeRetired:=TEmployeeRetired(employeeRetiredList.Items[0]);
CheckEquals(' YG000046',_employeeRetired._ID);
CheckEquals('李隆?/span>',_employeeRetired._Name);
employeeRetiredList.Free;
end;
释放?/span>employeeRetiredList之后Q其中的所有对象也p着被释放了?/span>
最后一步,设计一个展C提醒的退休h员信息的FormQ然后放|一个叫?/span>sgdRetire?/span>StringGridQ这时就是通过列表循环赋值到StringGird中就可以了?/span>
{ 作者:雉其蒙
创徏旉Q?/span>
Blog:blog.csdn.net/sslaowan
m.tkk7.com/sslaowan
}
procedure TForm1.showEmployeeRetired;
var employeeRetiredList:TObjectList;
i:integer;
_employeeRetired:TEmployeeRetired;
retire:TRetire;
begin
sgdRetire.Cells[0,0]:='员工~号';
sgdRetire.Cells[1,0]:='姓名';
sgdRetire.Cells[2,0]:='部门';
sgdRetire.Cells[3,0]:='生日';
sgdRetire.Cells[4,0]:='性别';
sgdRetire.Cells[5,0]:='工种';
sgdRetire.Cells[6,0]:='距离退休天?/span>';
retire:=TRetire.Create;
employeeRetiredList:=retire.retireAwoke(qryRetire);
sgdRetire.RowCount:= employeeRetiredList.Count+1;
for i:=0 to employeeRetiredList.Count-1 do
begin
_employeeRetired:=TEmployeeRetired(employeeRetiredList.Items[i]);
sgdRetire.Cells[0,i+1]:=_employeeRetired._ID;
sgdRetire.Cells[1,i+1]:=_employeeRetired._Name;
sgdRetire.Cells[2,i+1]:=_employeeRetired._Dept;
sgdRetire.Cells[3,i+1]:=_employeeRetired._Birthday;
sgdRetire.Cells[4,i+1]:=_employeeRetired._Sex;
sgdRetire.Cells[5,i+1]:=_employeeRetired._WorkType;
sgdRetire.Cells[6,i+1]:=_employeeRetired._DaysLeft;
end;
employeeRetiredList.Free;
retire.Free;
end;
但是一定要注意Q对象的释放问题Q对象生命周期的开始到完结Q一定要好好x楚。不知道那些痴迷?/span>C/C++的开发者,是如何处理内存释N题的Q我?/span>Delphi面向对象~程l验q不多Q对我而言Q仔l分析每个对象内存是否被释放了,真的是一仉常痛苦的事情。所以还是比较喜?/span>Java那样带垃圑֛收器的语a。但是通过创徏和释攑֯象,来理解对象的生命周期意义Q对于理解对象还是很有帮助的?/span>
仔细的设计测试用例,不仅是在思考对象具有哪些责任,同时也是在思考对象如何用。先ȝ一下?/span>TDD的几点好处:
1. 可以不断地测试,以保证代码是正确的。而且在正在开发的q一D|间内Q测试是可以不断q行的,期望的结果不会有什么大的变动。(旉长了׃好说了,比如本文l出的例子。)
2. ׃有了可以重复q行的测试保障,因此可以大胆的进行重构了?/span>
3. 在编写品代码之前考虑什么情冉|正确的,而且可以~写代码边增加测试数据,q样可以有利于全面的试。据说配合测试装|,q可以由业务人员填入试数据Q这h本就更大了?/span>
4. 因此l果是骗不了人的Q当U条亮vӞ你就知道是刚刚编写的那段代码错了?/span>
5. 在编写测试用例时Q就是在思考这个对象干什么的时候,q有利于L针对接口~程的好习惯Q同时这样也p然的为对象分配了职责?/span>
6. 在编写测试用例时Q还需要考虑q个对象是如何用的Q这无疑是在编写对象的使用说明书了?/span>
7. ׃以上两点Q可以我们Z使对象或对象的方法便于测试,而降低了对象间的耦合Q减了依赖?/span>
8. q行试驱动开发,q会使得我们們于领域驱动开发,而不?/span>UI驱动或数据库驱动Q同时这也有利于各层解耦?/span>
另外有以下几点值得思考:
1. 试驱动开发提高M效率的前提是什么?毫无疑问Q从长远来看Q测试驱动开发提高了代码质量Q而Y件的成本往往从维护阶D开始(譬如我们现在正在l护的这个项目,让hƲ死Ʋ活的)。但是,我在q行TDD实践Ӟ实比直接开发花费了更多旉Q包括思考测试用例应该怎样写,比如试持久层就想了半个多小时?/span>
2. 另外Q真的要x“异常时中?#8221;功能吗?有几ơL?/span>ORA***:missing expression错误Q我真的惌个断点看看到?/span>SQL语句成什么样了。虽焉误的发生在那两三行中,或许某一个不过20行的函数中,但是我还是花费了很多旉d复测试,仔细看代码,观察到底在哪错了Q因?/span>DUnit不会告诉你是哪行错了。(或许有告诉,我不知道在哪而已Q?/span>
3. 不知道ؓ什么,我是品代码所在的文gҎ(gu)在了search path中了Q可是M遇到产品代码更新了,试代码那边d的还不是最新结果。刚开始,我写C出现那个代码已更改的提醒Q之后代码就同步了,可是后来重启了一?/span>Delphi׃行了?/span>
4. 是内存释放问题Q在试代码中和产品代码中都要考虑内存释放问题Q很烦?/span>
虽然在进?/span>TDD实践q程中,到了不挫折,不过M而言Q我觉得驱动试开发让我在~写试用例时思考对象的工作方式Q是一U渐q的思考过E。其实,我一直的~程习惯是,面对一个问题先p一两天旉思考,把各U关p都搞清楚了Q然后在两个时内一气呵成,׃思考的很清楚,因此错误也挺的?/span>Planned Design和进化式设计两种方式都让我感到获益,XP之所以叫做极限编E,其中一个极限的部分可能是其简单设计的极限Q不需要Q何的架构设计Q就Ҏ(gu)用户故事开始编E。不q我觉得我需要更多的实践TDDQ那些编写测试用例遇到的困难Q我x许每个新手都会遇刎ͼ唯有多多实践才能真正的提高?/span>
在大目和大的团队中推行TDD是有困难的?/span>
1. 首先Q?/span>TDD需要编写品代码以外的试代码Q很多程序员Z快速完成Q务(有些人的旉只够~写产品代码Q,不会愿意写测试代码的。虽然它从长q考虑会有很多好处Q但是现在的E序员有多少会想很远呢(q也跟责d有关Q?可能{到l护Ӟ我都Ch了?/span>
2. 其次Q?/span>TDD需要开发h员有很强的设计能力,在这里我讨论OO设计Q不q我发现Q在中国Q程序员?/span>OO都知之甚。更甭说q行优秀?/span>OO设计了。况且,Delphi不支持垃圑֛Ӟ习惯?#8220;拖拉?#8221;方式的程序员估计要造成很多混ؕ了?/span>
3. 最后,比如我们q样的大型项目,数百张数据表Q上千个H体Q如何进?/span>TDDQ如果我自己没做q,真是很难说服老板推荐q么qӀ虽然我坚信Q这是可行的?/span>
或许在?zhn)的项目中Q已l成功地应用?/span>TDDQ那么希望?zhn)能够分n(zhn)的l验。如果?zhn)l历的大型项目正在?/span>TDDQ那么我们所有的读者都非常感兴趣?/span>
希望本文有Q何不之处,都请与我联系Q在我的Blog上留a或给我发邮gQ?/span>sslaowan@gmail.com
Your Design Values
驱动……发现你的设计价?/span>
Rebecca J. Wirfs-Brock
There will be variations of everything forever. … Ideas don’t disappear. They change form,
they merge with other ideas. —Bob Frankston, coinventor of VisiCalc
万物永远都是变化?#8230;…思想没有消失。它们改变了形式Q它们ƈ入了其他思想?/span>—?/span>Bob FrankstonQ?/span>VisiCalc发明?/span>
Today we have design or development approaches that are, for example, responsibility driven (RDD), test driven (TDD),behavior driven (BDD), domain driven(DDD), and model driven (MDD). Not all thought leaders in software development have been “driven”—Bertrand Meyer, for example, invented Design by Contract. But whether “driven” or not, these approaches all emphasize a core set of values and principles around which practices, techniques, and tools have emerged.
今天我们拥有的设计或开发方法是QD个例子,责Q驱动Q?/span>RDDQ,试驱动Q?/span>TDDQ,行ؓ驱动Q?/span>BDDQ,领域驱动Q?/span>DDDQ和模型驱动Q?/span>MDDQ。ƈ不是软g开发中的所有领D都?#8220;驱动”的——比?/span>Bertrand Meyer发明的契U式设计。但是无论是不是“驱动”Q这些方法都了一l围l已l出现的实践、技术和工具的核心h(hun)值和准则?/span>
A thoughtful designer should be able to pick and choose among practices without losing their essence. But not all practices are congruent. After stewing in this alphabet soup for years, I’m keen on exposing the common and complementary threads that are interwoven among various design practices.
一个有思想的设计者应该能够在q些实践中挑选而不失去它们的本质。但是不是所有的实践都是适合的。在q个字母汤里煮了多年之后Q我热衷于揭CZl在各种各样的设计实践中的共通之处和补充内部?/span>
Responsibility-driven design
责Q驱动设计
So how can you integrate various practices without watering them down or muddling your thinking with too many considerations? It’s certainly easy if you have one belief system with one small set of coherent values and practices that guide your work. In 1989, Brian Wilkerson and I authored the paper “Object-Oriented Design:A Responsibility-Driven Approach.”1 For better or worse, we started the trend of tagging design approaches as “driven.” To make our point, we oversimplistically divided object design approaches into two camps: those that focus first on structure and those that focus first on action (or responsibilities or behaviors). We argued that designers who first focus only on an object’s structure fail to maximize encapsulation.
Thinking too early about structure makes it too easy for implementation details to bleed into class interfaces.
所以你怎样各U不同的实践整合h而不会由于有太多需要考虑的事将它们全都随水一起倒掉或搞׃的思想Q如果你有一个信ȝl和一套一致的价值和实践指导你工作,那么q确实很Ҏ(gu)。在1989q_我和Brian Wilkerson写了名ؓ“面向对象设计Q一U责任驱动的Ҏ(gu)”的论文。ؓ了更好或更糟Q我们开始們于给设计Ҏ(gu)贴上“驱动”的标{。ؓ了声明我们的观点Q我们过分单U的对象设计方法分Z个阵营:一zN先关注结构,而另一zN先关注动作(或责L行ؓQ。我们认为首先只是关注一个对象的l构的设计者未能实现最佳的装。过早的考虑l构会导致过早的实现细节R染到cL口?/span>
We contrasted two approaches for designing a RasterImage class that represented a rectangular grid of pixels. (We wrote this paper when raster technology was new, and at the time we both worked at Tektronix, a leading provider of graphics workstations.) With a data-first approach,we started defining our RasterImage class by declaring the image data structure and then adding methods to retrieve and set the image and query its dimensions. Voila!—a class where form and function were inextricably intertwined.
我们Ҏ(gu)两种设计表现像素的一个矩形栅格的RasterImagecȝҎ(gu)。(我们写这论文时Q光栅技术还是新的,那时我们一起在Tektronix工作Q?/span>Tektronix是图形工作站的主要提供商。)用数据先行的Ҏ(gu)Q我们开始定义我们的RasterImagec,我们声明镜像数据l构Q然后添加方法去重新得到和设|镜像,查询它的寸。瞧Q——一个外形和功能避免不了的纠~在一L(fng)cR?/span>
The pixel grid data structure wasn’t considered a private implementation detail.Next, we demonstrated how a designer could think differently about the problem by asking,“What actions could this object be responsible for?” and “What information should it share with others?” This led us to first define operations for our RasterImage class to scale and rotate the image and access pixel values. The internal image representation, which we didn’t specify until after we’d defined the interface,was considered a private detail.
q个像素栅格数据l构不是被考虑成一个私有的实现l节。下一步,我们证明一个设计者能够以不同的方式来思考问题,通过询问“q个对象对于什么动作负责QQ?#8221;?#8220;它和别h׃n什么信?#8221;。这引导我们首先定义我们?/span>RasterImagecȝ操作来测量和旋{镜像和访问像素倹{我们在定义接口之前没有指定的内部镜像表C考虑成一个私有细节?/span>
By consciously assigning most objects actionoriented responsibilities, you can design even seemingly data-centric objects to perform some actions as well as encapsulate structural details.
DESIGN |
1 0 IEEE SOFTWARE www.computer.org/software |
通过自觉地分配给大多数对象面向动作的责QQ你甚至能够设计看v来以数据Z心的对象来执行一些动作和装l构的细节。隐藏结构那些l节Ҏ(gu)改变。对于我们而言Q看h一个设计者考虑事情的顺序深q影响作ؓl果的设计——甚臛_于一个像RasterImage一L(fng)单易懂的cR引用哲学家Samuel Alexander的话Q?#8220;一个对象不是先被想像或思考然后被期望……但是׃U极的被期望Q它被想像成未来Q由于有某种意志Q它是思想?#8221;
Since those early days I’ve added the notion of role stereotypes, 2 acknowledging that not all objects are active. Information holders—objects with responsibility for maintaining data—have a place in a design, too. But encapsulating their private details is important.
自从那些早些的日子我d了角色构造型的概念,知道不是所有的对象都是有效的。信息持有者——具有持有数据责ȝ对象——也在一个设计中有一席之地。但是封装它们的U有l节是重要的?/span>
Test-driven design
试驱动设计
RDD evolved in the highly interactive world of Smalltalk development, where developers routinely designed a little, coded a little, and tested a little in short cycles. The delightful tension of cycling between imagining what an object might do, building it, and then refining your ideas and cycling through your design again can lead to deep insights. This has led agile-programming thought leaders to promote test-driven development practices. Test-driven design emphasizes deciding on an interface and then writing code to test that interface, before implementing code to make the interface pass the test.
RDD在高度互动的Smalltalk开发世界进化,在那儿开发者例行公事的设计一块Q编一段代码Q然后再很短的周期里做少许测试。在惌一个对象做什么,然后构徏它,然后_你的x之间循环的o人愉快的压力而且循环通过你再一ơ设计能够你有更深的理解。这引导敏捷开发思想的领D提倡测试驱动开发实c测试驱动设计强调动对一个接口作出决定,然后~写代码L试那个接口,在实C码之前接口通过试?/span>
In Test-Driven Development by Example, Martin Fowler claims that TDD “gives you this sense of keeping just one ball in the air at once, so you can concentrate on that ball properly and do a really good job with it.”3 With a designtest-code-reflect-refactor rhythm, good code emerges alongside well-designed interfaces.
?/span>Test-Driven Development by Example中,Martin Fowler声称TDD“l你q种保持一ơ只有一个球在空中的感觉Q所以你可以完全集中_֊在那个球上和使用它真正地做好一件事?#8221;带着一U?#8220;设计试-~码-反馈-重构”的节奏,好的代码在一旁显C出设计良好的接口?/span>
Linda Crispin explains that TDD isn’t really about testing.4 Instead, it’s a practice that gets you thinking about as many aspects of a feature as you can before you code it. With frameworks such as Fit and Fitnesse, TDD has extended beyond its initial focus on just developers to enable nontechnical people to write concrete examples of inputs and expected results in tabular form. Programmers write test fixtures that use these behavioral specifications to test the code.
Lina Crispin说明TDD其实不是关于试的。相反,它是一U你在~码之前可能考虑一个特征的许多斚w的实c用像Fit?/span>Fitnessq些框架Q?/span>TDD扩大了它最初的x范围Q那时它只关注开发者非技术h员能够在表格中写出具体的输入h和期望的l果。程序员~写试装置来测试代码,试装置使用q些行ؓ的详l规D明书?/span>
As TDD practices have grown, new variants of them, along with newer testing frameworks, have emerged. Users of jMock use mocks that mimic unimplemented behaviors to drive out an appropriate distribution of responsibilities among collaborators .5 They don’t think that it’s just about testing, either. Mocking lets you incrementally design and build software, hypothesizing and refining your ideas as you go.
作ؓ一?/span>TDD实践者已l成长,它们中的新的变体和更新的试框架已经形成?/span>jMock的用者?/span>mocks来模仿未实现的行为来完成在协作者中一个适当责Q的分配。他们不认ؓ只是试?/span>Mock让你增量的设计和构徏软gQ当你前q时假定和精g的想法?/span>
Behavior-driven design
行ؓ驱动设计
BDD is another subtle refinement of TDD. BDD proponents firmly believe that how you talk about what you’re doing influences how you work. The focus is on writing small behavior specifications to drive out the appropriate design. As Dave Astels puts it, “A major difference is vocabulary. Instead of subclassing TestCase [as you would do using an xUnit framework], you subclass Context. Instead of writing methods that start with test, you start them with should.”6 Testing, to BDD proponents, connotes verifying code after it’s built. Instead, they want to encourage incremental design by writing small specifications, then implementing code that works according to spec.
BDD?/span>TDD另一U的l微的改q?/span>BDD支持者坚定的怿你如何讨Z正在做的事情会媄响你如何工作。焦Ҏ(gu)~写的行ؓ规范来导出合适的设计。当Dave Astels提出它时Q?#8220;一个主要的不同是词汇。代替子集化TestCase [当你使用一?/span>xUnit框架?/span>]Q你子集化上下文。代替书写用测试开始的Ҏ(gu)Q你使用意愿开始?#8221;试Q对?/span>BDD的支持者,意味着在构Z后检验代码。相反,他们惌鼓励增量设计通过书写的规格说明书,然后实现按照规格说明书工作的代码?/span>
Does every method
warrant a contract?
Probably not. Methods
that don’t cause side
effects probably don’t
need contracts.
Design by Contract
契约式设?/span>
In contrast, Design by Contract (DbC) has roots in formal specifications. To specify how they expect system elements to interact, designers write contracts specifying what must be true before a module can begin (preconditions), what must be preserved during its execution (invariants), and what it guarantees to be true after it completes (postconditions). You could specify contracts for components, services, or even individual methods. However, in practice, most contracts are written at the method level because existing programming languages and tools support work at that level. Writing contracts is easier if the languages and tools you use support them and you have good examples to emulate. The Eiffel language integrates contract support into the language and runtime environment; most other object-oriented languages don’t.
与之对照的,契约式设计根植于正式的规D明。ؓ了指Z们期望系l要素如何相互作用,设计者撰写契U详l说明在一个模块开始之前什么是必须正确的(前置条gQ,在执行时什么是必须保持不变的(不变式)Q在它完成之后它保证什么是正确的。你应该详细说明lg、服务或者甚x单独的方法的契约。然而,在实践中Q更多的契约是在Ҏ(gu)U上书写的,因ؓ既有的编E语a和工h持工作在那个层面上。如果你使用的语a和工h持他们而且你有好的例子仿效那么撰写契约是更Ҏ(gu)的?/span>Eiffel语言契U支持整合进语言和运行时环境Q大多数其他的面相对象语a不具备这U支持?/span>
Before looking at Contract4J, a DbC tool for Java, I thought that specifying contracts for languages without built-in support would be clunky. However, using aspect technology, Contract4J automatically weaves aspect-specific contract tests, which are specified in method comments, into your running code. Contracts specified this way leave method code uncluttered with assertion statements and leave a nice documentation trail of how methods should be invoked.
在看?/span>Contract4JQ?/span>Java?/span>DbC工具Q之前,我认Zؓ一U语a指定契约而没有内|的支持会非常郁闷。然而,使用斚w技术?/span>Contract4J自动细节方面的契约试l入你运行的代码Q它在方法注释中被指出,被这U方式指出的契约使用断言声明使方法代码保持整z,保留一份好的文档跟t方法是如何被调用的?/span>
If you choose to, you could apply TDD practices to understand your classes’ behaviors and then add contracts to methods whose behaviors you want verified at runtime. But I suspect these two communities differ considerably in their thinking. Some TDD proponents want to discourage after-the-fact verification, which to them seems antithetical to designing for quality. But adding contracts does tighten up how you use classes, theoretically making it easier to catch errors before they propagate.
如果你做出选择Q你可以应用TDD实践理解你的cȝ行ؓ然后契U加入到你想要在q行时检验其行ؓ的方法中。但是我怀疑这两个C在他们的思想中相当不同。一?/span>TDD支持者反对事后检验,q对他们而言看v来与Z质量设计是对立的。但是增加契U加Z你如何用类Q理Z使它更容易在他们传播之前捕捉到错误?/span>
When you change your design, sometimes contracts will naturally change, too. But once your design ideas settle down, you can finalize or add contractual details. But does every method warrant a contract? Probably not. Methods that don’t cause side ef
DESIGN |
|
当你改变你的设计Q有时契U也自然的改变。但是一旦你的设计想法稳定下来,你能够最l定下来或者添加契U的l节。但是每一个方法都保证一份契U?或许不是q样的,不会D副作用的Ҏ(gu)或许不需要契U。但是我知道我的发现如果契U详l规g是它们的文档的一部分更Ҏ(gu)使用cdQ即使他们不在运行时被验证?/span>
Domain-driven design
What about incorporating DDD ideas into your design practice? According to Eric Evans, DDD isn’t a technology or methodology but “a way of thinking and a set of priorities, aimed at accelerating software projects that have to deal with complicated domains” (www.domainlanguage.com/ddd/ index.html). A central activity in DDD is searching for the language that experts use to talk about the problem and then literally reflecting that language in classes and services in a domain layer. Eric believes that, “If developers don’t realize that changing code changes the model, then their refactoring will weaken the model rather than strengthen it.” Creating a domain model is intricately tied to expressing it in working code. Domaindriven design is an active, ongoing process of expressing this domain language in code.
怎样?/span>DDDx融入到设计实践中Q按?/span>Eric Evans的说法,DDD不是一Ҏ(gu)术获Ҏ(gu)是“一U思考方式和一l优先Q目的在于促q不得不解决复杂领域的Y仉?#8221;。在DDD中一Ҏ(gu)心活动是L专家用来讨论问题的语aQ然后逐字的将那种语言反映C于领域层的类和服务中?/span>Eric怿Q?#8220;如果开发者不了解改变代码时改变模型,然后他们的重构将削弱而不是增强模型?#8221;创徏一个领域模型杂q在工作着的代码中被依靠表辑֮。领域驱动设计是一U积极的、正在进行的使用代码表示领域语言的过E?/span>
Model-driven design
模型驱动设计
In contrast, adherents of MDD (some call it model-driven engineering to avoid the Object Management Group trademarked term) first develop a platform-independent model of their system (usually in UML or a domain-specific language) before translation tools transform the model into platform-specific code. MDD practitioners strive to clearly represent system concepts and behaviors with the goal of producing an abstract model, not working code (the translation tools do that for them). This view of model building followed by transformation probably causes the great divide between MDD practitioners and other design schools—even though they share many common design values. After recently listening to and talking with several well-known MDD proponents who were discussing what constitutes well-designed classes, methods, and components, I found myself nodding in agreement with many of their design guidelines.
与之对照的,MDD的拥护者在使用转换工具模型{换成具体q_代码之前Q先开发一个他们系l的q_无关模型Q通常使用UML货一U领域规范语aQ?/span>MDD开创者们带着生一个抽象模型而非工作的代码(转换工具替他们来做生产代码的事情Q的目的Q努力清晰的表现pȝ概念和行为。模型遵循{换构建的观点或许D?/span>MDD开开创者与其他设计学派的巨大分隔——即使他们共享许多通用设计价倹{在最q听到和与许多不同的众所周知?/span>MDD支持者(他们正在讨论什么构成了良好设计的类、方法和lgQ讨Z后,我发现自己非常赞同他们的设计指导思想中的很多内容?/span>
H |
ow you design should be based on your principles and values. Although a big division exists between those who believe the act of coding is what validates the design and those who don’t, you can learn many things about good design from each. My mantra has always been, “Be open to new ideas and techniques that make me a better designer.” I side with Canadian politician Dan Miller, who proclaims, “You know, we have our differences, everybody does, honest, real differences, but I do believe strongly that we as neighbors are drawn together far more than we’re driven apart.”
如何设计Q你应该Z你的原则和h(hun)倹{尽有巨大的区别存在于那些信代码的行为是查设计的标尺的h们和他们的反对者中Q但是你从他们中能够学会许多优秀设计的东ѝ我的箴a一直是Q?#8220;善于接受新观点和技术,使我成ؓ更优U的设计师”。我和加拿大政治家站在一P他宣Uͼ“你知道,我们有不同,每个人都一P诚实的,真实的不同,但是我深信作为邻国我们的共通之处远q多于我们的区别?#8221;
References
1. R. Wirfs-Brock and B. Wilkerson, “Object-Oriented Design: A Responsibility-Driven Approach,” Proc. 1989 ACM SIGPLAN Conf.
Object-Oriented Programming, Systems, Lan?/span>
guages, and Applications (OOPSLA 89), ACM Press, 1989, pp. 71–75.
2. R. Wirfs-Brock, “Characterizing Classes,” IEEE Software, Mar./Apr. 2006, pp. 9–11.
3. M. Fowler, Test-Driven Development by Example, Addison-Wesley, 2003.
4. L. Crispin, “Driving Software Quality: How Test-Driven Development Impacts Software Quality,” IEEE Software, Nov./Dec. 2006, pp. 70–71.
5. S. Freeman et al., “Mock Roles Not Objects,” Companion to 19th Ann. ACM SIGPLAN
Conf. Object-Oriented Programming, Sys?/span>
tems, Languages, and Applications (OOPSLA 04), ACM Press, 2004, pp. 236–246; www. jmock. org/oopsla2004.pdf.
6. D. Astels, “A New Look at Test-Driven Development,” http://blog.daveastels.com/files/BDD _Intro.pdf.
Rebecca J. Wirfs-Brock is president of Wirfs-Brock Associates and an adjunct professor at Oregon Health & Science University. Contact her at rebecca@wirfs-brock.com.