Вы находитесь на странице: 1из 34

1/23/2015

XP Epsiode

EngineerNotebook:AnExtremeProgrammingEpisode
byRobertC.MartinandRobertS.Koss

Figure1
Figure2

Thisarticleisderivedfromac hapteroftheforthcomingbookAdvancedPrinciples,PatternsandProcessofSoftware
Development,RobertC.Martin,PrenticeHall,2001.Copyright2000byRobertC.Martin,allrightsreserved.

Designandprogrammingarehumanactivitiesforgetthatandallislost.

Note

BjarneStroustrup,1991

AbouttheAuthors

InordertodemonstrateXP(eXtremeProgramming)practices,BobKoss(RSK)andBobMartin
(RCM)willpairprogramasimpleapplicationwhileyouwatchlik eaflyonthewall.Wewillusetestfirst
designandalotofrefactoringtocreateourapplication.Whatfollowsisafaithfulreenactmentofa
programmingepisodethatthetwoBob'sactuallydid.
RCM:"Willyouhelpmewritealittleapplicationthatcalculatesbowlingscores?"
RSK:(Reflectstohimself:TheXPpracticeofpairprogrammingsaysthatIcan'tsayno,whenasked
tohelp.Isupposethat'sespeciallytruewhenitisyourbosswhoisasking.)"SureBob,I'dbegladto
help."
RCM:"OK,Great.WhatI'dliketodoiswriteanapplicationthatkeepstrackofabowlingleague.It
needstorecordallthegames,determinetheranksoftheteams,determinethewinnersandlosersof
eachweeklymatch,andaccuratelyscoreeachgame."
RSK:"Cool.Iusedtobeaprettygoodbowler.Thiswillbefun.Yourattledoffseveraluserstories,
whichonewouldyouliketostartwith."
RCM:"Let'sbeginwithscoringasinglegame."
RSK:"Okay.Whatdoesthatmean?Whataretheinputsandoutputsforthisstory?"
RCM:"Itseemstomethattheinputsaresimplyasequenceofthrows.Athrowisjustanintegerthat
tellshowmanypinswereknockeddownbytheball.Theoutputisthedataonastandardbowling
scorecard,asetofframespopulatedwiththepinsknockeddownbyeachthrow,andmarksdenoting
sparesandstrikes.Themostimportantnumberineachframeisthecurrentgamescore."
RSK:"Letmesketchoutalittlepictureofthisscorecardtogiveusavisualreminderofthe
requirements."(SeeFigure1.)

Figure1
RCM:"Thatguyisprettyerratic."
RSK:"Ordrunk,butitwillserveasadecentacceptancetest."
RCM:"We'llneedothers,butlet'sdealwiththatlater.Howshouldwestart?Shallwecomeupwitha
designforthesystem?"
RSK:"Well,don'thateme,butIwouldn'tmindaUMLdiagramshowingtheproblemdomainconcepts
thatwemightseefromthescorecard.Thatwillgiveussomecandidateobjectsthatwecanexplore
http://www.objectmentor.com/resources/articles/xpepisode.htm

1/34

1/23/2015

XP Epsiode

furtherincode."
RCM:(Puttingonhispowerfulobjectdesignerhat)"OK,clearlyagameobjectconsistsofasequence
oftenframes.Eachframeobjectcontainsone,two,orthreethrows."
RSK:"Greatminds.ThatwasexactlywhatIwasthinking.Letmequicklydrawthat,butifyoutell
Kent,I'lldenyit."(SeeFigure2.)

Figure2
Kent:"I'malwayswatching."
RSK:"Well,pickaclass...anyclass.Shallwestartattheendofthedependencychainandwork
backwards?Thatwillmaketestingeasier."
RCM:"Sure,whynot.Let'screateatestcasefortheThrowclass."
RSK:(Startstyping)
//TestThrow.java--------------------------------import junit.framework.*;
public class TestThrow extends TestCase
{
public TestThrow(String name)
{
super(name);
}
// public void test????
}
RSK:"DoyouhaveacluewhatthebehaviorofaThrowobjectshouldbe?"
RCM:"Itholdsthenumberofpinsknockeddownbytheplayer."
RSK:"Okay,youjustsaidinnotsomanywordsthatitdoesn'treallydoanything.Maybeweshould
comebacktoitandfocusonanobjectthatactuallyhasbehavior,insteadofonethat'sjustadata
store."
RCM:"Hmm.YoumeantheThrowclassmightnotreallyexist?"
RSK:(Startstosweat.ThisismybossI'mworkingwith.)"Well,ifitdoesn'thaveanybehavior,how
importantcanitbe?Idon'tknowifitexistsornotyet.I'djustfeelmoreproductiveifwewereworking
onanobjectthathadmorethansettersandgettersformethods.Butifyouwanttodrive..."(slidesthe
keyboardtoRCM).
RCM:"Well,let'smoveupthedependencychaintoFrameandseeifthereareanytestcaseswecan
writethatwillforceustofinishThrow."(PushesthekeyboardbacktoRSK.)
RSK:(WonderingifRCMisleadingmedownablindalleytoeducatemeorifheisreallyagreeingwith
me)"Okay,newfile,newtestcase."
//TestFrame.java-----------------------------------import junit.framework.*;
public class TestFrame extends TestCase
{
public TestFrame( String name )
{
super( name );
http://www.objectmentor.com/resources/articles/xpepisode.htm

2/34

1/23/2015

XP Epsiode

}
}

//public void test???

RCM:"OK,that'sthesecondtimewe'vetypedthat.Now,canyouthinkofanyinterestingtestcases
forFrame?"
RSK:"Aframemightprovideitsscore,thenumberofpinsoneachthrow,whethertherewasastrike
oraspare..."
RCM:"Toomuchtalk,notenoughcode.Type!"
RSK:(types)
//TestFrame.java--------------------------------import junit.framework.*;
public class TestFrame extends TestCase
{
public TestFrame( String name )
{
super( name );
}

public void testScoreNoThrows()


{
Frame f = new Frame();
assertEquals( 0, f.getScore() );
}

//Frame.java--------------------------------------public class Frame


{
public int getScore()
{
return 0;
}
}
RCM:"OK,thetestcasepasses.ButScoreisareallystupidfunction.Itwillfailifweaddathrowto
theframe.Solet'swritethetestcasethataddssomethrowsandthenchecksthescore."
//TestFrame.java--------------------------------public void testAddOneThrow()
{
Frame f = new Frame();
f.add(5);
assertEquals(5, f.getScore());
}
RCM:"Thatdoesn'tcompile.There'snoaddmethodinFrame."
RSK:"I'llbetifyoudefinethemethoditwillcompile."
RCM:
//Frame.java--------------------------------------public class Frame
{
public int getScore()
{
http://www.objectmentor.com/resources/articles/xpepisode.htm

3/34

1/23/2015

XP Epsiode

return 0;

public void add(Throw t)


{
}

RCM:(Thinkingoutloud)"Thisdoesn'tcompilebecausewehaven'twrittentheThrowclass."
RSK:"Talktome,Bob.Thetestispassinganinteger,andthemethodexpectsaThrowobject.You
can'thaveitbothways.BeforewegodowntheThrowpathagain,canyoudescribeitsbehavior?"
RCM:"Wow!Ididn'tevennoticethatIhadwrittenf.add(5).Ishouldhavewrittenf.add(newThrow(5)),
butthat'suglyashell.WhatIreallywanttowriteisf.add(5)."
RSK:"Uglyornot,let'sleaveaestheticsoutofitforthetimebeing.Canyoudescribeanybehaviorofa
Throwobjectbinaryresponse,Bob?"
RCM:"101101011010100101.Idon'tknowifthereisanybehaviorinThrowI'mbeginningtothinka
Throwisjustanint.However,wedon'tneedtoconsiderthatyet,sincewecanwriteFrame.addto
takeanint."
RSK:"ThenIthinkweshoulddothatfornootherreasonthanit'ssimple.Whenwefeelpain,wecan
dosomethingmoresophisticated."
RCM:"Agreed."
//Frame.java--------------------------------------public class Frame
{
public int getScore()
{
return 0;
}

public void add(int pins)


{
}

RCM:"OK,thiscompilesandfailsthetest.Now,let'smakethetestpass."
//Frame.java--------------------------------------public class Frame
{
public int getScore()
{
return itsScore;
}

public void add(int pins)


{
itsScore += pins;
}
private int itsScore = 0;

RCM:"Thiscompilesandpassesthetests.Butit'sclearlysimplistic.What'sthenexttestcase?"
RSK:"Canwetakeabreakfirst?"

http://www.objectmentor.com/resources/articles/xpepisode.htm

4/34

1/23/2015

XP Epsiode

RCM:"That'sbetter.Frame.addisafragilefunction.Whatifyoucallitwithan11?"
RSK:"Itcanthrowanexceptionifthathappens.Butwhoiscallingit?Isthisgoingtobean
applicationframeworkthatthousandsofpeoplewilluseandwehavetoprotectagainstsuchthings,or
isthisgoingtobeusedbyyouandonlyyou?Ifthelatter,justdon'tcallitwithan11"(chuckle).
RCM:"Goodpoint,thetestsintherestofthesystemwillcatchaninvalidargument.Ifweruninto
trouble,wecanputthecheckinlater.So,theaddfunctiondoesn'tcurrentlyhandlestrikesorspares.
Let'swriteatestcasethatexpressesthat."
RSK:"Hmmmm...ifwecalladd(10)torepresentastrike,whatshouldgetScorereturn?Idon'tknow
howtowritetheassertion,somaybewe'reaskingthewrongquestion.Orwe'reaskingtheright
questiontothewrongobject."
RCM:"Whenyoucalladd(10),oradd(3)followedbyadd(7),thencallinggetScoreontheFrameis
meaningless.Theframewouldhavetolookaheadatlaterframestocalculateitsscore.Ifthoselater
framesdon'texist,thenitwouldhavetoreturnsomethinguglylike1.Idon'twanttoreturn1."
RSK:"Yeah,Ihatethe1ideatoo.You'veintroducedtheideaofframesknowingaboutotherframes.
Whoisholdingthesedifferentframeobjects?"
RCM:"TheGameobject."
RSK:"SoGamedependsonFrame,andFrameinturndependsonGame.Ihatethat."
RCM:"Framesdon'thavetodependuponGametheycouldbearrangedinalinkedlist.Eachframe
couldholdpointerstoitsnextandpreviousframes.Togetthescorefromaframe,theframewould
lookbackwardstogetthescoreofthepreviousframeandlookforwardsforanyspareorstrikeballsit
needs."
RSK:"Okay,I'mfeelingkindofdumbbecauseIcan'tvisualizethis.Showmesomecode,boss."
RCM:"Right.So,weneedatestcasefirst."
RSK:"ForGameoranothertestforFrame?"
RCM:"IthinkweneedoneforGame,sinceit'sGamethatwillbuildtheframesandhookthemupto
eachother."
RSK:"Doyouwanttostopwhatwe'redoingonFrameanddoamentallongjumptoGame,ordo
youjustwanttohaveaMockGameobjectthatdoesjustwhatweneedtogetFrameworking?"
RCM:"No,let'sstopworkingonFrameandstartworkingonGame.ThetestcasesinGameshould
provethatweneedthelinkedlistofFrames."
RSK:"I'mnotsurehowthey'llshowtheneedforthelist.Ineedcode."
RCM:
//TestGame.java-----------------------------------------import junit.framework.*;
public class TestGame extends TestCase
{
public TestGame(String name)
{
super(name);
}
public void testOneThrow()
{
Game g = new Game();
g.add(5);
http://www.objectmentor.com/resources/articles/xpepisode.htm

5/34

1/23/2015

XP Epsiode

assertEquals(5, g.score());

RCM:"Doesthatlookreasonable?"
RSK:"Sure,butI'mstilllookingforproofforthislistofFrames."
RCM:"Metoo.Let'skeepfollowingthesetestcasesandseewheretheylead."
//Game.java---------------------------------public class Game
{
public int score()
{
return 0;
}

public void add(int pins)


{
}

RCM:"OK,thiscompilesandfailsthetest.Nowlet'smakeitpass."
//Game.java---------------------------------public class Game
{
public int score()
{
return itsScore;
}

public void add(int pins)


{
itsScore += pins;
}
private int itsScore = 0;

RCM:"Thispasses.Good."
RSK:"Ican'tdisagreewithit.ButI'mstilllookingforthisgreatproofoftheneedforalinkedlistof
frameobjects.That'swhatledustoGameinthefirstplace."
RCM:"Yeah,that'swhatI'mlookingfortoo.Ifullyexpectthatoncewestartinjectingspareandstrike
testcases,we'llhavetobuildframesandtiethemtogetherinalinkedlist.ButIdon'twanttobuildthat
untilthecodeforcesusto."
RSK:"Goodpoint.Let'skeepgoinginsmallstepsonGame.Whataboutanothertestthatteststwo
throwsbutwithnospare?"
RCM:"OK,thatshouldpassrightnow.Let'stryit."
//TestGame.java-----------------------------------------public void testTwoThrowsNoMark()
{
Game g = new Game();
g.add(5);
g.add(4);
assertEquals(9, g.score());
}
http://www.objectmentor.com/resources/articles/xpepisode.htm

6/34

1/23/2015

XP Epsiode

RCM:"Yep,thatonepasses.Nowlet'stryfourballs,withnomarks."
RSK:"Well,thatwillpasstoo.Ididn'texpectthis.Wecankeepaddingthrows,andwedon'tevereven
needaFrame.Butwehaven'tdoneaspareorastrikeyet.Maybethat'swhenwe'llhavetomake
one."
RCM:"That'swhatI'mcountingon.However,considerthistestcase:"
//TestGame.java-----------------------------------------public void testFourThrowsNoMark()
{
Game g = new Game();
g.add(5);
g.add(4);
g.add(7);
g.add(2);
assertEquals(18, g.score());
assertEquals(9, g.scoreForFrame(1));
assertEquals(18, g.scoreForFrame(2));
}
RCM:"Doesthislookreasonable?"
RSK:"Itsuredoes.Iforgotthatwehavetobeabletoshowthescoreineachframe.Ah,oursketchof
thescorecardwasservingasacoasterformyDietCoke.Yeah,that'swhyIforgot."
RCM:(Sigh)"OK,firstlet'smakethistestcasefailbyaddingthescoreForFramemethodtoGame."
//Game.java---------------------------------public int scoreForFrame(int frame)
{
return 0;
}
RCM:"Great,thiscompilesandfails.Now,howdowemakeitpass?"
RSK:"Wecanstartmakingframeobjects.Butisthatthesimplestthingthatwillgetthetestto
pass?"
RCM:"No,actually,wecouldjustcreateanarrayofintegersinGame.Eachcalltoaddwould
appendanewintegerontothearray.EachcalltoscoreForFramewilljustworkforwardthroughthe
arrayandcalculatethescore."
//Game.java---------------------------------public class Game
{
public int score()
{
return itsScore;
}
public void add(int pins)
{
itsThrows[itsCurrentThrow++]=pins;
itsScore += pins;
}
public int scoreForFrame(int frame)
{
int score = 0;
for ( int ball = 0;
frame > 0 && (ball < itsCurrentThrow);
http://www.objectmentor.com/resources/articles/xpepisode.htm

7/34

1/23/2015

XP Epsiode

ball+=2, frame--)

score += itsThrows[ball] + itsThrows[ball+1];


}
return score;

}
private int itsScore = 0;
private int[] itsThrows = new int[21];
private int itsCurrentThrow = 0;

RCM:(Verysatisfiedwithhimself)"There,thatworks."
RSK:"Whythemagicnumber21?"
RCM:"That'sthemaximumpossiblenumberofthrowsinagame."
RSK:"Yuck.Letmeguess,inyouryouthyouwereaUnixhackerandpridedyourselfonwritingan
entireapplicationinonestatementthatnobodyelsecoulddecipher.
"scoreForFrameneedstoberefactoredtobemorecommunicative.Butbeforeweconsider
refactoring,letmeaskanotherquestion:IsGamethebestplaceforthismethod?Inmymind,Game
isviolatingBertrandMeyer'sSRP(SingleResponsibilityPrinciple)[1].Itisacceptingthrowsandit
knowshowtoscoreforeachframe.WhatwouldyouthinkaboutaScorerobject?"
RCM:(Makesarudeoscillatinggesturewithhishand)"Idon'tknowwherethefunctionslivenowright
nowI'minterestedingettingthescoringstufftowork.Oncewe'vegotthatallinplace,thenwecan
debatethevaluesoftheSRP.
"However,IseeyourpointabouttheUnixhackerstufflet'strytosimplifythatloop."
public int scoreForFrame(int theFrame)
{
int ball = 0;
int score=0;
for (int currentFrame = 0;
currentFrame < theFrame;
currentFrame++)
{
score += itsThrows[ball++] + itsThrows[ball++];
}
}

return score;

RCM:"That'salittlebetter,buttherearesideeffectsinthescore+=expression.Theydon'tmatter
herebecauseitdoesn'tmatterwhichorderthetwoaddendexpressionsareevaluatedin."(Ordoesit?
It'spossiblethatthetwoincrementscouldbedonebeforeeitherarrayoperations.)
RSK:"Isupposewecoulddoanexperimenttoverifythattherearen'tanysideeffects,butthat
functionisn'tgoingtoworkwithsparesandstrikes.Shouldwekeeptryingtomakeitmorereadableor
shouldwepushfurtheronitsfunctionality?"
RCM:"Theexperimentwouldonlyhavemeaningoncertaincompilers.Othercompilersmightuse
differentevaluationorders.Let'sgetridoftheorderdependencyandthenpushonwithmoretest
cases."
public int scoreForFrame(int theFrame)
{
int ball = 0;
int score=0;
for (int currentFrame = 0;
currentFrame < theFrame;
currentFrame++)
http://www.objectmentor.com/resources/articles/xpepisode.htm

8/34

1/23/2015

XP Epsiode

}
}

int firstThrow = itsThrows[ball++];


int secondThrow = itsThrows[ball++];
score += firstThrow + secondThrow;

return score;

RCM:"OK,nexttestcase.Let'stryaspare."
public void testSimpleSpare()
{
Game g = new Game();
}
RCM:"I'mtiredofwritingthis.Let'srefactorthetestandputthecreationofthegameinasetUp
function."
//TestGame.java-----------------------------------------import junit.framework.*;
public class TestGame extends TestCase
{
public TestGame(String name)
{
super(name);
}
private Game g;
public void setUp()
{
g = new Game();
}
public void testOneThrow()
{
g.add(5);
assertEquals(5, g.score());
}
public void testTwoThrowsNoMark()
{
g.add(5);
g.add(4);
assertEquals(9, g.score());
}
public void testFourThrowsNoMark()
{
g.add(5);
g.add(4);
g.add(7);
g.add(2);
assertEquals(18, g.score());
assertEquals(9, g.scoreForFrame(1));
assertEquals(18, g.scoreForFrame(2));
}

public void testSimpleSpare()


{
}

http://www.objectmentor.com/resources/articles/xpepisode.htm

9/34

1/23/2015

XP Epsiode

RCM:"That'sbetter,nowlet'swritethesparetestcase."
RSK:"I'lldrive."
public int scoreForFrame(int theFrame)
{
int ball = 0;
int score=0;
for (int currentFrame = 0;
currentFrame < theFrame;
currentFrame++)
{
int firstThrow = itsThrows[ball++];
int secondThrow = itsThrows[ball++];

}
}

int frameScore = firstThrow + secondThrow;


// spare needs next frames first throw
if ( frameScore == 10 )
score += frameScore + itsThrows[ball++];
else
score += frameScore;

return score;

RCM:(Grabbingthekeyboard)"OK,butIthinktheincrementofballintheframeScore==10case
shouldn'tbethere.Here'satestcasethatprovesmypoint."
public void testSimpleFrameAfterSpare()
{
g.add(3);
g.add(7);
g.add(3);
g.add(2);
assertEquals(13, g.scoreForFrame(1));
assertEquals(18, g.score());
}
RCM:"Ha!See,thatfails.Nowifwejusttakeoutthatpeskyextraincrement..."
if ( frameScore == 10 )
score += frameScore + itsThrows[ball];
RCM:"Uh,itstillfails....Coulditbethatthescoremethodiswrong?I'lltestthatbychangingthetest
casetousescoreForFrame(2)."
public void testSimpleFrameAfterSpare()
{
g.add(3);
g.add(7);
g.add(3);
g.add(2);
assertEquals(13, g.scoreForFrame(1));
assertEquals(18, g.scoreForFrame(2));
}

RCM:"Hmmmm....Thatpasses.Thescoremethodmustbemessedup.Let'slookatit."
public int score()
{
return itsScore;
}
http://www.objectmentor.com/resources/articles/xpepisode.htm

10/34

1/23/2015

XP Epsiode

public void add(int pins)


{
itsThrows[itsCurrentThrow++]=pins;
itsScore += pins;
}
RCM:"Yeah,that'swrong.Thescoremethodisjustreturningthesumofthepins,nottheproper
score.WhatweneedscoretodoiscallscoreForFramewiththecurrentframe."
RSK:"Wedon'tknowwhatthecurrentframeis.Let'saddthatmessagetoeachofourcurrenttests,
oneatatime,ofcourse."
RCM:"Right."
//TestGame.java-----------------------------------------public void testOneThrow()
{
g.add(5);
assertEquals(5, g.score());
assertEquals(1, g.getCurrentFrame());
}
//Game.java---------------------------------public int getCurrentFrame()
{
return 1;
}
RCM:"OK,thatworks.Butit'sstupid.Let'sdothenexttestcase."
public void testTwoThrowsNoMark()
{
g.add(5);
g.add(4);
assertEquals(9, g.score());
assertEquals(1, g.getCurrentFrame());
}
RCM:"Thatone'suninterestinglet'strythenext."
public void testFourThrowsNoMark()
{
g.add(5);
g.add(4);
g.add(7);
g.add(2);
assertEquals(18, g.score());
assertEquals(9, g.scoreForFrame(1));
assertEquals(18, g.scoreForFrame(2));
assertEquals(2, g.getCurrentFrame());
}
RCM:"Thisonefails.Nowlet'smakeitpass."
RSK:"Ithinkthealgorithmistrivial.Justdividethenumberofthrowsbytwo,sincetherearetwo
throwsperframe.Unlesswehaveastrike...butwedon'thavestrikesyet,solet'signorethemhere
too."
RCM:(Flailsaroundaddingandsubtractingoneuntilitworks)
public int getCurrentFrame()
{
http://www.objectmentor.com/resources/articles/xpepisode.htm

11/34

1/23/2015

XP Epsiode

return 1 + (itsCurrentThrow-1)/2;

RCM:"Thatisn'tverysatisfying."
RSK:"Whatifwedon'tcalculateiteachtime?WhatifweadjustacurrentFramemembervariable
aftereachthrow?"
RCM:"OK,let'strythat."
//Game.java---------------------------------public int getCurrentFrame()
{
return itsCurrentFrame;
}
public void add(int pins)
{
itsThrows[itsCurrentThrow++]=pins;
itsScore += pins;
if (firstThrow == true)
{
firstThrow = false;
itsCurrentFrame++;
}
else
{
firstThrow=true;;
}
}

private int itsCurrentFrame = 0;


private boolean firstThrow = true;

RCM:"OK,thisworks.Butitalsoimpliesthatthecurrentframeistheframeofthelastballthrown,
nottheframethatthenextballwillbethrowninto.Aslongaswerememberthat,we'llbefine."
RSK:"Idon'thavethatgoodofamemory,solet'smakeitmorereadable.Butbeforewegoscrewing
aroundwithitsomemore,let'spullthatcodeoutofaddandputitinaprivatememberfunctioncalled
adjustCurrentFrameorsomething."
RCM:"OK,thatsoundsgood"
public void add(int pins)
{
itsThrows[itsCurrentThrow++]=pins;
itsScore += pins;
adjustCurrentFrame();
}
private void adjustCurrentFrame()
{
if (firstThrow == true)
{
firstThrow = false;
itsCurrentFrame++;
}
else
{
firstThrow=true;;
}
}
http://www.objectmentor.com/resources/articles/xpepisode.htm

12/34

1/23/2015

XP Epsiode

RCM:"Nowlet'schangethevariableandfunctionnamestobemoreclear.Whatshouldwecall
itsCurrentFrame?"
RSK:"Ikindoflikethatname.Idon'tthinkwe'reincrementingitintherightplacethough.Thecurrent
frame,tome,istheframenumberthatI'mthrowingin.Soitshouldgetincrementedrightafterthelast
throwinaframe."
RCM:"Iagree.Let'schangethetestcasestoreflectthatthenwe'llfixadjustCurrentFrame."
//TestGame.java-----------------------------------------public void testTwoThrowsNoMark()
{
g.add(5);
g.add(4);
assertEquals(9, g.score());
assertEquals(2, g.getCurrentFrame());
}
public void testFourThrowsNoMark()
{
g.add(5);
g.add(4);
g.add(7);
g.add(2);
assertEquals(18, g.score());
assertEquals(9, g.scoreForFrame(1));
assertEquals(18, g.scoreForFrame(2));
assertEquals(3, g.getCurrentFrame());
}
//TestGame.java-----------------------------------------private void adjustCurrentFrame()
{
if (firstThrow == true)
{
firstThrow = false;
}
else
{
firstThrow=true;
itsCurrentFrame++;
}
}
}

private int itsCurrentFrame = 1;

RCM:"OK,that'sworking.Nowlet'stestgetCurrentFrameinthetwosparecases."
public void testSimpleSpare()
{
g.add(3);
g.add(7);
g.add(3);
assertEquals(13, g.scoreForFrame(1));
assertEquals(2, g.getCurrentFrame());
}
public void testSimpleFrameAfterSpare()
{
g.add(3);
g.add(7);
g.add(3);
g.add(2);
assertEquals(13, g.scoreForFrame(1));
http://www.objectmentor.com/resources/articles/xpepisode.htm

13/34

1/23/2015

XP Epsiode

assertEquals(18, g.scoreForFrame(2));
assertEquals(3, g.getCurrentFrame());

RCM:"Thisworks.Now,backtotheoriginalproblem.Weneedscoretowork.Wecannowwrite
scoretocallscoreForFrame(getCurrentFrame()1).
public void testSimpleFrameAfterSpare()
{
g.add(3);
g.add(7);
g.add(3);
g.add(2);
assertEquals(13, g.scoreForFrame(1));
assertEquals(18, g.scoreForFrame(2));
assertEquals(18, g.score());
assertEquals(3, g.getCurrentFrame());
}
//Game.java---------------------------------public int score()
{
return scoreForFrame(getCurrentFrame()-1);
}
RCM:"ThisfailstheTestOneThrowtestcase.Let'slookatit."
public void testOneThrow()
{
g.add(5);
assertEquals(5, g.score());
assertEquals(1, g.getCurrentFrame());
}
RCM:"Withonlyonethrow,thefirstframeisincomplete.Thescoremethodiscalling
scoreForFrame(0).Thisisyucky."
RSK:"Maybe,maybenot.Whoarewewritingthisprogramfor,andwhoisgoingtobecallingscore?
Isitreasonabletoassumethatitwon'tgetcalledonanincompleteframe?"
RCM:"Yeah.Butitbothersme.Togetaroundthis,wehavetakethescoreoutofthetestOneThrow
testcase.Isthatwhatwewanttodo?"
RSK:"Wecould.WecouldeveneliminatetheentiretestOneThrowtestcase.Itwasusedtorampus
uptothetestcasesofinterest.Doesitreallyserveausefulpurposenow?Westillhavecoverageinall
oftheothertestcases."
RCM:"Yeah,Iseeyourpoint.OK,outitgoes."(Editscode,runstest,andgetsgreenbar.)"Ahhh,
that'sbetter.
"Now,we'dbetterworkonthestriketestcase.Afterall,wewanttoseeallthoseFrameobjectsbuilt
intoalinkedlist,don'twe?"(snicker).
public void testSimpleStrike()
{
g.add(10);
g.add(3);
g.add(6);
assertEquals(19, g.scoreForFrame(1));
assertEquals(28, g.score());
assertEquals(3, g.getCurrentFrame());
http://www.objectmentor.com/resources/articles/xpepisode.htm

14/34

1/23/2015

XP Epsiode

}
RCM:"OK,thiscompilesandfailsaspredicted.Nowweneedtomakeitpass."
//Game.java---------------------------------public class Game
{
public void add(int pins)
{
itsThrows[itsCurrentThrow++]=pins;
itsScore += pins;
adjustCurrentFrame(pins);
}
private void adjustCurrentFrame(int pins)
{
if (firstThrow == true)
{
if( pins == 10 ) // strike
itsCurrentFrame++;
else
firstThrow = false;
}
else
{
firstThrow=true;
itsCurrentFrame++;
}
}
public int scoreForFrame(int theFrame)
{
int ball = 0;
int score=0;
for (int currentFrame = 0;
currentFrame < theFrame;
currentFrame++)
{
int firstThrow = itsThrows[ball++];
if (firstThrow == 10)
{
score += 10 + itsThrows[ball] + itsThrows[ball+1];
}
else
{
int secondThrow = itsThrows[ball++];

int frameScore = firstThrow + secondThrow;


// spare needs next frames first throw
if ( frameScore == 10 )
score += frameScore + itsThrows[ball];
else
score += frameScore;

}
return score;
}
private int itsScore = 0;
private int[] itsThrows = new int[21];
private int itsCurrentThrow = 0;
private int itsCurrentFrame = 1;
http://www.objectmentor.com/resources/articles/xpepisode.htm

15/34

1/23/2015

XP Epsiode

private boolean firstThrow = true;

RCM:"OK,thatwasn'ttoohard.Let'sseeifitcanscoreaperfectgame."
public void testPerfectGame()
{
for (int i=0; i<12; i++)
{
g.add(10);
}
assertEquals(300, g.score());
assertEquals(10, g.getCurrentFrame());
}
RCM:"Urg,it'ssayingthescoreis330.Whywouldthatbe?"
RSK:"Becausethecurrentframeisgettingincrementedallthewayto12."
RCM:"Oh!Weneedtolimititto10."
private void adjustCurrentFrame(int pins)
{
if (firstThrow == true)
{
if( pins == 10 ) // strike
itsCurrentFrame++;
else
firstThrow = false;
}
else
{
firstThrow=true;
itsCurrentFrame++;
}
itsCurrentFrame = Math.min(10, itsCurrentFrame);
}
RCM:"Damn,nowit'ssayingthatthescoreis270.What'sgoingon?"

RSK:"Bob,thescorefunctionissubtractingonefromgetCurrentFrame,soit'sgivingyouthescore
forframe9,not10."
RCM:What?YoumeanIshouldlimitthecurrentframeto11not10?I'lltryit.
itsCurrentFrame=Math.min(11,itsCurrentFrame)
RCM:"OK,sonowitgetsthescorecorrect,butfailsbecausethecurrentframeis11andnot10.Ick!
thiscurrentframethingisapaininthebutt.Wewantthecurrentframetobetheframetheplayeris
throwinginto,butwhatdoesthatmeanattheendofthegame?"
RSK:"Maybeweshouldgobacktotheideathatthecurrentframeistheframeofthelastballthrown."
RCM:"Ormaybeweneedtocomeupwiththeconceptofthelastcompletedframe?Afterall,the
scoreofthegameatanypointintimeisthescoreinthelastcompletedframe."
RSK:"Acompletedframeisaframethatyoucanwritethescoreinto,right?"
RCM:"Yes,aframewithaspareinitcompletesafterthenextball.Aframewithastrikeinit
completesafterthenexttwoballs.Aframewithnomarkcompletesafterthesecondballintheframe.
http://www.objectmentor.com/resources/articles/xpepisode.htm

16/34

1/23/2015

XP Epsiode

"Waitaminute....Wearetryingtogetthescoremethodtowork,right?Allweneedtodoisforce
scoretocallscoreForFrame(10)ifthegameiscomplete."
RSK:"Howdoweknowifthegameiscomplete?"
RCM:"IfadjustCurrentFrameevertriestoincrementitsCurrentFramepastthe10thframe,thenthe
gameiscomplete."
RSK:"Wait.AllyouaresayingisthatifgetCurrentFramereturns11,thegameiscompletethat's
thewaythecodeworksnow!"
RCM:"Hmm.Youmeanweshouldchangethetestcasetomatchthecode?"
public void testPerfectGame()
{
for (int i=0; i<12; i++)
{
g.add(10);
}
assertEquals(300, g.score());
assertEquals(11, g.getCurrentFrame());
}
RCM:"Well,thatworks.Isupposeit'snoworsethangetMonthreturningzeroforJanuary,butIstill
feeluneasyaboutit."
RSK:"Maybesomethingwilloccurrtouslater.Rightnow,IthinkIseeabug.MayI?"(Grabs
keyboard.)
public void testEndOfArray()
{
for (int i=0; i<9; i++)
{
g.add(0);
g.add(0);
}
g.add(2);
g.add(8); // 10th frame spare
g.add(10); // Strike in last position of array.
assertEquals(20, g.score());
}
RSK:"Hmm.Thatdoesn'tfail.Ithoughtsincethe21stpositionofthearraywasastrike,thescorer
wouldtrytoaddthe22ndand23rdpositionstothescore.ButIguessnot."
RCM:"Hmm,youarestillthinkingaboutthatscorerobject,aren'tyou?Anyway,Iseewhatyouwere
gettingat,butsincescorenevercallsscoreForFramewithanumberlargerthan10,thelaststrikeis
notactuallycountedasastrike.It'sjustcountedata10tocompletethelastspare.Weneverwalk
beyondtheendofthearray."
RSK:"OK,let'spumpouroriginalscorecardintotheprogram."
public void testSampleGame()
{
g.add(1);
g.add(4);
g.add(4);
g.add(5);
g.add(6);
g.add(4);
g.add(5);
g.add(5);
http://www.objectmentor.com/resources/articles/xpepisode.htm

17/34

1/23/2015

XP Epsiode

g.add(10);
g.add(0);
g.add(1);
g.add(7);
g.add(3);
g.add(6);
g.add(4);
g.add(10);
g.add(2);
g.add(8);
g.add(6);
assertEquals(133, g.score());

RSK:"Well,thatworks.Arethereanyothertestcasesthatyoucanthinkof?"
RCM:"Yeah,let'stestafewmoreboundaryconditions.Howaboutthepoorschmuckwhothrows11
strikesandthenafinal9."
public void testHeartBreak()
{
for (int i=0; i<11; i++)
g.add(10);
g.add(9);
assertEquals(299, g.score());
}
RCM:"Thatworks.OK,howabouta10thframespare?"

public void testTenthFrameSpare()


{
for (int i=0; i<9; i++)
g.add(10);
g.add(9);
g.add(1);
g.add(1);
assertEquals(270, g.score());
}

RCM:(Staringhappilyatthegreenbar)"Thatworkstoo.Ican'tthinkofanymore,canyou."
RSK:"No,Ithinkwe'vecoveredthemall.BesidesIreallywanttorefactorthismess.Istillseethe
scorerobjectintheresomewhere."
RCM:"OK,well,thescoreForFramefunctionisprettymessy.Let'sconsiderit."
public int scoreForFrame(int theFrame)
{
int ball = 0;
int score=0;
for (int currentFrame = 0;
currentFrame < theFrame;
currentFrame++)
{
int firstThrow = itsThrows[ball++];
if (firstThrow == 10)
{
score += 10 + itsThrows[ball] + itsThrows[ball+1];
}
else
{
http://www.objectmentor.com/resources/articles/xpepisode.htm

18/34

1/23/2015

XP Epsiode

int secondThrow = itsThrows[ball++];

int frameScore = firstThrow + secondThrow;


// spare needs next frames first throw
if ( frameScore == 10 )
score += frameScore + itsThrows[ball];
else
score += frameScore;

}
}

return score;

RCM:"I'dreallyliketoextractthebodyofthatelseclauseintoaseperatefunctionnamed
handleSecondThrow,butIcan'tbecauseitusesball,firstThrow,andsecondThrowlocal
variables."
RSK:"Wecouldturnthoselocalsintomembervariables."
RCM:"Yeah,thatkindofreinforcesyournotionthatwe'llbeabletopullthescoringoutintoitsown
scorerobject.OK,let'sgivethatatry."
RSK:(Grabskeyboard.)
private void adjustCurrentFrame(int pins)
{
if (firstThrowInFrame == true)
{
if( pins == 10 ) // strike
itsCurrentFrame++;
else
firstThrowInFrame = false;
}
else
{
firstThrowInFrame=true;
itsCurrentFrame++;
}
itsCurrentFrame = Math.min(11, itsCurrentFrame);
}
public int scoreForFrame(int theFrame)
{
ball = 0;
int score=0;
for (int currentFrame = 0;
currentFrame < theFrame;
currentFrame++)
{
firstThrow = itsThrows[ball++];
if (firstThrow == 10)
{
score += 10 + itsThrows[ball] + itsThrows[ball+1];
}
else
{
secondThrow = itsThrows[ball++];
int frameScore = firstThrow + secondThrow;
// spare needs next frames first throw
if ( frameScore == 10 )
http://www.objectmentor.com/resources/articles/xpepisode.htm

19/34

1/23/2015

XP Epsiode

score += frameScore + itsThrows[ball];


else
score += frameScore;

return score;
}
private int ball;
private int firstThrow;
private int secondThrow;
private int itsScore = 0;
private int[] itsThrows = new int[21];
private int itsCurrentThrow = 0;
private int itsCurrentFrame = 1;
private boolean firstThrowInFrame = true;
RSK:"Ihadn'texpectedthenamecollision.WealreadyhadaninstancevariablenamedfirstThrow.
ButitisbetternamedfirstThrowInFrame.Anyway,thisworksnow.Sowecanpulltheelseclause
outintoitsownfunction."
public int scoreForFrame(int theFrame)
{
ball = 0;
int score=0;
for (int currentFrame = 0;
currentFrame < theFrame;
currentFrame++)
{
firstThrow = itsThrows[ball++];
if (firstThrow == 10)
{
score += 10 + itsThrows[ball] + itsThrows[ball+1];
}
else
{
score += handleSecondThrow();
}
}
}

return score;

private int handleSecondThrow()


{
int score = 0;
secondThrow = itsThrows[ball++];

int frameScore = firstThrow + secondThrow;


// spare needs next frames first throw
if ( frameScore == 10 )
score += frameScore + itsThrows[ball];
else
score += frameScore;
return score;

RCM:"LookatthestructureofscoreForFrame!Inpseudocode,itlookssomethinglikethis:"
if strike
score += 10 + nextTwoBalls();
else
http://www.objectmentor.com/resources/articles/xpepisode.htm

20/34

1/23/2015

XP Epsiode

handleSecondThrow.
RCM:"Whatifwechangeditto:"
if strike
score += 10 + nextTwoBalls();
else if spare
score += 10 + nextBall();
else
score += twoBallsInFrame()
RSK:"Geez!That'sprettymuchtherulesforscoringbowlingisn'tit?OK,let'sseeifwecangetthat
structureintherealfunction.Firstlet'schangethewaytheballvariableisbeingincrementedsothat
thethreecasesmanipulateitindependently."
public int scoreForFrame(int theFrame)
{
ball = 0;
int score=0;
for (int currentFrame = 0;
currentFrame < theFrame;
currentFrame++)
{
firstThrow = itsThrows[ball];
if (firstThrow == 10)
{
ball++;
score += 10 + itsThrows[ball] + itsThrows[ball+1];
}
else
{
score += handleSecondThrow();
}
}
}

return score;

private int handleSecondThrow()


{
int score = 0;
secondThrow = itsThrows[ball+1];

int frameScore = firstThrow + secondThrow;


// spare needs next frames first throw
if ( frameScore == 10 )
{
ball+=2;
score += frameScore + itsThrows[ball];
}
else
{
ball+=2;
score += frameScore;
}
return score;

RCM:(Grabskeyboard.)"OK,nowlet'sgetridofthefirstThrowandsecondThrowvariablesand
replacethemwithappropriatefunctions."
public int scoreForFrame(int theFrame)
{
http://www.objectmentor.com/resources/articles/xpepisode.htm

21/34

1/23/2015

XP Epsiode

ball = 0;
int score=0;
for (int currentFrame = 0;
currentFrame < theFrame;
currentFrame++)
{
firstThrow = itsThrows[ball];
if (strike())
{
ball++;
score += 10 + nextTwoBalls();
}
else
{
score += handleSecondThrow();
}
}
}

return score;

private boolean strike()


{
return itsThrows[ball] == 10;
}
private int nextTwoBalls()
{
return itsThrows[ball] + itsThrows[ball+1];
}
RCM:"Thatstepworkslet'skeepgoing."
private int handleSecondThrow()
{
int score = 0;
secondThrow = itsThrows[ball+1];

int frameScore = firstThrow + secondThrow;


// spare needs next frames first throw
if ( spare() )
{
ball+=2;
score += 10 + nextBall();
}
else
{
ball+=2;
score += frameScore;
}
return score;

private boolean spare()


{
return (itsThrows[ball] + itsThrows[ball+1]) == 10;
}
private int nextBall()
{
return itsThrows[ball];
}
http://www.objectmentor.com/resources/articles/xpepisode.htm

22/34

1/23/2015

XP Epsiode

RCM:"OK,thatworkstoo.Nowlet'sdealwithframeScore."
private int handleSecondThrow()
{
int score = 0;
secondThrow = itsThrows[ball+1];

int frameScore = firstThrow + secondThrow;


// spare needs next frames first throw
if ( spare() )
{
ball+=2;
score += 10 + nextBall();
}
else
{
score += twoBallsInFrame();
ball+=2;
}
return score;

private int twoBallsInFrame()


{
return itsThrows[ball] + itsThrows[ball+1];
}
RSK:"Bob,youaren'tincrementingballinaconsistentmanner.Inthespareandstrikecase,you
incrementbeforeyoucalculatethescore.InthetwoBallsInFramecase,youincrementafteryou
calculatethescore.Andthecodedependsuponthisorder!What'sup?"
RCM:"Sorry,Ishouldhaveexplained.I'mplanningonmovingtheincrementsintostrike,spare,and
twoBallsInFrame.Thatwaythey'lldisappearfromthescoreForFramefunction,andthefunctionwill
lookjustlikeourpseudocode."
RSK:"OK,I'lltrustyouforafewmoresteps,butremember,I'mwatching."
Kent:"SoamI."
RCM:"OK,nowsincenobodyusesfirstThrow,secondThrow,andframeScoreanymore,wecan
getridofthem."
public int scoreForFrame(int theFrame)
{
ball = 0;
int score=0;
for (int currentFrame = 0;
currentFrame < theFrame;
currentFrame++)
{
if (strike())
{
ball++;
score += 10 + nextTwoBalls();
}
else
{
score += handleSecondThrow();
}
}
}

return score;

http://www.objectmentor.com/resources/articles/xpepisode.htm

23/34

1/23/2015

XP Epsiode

private int handleSecondThrow()


{
int score = 0;
// spare needs next frames first throw
if ( spare() )
{
ball+=2;
score += 10 + nextBall();
}
else
{
score += twoBallsInFrame();
ball+=2;
}
return score;
}
RCM:(Thesparkleinhiseyesisareflectionofthegreenbar.)"Now,sincetheonlyvariablethat
couplesthethreecasesisball,andsinceballisdealtwithindependentlyineachcase,wecan
mergethethreecasestogether."
public int scoreForFrame(int theFrame)
{
ball = 0;
int score=0;
for (int currentFrame = 0;
currentFrame < theFrame;
currentFrame++)
{
if (strike())
{
ball++;
score += 10 + nextTwoBalls();
}
else if ( spare() )
{
ball+=2;
score += 10 + nextBall();
}
else
{
score += twoBallsInFrame();
ball+=2;
}
}
}

return score;

RSK:(PeterLorrieGasp)"Master...Master...Letmedoit.Please,letmedoit."
RCM:"Ah,Igor,youwouldliketomovetheincrements?"
RSK:"Yes,master.Ohyesmaster."(Grabskeyboard.)
public int scoreForFrame(int theFrame)
{
ball = 0;
int score=0;
for (int currentFrame = 0;
currentFrame < theFrame;
http://www.objectmentor.com/resources/articles/xpepisode.htm

24/34

1/23/2015

XP Epsiode

}
}

currentFrame++)
if (strike())
score += 10 + nextTwoBalls();
else if (spare())
score += 10 + nextBall();
else
score += twoBallsInFrame();

return score;

private boolean strike()


{
if (itsThrows[ball] == 10)
{
ball++;
return true;
}
return false;
}
private boolean spare()
{
if ((itsThrows[ball] + itsThrows[ball+1]) == 10)
{
ball += 2;
return true;
}
return false;
}
private int nextTwoBalls()
{
return itsThrows[ball] + itsThrows[ball+1];
}
private int nextBall()
{
return itsThrows[ball];
}
private int twoBallsInFrame()
{
return itsThrows[ball++] + itsThrows[ball++];
}
RCM:"WelldoneIgor!"
RSK:"Thankyoumaster."
RCM:"LookatthatscoreForFramefunction.That'stherulesofbowlingstatedaboutassuccinctlyas
possible."
RSK:"But,Bob,whathappenedtothelinkedlistofFrameobjects?"(snicker,snicker)
RCM:(Sigh)"Wewerebedevilledbythedaemonsofdiagramaticoverdesign.MyGod,threelittle
boxesdrawnonthebackofanapkin,Game,Frame,andThrow,anditwasstilltoocomplicatedand
justplainwrong."
RSK:"WemadeamistakestartingwiththeThrowclass.WeshouldhavestartedwiththeGame
classfirst!"
http://www.objectmentor.com/resources/articles/xpepisode.htm

25/34

1/23/2015

XP Epsiode

RCM:"Indeed!So,nexttimelet'strystartingatthehighestlevelandworkdown."
RSK:(Gasp)"TopDownDesign!??!?!CouldDeMarcohavebeenrightallalong?"
RCM:"Correction:TopDownTestFirstDesign.Frankly,Idon'tknowifthisisagoodruleornot.It's
justwhatwouldhavehelpedusinthiscase.Sonexttime,I'mgoingtotryitandseewhathappens."
RSK:"Yeah,OK.Anywaywestillhavesomerefactoringtodo.Theballvariableisjustaprivate
iteratorforscoreForFrameanditsminions.Theyshouldallbemovedintoadifferentobject."
RCM:"Oh,yes,yourScorerobject.Youwererightafterall.Let'sdoit."
RSK:(Grabskeyboardandtakesseveralsmallstepspunctuatedbyteststocreatethefollowing.)
//Game.java---------------------------------public class Game
{
public int score()
{
return scoreForFrame(getCurrentFrame()-1);
}
public int getCurrentFrame()
{
return itsCurrentFrame;
}
public void add(int pins)
{
itsScorer.addThrow(pins);
itsScore += pins;
adjustCurrentFrame(pins);
}
private void adjustCurrentFrame(int pins)
{
if (firstThrowInFrame == true)
{
if( pins == 10 ) // strike
itsCurrentFrame++;
else
firstThrowInFrame = false;
}
else
{
firstThrowInFrame=true;
itsCurrentFrame++;
}
itsCurrentFrame = Math.min(11, itsCurrentFrame);
}
public int scoreForFrame(int theFrame)
{
return itsScorer.scoreForFrame(theFrame);
}

private int itsScore = 0;


private int itsCurrentFrame = 1;
private boolean firstThrowInFrame = true;
private Scorer itsScorer = new Scorer();

//Scorer.java----------------------------------public class Scorer


http://www.objectmentor.com/resources/articles/xpepisode.htm

26/34

1/23/2015

XP Epsiode

public void addThrow(int pins)


{
itsThrows[itsCurrentThrow++] = pins;
}
public int scoreForFrame(int theFrame)
{
ball = 0;
int score=0;
for (int currentFrame = 0;
currentFrame < theFrame;
currentFrame++)
{
if (strike())
score += 10 + nextTwoBalls();
else if (spare())
score += 10 + nextBall();
else
score += twoBallsInFrame();
}
}

return score;

private boolean strike()


{
if (itsThrows[ball] == 10)
{
ball++;
return true;
}
return false;
}
private boolean spare()
{
if ((itsThrows[ball] + itsThrows[ball+1]) == 10)
{
ball += 2;
return true;
}
return false;
}
private int nextTwoBalls()
{
return itsThrows[ball] + itsThrows[ball+1];
}
private int nextBall()
{
return itsThrows[ball];
}
private int twoBallsInFrame()
{
return itsThrows[ball++] + itsThrows[ball++];
}
private int ball;
private int[] itsThrows = new int[21];
private int itsCurrentThrow = 0;

http://www.objectmentor.com/resources/articles/xpepisode.htm

27/34

1/23/2015

XP Epsiode

}
RSK:"That'smuchbetter.NowGamejustkeepstrackofframes,andScorerjustcalculatesthe
score.TheSRProcks!"
RCM:"Whatever.Butitisbetter.DidyounoticethattheitsScorevariableisnotbeingused
anymore?"
RSK:"Ha!You'reright.Let'skillit."(Gleefullystartserasingthings.)
public void add(int pins)
{
itsScorer.addThrow(pins);
adjustCurrentFrame(pins);
}
RSK:"Notbad.NowshouldwecleanuptheadjustCurrentFramestuff?"
RCM:"OK,let'slookatit."
private void adjustCurrentFrame(int pins)
{
if (firstThrowInFrame == true)
{
if( pins == 10 ) // strike
itsCurrentFrame++;
else
firstThrowInFrame = false;
}
else
{
firstThrowInFrame=true;
itsCurrentFrame++;
}
itsCurrentFrame = Math.min(11, itsCurrentFrame);
}
RCM:"OK,firstlet'sextracttheincrementsintoasinglefunctionthatalsorestrictstheframeto11.
(Brrrr.Istilldon'tlikethat11.)"
RSK:"Bob,11meansendofgame."
RCM:"Yeah.Brrrr."(Grabskeyboard,makesacoupleofchangespunctuatedbytests.)
private void adjustCurrentFrame(int pins)
{
if (firstThrowInFrame == true)
{
if( pins == 10 ) // strike
advanceFrame();
else
firstThrowInFrame = false;
}
else
{
firstThrowInFrame=true;
advanceFrame();
}
}
private void advanceFrame()
{
http://www.objectmentor.com/resources/articles/xpepisode.htm

28/34

1/23/2015

XP Epsiode

itsCurrentFrame = Math.min(11, itsCurrentFrame + 1);

RCM:"OK,that'salittlebetter.Nowlet'sbreakoutthestrikecaseintoitsownfunction."(Takesafew
smallstepsandrunstestsbetweeneach.)
private void adjustCurrentFrame(int pins)
{
if (firstThrowInFrame == true)
{
if (adjustFrameForStrike(pins) == false)
firstThrowInFrame = false;
}
else
{
firstThrowInFrame=true;
advanceFrame();
}
}
private boolean adjustFrameForStrike(int pins)
{
if (pins == 10)
{
advanceFrame();
return true;
}
return false;
}
RCM:"That'sprettygood.Now,aboutthat11."
RSK:"Youreallyhatethatdon'tyou."
RCM:"Yeah,lookatthescorefunction:"
public int score()
{
return scoreForFrame(getCurrentFrame()-1);
}
RCM:"That1isodd.It'stheonlyplacewetrulyusegetCurrentFrame,andyetweneedtoadjust
whatitreturns."
RSK:"Damn,you'reright.Howmanytimeshavewereversedourselvesonthis?"
RCM:"Toomany.Butthereitis.ThecodewantsitsCurrentFrametorepresenttheframeofthelast
thrownball,nottheframeweareabouttothrowinto."
RSK:"Sheesh,that'sgoingtobreaklotsoftestscases."
RCM:"Actually,IthinkweshouldremovegetCurrentFramefromallthetestcasesandremovethe
getCurrentFramefunctionitself.Nobodyreallyusesit."
RSK:"OK,Igetyourpoint.I'lldoit.It'llbelikeputtingalamehorseoutofitsmisery."(Grabs
keyboard.)
//Game.java---------------------------------public int score()
{
return scoreForFrame(itsCurrentFrame);
http://www.objectmentor.com/resources/articles/xpepisode.htm

29/34

1/23/2015

XP Epsiode

}
private void advanceFrame()
{
itsCurrentFrame = Math.min(10, itsCurrentFrame + 1);
}
RCM:"Ohforcryingoutloud.Youmeantotellmethatwewerefrettingoverthat.Allwedidwas
changethelimitfrom11to10andremovethe1.Cripe."
RSK:"Yeah,UncleBob,itreallywasn'tworthalltheangstwegaveit."
RCM:"OK,lookslikewearedone.Let'sjustreadthroughthewholeprogramandseeifit'sassimple
andcommunicativeasitcanbe."
//Game.java---------------------------------public class Game
{
public int score()
{
return scoreForFrame(itsCurrentFrame);
}
public void add(int pins)
{
itsScorer.addThrow(pins);
adjustCurrentFrame(pins);
}
private void adjustCurrentFrame(int pins)
{
if (firstThrowInFrame == true)
{
if (adjustFrameForStrike(pins) == false)
firstThrowInFrame = false;
}
else
{
firstThrowInFrame=true;
advanceFrame();
}
}
private boolean adjustFrameForStrike(int pins)
{
if (pins == 10)
{
advanceFrame();
return true;
}
return false;
}
private void advanceFrame()
{
itsCurrentFrame = Math.min(10, itsCurrentFrame + 1);
}
public int scoreForFrame(int theFrame)
{
return itsScorer.scoreForFrame(theFrame);
}
http://www.objectmentor.com/resources/articles/xpepisode.htm

30/34

1/23/2015

XP Epsiode

private int itsCurrentFrame = 0;


private boolean firstThrowInFrame = true;
private Scorer itsScorer = new Scorer();

//Scorer.java----------------------------------public class Scorer


{
public void addThrow(int pins)
{
itsThrows[itsCurrentThrow++] = pins;
}
public int scoreForFrame(int theFrame)
{
ball = 0;
int score=0;
for (int currentFrame = 0;
currentFrame < theFrame;
currentFrame++)
{
if (strike())
score += 10 + nextTwoBalls();
else if (spare())
score += 10 + nextBall();
else
score += twoBallsInFrame();
}
}

return score;

private boolean strike()


{
if (itsThrows[ball] == 10)
{
ball++;
return true;
}
return false;
}
private boolean spare()
{
if ((itsThrows[ball] + itsThrows[ball+1]) == 10)
{
ball += 2;
return true;
}
return false;
}
private int nextTwoBalls()
{
return itsThrows[ball] + itsThrows[ball+1];
}
private int nextBall()
{
return itsThrows[ball];
}
private int twoBallsInFrame()
{
http://www.objectmentor.com/resources/articles/xpepisode.htm

31/34

1/23/2015

XP Epsiode

return itsThrows[ball++] + itsThrows[ball++];

private int ball;


private int[] itsThrows = new int[21];
private int itsCurrentThrow = 0;

RCM:"OK,thatlooksprettygood.Ican'tthinkofanythingelsetodo."
RSK:"Yeah,it'spretty.Let'slookoverthetestsforgoodmeasure."
//TestGame.java-----------------------------------------import junit.framework.*;
public class TestGame extends TestCase
{
public TestGame(String name)
{
super(name);
}
private Game g;
public void setUp()
{
g = new Game();
}
public void testTwoThrowsNoMark()
{
g.add(5);
g.add(4);
assertEquals(9, g.score());
}
public void testFourThrowsNoMark()
{
g.add(5);
g.add(4);
g.add(7);
g.add(2);
assertEquals(18, g.score());
assertEquals(9, g.scoreForFrame(1));
assertEquals(18, g.scoreForFrame(2));
}
public void testSimpleSpare()
{
g.add(3);
g.add(7);
g.add(3);
assertEquals(13, g.scoreForFrame(1));
}
public void testSimpleFrameAfterSpare()
{
g.add(3);
g.add(7);
g.add(3);
g.add(2);
assertEquals(13, g.scoreForFrame(1));
assertEquals(18, g.scoreForFrame(2));
assertEquals(18, g.score());
http://www.objectmentor.com/resources/articles/xpepisode.htm

32/34

1/23/2015

XP Epsiode

}
public void testSimpleStrike()
{
g.add(10);
g.add(3);
g.add(6);
assertEquals(19, g.scoreForFrame(1));
assertEquals(28, g.score());
}
public void testPerfectGame()
{
for (int i=0; i<12; i++)
{
g.add(10);
}
assertEquals(300, g.score());
}
public void testEndOfArray()
{
for (int i=0; i<9; i++)
{
g.add(0);
g.add(0);
}
g.add(2);
g.add(8); // 10th frame spare
g.add(10); // Strike in last position of array.
assertEquals(20, g.score());
}
public void testSampleGame()
{
g.add(1);
g.add(4);
g.add(4);
g.add(5);
g.add(6);
g.add(4);
g.add(5);
g.add(5);
g.add(10);
g.add(0);
g.add(1);
g.add(7);
g.add(3);
g.add(6);
g.add(4);
g.add(10);
g.add(2);
g.add(8);
g.add(6);
assertEquals(133, g.score());
}
public void testHeartBreak()
{
for (int i=0; i<11; i++)
g.add(10);
g.add(9);
assertEquals(299, g.score());
}
http://www.objectmentor.com/resources/articles/xpepisode.htm

33/34

1/23/2015

XP Epsiode

public void testTenthFrameSpare()


{
for (int i=0; i<9; i++)
g.add(10);
g.add(9);
g.add(1);
g.add(1);
assertEquals(270, g.score());
}

RSK:"Thatprettymuchcoversit.Canyouthinkofanymoremeaningfultestcases?"
RCM:"No,Ithinkthat'stheset.Therearen'tanytherethatI'dbecomfortableremovingatthispoint."
RSK:"Thenwe'redone."
RCM:"I'dsayso.Thanksalotforyouhelp."
RSK:"Noproblem,itwasfun."
Note
[1]BertrandMeyer.ObjectOrientedSoftwareConstruction(PrenticeHall,1999).

AbouttheAuthors
RobertC.Martinhasbeenasoftwareprofessionalsince1970.HeispresidentofObjectMentorInc.,
afirmofhighlyexperiencedexpertsthatoffershighlevelobjectorientedsoftwaredesignconsulting,
training,anddevelopmentservicestomajorcorporationsaroundtheworld.In1995,heauthoredthe
bestsellingbook:DesigningObjectOrientedC++ApplicationsusingtheBoochMethod,publishedby
PrenticeHall.In1997,hewaschiefeditorofthebook:PatternLanguagesofProgramDesign3,
publishedbyAddisonWesley.In2000,hewaseditorofthebookMoreC++Gems,publishedby
CambridgePress.From1996to1998,hewastheeditorinchiefoftheC++Report.Hehaspublished
dozensofarticlesinvarioustradejournalsandisaregularspeakeratinternationalconferencesand
tradeshows.Heiscurrentlyworkingintheareaoflightweightproductiveprocessesandisastrong
advocateandsupporterofExtremeProgramming.
Dr.RobertSKosshasbeenprogrammingsince1973,startinginFortran,migratingtoC,thento
C++,andlatelytoJava,Smalltalk,andPython.HehasbeenaseniorconsultantatObjectMentorInc.
forthelasttwoyears,wherehisdutiesincludewritingandteachingcourses,mentoringclients,and
participatinginthedevelopmentofprojectsthatareoutsourcedtothecompany.Hehasdeveloped
manysystemsinavarietyofareas,fromrealtimeembeddedcontrollerstolargedatabasesystems.
HeisalsoastrongproponentoflightweightprocessesandaveryvocaladvocateofExtreme
Programming.

http://www.objectmentor.com/resources/articles/xpepisode.htm

34/34