99 Bottles of OOP: A Practical Guide to Object-Oriented Design

283

Transcript of 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Page 1: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design
Page 2: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

99BottlesofOOPSandiMetz⋅KatrinaOwen–0.3,2016-10-06|beta-2

TableofContents

ColophonDedicationPreface

WhatThisBookIsAboutWhoShouldReadThisBookBeforeYouReadThisBookHowtoreadthisbookCodeExamplesErrataAbouttheAuthors

Introduction1.RediscoveringSimplicity

1.1.SimplifyingCode1.1.1.IncomprehensiblyConcise

ConsistencyDuplicationNames

1.1.2.SpeculativelyGeneral1.1.3.ConcretelyAbstract1.1.4.ShamelessGreen

1.2.JudgingCode

Page 3: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

1.2.1.EvaluatingCodeBasedonOpinion1.2.2.EvaluatingCodeBasedonFacts

SourceLinesofCodeCyclomaticComplexityAssignments,BranchesandConditions(ABC)Metric

1.2.3.ComparingSolutions1.3.Summary

2.TestDrivingShamelessGreen2.1.UnderstandingTesting2.2.WritingtheFirstTest2.3.RemovingDuplication2.4.UnderstandingTransformations2.5.ToleratingDuplication2.6.HewingtothePlan2.7.ExposingResponsibilities2.8.ChoosingNames2.9.RevealingIntentions2.10.WritingCost-EffectiveTests2.11.AvoidingtheEcho-Chamber2.12.ConsideringOptions2.13.Summary

3.UnearthingConcepts3.1.ListeningtoChange3.2.StartingWiththeOpen/ClosedPrinciple3.3.RecognizingCodeSmells3.4.IdentifyingtheBestPointofAttack3.5.RefactoringSystematically3.6.FollowingtheFlockingRules

Page 4: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

3.7.ConvergingonAbstractions3.7.1.FocusingonDifference3.7.2.SimplifyingHardProblems3.7.3.NamingConcepts3.7.4.MakingMethodicalTransformations3.7.5.RefactoringGradually

3.8.Summary4.PracticingHorizontalRefactoring

4.1.ReplacingDifferenceWithSameness4.2.EquivocatingAboutNames4.3.DerivingNamesFromResponsibilities4.4.ChoosingMeaningfulDefaults4.5.SeekingStableLandingPoints4.6.ObeyingtheLiskovSubstitutionPrinciple4.7.TakingBiggerSteps4.8.DiscoveringDeeperAbstractions4.9.DependingonAbstractions4.10.Summary

5.SeparatingResponsibilities5.1.SelectingtheTargetCodeSmell

5.1.1.IdentifyingPatternsinCode5.1.2.SpottingCommonQualities5.1.3.EnumeratingFlockedMethodCommonalities5.1.4.InsistingUponMessages

5.2.ExtractingClasses5.2.1.ModelingAbstractions5.2.2.NamingClasses5.2.3.ExtractingBottleNumber

Page 5: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

5.2.4.RemovingArguments5.2.5.TrustingtheProcess

5.3.AppreciatingImmutability5.4.AssumingFastEnough5.5.CreatingBottleNumbers5.6.RecognizingLiskovViolations5.7.Summary

6.ReplacingConditionalswithObjectsAppendixA:Prerequisites

A.1.RubyA.2.Minitest

AppendixB:InitialExerciseB.1.GettingtheexerciseB.2.DoingtheexerciseB.3.TestSuite

Acknowledgements

Page 6: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

ColophonVersion:0.3

VersionDate:2016-10-06

VersionNotes:beta-2

ISBN-10:1-944823-00-X

ISBN-13:978-1-944823-00-9

PublishedBy:PotatoCanyonSoftware,LLC

1stEdition

Copyright:2016

CoverDesignandArtbyLindseyMorris.

CreatedusingAsciidoctor.

Page 7: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

DedicationSandi

ToAmy,foreverythingsheisanddoes,andtoJasper,whotaughtmethatnothingtrumpsagoodwalk.

Katrina

ToSander,whosepersistenceisoutofthisworld.

Page 8: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

PrefaceItturnsoutthateverythingyouneedtoknowaboutObject-OrientedDesign(OOD)canbelearnedfromthe99BottlesofBeersong.

Well,perhapsnoteverything,butquitecertainly,agreatmanythings.

Thesongissimultaneouslyeasytounderstandandfullofhiddencomplexity,whichmakesittheperfectskeletonuponwhichtohanglessonsinOOD.Thelessonsembeddedwithinthesongaresouseful,andsobroad,thatoverthelastthreeyearsithasbecomeacorepartofthecurriculumofSandiMetz’sPracticalObject-OrientedDesigncourse.

ThethoughtsinthisbookreflectcountlesshoursofdiscussionandcollaborationbetweenSandiandKatrinaOwen.Theseideashavebeenbattle-testedbyhundredsofstudents,andrefinedbyaseriesofdeeplythoughtfulco-instructors,beginningwithKatrina.WhileneitherKatrinanorSandihavethehubristoclaimperfectunderstanding,bothhavelearnedagreatdealaboutObject-OrientedDesignfromteachingthissong,andhavecometofeelthatit’stimetobuckitupandwriteitdown.

Therefore,thisbook.Wehopethatyoufinditbothusefulandenjoyable.

WhatThisBookIsAboutThisbookisaboutwritingcost-effective,maintainable,andpleasingcode.

Chapter1exploreshowtodecideifcodeis"goodenough."Thischapterusesmetricstocompareseveralpossiblesolutionstothe

Page 9: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

99Bottlesproblem.ItintroducesatypeofsolutionknownasShamelessGreen,andarguesthatalthoughShamelessGreenisneitherclevernorchangeable,itisthebestinitialsolutiontomanyproblems.

Chapter2isaprimerforTest-DrivenDevelopment(TDD),whichisusedtofindShamelessGreen.Thischapterisconcernedwithdecidingwhattotest,andwithcreatingteststhathappilytoleratechangestotheunderlyingcode.

Chapter3introducesanewrequirement(six-pack),whichleadstoadiscussionofhowtodecidewheretostartwhenchangingcode.ThischapterexaminestheOpen/ClosedPrinciple,andthenexplorescodesmells.ThechapterthendefinesasimplesetofFlockingRuleswhichguideastep-by-steprefactoringofcode.

Chapter4continuesthestep-by-steprefactoringbeguninChapter3.ItiterativelyappliestheFlockingRules,eventuallystumblesacrosstheneedfortheLiskovSubstitutionPrinciple,andultimatelyunearthsadeeplyhiddenabstraction.

Chapter5inventoriestheexistingcodeforsmells,choosesthemostprominentone,andusesittotriggerthecreationofanewclass.Alongthewayittakesahardlookatimmutability,performance,andcaching.

Chapter6isnotyetavailable.Thischapterperformsamiraclewhichnotonlyremovesallconditionals,butalsoallowsyoutofinallyimplementthenewsix-packrequirementwithoutalteringanyexistingcode.

WhoShouldReadThisBookThelessonsinthebookhavebeenfoundusefulbyprogrammerswithabroadrangeofexperience,fromranknovicethroughgrizzledveteran.Despitewhatonemightpredict,novicesoftenhaveaneasiertimewiththismaterial.Astheyare

Page 10: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

unencumberedbypriorknowledge,theirmindsareopen,andeasilyabsorbtheseideas.

It’stheveteranswhostruggle.Theirhabitsaredeeplyingrained.Theyknowthemselvestobegoodatprogramming.Theyfeelquick,andefficient,andsoresistnewtechniques,especiallywhenthosetechniquestemporarilyslowthemdown.

Thisbookwillbeusefulifyouareaveteran,butitcannotbedeniedthatitteachesprogrammingtechniquesthatlikelycontradictyourcurrentpractice.Changingentrenchedideascanbepainful.However,youcannotmakeinformeddecisionsaboutthevalueofnewideasunlessyouthoroughlyunderstandthem,andtounderstandthemyoumustcommit,wholeheartedly,tolearningthem.

Therefore,ifyouareaveteran,it’sbesttoadoptthenovicemindsetbeforereadingon.Setasidepriorbeliefs,anddedicateyourselftowhatfollows.Whilereading,resisttheurgetoresist.Readtheentirebook,worktheproblems,andonlythendecidewhethertointegratetheseideasintoyourdailypractice.

BeforeYouReadThisBookYou’lllearnmorefromthisbookifyouspend30minutesworkingonthe99BottlesofBeerproblembeforestartingtoread.Seetheappendixforinstructions.

Ifyoujustwanttoreadonbutyoudon’tknowRuby,havenofear.Thesyntaxofthelanguageissostraightforwardthatyou’llhavenotroubleunderstandingwhatfollows.TheideasinthisbookarenotaboutRuby,they’reaboutobject-orientedprogramminganddesign.

Howtoreadthisbook

Page 11: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Thechaptersbuildupononeanother,andsoshouldbereadinorder.Whileisolatedsectionsmaybeuseful,thewholeismorethanthesumofitsparts.Theideasgainpowerinrelationtooneanother.

Togetthemostfromthebook,workthecodesamplesasyoureadalong.Withactiveparticipationyou’lllearnmore,understandbetter,andretainlonger.Whilereadinghasvalue,doinghasmore.

CodeExamplesTheexamplesarewritteninRuby,andtheexercisesrelyonMinitest.Thecodeisavailableinthe99bottlesrepositoryonGitHub,whichcontainsabranchforeachchapter.

ErrataAcurrentlistoferrataislocatedatwww.sandimetz.com/99bottles/errata.Ifyoufindadditionalerrors,[email protected].

AbouttheAuthorsSandiMetz

SandiistheauthorofPracticalObject-OrientedDesigninRuby.Shehasthirtyyearsofexperienceworkingonlargeobject-orientedapplications.She’sspokenaboutprogramming,object-orienteddesignandrefactoringatnumerousconferencesincludingAgileAllianceTechnicalConference,CraftConf,Øredev,RailsConf,andRubyConf.Shebelievesinsimplecodeandstraightforwardexplanations,andistheproudrecipientofaRubyHeroawardforhercontributiontotheRubycommunity.Sheprefersworkingsoftware,practicalsolutionsandlengthybicycletrips(notnecessarilyinthatorder).Findoutmoreabout

Page 12: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Sandiatsandimetz.com.

KatrinaOwen

KatrinaworksforGitHubasanAdvocateontheOpenSourceteam.KatrinahastenyearsofexperienceandworksprimarilyinGoandRuby.Sheisthecreatorofexercism.io,aplatformforprogrammingskilldevelopmentinmorethan30languages.She’sspokenaboutrefactoringandopensourceatinternationalconferencessuchasNordicRuby,Mix-IT,SoftwareCraftsmanshipNorthAmerica,OSCON,BathRubyandRailsConf.ShereceivedaRubyHeroawardforhercontributiontotheRubycommunity.Whenprogramming,herfocusisonautomation,workflowoptimization,andrefactoring.FindoutmoreaboutKatrinaatkytrinyx.com.

Page 13: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

IntroductionThisbookcreatesasimplesolutiontothe99BottlesofBeersongproblem,andthenappliesaseriesofrefactoringstoimprovethedesignofthecode.

Putthatway,thetopicsoundssopainfullyobviousthatonemightreasonablywonderifthisentiretomecouldbereplacedbyafewsamplesofcode.Theserefactoring"endpoints"wouldbeafractionofthesizeofthisbook,andavastlyquickerread.Unfortunately,theywouldteachyoualmostnothingaboutprogramming.Writingcodeistheprocessofworkingyourwaytothenextstableendpoint,nottheendpointitself.Youdon’tknowtheanswerinadvance,insteadyouareseekingit.

Thisbookdocumentseverystepdowneverypathofcode,andsoprovidesaguided-tourofthedecisionsmadealongtheway.Itnotonlyshowshowgoodcodelookswhenit’sdone,itrevealsthethoughtsthatproducedit.Itaimstoleavenothingout.Itflingsbacktheveilandexposesthesausagebeingmade.

Onefinalnotebeforedivingintothebookproper.Thechaptersthatfollowapplyageneral,broad,solutiontoaspecific,narrow,problem.Theauthorscheerfullystipulatetothefactthatyouareunlikelytoencounterthe99BottlesofBeersonginyourdailywork,andthatproblemsofsimilarsizearebestsolvedverysimply.Forthepurposesofthisbook,99Bottlesisconvenientbecauseit’ssimultaneouslyeasilyunderstandableandsurprisinglycomplex,andsoprovidesarefreshingstand-inforlargerproblems.Onceyouunderstandthesolutionshere,you’llbeabletoapplythemtothemuchlargerrealworld.

Withthat,ontothebook.

Page 14: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

1.RediscoveringSimplicityWhenyouwerenewtoprogrammingyouwrotesimplecode.Althoughyoumaynothaveappreciateditatthetime,thiswasagreatstrength.Sincethen,you’velearnednewskills,tackledharderproblems,andproducedincreasinglycomplexsolutions.Experiencehastaughtyouthatmostcodewillsomedaychange,andyou’vebeguntocraftitinanticipationofthatday.Complexityseemsbothnaturalandinevitable.

Whereyouonceoptimizedcodeforunderstandability,younowfocusonitschangeability.Yourcodeislessconcretebutmoreabstract—you’vemadeitinitiallyhardertounderstandinhopesthatitwillultimatelybeeasiertomaintain.

ThisisthebasicpromiseofObject-OrientedDesign(OOD):thatifyou’rewillingtoacceptincreasesinthecomplexityofyourcodealongsomedimensions,you’llberewardedwithdecreasesincomplexityalongothers.OODdoesn’tclaimtobefree;itmerelyassertsthatitsbenefitsoutweighitscosts.

Designdecisionsinevitablyinvolvetrade-offs.There’salwaysacost.Forexample,ifyou’veduplicatedabitofcodeinmanyplaces,theDon’tRepeatYourself(DRY)principletellsyoutoextracttheduplicationintoasinglecommonmethodandtheninvokethisnewmethodinplaceoftheoldcode.DRYisagreatidea,butthatdoesn’tmeanit’sfree.ThepriceyoupayforDRYingoutcodeisthattheinvokerofthenewmethodnolongerknowstheresult,onlythemessageitshouldsend.Ifyou’rewillingtopaythisprice(i.e.beingwillinglyignorantoftheactualbehavior),therewardyoureapisthatwhenthebehaviorchanges,youneedalteryourcodeinonlyoneplace.TheargumentthatOODmakesisthatthisbargainwillsaveyoumoney.

Page 15: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Didyoudivideonelargeclassintomanysmallones?Youcannowreusethenewclassesindependentlyofoneanother,butit’snolongerobvioushowtheyfittogetherfortheoriginalcase.Haveyouinjectedadependencyinsteadofhard-codingtheclassnameofacollaborator?Thereceivercannowfreelydependonnewandpreviouslyunforeseenobjects,butitmustremainignorantoftheiractualclass.

Theexamplesabovechangecodebyincreasingitslevelofabstraction.DRYingoutcodeinsertsalevelofindirectionbetweentheplacethatusesbehaviorandtheplacethatdefinesit.Breakingonelargeclassintomanyforcesthecreationofsomethingnewtoembodytherelationshipbetweenthepieces.Injectingadependencytransformsthereceiverintosomethingthatdependsonanabstractroleratherthanaconcreteclass.

Eachofthesedesignchoiceshascosts,anditonlymakessensetopaythesecostsifyoualsoaccruesomeoffsettingbenefits.Designisthusaboutpickingtherightabstractions.Ifyouchoosewell,yourcodewillbeexpressive,understandableandflexible,andeveryonewilllovebothitandyou.However,ifyougettheabstractionswrong,yourcodewillbeconvoluted,confusing,andcostly,andyourprogrammingpeerswillhateyou.

Unfortunately,abstractionsarehard,andevenwiththebestofintentions,it’seasytogetthemwrong.Well-meaningprogrammerstendtooveranticipateabstractions,inferringthemprematurelyfromincompleteinformation.Earlyabstractionsareoftennotquiterightandthereforetheycreateacatch-22.[1]Youcan’tcreatetherightabstractionuntilyoufullyunderstandthecode,buttheexistenceofthewrongabstractionmaypreventyoufromeverdoingso.Thissuggeststhatyoushouldnotreachforabstractions,butinstead,youshouldresistthemuntiltheyabsolutelyinsistuponbeingcreated.

Thisbookisaboutfindingtherightabstraction.Thisfirstchapterstartsbypeelingawaythefogofcomplexityanddefiningwhatit

Page 16: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

meanstowritesimplecode.

1.1.SimplifyingCodeThecodeyouwriteshouldmeettwooftencontradictorygoals.Itmustremainconcreteenoughtobeunderstoodwhilesimultaneouslybeingabstractenoughtoallowforchange.

Imagineacontinuumwith"mostconcrete"atoneendand"mostabstract"attheother.Codeattheconcreteendmightbeexpressedasasinglelongprocedurefullof if statements.Codeattheabstractendmightconsistofmanyclasses,eachwithonemethodcontainingasinglelineofcode.

Thebestsolutionformostproblemsliesnotattheextremesofthiscontinuum,butsomewhereinthemiddle.There’sasweetspotthatrepresentstheperfectcompromisebetweencomprehensionandchangeability,andit’syourjobasaprogrammertofindit.

Thissectiondiscussesfourdifferentsolutionstothe99BottlesofBeerproblem.Thesesolutionsvaryincomplexityandthusillustratedifferentpointsalongthiscontinuum.

Youmustnowmakeadecision.Asyouwereforewarnedinthepreface,thebestwaytolearnfromthisbookistoworktheexercisesyourself.Ifyoucontinuereadingbeforesolvingtheprobleminyourownway,yourideaswillbecontaminatedbythecodethatfollows.Therefore,ifyouplantoworkalong,godothe99Bottlesexercisenow.Whenyou’refinished,you’llbereadytoexaminethefollowingfoursolutions.

1.1.1.IncomprehensiblyConciseHere’sthefirstoffourdifferentsolutionstothe99Bottlessong.

Page 17: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Listing1.1:IncomprehensiblyConcise

1 classBottles2 defsong3 verses(99,0)4 end5 6 defverses(hi,lo)7 hi.downto(lo).map{|n|verse(n)}.join("\n")8 end9 10 defverse(n)11 "#{n==0?'Nomore':n}bottle#{'s'ifn!=1}"+12 "ofbeeronthewall,"+13 "#{n==0?'nomore':n}bottle#{'s'ifn!=1}ofbeer.\n"+14 "#{n>0?"Take#{n>1?'one':'it'}downandpassitaround"15 :"Gotothestoreandbuysomemore"},"+16 "#{n-1<0?99:n-1==0?'nomore':n-1}bottle#{'s'ifn-1!=1}"+17 "ofbeeronthewall.\n"18 end19 end

Thisfirstsolutionembedsagreatdealoflogicintotheversestring.Thecodeaboveperformsaneattrick.Itmanagestobeconcisetothepointofincomprehensibilitywhilesimultaneouslyretainingloadsofduplication.Thiscodeishardtounderstand

Page 18: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

becauseitisinconsistentandduplicative,andbecauseitcontainshiddenconceptsthatitdoesnotname.

ConsistencyThestyleoftheconditionalsisinconsistent.Mostusetheternaryform,asonline11:

Otherstatementsaremadeconditionalbyaddingatrailing if .Line11againprovidesanexample:

Finally,there’stheternarywithinaternaryonline16,whichisbestleftwithoutcomment:

Everytimethestyleoftheconditionalschanges,thereaderhastopressamentalresetbuttonandstartthinkinganew.Inconsistentstylingmakescodeharderforhumanstoparse;itraisescostswithoutprovidingbenefits.

DuplicationThecodeduplicatesbothdataandlogic.Havingmultiplecopiesofthestrings"ofbeer"and"onthewall"isn’tgreat,butatleaststringduplicationiseasytoseeandunderstand.Logic,however,ishardertocomprehendthandata,andduplicatedlogicisdoublyso.Ofcourse,ifyouwanttoachievemaximumconfusion,youcaninterpolateduplicatedlogicinsidestrings,asdoestheverse methodabove.

Forexample,"bottle"pluralizationisdoneinthreeplaces.The

n==0?'Nomore':n

's'ifn!=1

n-1<0?99:n-1==0?'nomore':n-1

Page 19: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

codetodothisisidenticalintwooftheplaces,onLines11and13:

Butlater,online16,thepluralizationlogicissubtlydifferent.Suddenlyit’snot n thatmatters,but n-1 :

Duplicationoflogicsuggeststhatthereareconceptshiddeninthecodethatarenotyetvisiblebecausetheyhaven’tbeenisolatedandnamed.Theneedtosometimessay"bottle"andothertimessay"bottles"meanssomething,andtheneedtosometimesuse n andothertimesuse n-1 meanssomethingelse.Thecodegivesnoclueaboutwhatthesemeaningsmightbe;you’relefttofigurethisoutforyourself.

NamesThemostobviouspointtobemadeaboutthenamesinthe versemethodofListing1.1:IncomprehensiblyConciseisthattherearen’tany.Theversestringcontainsembeddedlogic.Eachbitoflogicservessomepurpose,anditisuptoyoutoconstructamentalmapofwhatthesepurposesmightbe.

Thiscodewouldbeeasiertounderstandifitdidnotplacethatburdenuponyou,theintrepidreader.Thelogicthat’shiddeninsidetheversestringshouldbedispersedintomethods,andverse shouldfillitselfwithvaluesbysendingmessages.

Terminology:MethodversusMessageA"method"isdefinedonanobject,and

containsbehavior.Inthepreviousexample,

's'ifn!=1

's'ifn-1!=1

Page 20: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

theBottles classdefinesamethodnamedsong .

A"message"issentbyanobjecttoinvokebehavior.Intheaforementionedexample,the song methodsendsthe versesmessagetotheimplicitreceiver self .

Therefore,methodsaredefined,andmessagesaresent.

Theconfusionbetweenthesetermscomesaboutbecauseitiscommonforthereceiverofamessagetodefineamethodwhosenameexactlycorrespondstothatmessage.Considertheexampleabove.The song methodsendsthe versesmessageto self ,whichresultsinaninvocationoftheverses method.Thefactthatthemessagenameandthemethodnameareidenticalmaymakeitseemasifthetermsaresynonymous.

Theyarenot.Thinkofobjectsasblackboxes.Methodsaredefinedwithinablackbox.Messagesarepassedbetweenthem.Therearemanywaysforanobjecttocheerfullyrespondtoamessageforwhichitdoesnotdefineamatchingmethod.Whileitiscommonformessagenamestomapdirectlytomethodnames,thereisnorequirementthatthisbeso.

DrawingadistinctionbetweenmessagesandmethodsimprovesyourOOmindset.Itallowsyoutoisolatetheintentionofthesenderfromtheimplementationinthereceiver.OOpromisesthatifyousendtherightmessage,thecorrectbehaviorwilloccur,regardlessofthenamesofthemethodsthateventuallygetinvoked.

Creatingamethodrequiresidentifyingthecodeyou’dliketoextractanddecidingonamethodname.This,inturn,requires

Page 21: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

namingtheconcept,andnamingthingsisjustplainhard.Inthecaseabove,it’sespeciallyhard.Thiscodenotonlycontainsmanyhiddenconcepts,butthoseconceptsaremixedtogether,conflated,suchthattheirindividualnaturesareobscured.Combiningmanyideasintoasmallsectionofcodemakesitdifficulttoisolateandnameanysingleconcept.

Whenyoufirstwriteapieceofcode,youobviouslyknowwhatitdoes.Therefore,duringinitialdevelopment,thepriceyoupayforpoornamesisrelativelylow.However,codeisreadmanymoretimesthanitiswritten,anditsultimatecostisoftenveryhighandpaidbysomeoneelse.Writingcodeislikewritingabook;youreffortsareforotherreaders.Althoughthestruggleforgoodnamesispainful,itisworththeeffortifyouwishyoureffortstosurvivetoberead.Codeclarityisbuiltuponnames.

Problemswithconsistency,duplicationandnamingconspiretomakethecodeinListing1.1:IncomprehensiblyConciselikelytobecostly.

Notethattheaboveassertionis,atthispoint,anunsupportedopinion.Thebestwaytojudgecodewouldbetocompareitsvaluetoitscost,butunfortunatelyit’shardtogetgooddata.Judgmentsaboutcodearethereforecommonlyreducedtoindividualopinion,andhumansarenotalwaysinaccord.There’snoperfectsolutiontothisproblem,buttheJudgingCodesection,laterinthischapter,suggestswaystoacquireempiricaldataaboutthegoodnessofcode.

Independentofalljudgmentabouthowwellabitofcodeisarranged,codeisalsochargedwithdoingwhatit’ssupposedtodonowaswellasbeingeasytoaltersothatitcandomorelater.Whileit’sdifficulttogetexactfiguresforvalueandcost,askingthefollowingquestionswillgiveyouinsightintothepotentialexpenseofabitofcode:

Page 22: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

1. Howdifficultwasittowrite?

2. Howhardisittounderstand?

3. Howexpensivewillitbetochange?

Thepast("wasit")isamemory,thefuture("willitbe")isimaginary,butthepresent("isit")istruerightnow.Theveryactoflookingatapieceofcodedeclaresthatyouwishtounderstanditatthismoment.Questions1and3abovemayormaynotconcernyou,butquestion2alwaysapplies.

Codeiseasytounderstandwhenitclearlyreflectstheproblemit’ssolvingandthusopenlyexposesthatproblem’sdomain.IfListing1.1:IncomprehensiblyConciseopenlyexposedthe99Bottlesdomain,abriefglanceatthecodewouldanswerthesequestions:

1. Howmanyversevariantsarethere?

2. Whichversesaremostalike?Inwhatway?

3. Whichversesaremostdifferent,andinwhatway?

4. Whatistheruletodeterminewhichversecomesnext?

Thesequestionsreflectcoreconceptsoftheproblem,yetnoneoftheiranswersareapparentinthissolution.Thenumberofvariants,thedifferencebetweenthevariants,andthealgorithmforloopingaredistressinglyobscure.Thiscodedoesnotreflectitsdomain,andthereforeyoucaninferthatitwasdifficulttowriteandwillbeachallengetochange.IfyouhadtocharacterizethegoalofthewriterofListing1.1:IncomprehensiblyConcise,youmightsuggestthattheirhighestprioritywasbrevity.Brevitymaybethesoulofwit,butitquicklybecomestediousincode.

Page 23: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Incomprehensibleconcisenessisclearlynotthebestsolutionforthe99Bottlesproblem.It’stimetoexamineonethat’smoreverbose.

1.1.2.SpeculativelyGeneralThisnextsolutionerrsinadifferentdirection.Itdoesmanythingswellbutcan’tresistindulginginunnecessarycomplexity.Havealookatthecodebelow:

Listing1.2:SpeculativelyGeneral

1 classBottles2 NoMore=lambdado|verse|3 "Nomorebottlesofbeeronthewall,"+4 "nomorebottlesofbeer.\n"+5 "Gotothestoreandbuysomemore,"+6 "99bottlesofbeeronthewall.\n"7 end8 9 LastOne=lambdado|verse|10 "1bottleofbeeronthewall,"+11 "1bottleofbeer.\n"+12 "Takeitdownandpassitaround,"+13 "nomorebottlesofbeeronthewall.\n"14 end15 16 Penultimate=lambdado|verse|17 "2bottlesofbeeronthewall,"+18 "2bottlesofbeer.\n"+19 "Takeonedownandpassitaround,"+20 "1bottleofbeeronthewall.\n"21 end

Page 24: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

22 23 Default=lambdado|verse|24 "#{verse.number}bottlesofbeeronthewall,"+25 "#{verse.number}bottlesofbeer.\n"+26 "Takeonedownandpassitaround,"+27 "#{verse.number-1}bottlesofbeeronthewall.\n"28 end29 30 defsong31 verses(99,0)32 end33 34 defverses(finish,start)35 (finish).downto(start).map{|verse_number|36 verse(verse_number)}.join("\n")37 end38 39 defverse(number)40 verse_for(number).text41 end42 43 defverse_for(number)44 casenumber45 when0thenVerse.new(number,&NoMore)46 when1thenVerse.new(number,&LastOne)47 when2thenVerse.new(number,&Penultimate)48 elseVerse.new(number,&Default)49 end

Page 25: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

50 end51 end52 53 classVerse54 attr_reader:number55 definitialize(number,&lyrics)56 @number=number57 @lyrics=lyrics58 end59 60 deftext61 @lyrics.callself62 end63 end

Ifyoufindthiscodelessthanclear,you’renotalone.It’sconfusingenoughtowarrantanexplanation,butbecausetheexplanationnaturallyreflectsthecode,it’sconfusinginitsownright.Don’tworryifthefollowingparagraphsmuddlethingsfurther.Theirpurposeistohelpyouappreciatethecomplexityratherthanunderstandthedetails.

Thecodeabovefirstdefinesfourlambdas(lines2,9,16,and23)andsavesthemasconstants( NoMore , LastOne , Penultimate ,andDefault ).Noticethateachlambdatakesargument verse butonlyDefault actuallyreferstoit.Thecodethendefinesthe song andverses methods.Nextcomesthe verse method,whichpassesthecurrentversenumberto verse_for andsends text totheresult(line40).Thisisthelineofcodethatreturnsthecorrectstringforaverseofthesong.

Thingsgetmoreinterestingin verse_for ,butbeforeponderingthatmethod,lookaheadtothe Verse classonline53. Verseinstancesareinitializedwithtwoarguments, number and &lyrics ,

Page 26: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

andtheyrespondtotwomessages, number and text .The numbermethodsimplyreturnstheversenumberthatwaspassedduringinitialize.The text methodismorecomplicated;itsends call tolyrics ,passing self asanargument.

Ifyounowreturnto verse_for andexaminelines45-48,youcanseethatwheninstancesof Verse arecreated,the numberargumentisaversenumberandthe &lyrics argumentisoneofthelambdas.The verse_for methodgetsinvokedforeveryverseofthesong,andtherefore,onehundredinstancesof Verse willbecreated,eachcontainingaversenumberandthelambdathatcorrespondstothatnumber.

Tosummarize,sending verse(number) toaninstanceof Bottlesinvokes verse_for(number) ,whichusesthevalueof number toselectthecorrectlambdaonwhichtocreateandreturnaninstanceofVerse .The verse methodthensends text tothereturned Verse ,whichinturnsends call tothelambda,passing self asanargument.Thisinvokesthelambda,whichmayormaynotactuallyusetheargumentthatwaspassed.Regardless,executingthelambdareturnsastringthatcontainsthelyricsforoneverseofthesong.

Youcanbeforgivenifyoususpectthatthisisundulycomplicated.Itis.However,it’scuriousthatdespitethiscomplexity,Listing1.2:SpeculativelyGeneraldoesamuchbetterjobthanListing1.1:IncomprehensiblyConciseofansweringthedomainquestions:

1. Howmanyversevariantsarethere?Therearefourversevariants(theystartonlines2,9,16and23above).

2. Whichversesaremostalike?Inwhatway?Verses3-99aremostalike(asevidencedbythefactthatallareproducedbythe Default variant).

Page 27: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

3. Whichversesaremostdifferent?Inwhatway?Verses0,1and2areclearlydifferentfrom3-99,althoughit’snotobviousinwhatway.

4. Whatistheruletodeterminewhichverseshouldbesungnext?Burieddeepwithinthe NoMore lambdaisahard-coded"99,"whichmightcauseonetoinferthatverse99followsverse0.

Thissolution’sanswerstothefirstthreequestionsabovearequiteanimprovementoverthoseofListing1.1:IncomprehensiblyConcise.However,allisnotperfect;itstilldoespoorlyonthevalue/costquestions:

1. Howdifficultwasittowrite?There’sfarmorecodeherethanisneededtopassthetests.Thisunnecessarycodetooktimetowrite.

2. Howhardisittounderstand?Themanylevelsofindirectionareconfusing.Theirexistenceimpliesnecessity,butyoucouldstudythiscodeforalongtimewithoutdiscerningwhytheyareneeded.

3. Howexpensivewillitbetochange?Themerefactthatindirectionexistssuggeststhatit’simportant.Youmayfeelcompelledtounderstanditspurposebeforemakingchanges.

Asyoucanseefromtheseanswers,thissolutiondoesagoodjobofexposingcoreconcepts,butdoesabadjobofbeingworthitscost.Thisgoodjob/badjobdividereflectsafundamentalfissureinthecode.

Asidefromthe song and verses methods,thecodedoestwobasicthings.First,itdefinestemplatesforeachkindofverse(lines2-28),andsecond,itchoosestheappropriatetemplateforaspecificversenumberandrendersthatverse’slyrics(lines39-63).

Page 28: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Noticethattheversetemplatescontainalloftheinformationneededtoanswerthedomainquestions.Therearefourtemplates,andtherefore,theremustbefourversevariants.TheDefault templatehandlesverses3through99,andtheseversesareclearlymostalike.Verses0,1,and2havetheirownspecialtemplates,soeachmustbeunique.Thefourtemplates(ifyouignorethefactthatthey’restoredinlambdas)areverystraightforward,whichmakesansweringthedomainquestionseasy.

Butit’snotthetemplatesthatarecostly;it’sthecodethatchoosesatemplateandrendersthelyricsforaverse.Thischoosing/renderingcodeisoverlycomplicated,andwhilecomplexityisnotforbidden,itisrequiredtopayitsownway.Inthiscase,complexitydoesnot.

Insteadof1)definingalambdatoholdatemplate,2)creatinganewobjecttoholdthelambda,and3)invokingthelambdawithself asanargument,thecodecouldmerelyhaveputeachofthefourtemplatesintoamethodandthenusedthecasestatementonlines45-48toinvokethecorrectone.Thelambdasaren’tneeded,noristhe Verse class,andtheroutebetweenthemisaseriesofpointlessjumpsthroughneedlesshoops.

Giventheobvioussuperiorityofthisalternativeimplementation,howonearthdidthe"callingalambda"variantcomeabout?Atthisremove,it’sdifficulttobecertainofthemotivation,butthecodegivestheimpressionthatitsauthorfearedthatthelogicforselectingorinvokingatemplatewouldsomedayneedtochange,andsoaddedlevelsofindirectioninamisguidedattempttoprotectagainstthatday.

Theydidnotsucceed.Relativetothealternative,Listing1.2:SpeculativelyGeneralishardertounderstandwithoutbeingeasiertochange.Theadditionalcomplexitydoesnotpayoff.Theauthormayhaveactedwiththebestofintentions,butsomewherealongtheway,theircommitmenttotheplan

Page 29: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

overcamegoodsense.

Programmersloveclevercode.It’slikeaneatcardtrickthatusessleightofhandandmisdirectiontomakemagic.Writingit,orsuddenlyunderstandingit,suppliesalittleburstofappreciativepleasure.However,thisverypleasuredistractstheeyeandseducesthemind,andallowsclevernesstowormitswayintoinappropriateplaces.

Youmustresistbeingcleverforitsownsake.IfyouarecapableofconceivingandimplementingasolutionascomplexasListing1.2:SpeculativelyGeneral,itisincumbentuponyoutoacceptthehardertaskandwritesimplercode.

NeitherListing1.2:SpeculativelyGeneralnorListing1.1:IncomprehensiblyConciseisthebestsolutionfor99Bottles.Perhaps,aswastrueforporridge,thethirdsolutionwillbejustright.[2]

1.1.3.ConcretelyAbstractThissolutionvaliantlyattemptstonametheconceptsinthedomain.Here’sthecode:

Listing1.3:ConcretelyAbstract

1 classBottles2 3 defsong4 verses(99,0)5 end6 7 defverses(bottles_at_start,bottles_at_end)8 bottles_at_start.downto(bottles_at_end).mapdo

Page 30: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

|bottles|9 verse(bottles)10 end.join("\n")11 end12 13 defverse(bottles)14 Round.new(bottles).to_s15 end16 end17 18 classRound19 attr_reader:bottles20 definitialize(bottles)21 @bottles=bottles22 end23 24 defto_s25 challenge+response26 end27 28 defchallenge29 bottles_of_beer.capitalize+""+on_wall+","+30 bottles_of_beer+".\n"31 end32 33 defresponse34 go_to_the_store_or_take_one_down+","+35 bottles_of_beer+""+on_wall+".\n"36 end37

Page 31: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

38 defbottles_of_beer39 "#{anglicized_bottle_count}#{pluralized_bottle_form}of#{beer}"40 end41 42 defbeer43 "beer"44 end45 46 defon_wall47 "onthewall"48 end49 50 defpluralized_bottle_form51 last_beer??"bottle":"bottles"52 end53 54 defanglicized_bottle_count55 all_out??"nomore":bottles.to_s56 end57 58 defgo_to_the_store_or_take_one_down59 ifall_out?60 @bottles=9961 buy_new_beer62 else63 lyrics=drink_beer64 @bottles-=165 lyrics66 end67 end

Page 32: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

68 69 defbuy_new_beer70 "Gotothestoreandbuysomemore"71 end72 73 defdrink_beer74 "Take#{it_or_one}downandpassitaround"75 end76 77 defit_or_one78 last_beer??"it":"one"79 end80 81 defall_out?82 bottles.zero?83 end84 85 deflast_beer?86 bottles==187 end88 end

Thissolutionischaracterizedbyhavingmanysmallmethods.Thisisnormallyagoodthing,butsomehowinthiscaseit’sgonebadlywrong.Havealookathowthissolutiondoesonthedomainquestions:

1. Howmanyversevariantsarethere?It’salmostimpossibletotell.

2. Whichversesaremostalike?Inwhatway?

Page 33: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Ditto.

3. Whichversesaremostdifferent?Inwhatway?Ditto.

4. Whatistheruletodeterminewhichverseshouldbesungnext?Ditto.

Itfaresnobetteronthevalue/costquestions.

1. Howdifficultwasittowrite?Difficult.Thisclearlytookafairamountofthoughtandtime.

2. Howhardisittounderstand?Theindividualmethodsareeasytounderstand,butdespitethis,it’stoughtogetasenseoftheentiresong.Thepartsdon’tseemtoadduptothewhole.

3. Howexpensivewillitbetochange?Whilechangingthecodeinsideanyindividualmethodischeap,inmanycases,onesimplechangewillcascadeandforcemanyotherchanges.

It’sobviousthattheauthorofthiscodewascommittedtodoingtherightthing,andthattheycarefullyfollowedtheRed,Green,Refactorstyleofwritingcode.Thevariousstringsthatmakeupthesongareneverrepeated—itlooksasthoughthesestringswererefactoredintoseparatemethodsatthefirstsignofduplication.

ThecodeisDRY,andDRYingoutcodeshouldsaveyoumoney.DRYpromisesthatifyouputachunkofcodeintoamethodandtheninvokethatmethodinsteadofduplicatingthecode,youwillsavemoneylaterifthebehaviorofthatchunkchanges.Whenyouinvokeamethodinsteadofimplementingbehavior,youaddalevelofindirection.Thisindirectionmakesthedetailsofwhat’shappeninghardertounderstand,butDRYpromisesthatin

Page 34: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

return,yourcodewillbeeasiertochange.

TheDon’tRepeatYourselfprinciple,likeallprinciplesofobject-orienteddesign,iscompletelytrue.However,despitethatfactthatthecodeaboveisDRY,therearemanywaysinwhichit’sexpensivetochange.

Oneofmanypossibleexamplesisthe beer methodonline42.Thismethodreturnsthestring"beer,"whichoccursnowhereelseinthecode.Tochangethedrinkto"Kool-Aid,"youneedonlychangeline43toreturn"Kool-Aid"insteadof"beer."Asthisonesmallchangeisallthat’sneededtomeetthe"Kool-Aid"requirement,onthesurface,DRYhasfulfilleditspromise.However,stepbackaminuteandconsidertheresultingmethod:

Orpondersomeoftheothermethodnames:

Inlightofthe"Kool-Aid"change,thesenamesareterriblyconfusing.Thesemethodnamesnolongermakesensewheretheyaredefined,andtheyaretotallymisleadinginplaceswheretheyareused.Tomitigatethisconfusion,younotonlyhavetochange"beer"to"Kool-Aid"insidethismethod,butyoualsohavetomakethesamechangetoeverymethodnamethatincludestheword"beer"andthenagaintoeverysenderofoneofthosemessages.

Thissmallchangeinrequirementsforcesachangeinmanyplaces,whichisexactlytheproblemDRYpromisestoavoid.The

defbeer"Kool-Aid"end

defbottles_of_beerdefbuy_new_beerdefdrink_beerdeflast_beer?

Page 35: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

faulthere,however,liesnotwiththeDRYprinciple,butwiththenamesofthemethods.

Whenyouchoose beer asthenameofamethodthatreturnsthestring"beer,"you’venamedthemethodafterwhatitdoesrightnow.Unfortunately,whenyounameamethodafteritscurrentimplementation,youcanneverchangethatinternalimplementationwithoutruiningthemethodname.

Youshouldnamemethodsnotafterwhattheydo,butafterwhattheymean,whattheyrepresentinthecontextofyourdomain.Ifyouweretoaskyourcustomerwhat"beer"isinthecontextofthe99Bottlessong,theywouldnotanswer"Beeristhebeer,"theywouldsaysomethinglike"Beeristhethingyoudrink"or"Beeristhebeverage."

"Beer"and"Kool-Aid"arekindsofbeverages;theword"beverage"isonelevelofabstractionhigherthan"beer."Namingthemethodatthisslightlyhigherlevelofabstractionisolatesthecodefromchangesintheimplementationdetails.Ifyouchoosebeverage forthemethodname,goingfrom:

to:

makesperfectsenseandrequiresnootherchange.

Listing1.3:ConcretelyAbstractcontainsmanysmallmethods,andthestringsthatmakeupthesongarecompletelyDRY.Thesetwothingsexertaforceforgoodthatshouldresultincodethat’s

defbeverage"beer"end

defbeverage"Kool-Aid"end

Page 36: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

easytochange.However,inConcretelyAbstract,thisforceisovercomebythehighcostofdealingwithmethodsthatarenamedatthewronglevelofabstraction.Thesemethodnamesraisethecostofchange.

Therefore,onelessontobegleanedfromthissolutionisthatyoushouldnamemethodsaftertheconcepttheyrepresentratherthanhowtheycurrentlybehave.However,noticethatevenifyoueditedthecodetoimproveeverymethodname,thiscodestillisn’tquiteright.

Changingthenameofthe beer methodto beverage makesiteasytoreplacethestring"beer"withthestring"Kool-Aid"butdoesnothingtoimprovethiscode’sscoreonthedomainquestions.Theproblemgoesfardeeperthanhavingmethodsthatarenamedatthewronglevelofabstraction.It’snotjustthenamesthatarewrong,butthemethodsthemselves.Manymethodsinthiscoderepresentthewrongabstractions.

Theproblemofidentifyingtherightabstractionsisexploredinfuturechapters,butmeanwhileit’stimetoconsideronemoresolution.

1.1.4.ShamelessGreenNoneofthesolutionsshownthusfardoverywellonthevalue/costquestions.IncomprehensiblyConcisecaresonlyforterseness.SpeculativelyGeneraltriesforextensibilitybutachievesunwarrantedcomplexity.ConcretelyAbstract'sheartisintherightplacebutcan’tgetitsfeetoutofthemud.

Solvingthe99Bottlesprobleminanyofthesewaysrequiresmoreeffortthanisnecessaryandresultsinmorecomplexitythanisneeded.Thesesolutionscosttoomuch;theydotoomanyofthewrongthingsandtoofewoftheright.

SpeculativelyGeneralandConcretelyAbstractwerebothwritten

Page 37: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

withaneyetowardreducingfuturecosts,anditisdistressingtoseegoodintentionsfailsospectacularly.It’saparticularshamethattheabstractionsarewrongbecause,giventheopportunitytodoso,thecodeiscompletelywillingtorevealabstractionsthatareright.Thefailurehereisnotbadintention—it’sinsufficientpatience.

Thisnextexampleispatientandsoprovidesanantidoteforallthathascomebefore.ThefollowingsolutionisknownasShamelessGreen:

Listing1.4:ShamelessGreen

1 classBottles2 3 defsong4 verses(99,0)5 end6 7 defverses(starting,ending)8 starting.downto(ending).map{|i|verse(i)}.join("\n")9 end10 11 defverse(number)12 casenumber13 when014 "Nomorebottlesofbeeronthewall,"+15 "nomorebottlesofbeer.\n"+16 "Gotothestoreandbuysomemore,"+17 "99bottlesofbeeronthewall.\n"18 when1

Page 38: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

19 "1bottleofbeeronthewall,"+20 "1bottleofbeer.\n"+21 "Takeitdownandpassitaround,"+22 "nomorebottlesofbeeronthewall.\n"23 when224 "2bottlesofbeeronthewall,"+25 "2bottlesofbeer.\n"+26 "Takeonedownandpassitaround,"+27 "1bottleofbeeronthewall.\n"28 else29 "#{number}bottlesofbeeronthewall,"+30 "#{number}bottlesofbeer.\n"+31 "Takeonedownandpassitaround,"+32 "#{number-1}bottlesofbeeronthewall.\n"33 end34 end35 36 end

Themostimmediatelyapparentqualityofthiscodeishowverysimpleitis.There’snothingtrickyhere.Thecodeisgratifyinglyeasytocomprehend.Notonlythat,despiteitslackofcomplexitythissolutiondoesextremelywellonthedomainquestions.

1. Howmanyversevariantsarethere?Clearly,four.

2. Whichversesaremostalike?Inwhatway?3-99,whereonlytheversenumbervaries.

3. Whichversesaremostdifferent?Inwhatway?

Page 39: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

0,1and2aredifferentfrom3-99,thoughfiguringouthowrequiresparsingstringswithyoureyes.

4. Whatistheruletodeterminewhichverseshouldbesungnext?Thisisstillnotexplicit.The0versecontainsadeeplyburied,hard-coded99.

TheseanswersareidenticaltothoseachievedbyListing1.2:SpeculativelyGeneral.ShamelessGreenandSpeculativelyGeneraldiffer,though,inhowtheycompareonthevalue/costquestions.ShamelessGreenisasubstantialimprovement.

1. Howdifficultwasthistowrite?Itwaseasytowrite.

2. Howhardisittounderstand?Itiseasytounderstand.

3. Howexpensivewillitbetochange?Itwillbecheaptochange.Eventhoughtheversestringsareduplicated,ifonechangesit’seasytokeeptheothersinsync.

Bythecriteriathathavebeenestablished,ShamelessGreenisclearlythebestsolution,yetalmostnoonewritesit.Itfeelsembarrassinglyeasy,andismissingmanyqualitiesthatyouexpectingoodcode.Itduplicatesstringsandcontainsfewnamedabstractions.

Mostprogrammershaveapowerfulurgetodomore,butsometimesit’sbesttostoprighthere.Ifyouwerechargedwithwritingthecodetoproducethelyricstothe99Bottlessong,itisdifficulttoimaginefulfillingthatrequirementinamorecost-effectiveway.

TheShamelessGreensolutionisdisturbingbecause,althoughthecodeiseasytounderstand,itmakesnoprovisionforchange.Inthisparticularcase,thesongissounlikelytochangethatbetting

Page 40: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

thatthecodeis"goodenough"shouldpayoff.However,ifyoupretendthatthisproblemisaproxyforareal,productionapplication,thepropercourseofactionisnotsoclear.

WhenyouDRYoutduplicationorcreateamethodtonameabitofcode,youaddlevelsofindirectionthatmakeitmoreabstract.Intheorytheseabstractionsmakecodeeasiertounderstandandchange,butinpracticetheyoftenachievetheopposite.Oneofthebiggestchallengesofdesignisknowingwhentostop,anddecidingwellrequiresmakingjudgmentsaboutcode.

1.2.JudgingCodeYounowhaveaccesstofivedifferentsolutionstothe99BottlesofBeerproblem;thefourlistedintheprecedingsectionandtheoneyouwroteyourself.

Whichisbest?

Youlikelyhaveanopiniononthisquestion—onewhich,granted,mayhavebeenswayedbythecommentaryabove.However,independentofthatgentleinfluence,thesumofyourexperiencesandexpectationspredisposeyoutoassessthegoodnessofcodeinyourownuniqueway.

Youjudgecodeconstantly.Writingcoderequiresmakingchoices;thechoicesyoumakereflectpersonal,internalizedcriteria.Youintendtowrite"good"codeandif,inyourestimation,you’vewritten"bad"code,youareclearlyawarethatyou’vedoneso.Regardlessofhowimplicit,unachievable,orunhelpfultheymaybe,youalreadyhaverulesaboutcode.

Whilehavingstandardsofanysortisavirtue,thechanceofachievingyourstandardsisimprovediftheyareexplicitandquantifiable.Answeringthequestion"Whatmakescodegood?"thusrequiresdefininggoodnessinconcreteandactionableways.

Page 41: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Thisisharderthanonemightthink.

1.2.1.EvaluatingCodeBasedonOpinionYou’dthinkthatbynow,therewouldexistauniversallyagreedupondefinitionofgoodcodethatcouldunambiguouslyguideourprogrammingbehavior.Theunfortunatetruthisthat,notonlyarethereamultitudeofdefinitions,butthatthesedefinitionsgenerallydescribehowcodelookswhenit’sdonewithoutprovidinganyconcreteguidanceabouthowtogetthere.

Justas"Everybodycomplainsabouttheweatherbutnobodydoesanythingaboutit",[3]everyonehasanopinionaboutwhatgoodcodelookslike,butthoseopinionsusuallydon’ttelluswhatactiontotaketocreateit.Robert"UncleBob"MartinopenshisbookCleanCodebyaskinganumberofluminariesforadefinitionofcleancode.Theirthoughtfulanswerscoulddescribeartorwineaseasilyassoftware.

Ilikemycodetobeelegantandefficient.—BjarneStroustrup

inventorofC++

Cleancodeis…fullofcrispabstractions…—GradyBooch

authorofObjectOrientedAnalysisandDesignwithApplications

Cleancodewaswrittenbysomeonewhocares.—MichaelFeathers

authorofWorkingEffectivelywithLegacyCode

Yourowndefinitionprobablyfollowsalongthesesamelines.Anypileofcodecanbemadetowork;goodcodenotonlyworks,butisalsosimple,understandable,expressiveandchangeable.

Theproblemwiththesedefinitionsisthatalthoughthey

Page 42: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

accuratelydescribehowgoodcodelooksonceit’swritten,theygivenohelpwithachievingthisstate,andprovidelittleguidanceforchoosingbetweencompetingsolutions.Theattributestheyusetodescribegoodcodearequalitative,notquantitative.

Whatdoesitmeantobe"elegant?"Whatmakesanabstraction"crisp?"Despitethefactthatthesedefinitionsareundeniablycorrect,nonearepreciseinameasurableway.Thislackofprecisionmeansthatwell-meaningprogrammerscanholdidenticallyhighstandardsandstillhavesignificantdisagreementsaboutrelativegoodness.Thus,wearguefruitlesslyaboutcode.

Sinceformfollowsfunction,goodcodecanalsobedefinedsimply,andsomewhatcircularly,asthatwhichprovidesthehighestvalueforthelowestcost.Oursenseofelegance,expressivenessandsimplicityisanoutgrowthofourexperienceswhenreadingandmodifyingcode.Codethatiseasytounderstandandapleasuretoextendnaturallyfeelssimpleandelegant.

Ifyoucouldidentifyandmeasurethesequalities,youcouldseekafterthemdiligentlyanddeliberately.Therefore,althoughyouropinionsaboutcodematter,youwouldbewellservedbyfacts.

1.2.2.EvaluatingCodeBasedonFactsA"metric"isameasureofsomequalityofcode.Metricsare,obviously,createdbypeople,soonecouldarguethattheymerelyexpressoneindividual’sopinion.Thatassertion,however,vastlyunderstatestheirworth.Measuresthatrisetobecomemetricsarebackedbyresearchthathasstoodthetestoftime.They’vebeenscrutinizedbymanypeopleovermanyyears.Youcanthinkofmetricsascrowd-sourcedopinionsaboutthequalityofcode.

Ifyouapplythesamemetrictotwodifferentpiecesofsourcecode,youcanthencomparethatcode(atleastintermsofwhat

Page 43: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

themetricmeasures)bycomparingtheresultingnumbers.Whileit’spossibletodisagreewiththepremiseofaspecificmetric,andtoinsistthatthethingitmeasuresisn’tuseful,therulesofmathematicsrequirealltoconcedethatthenumbersproducedbymetricsarefacts.

Itwouldbeextremelyhandytohaveagreed-uponfactswithwhichtocomparecode.Insearchofthesefacts,thissectionexaminesthreedifferentmetrics:SourceLinesofCode,CyclomaticComplexity,andABC.

SourceLinesofCodeInthedaysofyore,thedesireforreproducible,reliableinformationaboutthecostofdevelopingapplicationsledtothecreationofametricknownsimplyasSourceLinesofCode(SLOC,sometimesshortenedtojustLOC).Thisonenumberhasbeenusedtopredictthetotaleffortneededtodevelopsoftware,tomeasuretheproductivityofthosewhowriteit,andtopredictthecostofmaintainingit.

Themetrichastheadvantageofbeingeasilygarneredandreproduced,butsuffersfrommanyflaws.

UsingSLOCtopredictthedevelopmenteffortneededforanewprojectisdonebycountingtheSLOCofexistingprojectsforwhichtotaleffortisknown,decidingwhichofthoseexistingprojectsthenewprojectmostresembles,andthenrunningacostestimationmodeltomaketheprediction.Ifthepersondoingtheestimatingiscorrectaboutwhichexistingproject(s)thenewprojectmostcloselyresembles,thispredictionmaybeaccurate.

Measuringprogrammerproductivitybycountinglinesofcodeassumesthatallprogrammerswriteequallyefficientcode.However,noviceprogrammersareoftenfarmoreverbosethanthosewithmoreexperience.Despitethefactthatnoviceswritemorecodetoproducelessfunction,bythismetric,theycanseem

Page 44: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

moreproductive.

Whilethecostofmaintenanceisrelatedtothesizeofanapplication,thewayinwhichcodeisorganizedalsomatters.Itischeapertomaintainawell-designedapplicationthanitistomaintainapileofspaghetti-code.

SLOCnumbersreflectcodevolume,andwhileit’susefulforsomepurposes,knowingSLOCaloneisnotenoughtopredictcodequality.

CyclomaticComplexityIn1976,ThomasJ.McCabe,Sr.publishedAComplexityMeasure,inwhichheasserted:

What is needed is a mathematical technique that willprovideaquantitativebasisformodularizationandallowustoidentifysoftwaremodulesthatwillbedifficulttotestormaintain.

A"mathematicaltechnique"toidentifycodethatis"difficulttotestormaintain"--thiscouldbetheperfecttoolforassessingcode.Inhispaper,McCabedescribeshisCyclomaticComplexitymetric,analgorithmthatcountsthenumberofuniqueexecutionpathsthroughabodyofsourcecode.Thinkofthisalgorithmasalittlemachinethatpondersyourcodeandthenmapsoutallthepossibleroutesthrougheverycombinationofeverybranchofeveryconditional.Amethodwithmanydeeplynestedconditionalswouldscoreveryhigh,whileamethodwithnoconditionalsatallwouldscore0.

Cyclomaticcomplexitydoesnotpredictapplicationdevelopmenttimenordoesitmeasureprogrammerproductivity.Itsdesiretoidentifycodethatisdifficulttotestormaintainaimsitdirectlyatcodequality.

Page 45: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Cyclomaticcomplexitycanbeusedinseveralways.First,youcanuseittocomparecode.Ifyouhavetwovariantsofthesamemethod,youcanchoosebetweenthembasedontheircyclomaticcomplexity.Lowerscoresarebetterandsobyextensionthecodewiththelowestscoreisthebest.

Next,youcanuseittolimitoverallcomplexity.Youcansetstandardsforhowhighascoreyou’rewillingtoaccept,andrequireexplicitdispensationbeforeallowingcodetoexceedthismaximum.

Finally,youcanuseittodetermineifyou’vewrittenenoughtests.Cyclomaticcomplexitytellsyoutheminimumnumberoftestsneededtocoverallofthelogicinthecode.Ifyouhavefewerteststhancyclomaticcomplexityrecommends,youdon’thavecompletetestcoverage.

Cyclomaticcomplexitysoundsgreat,andit’seasytoseethatitcouldbeuseful,butitviewstheworldofcodethroughanarrowlens.

Assignments,BranchesandConditions(ABC)MetricTheproblemwithcyclomaticcomplexityisthatitdoesn’ttakeeverythingintoaccount.Codedoesmorethanjustevaluateconditions;italsoassignsvaluestovariablesandsendsmessages.Thesethingsaddup,andasyoudomoreandmoreofeach,yourcodebecomesincreasinglydifficulttounderstand.

In1997,twenty-oneyearsaftertheunveilingofcyclomaticcomplexity,JerryFitzpatrickpublishedApplyingtheABCMetrictoC,C++,andJava,inwhichhedescribesametricthatdoesconsidermorethanconditionals.HisABCstandsforassignments,branchesandconditions,where:

Assignmentsisacountofvariableassignments.

Page 46: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Branchescountsnotbranchesofanifstatement(asonecouldforgivablyinfer)butbranchesofcontrol,i.e.,itcountsfunctioncallsormessagesends.

Conditionscountsconditionallogic.

FitzpatrickdescribestheABCmetricasameasureofsize,asifABCisamoresophisticatedversionofSLOC.Thisishismetricsohecertainlygetstosaywhatitrepresents,butyouwillnotgowrongifyouthinkofABCscoresasreflectingcognitiveasopposedtophysicalsize.HighABCnumbersindicatecodethattakesupalotofmentalspace.Inthissense,ABCisameasureofcomplexity.Highlycomplexcodeisdifficulttounderstandandchange,thereforeABCscoresareaproxyforcodequality.

ThemostpopulartoolforgeneratingABCscoresforRubycodeisRyanDavis’sFlog.FlogismoreABC-ishthanstrictlyABC.DavishasspecificallytunedittoreflecthisconsideredopinionaboutwhatmakesforgoodRubycode.Ifyou’reinterestedinthewaysinwhichFlogdiffersfromclassicABC,youcanfindoutbysimplybrowsingthesourcecode,butyoudon’thavetodelveintothegorydetailstobenefitfromrunningthismetricagainstyourowncode.

Flogscoresprovideanindependentperspectivethatmaychallengeyourideasaboutcomplexityanddesign.Highscoressuggestthatcodewillbehardtotestandexpensivetomaintain.IfyoubelieveyourcodetobesimplebutFlogsaysotherwise,youshouldthinkagain.

EveryexampleinthisbookwilleventuallyberunthroughFlogandtherelativescoreswillbecomparedanddiscussed.AlthoughFlogscoresaren’teverything,theyareverydefinitelyausefulsomething.

Metricsarefalliblebuthumanopinionisnomoreprecise.Checkingmetricsregularlywillkeepyouhumbleandimprove

Page 47: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

yourcode.

1.2.3.ComparingSolutionsNowthatyouhavesomeinsightintocodemetrics,it’stimetoexaminesomescoresforthecodeexamplesshowninthischapter.

Thefollowingtableshowseachsolution’stotallinesofcode(SLOC),totalFlogscore,andworstscoring"bit."

Table1.1:FlogScores

Solution SLOC FlogTotal

FlogWorstBit

Listing1.1:IncomprehensiblyConcise

19 42.5 #verse 36.2

Listing1.2:SpeculativelyGeneral

63 50.6 lambdas 26.5

Listing1.3:ConcretelyAbstract

92 61.9 #challenge 14.4

Listing1.4:ShamelessGreen

34 25.6 #verse 19.3

Inmostcases,theworstscoringbitisamethod,butinthecaseofListing1.2:SpeculativelyGeneral,theworstscoreisearnedbythegroupoflambdasthataredefinedasconstants.

Thefollowingchartmakesthenumberseasiertocompare.AlthoughSLOCisnotrelatedtoFlogscore,thevaluesarein

Page 48: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

similarrangessoit’sconvenienttodisplayeverythingonthesamechart.

Figure1.1:FlogScoreChart

Thisgraphexposesanumberofinterestingpatterns.

First,itisunsurprisingthatsolutionswithmorelinesofcodetendtoFlogtohigherscores,i.e.,thattotalFlogscoregenerallyrisesintandemwithSLOC.ShamelessGreenisthenotableexception—itissecondlowestinSLOCbutlowestintotalFlogscorebyaconsiderablemargin.

Next,ConcretelyAbstractscoresattheextremeofeverydimension.Itcontainsthemostcode,Flogstothehighesttotal,andhasthebest,ifyouwill,"WorstBit"Flogscore.ThetotalFlogscoreisreasonableinlightofthetotallinesofcode,andthelowWorstBitscoreindicatesthatthemethodsaresmallandfocused.

ThesemetricssuggestthatConcretelyAbstractcontainsgood

Page 49: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

code,butasyoumayrecall,itdoesnot.Metricsclearlydon’ttellthewholestory.Theproblemhereisthatalthoughthecodeisnicelyarranged,itcontainsnamesthatareatthewronglevelofabstraction.ThesenamesmakeConcretelyAbstractexpensivetochangedespiteitsorderlyarrangement.ThemetricsoverstatethequalityofConcretelyAbstractbecausetheyapproveofthecodestructure,buttheyareunabletorecognizethepoornames.

SpeculativelyGeneralisthesecondlongestsolution,andhasthesecondhighestFlogandWorstBitscores.Itslengthandcomplexityreflectanattempttoarrangethecodesuchthatcertainimaginedchangeswillbeeasy,i.e.toguessthefuture.Theseguessesareunlikelytopayoffandrelativetotheothersolutions,SpeculativelyGeneralisbothlongerandmorecomplexthannecessary.

Thefinaltwoexamples,IncomprehensiblyConciseandShamelessGreen,aresimilarinthatmostoftheircomplexityiscontainedinasinglemethod.Ineachcase,thescoreoftheirworstbitisveryneartotheirtotalscore.Thisreflectsthefactthatbotharebasicallyproceduresandthatneitherhasattemptedtoidentifyabstractions.

Despitethissimilarity,ifyoucomparetheirSLOCscorestotheirtotalFlogscores,you’llseethattheyarealsoverydifferent.IncomprehensiblyConcisehasahighFlogscorerelativetoSLOC,ShamelessGreenhastheopposite.IncomprehensiblyConcisepacksalotofcomplexityintoafewlinesofcode.ShamelessGreenisbiasedintheotherdirection;ithasmorecodebutismuchsimpler.

Overall,ShamelessGreenhasthelowesttotalFlogscore,thesecondlowestSLOC,andthesecondlowestWorstBitscore.Ifyourgoalistowritestraightforwardcode,thesemetricspointyoutowardShamelessGreen.

Page 50: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

1.3.SummaryAsprogrammersgrow,theygetbetteratsolvingchallengingproblems,andbecomecomfortablewithcomplexity.Thishigherlevelofcomfortsometimesleadstothebeliefthatcomplexityisinevitable,asifit’sthenatural,inescapablestateofallfinishedcode.However,there’ssomethingbeyondcomplexity—ahigherlevelofsimplicity.Infinitelyexperiencedprogrammersdonotwriteinfinitelycomplexcode;theywritecodethat’sblindinglysimple.

Thischapterexaminedfourpossiblesolutionstothe99Bottlesproblemasapreludetodefiningwhatitmeanstowritesimplecode.Itusedmetricsasastartingpoint,injectedabitofcommonsense,andlandedonShamelessGreen.

ShamelessGreenisdefinedasthesolutionwhichquicklyreachesgreenwhileprioritizingunderstandabilityoverchangeability.Itusesteststodrivecomprehension,andpatientlyaccumulatesconcreteexampleswhileawaitinginsightintounderlyingabstractions.Itdoesn’tdisputethatDRYisgood,ratheritbelievesthatitischeapertomanagetemporaryduplicationthantorecoverfromincorrectabstractions.

WritingShamelessGreenisfast,andtheresultingcodemightbe"goodenough."Mostprogrammersfinditembarrassinglyduplicative,andthecodeiscertainlynotveryobject-oriented.However,ifnothingeverchanges,themostcost-effectivestrategyistodeploythiscodeandwalkaway.

Thechallengecomeswhenachangerequestarrives.Codethat’sgoodenoughwhennothingeverchangesmaywellbecodethat’snotgoodenoughwhenthingsdo.Chapter3introducesjustsuchachange,andinthatchapteryou’llbeginimprovingthecode.Beforemovingon,however,it’stimetotakeastepback,andlearnhowtotest-driveShamelessGreen.

Page 51: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

2.TestDrivingShamelessGreenThepreviouschapterexaminedfoursolutionstothe99Bottlesproblem,andassertedthattheoneknownasShamelessGreenisbest.TheShamelessGreensolutionconsistsofintention-revealing,workingsoftware,andistheresultofwritingsimplecodetopassaseriesofpre-suppliedtests.

TheprovenanceofthecodethatwaswritteninChapter1isobvious,butthetestsappearedwithoutexplanation.Itisnowtimetotakeastepback,andinvestigatehowtocreateteststhatleadtoShamelessGreen.

2.1.UnderstandingTestingAgenerationago,ahandfulofextremeprogramming(XP)practitionersbeganwritingautomatedtestsusingatechniquetheycalled"testfirstdevelopment."Theirideasweresoinfluentialthatautomatedtestsarenowthenorm,andthesetestsareoftenwrittenfirst,inpreludetowritingcode.

Thepracticeofwritingtestsbeforewritingcodebecomeknownastest-drivendevelopment(TDD).Initssimplestform,TDDworkslikethis:

1. Writeatest.Becausethecodedoesnotyetexist,thistestfails.Testrunnersusuallydisplayfailingtestsinred.

2. Makeitrun.Writethecodetomakethetestpass.Testrunnerscommonlydisplaypassingtestsingreen.

3. Makeitright.Eachtimeyoureturntogreen,youcanrefactoranycodeinto

Page 52: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

abettershape,confidentthatitremainscorrectifthetestscontinuetopass.

InTest-DrivenDevelopmentbyExample,KentBeckdescribesthisastheRed/Green/Refactorcycleandcallsit"theTDDmantra."

Theideasoftesting,andoftestingfirst,havewontheheartsandmindsofprogrammers.However,acommitmenttowritingtestsdoesn’tmakethiseasy.TDDpresentsanever-endingchallenge.Youmustrepeatedlydecidewhichtesttowritenext,howtoarrangecodesothatthetestpasses,andhowmuchrefactoringtodoonceitdoes.Eachdecisionrequiresjudgmentandhasconsequences.

IfyourTDDjudgmentisnotyetfullydeveloped,it’sreasonabletotemporarilyadoptthatofamaster.Here’sanexcellentguidingprinciple:

Quickgreenexcusesallsins.—KentBeck

Test-DrivenDevelopmentbyExample

Greenmeanssafety.Greenindicatesthat,atleastasevidencedbythetestsathand,youunderstandtheproblem.Greenisthewallatyourbackthatletsyoumoveforwardwithconfidence.Gettingtogreenquicklysimplifiesallthatfollows.

Thischapterillustrateshowtoincrementallycreatetestsandthenusetheseteststodrivethedevelopmentofcode.TheexamplesobedientlyfollowtheRed/Green/Refactorcycle,butarefairlyconservative.Becausetheinitialgoalismoreaboutreachinggreenthanwritingperfectcode,therefactoringstepsometimesremovesduplicationandothertimesretainsit.

Theplanistocreateteststhatthoroughlydescribethe99Bottlesproblem,andthentosolvetheproblemwiththeimplementation

Page 53: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

knownasShamelessGreen.TheShamelessGreensolutionstrivesformaximumunderstandabilitybutisgenerallyunconcernedwithchangeability.ShamelessGreendoesnotassertthatchangeabilityisn’timportant;itmerelyrecognizesthatgettingtogreenquicklyisoftenatoddswithwritingperfectlychangeablecode.Thischapterconcentratesoncreatingthetestsandwritingsimplecodetopassthem.Futurechaptersrefactortheresultingcodetoimprovethedesign.

2.2.WritingtheFirstTestThefirsttestisoftenthemostdifficulttowrite.Atthispoint,youknowtheleastaboutwhateveritisyouintendtodo.Yourproblemisabig,fuzzy,amorphousblob,andit’schallengingtoreachinandcarveoffasinglepiece.Itfeelsimportanttochoosewell,becausewhereyoustartinformshowyou’llproceed,andultimatelydetermineswhereyou’llend.Thefirsttestcanthereforeseemfraughtwithperil.

Despiteitsapparentimport,thechoiceyoumakeherehardlymatters.Inthebeginning,youhaveideasabouttheproblembutactuallyknowverylittle.Yourideasmayturnouttobecorrect,butit’sjustaspossiblethattimewillprovethemwrong.Youcan’tfigureoutwhat’srightuntilyouwritesometests,atwhichtimeyoumayrealizethatthebestcourseofactionistothroweverythingawayandstartover.Therefore,thepurposeofsomeofyourtestsmightverywellbetoprovethattheyrepresentbadideas.Learningwhichideaswon’tworkisforwardprogress,howeverdisappointingitmaybeinthemoment.

So,whileitisimportanttoconsidertheproblemandtosketchoutanoverallplanbeforewritingthefirsttest,don’toverthinkit.Findastartingplaceandgetgoing,infaiththatasyouproceed,thefogwillclear.

IfyouweretosketchoutapublicApplicationProgrammingInterface(API)for99Bottles,itmightlooklikethis:

Page 54: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

verse(n)Returnthelyricsfortheversenumbern

verses(a,b)Returnthelyricsforversesnumberedathroughb

songReturnthelyricsfortheentiresong

ThisAPIallowsotherstorequestasingleverse,arangeofverses,ortheentiresong.

NowthatyouhaveaplanfortheAPI,thereareanumberofpossibilitiesforthefirsttest.Youcouldwriteatestfortheentiresong,foraseriesofcontiguousverses,orforanysingleverse.Becausetheeasiestwaytogetstartedistotacklesomethingthatyouthoroughlyunderstand,itmakessensetobeginbytestingasingleverse,andthemostlogicalfirstversetotestisthefirstversetobesung.Here’sthattest,writteninMinitest:

Listing2.1:Verse99Test

1 classBottlesTest<Minitest::Test2 deftest_the_first_verse3 expected="99bottlesofbeeronthewall,"+4 "99bottlesofbeer.\n"+5 "Takeonedownandpassitaround,"+6 "98bottlesofbeeronthewall.\n"7 assert_equalexpected,Bottles.new.verse(99)8 end9 end

Thetestaboveisassimpleascanbe,butnoticethatwritingitrequiredmakingmanydecisions.Itcontainsbothaclassname( Bottles )andamethodname( verse(n) ).Thistestassumesthat

Page 55: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

the Bottles classdefinesa verse methodthattakesanumberasanargument,anditassertsthatinvokingthatmethodwithanargumentof 99 returnsthelyricsforthe99thverse.

Thistest,likealltests,containsthreeparts:

SetupCreatethespecificenvironmentrequiredforthetest.

DoPerformtheactiontobetested.

VerifyConfirmtheresultisasexpected.

Lines3-6abovedefinetheexpectedresultandarethuspartofthesetup.Setupcontinuesonline7,whereanewbottleiscreatedvia Bottles.new .Line7alsosends verse(99) ,whichistheaction,andthenverifiestheresultwith assert_equal .

Runningthattestproducesthiserror:

TDDtellsyoutowritethesimplestcodethatwillpassthistest.Inthiscase,yourgoalistowriteonlyenoughcodetochangetheerrormessage.Theaboveerrorstatesthatthe Bottles classdoesnotyetexistsothefirststepistodefineit,asfollows:

Ifyou’renewtoTDD,thismayseemlikearidiculouslysmallstep.Becauseyouwrotethetest,youcanconfidentlypredictthatrunningitasecondtimewillnowproducethefollowingerror:

1)Error:BottlesTest#test_the_first_verse:NameError:uninitializedconstantBottlesTest::Bottlestest/bottles_test.rb:16:in`test_the_first_verse'

classBottlesend

Page 56: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Youcanchangethiserrormessagebyaddinga verse method.

Runningthetestnowproducesthiserror:

The verse methodrequiresanargument.Noticethatnothingaboutthismessagerequiresyoutoaddcodetothe verse method,somerelyaddinganargumentwillsufficetochangetheerror.

Rubyprogrammersbyconventionuse _ forthenameofanunusedargument.Thisargumentisunused,atleastatthismoment,so _ isareasonablenamefornow.

Runningthetestagainproducesthefollowingerror:

1)Error:BottlesTest#test_the_first_verse:NoMethodError:undefinedmethod`verse'for#<Bottles:0x007fde360741f0>test/bottles_test.rb:16:in`test_the_first_verse'

classBottlesdefverseendend

1)Error:BottlesTest#test_the_first_verse:ArgumentError:wrongnumberofarguments(1for0)Usersskm/Projects/books/99bottles/lib/bottles.rb:6:in`verse'test/bottles_test.rb:16:in`test_the_first_verse'

classBottlesdefverse(_)endend

1)Failure:

Page 57: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

There’sfinallysufficientcodesothatthetestfailsbecausetheoutputisnotasexpectedinsteadofdyingbecauseofanexception.

Minitestshowsthedifferencebetweenexpectedandactualoutputbyprefixingtheexpectedwith'-'andtheactualwith'+'.Therefore,youcaninterprettheabovefailureasindicatingthatMinitestexpected

"99bottlesofbeeronthewall,99bottlesofbeer."followedbyanewline,followedby

"Takeonedownandpassitaround,98bottlesofbeeronthewall."followedbyanothernewline

butinsteadgot nil .

Payparticularattentiontohownewlinesarerepresentedabove.Theexpectedoutputstringcontainstwonewlines,specifiedas \ninthetestandshownaslinebreaksabove.Thefinalexpectedline, -" ,representsthesecondnewline.

Onceyoureachthispoint,it’seasytomakethetestpass;justcopytheexpectedoutputintothe verse method:

Listing2.2:Verse99Code

1 classBottles

BottlesTest#test_the_first_verse[test/bottles_test.rb:16]:---expected+++actual@@-1,3+1@@-"99bottlesofbeeronthewall,99bottlesofbeer.-Takeonedownandpassitaround,98bottlesofbeeronthewall.-"+nil

Page 58: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

2 defverse(_)3 "99bottlesofbeeronthewall,"+4 "99bottlesofbeer.\n"+5 "Takeonedownandpassitaround,"+6 "98bottlesofbeeronthewall.\n"7 end8 end

TheAPIsaysthat verse takesanargument,butyoucanmakethisfirsttestpasswithoutactuallyusingit.Therefore,theargumentcontinuestobenamed _ inline2above.

Althoughthiscodepassesthetest,itclearlydoesn’tsolvetheentireproblem.Asamatteroffact,writingasecondtestwillbreakit.Whileitmayseempointlesstowriteanobviouslytemporaryandtransitionalbitofcode,thisistheessenceofTDD.

Youasthewriteroftestsknowthatthe verse methodmusteventuallytakethevalueofitsargumentintoaccount,butyouasthewriterofcodemustactinignoranceofthisfact.WhendoingTDD,youtogglebetweenwearingtwohats.Whileinthe"writingtests"hat,youkeepyoureyeonthebigpictureandworkyourwayforwardwiththeoverallplaninmind.Wheninthe"writingcode"hat,youpretendtoknownothingotherthantherequirementsspecifiedbythetestsathand.Thus,althougheachindividualtestiscorrect,untilareallwritten,thecodeisincomplete.

2.3.RemovingDuplicationNowthatthefirsttestpasses,youmustdecidewhatbesttotestnext.Thisnexttestshoulddothesimplest,mostusefulthingthatprovesyourexistingcodetobeincorrect.Whileitmayhavebeendifficulttoconceiveofthefirsttestbecausethepossibilitiesseeminfinite,thisnexttestisofteneasierbecauseitcheckssomething

Page 59: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

relativetothefirst.

Verses99through3arenearlyidentical—theydifferonlyinthatthenumberchangeswithineachverse.Thetestabovealreadychecksthehighendofthisrange,andthereforeitnowmakessensetotestthelow.

Thefollowingtestforverse3exposesthecurrent verse methodtobeinsufficient:

Listing2.3:Verse3Test

1 deftest_another_verse2 expected="3bottlesofbeeronthewall,"+3 "3bottlesofbeer.\n"+4 "Takeonedownandpassitaround,"+5 "2bottlesofbeeronthewall.\n"6 assert_equalexpected,Bottles.new.verse(3)7 end

TDDrequiresthatyoupasstestsbywritingsimplecode.However,mostprogrammingproblemshavemanysolutions,andit’snotalwaysclearwhichoneissimplest.Forexample,thefollowingcodepassesthesetestsbyaddingaconditionalthatchecksthevalueof number andreturnsthecorrectstring:

Listing2.4:Conditional

1 defverse(number)2 ifnumber==993 "99bottlesofbeeronthewall,"+4 "99bottlesofbeer.\n"+5 "Takeonedownandpassitaround,"+

Page 60: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

6 "98bottlesofbeeronthewall.\n"7 else8 "3bottlesofbeeronthewall,"+9 "3bottlesofbeer.\n"+10 "Takeonedownandpassitaround,"+11 "2bottlesofbeeronthewall.\n"12 end13 end

Atfirstglance,thiscodeappearstohaveachievedtheultimateinsimplicity.Itcanproduceonlythelyricsforverses99and3,andsodoestheabsoluteminimumneededtopassthetests.

Butconsiderthatitnowcontainsaconditionalwherenoneexistedbefore.ThismaycauseyoutorecallthediscussiononCyclomaticComplexityinChapter1.Thisconditionaladdsanewexecutionpaththroughthecode,andadditionalexecutionpathsincreasecomplexity.Thiscodeissimpleinthesensethatitcan’tdomuch,butitdoesthatonesmallthinginanoverlycomplexway.

Partoftheproblemisthatalthoughthe if statementswitchesonnumber ,thetrueandfalsebranchescontainmanythingsthatdon’tvarybasedon number .Thebranchesdodifferinthatonesays99/98,andtheother3/2,buttheyarethesameforalloftheotherlyrics.Thiscodeconflatesthingsthatchangewiththingsthatremainthesame,andsoforcesyoutoparsestringswithyoureyestofigureouthow number matters.

Ifyouweretoalterthe if statementtoreturnonlythethingsthatchange,thecodewouldlooklikethis:

Listing2.5:SparseConditional

1 defverse(number)

Page 61: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

2 ifnumber==993 n=994 else5 n=36 end7 8 "#{n}bottlesofbeeronthewall,"+9 "#{n}bottlesofbeer.\n"+10 "Takeonedownandpassitaround,"+11 "#{n-1}bottlesofbeeronthewall.\n"12 end13 end

Thiscodeisstillveryspecifictothetwoexistingtests—itcanproducethelyricsforverses99and3,andnoother.Notice,however,thatitnowhastwoparts.Thefirstpart(lines2-6)containstheconditional,andthesecond(lines8-11)containsatemplatethatcouldcorrectlygeneratemanyverses.Lines2-6arestillspecifictotheexistingtests,butnowthatyou’veseparatedthethingsthatchangefromthethingsthatremainthesame,lines8-11aregeneralizabletoeveryversebetween99and3.

Ifyouweretocontinuedownthe"specific"path,youwouldprogressivelyaddtestsfortheversesbetween97and4,eachtimealteringthe if statementtoaddaconditiontocheckforthatnumber.Followingthisstrategywouldultimatelyresultin97nearlyidenticaltestsand97nearlyidenticalverses;eachwoulddifferonlyinthevaluesofthenumbers.

Theobviousalternativeistoinsteadmakethecodemoregeneral.Becausetheexistingtemplatealreadyworksforeveryversebetween99and3,youcouldchangethiscodetoproducethoseversesbydeletingthe if statementandalteringthetemplatetoreferto number ,asshownhere:

Page 62: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Listing2.6:Interpolation

1 defverse(number)2 "#{number}bottlesofbeeronthewall,"+3 "#{number}bottlesofbeer.\n"+4 "Takeonedownandpassitaround,"+5 "#{number-1}bottlesofbeeronthewall.\n"6 end

Lefttoyourowndevices,yourinstinctwouldlikelyhavebeentowritethecodeabovewithoutbotheringwiththeintermediatestepsshowninListing2.4:ConditionalandListing2.5:SparseConditional.However,evenifyouwouldnaturallyhavestartedwiththismoregeneralversion,it’simportanttounderstandandbeabletoarticulatetheimplicationsoftheotherimplementation.

Thedifferencebetweenthesolutionthataddsaconditionalandthesolutionthatinterpolatesavariableintoastringisthatinthefirst,asthetestsgetmorespecific,thecodestaysequallyspecific.Everyversehasitsownpersonaltestanditsownindividualcode;therewillneverbeatimewhenthecodecandoanythingwhichisnotexplicitlytested.

However,inListing2.6:Interpolation,asthetestsgetmorespecific,thecodegetsmoregeneric.[4]Oncethetestofverse3iswritten,thecodeisthengeneralizedtoproducelyricsforallverseswithinthe3-99range.

RememberthatthepurposeofthischapteristoquicklygettoShamelessGreen.Withthatgoalinmind,considertheabovesolutionsandanswerthisquestion:Whichissimplest?

Aspreviouslynoted,metricsaren’teverything,buttheycan

Page 63: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

certainlybeausefulsomething.Inhopesthatdatawillhelpanswerthisquestion,thefollowingchartshowsSourceLinesOfCode,FlogscoreandCyclomaticComplexityforthevariants.

Table2.1:MetricsforCodeVariantsAfterTestsofVerse97and3

Solution SLOC FlogTotal CyclomaticComplexity

Listing2.4:Conditional

15 9.2 2

Listing2.5:SparseConditional

14 7.4 2

Listing2.6:Interpolation

7 5.1 1

Asyoucansee,astheexamplesprogress,theygetshorter,Flogtolowerscores,anddecreaseinCyclomaticComplexity.Thesemetricsindicatethateachsubsequentexampleisbetterthantheprevious,andthatthegeneralsolution,Listing2.6:Interpolation,isbestofall.Youmust,ofcourse,takemetricswithagrainofsalt,butheretheycastaclearvoteforListing2.6:Interpolation.

2.4.UnderstandingTransformationsTheprogressionshowninthemetricsabovelikelymapsnicelytoyourintuitivesenseofcorrectness.Intuition,however,ismerelyanunconsciousproddingtofollowanunarticulatedrule,anditturnsoutthatRobertMartinhasdraggedasetoftheseunconsciousrulesintothebrightlightofday.

IntheTransformationPriorityPremise,MartindefinesTransformationsas"simpleoperationsthatchangethebehaviorofcode."Notonlydoeshedescribeasetoftransformationsthat

Page 64: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

movecodefrommorespecifictomoregeneric,hearrangesthesetransformationsin"priority"order,fromsimplertomorecomplex.Heassertsthatwhenaproblemcanbesolvedwithanyoneofseveraltransformations,thetransformationwiththehighestpriorityissimplestandthereforebest.

Intheexamplesabove,Listing2.6:Interpolationtransformsthecodebyinterpolatingavariableintoastring.InMartin’sterminology,thistransformationisanexampleofconstant→scalar("replacingaconstantwithavariableoranargument"),whichisfourthinpriorityonhislist.Listing2.4:ConditionalandListing2.5:SparseConditionalbothtransformcodebyaddingaconditionalwherenonepreviouslyexisted.Martincallsthisunconditional→if("splittingtheexecutionpath")andplacesitsixthinpriority.

Becauselowernumberedtransformationhavepriorityoverhighernumberedones,theTransformationPriorityPremisealsocastsavoteforListing2.6:Interpolationasthesimplersolution.Interpolatingavariableintoastringissimplerthanaddinganewconditional.

Metrics,theTransformationPriorityPremise,andcommonsenseconvergeonListing2.6:Interpolationasthebestsolution.Thissolutiongeneralizesthecode,whichcreatesanabstraction.Theabstractionexpressesaconcept,atruthifyouwill,aboutthe99Bottlesdomain,i.e.thatverses99through3arealikeinthattheirnumberschangeinacommonandpredictableway.

Noticethatthisgeneralizationisnotonebitspeculative.Youhaven’toverreached,ormadeguessesabouttheuncertainfuture.Here,inthismoment,thereare97exampleswhichfollowthesamestraightforwardrule.There’splentyofevidencetosupportreplacingduplicationwithanabstraction,anddoingsoheresimplifiesthecode.

Thenextsectionexaminesanearlyidenticalsituationwherethe

Page 65: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

choiceofwhattodoaboutduplicationisnotnearlysoclear-cut.

2.5.ToleratingDuplicationVerses2,1and0muststillbetested,andeachisunique.Havingestablishedapatternoftestingversesintheorderthattheyappear,itmakessensetonexttestverse2.

Verse2differsinonesmallwayfromtheprevious97.Thefinalphraseinallpreviousversesrefersto"nnbottles"onthewall,i.e.theword"bottles"isplural.Hereinverse2,however,thefinalphrasereads"1bottle."Therefore,inline5ofthefollowingtestofverse2,theword"bottle"issingularinsteadofplural.

Listing2.7:Verse2Test

1 deftest_verse_22 expected="2bottlesofbeeronthewall,"+3 "2bottlesofbeer.\n"+4 "Takeonedownandpassitaround,"+5 "1bottleofbeeronthewall.\n"6 assert_equalexpected,Bottles.new.verse(2)7 end

Runningthattestproducesthefollowingfailure:

Thisfailureisperfect;thetestexpected 1bottle ,butgot 1bottles .

Aswastruewiththetestforverse3,therearetwofundamentallydifferentwaystopassthistest.Youcanaddanewconditional

-Takeonedownandpassitaround,1bottleofbeeronthewall.+Takeonedownandpassitaround,1bottlesofbeeronthewall.

Page 66: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

aroundtheexistingcode,orusethevalueof number insomewaywithinit.

Thisnextexampleillustratesthefirstpossibilitybywrappingthecodeinanewconditional:

Listing2.8:StarkConditional

1 defverse(number)2 ifnumber==23 "2bottlesofbeeronthewall,"+4 "2bottlesofbeer.\n"+5 "Takeonedownandpassitaround,"+6 "1bottleofbeeronthewall.\n"7 else8 "#{number}bottlesofbeeronthewall,"+9 "#{number}bottlesofbeer.\n"+10 "Takeonedownandpassitaround,"+11 "#{number-1}bottlesofbeeronthewall.\n"12 end13 end

Incontrast,thefollowingalternativeembedsinterpolatedlogicintotheexistingversestring:

Listing2.9:InterpolatedConditional

1 defverse(number)2 "#{number}bottlesofbeeronthewall,"+3 "#{number}bottlesofbeer.\n"+4 "Takeonedownandpassitaround,"+

Page 67: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

5 "#{number-1}bottle#{'s'unless(number-1)==1}ofbeer"+6 "onthewall.\n"7 end

Atfirstglance,thesetwosolutionslookalotlikethealternativespreviouslyexploredforverse3.Listing2.8:StarkConditionalwrapstheexistingcodeinanewconditional(asdidListing2.4:Conditional).Moreover,Listing2.9:InterpolatedConditionaladdsinterpolationtotheversestring(similartoListing2.6:Interpolation).

Thechoiceofthebestalternativeforverse3wasguidedbothbymetricsandthe"TransformationPriorityPremise,"andthosethingsmightagainbeusefulhere.Thefollowingtableshowsmetricsforthenewexamples:

Table2.2:MetricsforCodeVariantsAfterTestofVerse2

Solution SLOC FlogTotal

CyclomaticComplexity

Listing2.8:StarkConditional

15 10.8 2

Listing2.9:InterpolatedConditional

9 10.9 saikuroreports1

ThetableaboveshowsthatListing2.9:InterpolatedConditionalisnoticeablyshorterandonlyfractionallymorecomplexthanthealternative.Also,intheprevioussection,thetestofverse3presentedanapparentlyidenticalproblem,andinthatcase,theinterpolatedversionwasthebestsolution.Inthatcase,itwassimplertointerpolate number intothestringthantowrapthe

Page 68: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

stringinanewconditional.

ThemetricsandrecenthistoryseemtobecastingvotesforListing2.9:InterpolatedConditional,butinthiscasetheyarebothmisleading.

Thereareseveralproblemshere.First,oneofthemetricsshownaboveisjustplainwrong.SaikuroreportsthatListing2.9:InterpolatedConditionalhasaCyclomaticComplexityof1.However,line5ofthatexamplestates:

The unless keyworddefinesaconditionalwhichusesthevalueof number todeterminewhethertoappendtheletter"s"to"bottle."SaikurofailedtonoticethiskeywordandsoreportedtheCyclomaticComplexitytobe1,whichisincorrect.BothexamplesactuallyhaveaCyclomaticComplexityof2.

Therefore,theCyclomaticComplexityscoresareidenticalandtheFlogscoresvirtuallyso.Theonlydifferencebetweentheexamples,asleastasfarasthemetricsareconcerned,isthatListing2.9:InterpolatedConditionalisshorter.Shorterisoftenbetter,but,unfortunately,notinthiscase.

Aswasstatedintheprevioussection,astestsgetmorespecific,codeshouldbecomemoregeneric.Codebecomesmoregenericbybecomingmoreabstract.OnewaytomakecodemoreabstractistoDRYitout,thatis,toextractduplicatebitsofcodeintoasinglemethod,togivethatmethodaname,andthentorefertothecodebythisnewname.DRYingoutcoderemovestheduplicationandthusreducesitsoverallsize.

InListing2.9:InterpolatedConditional,thecodehasdefinitelygottenshorter.Onewouldhopethishappenedbecausethecodegotmoreabstract,butsadly,thisisnotthecase.Examinethenew

"#{number-1}bottle#{'s'unless(number-1)==1}ofbeer"

Page 69: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

conditional(repeatedbelowforconvenience):

Noticethat,evenifanabstractionlurkshere,itcertainlyhasnotbeennamed.Ifforcedtosuggestanameyoumightcalltheunderlyingconcept"pluralization,"assertingthatthenewconditionalhandlespluralizationbyaddingan"s"tothestring"bottle"when (number-1) isotherthan1.

Ifpluralizationisameaningfulabstractionfor99Bottles,perhapsyoushouldcreatea pluralize method,asfollows:

Unfortunately,thecodeabovejustconfusestheissue.Theconceptofpluralizationisaredherring.[5]Theneedforitappearedsuddenlyandsoitfeelslikeanimportant,meaningful,test-drivenidea,butonlybecauseyou’reworkingwithincompleteinformation.

ExamineListing2.9:InterpolatedConditionalandcountthenumberoftimestheword"bottle"occurs,regardlessofwhetherinsingularorpluralform.Thefactthat"bottle"isduplicatedmanytimessignalsthatthere’sanunderlyingconceptthathasnotyetbeenunearthed.Withinthedomainofthesong,"bottle/bottles"representssomethingimportant,andthatthingisnotpluralization.Thesewordsallhavesomethingincommon,andisolatingasingleoccurrencebehindpluralizationlogic

"#{number-1}bottle#{"s"unless(number-1)==1}ofbeer"

defverse#..."#{number-1}#{pluralize(number)}ofbeer"#...end

defpluralize(number)"bottle#{'s'unless(number-1)==1}"end

Page 70: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

obscuresthiscommonality.Makingonelookdifferentwillultimatelymakeithardertoseehowallarethesame.

Codelikethis pluralize methodgetswrittenwhenprogrammerstaketheDRYprincipletoextremes,asifthey’reallergictoduplication.DRYisimportantbutifappliedtooearly,andwithtoomuchvigor,itcandomoreharmthangood.Whenfacedwithasituationlikethis,askthesequestions:

DoesthechangeI’mcontemplatingmakethecodehardertounderstand?Whenabstractionsarecorrect,codeiseasytounderstand.Besuspiciousofanychangethatmuddiesthewaters;thissuggestsaninsufficientunderstandingoftheproblem.

Whatisthefuturecostofdoingnothingnow?Somechangescostthesameregardlessofwhetheryoumakethemnowordelaythemuntillater.Ifitdoesn’tincreaseyourcosts,delaymakingchanges.Thedaymaynevercomewhenyou’reforcedtomakethechange,ortimemayprovidebetterinformationaboutwhatthechangeshouldbe.Eitherway,waitingsavesyoumoney.

Whenwillthefuturearrive,i.e.howsoonwillIgetmoreinformation?Ifyou’reinthemiddleofwritingatestsuite,betterinformationisascloseasthenexttest.Squeezingallduplicationoutattheendofeverytestisnotnecessary.It’sperfectlyreasonabletotolerateabitofduplicationacrossseveraltests,hopingthatcodingupanumberofslightlyduplicativeexampleswillrevealthecorrectabstraction.It’sbettertotolerateduplicationthantoanticipatethewrongabstraction.

BothListing2.8:StarkConditionalandListing2.9:InterpolatedConditionalusethesametransformation(unconditional→if)andhavenearlyidenticalFlogandCyclomaticComplexityscores.

Page 71: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Fromthemetricspointofview,theonlymeasurabledifferencebetweentheexamplesisthatListing2.9:InterpolatedConditionalisshorter.Unfortunately,itisn’tshorterbecauseitcontainsanabstraction;it’sshorterbecauseitcramslackofunderstandingintoaverysmallspace.Thisbrevitymakesthecodehardertounderstand,andobscurestheconceptthatunderlies"bottles."

WritingShamelessGreenmeansoptimizingforunderstandability,notchangeability,andpatientlytoleratingduplicationifdoingsowillhelprevealtheunderlyingabstraction.Subsequenttests,orfuturerequirements,willprovidetheexactinformationnecessarytoimprovethecode.

AlthoughListing2.8:StarkConditionalretainssomeduplication,itresistscreatinganabstractioninadvanceofallavailableinformation,andsoisthebetterofthesetwosolutions.

2.6.HewingtothePlanAsyou’veseen,whenworkingtowardsShamelessGreen,itmakessensesometimestoeliminateduplicationandothertimestoretainit.TheShamelessGreensolutionisoptimizedtobestraightforwardandintention-revealing,anditdoesn’tmuchconcernitselfwithchangeabilityorfuturemaintenance.Thegoalistousegreentomaximizeyourunderstandingoftheproblemandtounearthallavailableinformationbeforecommittingtoabstractions.

Atsomepoint(actually,bytheendofthischapter)youwillhavewrittenafulltestsuitefor99Bottles,andacompleteShamelessGreensolution.Oncethat’sdone,you’llhavetwochoices.Youcoulddeploytheshamelesscodetoproductionandwalkaway,oryoucouldrefactoritintoamorechangeablearrangementbyDRYingoutduplicationandextractingabstractions.

WithinShamelessGreen,itisperfectlyacceptabletocreateabstractionsofideasforwhichyouhavemanyunambiguous

Page 72: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

examples.Forexample,Listing2.6:Interpolationreduced97versestoasingleabstraction.Having97examplesgivesyouconfidencethatyouareseeingthecorrectabstraction,andcreatingitmakesthecodeeasiertounderstand.

WhenwritingShamelessGreen,youshouldexpresstheunambiguousabstractionsbutavoidgraspingforthenot-quitevisibleones.Listing2.9:InterpolatedConditionaljammedaconditionalinsidetheversestringtoavoidhavingtowriteaseparate,mostlyduplicate,copyofverse2.Inthiscasethenewcodewasconfusingandtherewereonlytwoexamples,sohereit’sbettertotakeadeepbreathandwritedownallofverse2whileawaitingmoreinformation.

ThinkofthepathtoShamelessGreenasrunningonahorizontalaxis.Somechangespropelyouforwardalongthispathandhelpyouquicklyreachgreen,whileothersarespeculativeandpossiblydistractingtangentsinaverticaldirection.Youshouldcompletetheentirehorizontalpathbeforeindulginginanyverticaldigressions.

Nowthatyouhavecodeforverses99-2,itmakessensetocontinuealongthehorizontalpathandwriteatestforverse1,asfollows:

Listing2.10:Verse1Test

1 deftest_verse_12 expected="1bottleofbeeronthewall,"+3 "1bottleofbeer.\n"+4 "Takeitdownandpassitaround,"+5 "nomorebottlesofbeeronthewall.\n"6 assert_equalexpected,Bottles.new.verse(1)7 end

Page 73: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Verse1isdifferentfromtheothersinanumberofways:

Itbeginswith"1bottle"insteadof"1bottles"

Itsays"Takeitdown"insteadof"Takeonedown"

Itendswith"nomorebottles"insteadof"0bottles"

Whileit’spossibletopassthistestbyaddinginterpolatedlogictotheversestring,yourexperiencewiththepriorexampleshoulddissuadeyoufromchoosingtodoso.Verse1isevenmorespecialthanwasverse2,andhavingdecidedthatverse2wasdifferentenoughtojustifyaddingacondition,thepatientpathtoShamelessGreenrequiresthatyoumakethesamedecisioninthecaseofverse1.

Thefollowingexampleaddsthecodeforverse1.Whiledoingsoitconvertstheexisting if statementtoa case statement:

Listing2.11:Verse1Code

1 defverse(number)2 casenumber3 when14 "1bottleofbeeronthewall,"+5 "1bottleofbeer.\n"+6 "Takeitdownandpassitaround,"+7 "nomorebottlesofbeeronthewall.\n"8 when29 "2bottlesofbeeronthewall,"+10 "2bottlesofbeer.\n"+11 "Takeonedownandpassitaround,"+12 "1bottleofbeeronthewall.\n"13 else

Page 74: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

14 "#{number}bottlesofbeeronthewall,"+15 "#{number}bottlesofbeer.\n"+16 "Takeonedownandpassitaround,"+17 "#{number-1}bottlesofbeeronthewall.\n"18 end19 end

Giventhepriordiscussion,itmakessensetoaddanewbranchtotheconditionalforverse1,butthisexamplealsoswitchedfromif to case .Thesekeywordstelladifferentstory.

Lookatthefollowingpseudocodeandpondertheinferencesafuturereadermightdraw.Putyourselfintheirplace;imaginethatyoudidn’twritethecodeandthatyoudon’tcompletelyunderstandit.Whatdoesitmeantowrite if ratherthan case?

Useof if / elsif impliesthateachsubsequentconditionvariesin

ifnumber==1#somethingelsifnumber==2#somethingelseelse#defaultend

casenumberwhen1#somethingwhen2#somethingelseelse#defaultend

Page 75: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

ameaningfulway.Because elsif 'softentestwildlydifferentconditions,futurereaderswillfeelobligedtocloselyexamineeachone.

Incontrast,useof case impliesthateveryconditionchecksforequalityagainstanexplicitvalue.Whileit’struethatthe whenclausesupportsmorecomplicatedoperations,theformaboveismostcommonandistheoneyourreaderswillexpect.Readersofcase statementsexpectconditionstobefundamentallythesame.

Inthe99Bottlescaseabove,theconditionsarefundamentallythesame.Switchingfrom if to case whenyouaddthecodeforverse1impliesthissameness,andsoisanactofkindnesstowardsyourreader.Intention-revealingcodeisbuiltfromtheaccumulationofsuchthoughtfulacts.

The verse methodisaccumulatinglotsofduplication,andthismayfeeltroubling.However,youareveryclosetohavingcodetoproduceeveryverse.WhileitmaybetemptingtoveerontotheverticalpathandbeginDRYingoutduplication,it’sbesttopushforwardhorizontally.

Withtheendinsight,thecostoffinishingthehorizontalpathislow.Onceit’scomplete,you’llhaveanexampleofeverydifferentkindofverse,andthereforemaximalinformationabouttheproblem.Whenthecurrentcodeiseasytounderstand,andmoreinformationisimminent,beshamelessandscrambletowardsgreen.

Proceedinghorizontally,then,here’sthetestforverse0:

Listing2.12:Verse0Test

1 deftest_verse_02 expected="Nomorebottlesofbeeronthewall,"+

Page 76: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

3 "nomorebottlesofbeer.\n"+4 "Gotothestoreandbuysomemore,"+5 "99bottlesofbeeronthewall.\n"6 assert_equalexpected,Bottles.new.verse(0)7 end

Verse0isuniqueinthefollowingways:

Itsays"No/nomorebottles"insteadof"0bottles"

Itsays"Gotothestoreandbuysomemore"insteadof"Takeit/onedownandpassitaround"

Itendswith"99bottles"

Atthispointyouwilllikelybeunsurprisedtofindthatverse0getsitsownbranchintheconditional,asshownhere:

Listing2.13:Verse0Code

1 defverse(number)2 casenumber3 when04 "Nomorebottlesofbeeronthewall,"+5 "nomorebottlesofbeer.\n"+6 "Gotothestoreandbuysomemore,"+7 "99bottlesofbeeronthewall.\n"8 when19 "1bottleofbeeronthewall,"+10 "1bottleofbeer.\n"+11 "Takeitdownandpassitaround,"+12 "nomorebottlesofbeeronthewall.\n"

Page 77: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

13 when214 "2bottlesofbeeronthewall,"+15 "2bottlesofbeer.\n"+16 "Takeonedownandpassitaround,"+17 "1bottleofbeeronthewall.\n"18 else19 "#{number}bottlesofbeeronthewall,"+20 "#{number}bottlesofbeer.\n"+21 "Takeonedownandpassitaround,"+22 "#{number-1}bottlesofbeeronthewall.\n"23 end24 end

Thiscodecompletesthe verse method.Younowhavetestsforalltheversevariants,andcodetomakeeachtestpass.

Thisimplementationrevealssomeimportantconceptsinthedomain.It’seasy,forexample,toseethatthereare4basicversevariants:verse0,verse1,verse2andverses3-99.Also,verses3-99aresomuchalikethatitmadesensetoproducethemwiththesamebitofcode.

Theotherversesdiffer,notonlyfromthe3-99case,butalsofromeachother.Thecasestatementabovemakesitobviousthat0,1and2arespecial,althoughgranted,it’sdifficulttoseeinwhatway.Youhavetoreadthecodecarefullytoseehowtheversesareunique.

Thecodeiseasytounderstandbecausetherearen’tmanylevelsofindirection.Thislackofindirectionisadirectresultofthedearthofabstractions.Followingthehorizontalpathmeanswritingcodetoproduceeverykindofversebeforediverging

Page 78: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

ontotangentstoDRYoutsmallbitsofcodethattheverseshaveincommon.Thegoalistoquicklymaximizethenumberofwholeexamplesbeforeextractingabstractionsfromtheirparts.

Nowthatyoucanproduceanysingleverse,it’stimetoturnyourattentiontoproducinggroupsofverses.

2.7.ExposingResponsibilitiesTheplanisforthe verses(a,b) methodtotaketwoarguments.Theseargumentsarenumbersthatspecifytherangeofversesforwhichthemethodshouldgeneratelyrics.Thehigh-levelAPIhasbeendefined,butbeforewritingthenexttest,youmustmakeseveralmoreprecisedecisions:

Inwhatorderdotheseargumentsappear?Doesthefirstargumentrepresentthefirstversetosing,suchthatitisalwaysgreaterthanthesecond,orviceversa?Inessence,whatexactlydo a and b represent,andhowshouldtheybenamed?

Dotheargumentsdenoteaninclusivelist,i.e.shouldyouproducelyricsfortheentirerangespecified?

Whatactualargumentvaluesdoesitmakemostsensetotest?

Groupsofversesgetsungfromahighertoalowernumber,soitmakessensetohavetheinitialargumentrepresentthefirstversetosing,andthusthehighernumber.Italsoseemsnaturaltospecifyaninclusivelistofversenumbers.Onceyoumakethesedecisions,you’vefinalizedthispartoftheAPIandcanbeginconsideringthetests.

Thefirst verses test,likethefirst verse test,shouldbethesimplestthingimaginable.Atthebeginningofthischapter,whenwritingtheinitial verse test,itmadesensetostartwiththefirstverseofthesong.Followingthatpattern,hereitmakessenseto

Page 79: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

startinthesameplace,withverse99.However,sincethe versesmethodproducesasequenceofverses,itneedstwoarguments.Theshortestpossiblesequenceistwo,soit’sreasonableforthisfirsttesttobeforthesequencefrom99to98.

Here’sthetest:

Listing2.14:Verses9998Test

1 deftest_a_couple_verses2 expected="99bottlesofbeeronthewall,"+3 "99bottlesofbeer.\n"+4 "Takeonedownandpassitaround,"+5 "98bottlesofbeeronthewall.\n"+6 "\n"+7 "98bottlesofbeeronthewall,"+8 "98bottlesofbeer.\n"+9 "Takeonedownandpassitaround,"+10 "97bottlesofbeeronthewall.\n"11 assert_equalexpected,Bottles.new.verses(99,98)12 end

Here’sonepossiblewaytopassthattest:

Listing2.15:Verses9998Literal

1 defverses(_,_)2 "99bottlesofbeeronthewall,"+3 "99bottlesofbeer.\n"+4 "Takeonedownandpassitaround,"+5 "98bottlesofbeeronthewall.\n"+

Page 80: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

6 "\n"+7 "98bottlesofbeeronthewall,"+8 "98bottlesofbeer.\n"+9 "Takeonedownandpassitaround,"+10 "97bottlesofbeeronthewall.\n"11 end

Althoughthecodeaboveclearlypassesthetest,manyprogrammerswillfinditobjectionable.Ifaskedtoarticulatetheflaw,youmightcomplainthatitduplicatescodefromthe versemethod.Thisiscertainlytrue.The verse methodalreadycontainsafairamountofduplication,andthisnew versesmethodrepeatssomeofthatexistingcode.

SomeduplicationistolerableduringthesearchforShamelessGreen.However,notallduplicationishelpful,andthere’ssomethingabouttheduplicationintroducedabovethatmeansitshouldnotbetolerated.Thisnewcodemuddiesratherthanclarifiesthewaters,andit’simportanttounderstandwhy.

Duplicationisusefulwhenitsuppliesindependent,specificexamplesofageneralconceptthatyoudon’tyetunderstand.Forexample,inthepriorsection,thecasestatementwithin verseevolvedtocontainfourdifferenttemplates.Thosetemplatesareconcreteexamplesofamoregenericverse.Eachsuppliesuniqueinformation,buttogethertheypointyoutowardstheunderlyingabstraction.

Theproblemwiththe verses implementationaboveisthatitdoesnotisolateanew,independentexample,butinstead,itduplicatesonethatyou’vealreadyidentified.Thecodetoproduceverses99and98alreadyexistsinthe else clauseofthecase statementof verse (repeatedbelow).

Listing2.16:VerseCaseStatementElseBranch

Page 81: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

1 defverse(number)2 casenumber3 #...4 else5 "#{number}bottlesofbeeronthewall,"+6 "#{number}bottlesofbeer.\n"+7 "Takeonedownandpassitaround,"+8 "#{number-1}bottlesofbeeronthewall.\n"9 end10 end

NotethatListing2.15:Verses9998Literalisjustthenon-generalizedversionoftheabovepattern.Thus,thisnewcodeduplicatesanexamplethatalreadyexistsandsosuppliesnonewinformationabouttheproblem.Inaddition,duplicatingthisalready-existingcodemasksthetrueresponsibilityof verses .Thismethodwouldbemoreintention-revealingifthishiddenresponsibilitywereexposedinsteadofobscured.

The verses methodisresponsibleforunderstandingitsinputarguments,andforknowinghowtousetheseargumentstoproducethecorrectoutput.Itsjobisnottoknowtheexactlyricsforanyverse.Itsjobis,rather,torepeatedlyreferthisquestionontothe verse method,andtoaccumulatetheanswersintoamulti-versestring.

Codelongstobeasignorantaspossible.Whileitmakesperfectsenseforthe verse methodtoberesponsibleforknowingtheversetemplates,once verse assumesthisresponsibility,otherpartsofyourapplicationshouldnotusurpit.

Here’sanalternativeimplementationof verses thatknowsless

Page 82: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

butrevealsmore:

Listing2.17:Verses9998Message

1 defverses(_,_)2 verse(99)+"\n"+verse(98)3 end

Thestorythiscodetellsisthat verses aremadeupof verse s(sorry),thatthere’sarelationshipbetweenasequenceofversesandanindividualverse.Listing2.15:Verses9998Literalhidthatrelationship,whilethisexamplebeginstoexposeit.

Thecodeaboveisthesimplestthingthatpassesthistest,butyou’reprobablychompingatthebittodomore.Youaresurelyawarethatthe verses methodmustultimatelyproducelyricsforall100verses.Yourecognizethatthecodeaboveisincompleteandthereforetemporary.Youknowthatthereal versesimplementationwillultimatelyloopfromstartingtoendingnumber,invoking verse foreachnumberandaccumulatingtheresponse.Followingthe"simplest-thing"ruleheremayfeeltediousandtime-consumingwhentherealsolutionissoobvious.

InChapter28ofTest-DrivenDevelopmentbyExample,KentBeckdescribesdifferentwaystomaketestspass.Threeofhis"GreenBarPatterns"are:

FakeIt("TilYouMakeIt")

ObviousImplementation

Triangulate

Theprevioustwoattemptsat verses (Listing2.15:Verses9998LiteralandListing2.17:Verses9998Message)areexamplesofFakeItbecausealthougheachimplementationpassesthecurrent

Page 83: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

test,thetestsarenotyetcomplete.Thefirstexamplewasabandonedinfavorofthesecond,butbothareFakesbecauseneitherdoeseverythingthefinalspecwillrequire.

AnObviousImplementationsolutionis,well,obvious,andwhat’sobvioushereisthatthe verses shouldloopfrom99downto0,invoking verse foreachnumberandconcatenatingtheresults.Whentheobviousimplementationisevident,itmakessensetojumpstraighttoit.Ifyouareabsolutelycertainofthecorrectimplementation,there’snoneedtowearahairshirt[6]andrepetitivelyinchthroughaseriesoftinysteps.

Notice,however,thatattractivethoughthisideais,itisfraughtwithperil.ThesmallstepsofTDDacttoincrementallyrevealthecorrectimplementation.Ifyourabsolutecertaintyturnsouttobewrong,skippingtheseincrementalstepsmeansyoumisstheopportunityofbeingsetright.Anapparently"obvious"implementationthatisactuallyanincorrectguesswillcauseaworldofdownstreampain.

FakeItstyleTDDmayinitiallyseemawkwardandtedious,butwithpracticeitbecomesbothnaturalandspeedy.Developingthehabitofwritingjustenoughcodetopassthetestsforcesyoutowritebettertests.Italsoprovidesanantidoteforthehubrisofthinkingyouknowwhat’srightwhenyou’reactuallywrong.Althoughitsometimesmakessensetoskipthesmallstepsandjumpimmediatelytothefinalsolution,exercisecaution.It’sbesttosave"ObviousImplementation"forverysmallleaps.

ThenextGreenBarPatternisTriangulate,whichBeckdescribesasawayto"conservativelydriveabstractionwithtests."Triangulationrequireswritingseveraltestsatonce,whichmeansyou’llhavemultiplesimultaneousbrokentests.Theideaistowriteonebitofcodewhichmakesallofthetestspass.Triangulationismeanttoforceyoutoconvergeuponthecorrectabstractioninyourcode.

Page 84: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

TriangulationissuchausefulideathatShamelessGreenexpandsitfromteststocode.Youcanexposeacommon,underlyingabstractionthroughtheaccumulationofmultipleconcreteexamples.Theseconcretecodeexamplesoftencontainsomeduplication,butthisduplicationisfineaslongaseachoverallexampleisindependentandunique.

Nowthatthe verses methodworksfor99and98,thenextstepistowriteatestthatassertsitcangenerateothersequences.Atthispoint,itmakessensetotesttheotherendoftherange.Here’satestfortheversesfrom2downto0:

Listing2.18:Verses2,1,0Test

1 deftest_a_few_verses2 expected="2bottlesofbeeronthewall,"+3 "2bottlesofbeer.\n"+4 "Takeonedownandpassitaround,"+5 "1bottleofbeeronthewall.\n"+6 "\n"+7 "1bottleofbeeronthewall,"+8 "1bottleofbeer.\n"+9 "Takeitdownandpassitaround,"+10 "nomorebottlesofbeeronthewall.\n"+11 "\n"+12 "Nomorebottlesofbeeronthewall,"+13 "nomorebottlesofbeer.\n"+14 "Gotothestoreandbuysomemore,"+15 "99bottlesofbeeronthewall.\n"16 assert_equalexpected,

Page 85: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Bottles.new.verses(2,0)17 end

Onceagainyoumustchoosebetweenhard-codinganewspecialcaseorgeneralizingthecode.Forexample,youcouldmakethetestpassbyexplicitlyaddinganewconditionaltothe versesmethod,likeso:

Listing2.19:VersesSpecificRanges

1 defverses(starting,ending)2 ifstarting==993 verse(99)+"\n"+verse(98)4 else5 verse(2)+"\n"+verse(1)+"\n"+verse(0)6 end7 end

Alternatively,youcouldalterthecodetomakeitmoreabstract,asfollows:

Listing2.20:VersesWithinaRange

1 defverses(starting,ending)2 starting.downto(ending).collect{|i|verse(i)}.join("\n")3 end

Thischoicebetweena)addingaconditionalorb)makingthecodemoreabstractshouldremindyouofanearlierdiscussion.BackintheRemovingDuplicationsection,youfacedtheidenticalsituationwhenaltering verse topassthetestforverse3.

Page 86: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Inbothcases,therearemanyexistingexamplesoftheproblem(i.e.youknowallofthepossibleverseranges),andtheunderlyingabstractioniswellunderstood.Therefore,theargumentsmadeinRemovingDuplicationapplyherejustastheydidpreviously.

Relativetoitsalternative,Listing2.20:VersesWithinaRangeiseasiertounderstandandjustascheaptoimplement,andyouhavealltheinformationyouneedtofeelconfidentthatit’scorrect.Itisthebestsolutionnotonlybecauseitpassesthetest,butalsobecauseitclearlyexposestheresponsibilityof verses toproduceanyrangeofverses.Itgeneralizesthecode,whichisthebestchoicewhenyouareconfidentthatyouunderstandtheabstraction.

Nowthatyoucangenerateanysequenceofverses,thefinaltaskistoproducelyricsfortheentiresong.

2.8.ChoosingNamesAtthestartofthischapter,theplanwastocreatea Bottles classthatimplementedthefollowingAPI:

verse(n)

verses(starting,ending)#initiallyverses(a,b)

song

Thusfar,thisplanhasworkedswimmingly.The verse and versesmethodsarecomplete;it’stimetomoveonto song .

Thecodetoproducetheentiresongisquitestraightforward,asshownhere:

Listing2.21:SongCode

Page 87: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

1 defsong2 verses(99,0)3 end

ThisisagoodtimetoreflectupontheAPIasawhole,andtoreconsiderthe song method.Thebodyof song isscarcelylongerthanitsname.Asthe verses methodisalreadyinthepublicAPI,usersof Bottles don’tneedthe song methodatall—theycouldsend verses(99,0) andgetbackthesameoutput.

Extraneouscodeaddscostswithoutprovidingbenefits,andatthispoint,it’squitereasonabletochallengetheneedfor song .Does song serveapurposeindependentof verses ,orisitredundantandthusacandidatefordeletion?

Answeringthisquestionrequiresthinkingabouttheproblemfromthemessagesender’spointofview.Whileit’struethatverses(99,0) and song returnthesameoutput,theydifferwidelyintheamountofknowledgetheyrequirefromthesender.Fromthesender’spointofview,itisonethingtoknowthatyouwantallofthelyricstothe99Bottlessongbutitisquiteanothertoknowhow Bottles producesthoselyrics.

Knowledgethatoneobjecthasaboutanothercreatesadependency.Dependenciestieobjectstogether,exacerbatingthecostofchange.Yourgoalasamessagesenderistoincuralimitednumberofdependencies,andyourobligationasamethodprovideristoinflictfew.

The song methodimposesasingledependency;touseit,youneedonlyknowitsname.

Usingthe verses methodtorequesttheentiresong,however,requiressignificantlymoreknowledge.Thesendermustknow:

Page 88: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

thenameofthe verses method

thatthemethodrequirestwoarguments

thatthefirstargumentistheverseonwhichtostart

thatthesecondargumentistheverseonwhichtoend

thatthesongstartsonverse99

thatthesongendsonverse0

Thisisalotofknowledge.Therearemanywaysinwhichtheverses methodcouldchangethatwouldbreaksendersofthismessage.

2.9.RevealingIntentionsKentBeckexplainsthedifferencebetweenintentionandimplementation.

Thedistinctionbetweenintentionandimplementation[…]allows you to understand a computation first in essenceandlater,ifnecessary,indetail.

—KentBeckImplementationPatterns(p.69)

Here song istheintention,and verses(99,0) istheimplementation.There’sabigdifferencebetweenwantingthelyricsforarangeofverses,andwantingthelyricsfortheentiresong.The verses methodisinthepublicAPI,soitmustcontinuetoexist,butitsexistencedoesn’tobviatetheneedfor song .Sendersofthe song messagewantalloftheverses,andtheyoughtn’tbeforcedtotroublethemselveswithdetailsabouthowthishappens.

Page 89: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

The song methodhavingdefendeditsworth,here’sthefullShamelessGreenfor99Bottles.

Listing2.22:ShamelessGreenInitial

1 classBottles2 defsong3 verses(99,0)4 end5 6 defverses(starting,ending)7 starting.downto(ending).collect{|i|verse(i)}.join("\n")8 end9 10 defverse(number)11 casenumber12 when013 "Nomorebottlesofbeeronthewall,"+14 "nomorebottlesofbeer.\n"+15 "Gotothestoreandbuysomemore,"+16 "99bottlesofbeeronthewall.\n"17 when118 "1bottleofbeeronthewall,"+19 "1bottleofbeer.\n"+20 "Takeitdownandpassitaround,"+21 "nomorebottlesofbeeronthewall.\n"22 when223 "2bottlesofbeeronthewall,"+24 "2bottlesofbeer.\n"+25 "Takeonedownandpassitaround,"+

Page 90: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

26 "1bottleofbeeronthewall.\n"27 else28 "#{number}bottlesofbeeronthewall,"+29 "#{number}bottlesofbeer.\n"+30 "Takeonedownandpassitaround,"+31 "#{number-1}bottlesofbeeronthewall.\n"32 end33 end34 end

Pleasingasthiscodemaybe,thealertreaderwillhavenoticedthatthe song methodwasintroducedwithoutfirstwritingatest.ThisisaclearviolationofTDD.

Indeed,thereareanumberofgapsinthetests.Forexample,thereisnocoverageforindividualverses4through97,andthere’snoguaranteethattheseversesappearinthecorrectorder.

Bottles nowproducesthatcorrectoutput,andit’stemptingtowalkawayatthispoint.However,doingsotransferstheburdenofkeepingthiscoderunningtosomepoordownstreamprogrammer,onewhohasfarlessunderstandingoftheproblemthanyoudorightnow.

Thenextsection,therefore,isconcernedwithtighteningupthetests.

2.10.WritingCost-E ectiveTestsTDDpromisesstraightforward,bug-freesoftwarethatcanbeconfidentlyandeasilychanged.TDDdoesnotclaimtobefree,

Page 91: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

merelythatitsbenefitsoutweighitscosts.

BeliefinthevalueofTDDhasbecomemainstream,andthepressuretofollowthispracticeapproachesanunspokenmandate.Acceptanceofthismandateisillustratedbythefactthatit’scommonforfolkswhodon’ttesttosheepishlyapologizefornotdoingso.Eventhosewhodon’ttestseemtobelievetheyoughtto.

Despitethisgeneralagreement,thesadtruthisthatthepromiseofTDDhasnotbeenuniversallyfulfilled.Manyapplicationshaveteststhataredifficulttounderstand,challengingtochange,andprohibitivelytime-consumingtorun.Insteadofenablingchange,thesetestsactivelyimpedeit.Theworldislitteredwithtestsuitesthatareroundlyhatedbytheirmaintainers,sometimestothepointofabandonment.

Agreatdealofthispainoriginateswithteststhataretiedtoocloselytocode.Whenthisistrue,everyimprovementtothecodebreaksthetests,forcingthemtochangeinturn.Therefore,thefirststepinlearningtheartoftestingistounderstandhowtowriteteststhatconfirmwhatyourcodedoeswithoutanyknowledgeofhowyourcodedoesit.

Thissectionexplorestheproblemoftest-to-codecoupling.Asareminderofthecurrentstateofaffairs,herearethecurrenttests:

Listing2.23:NoSongTest

1 classBottlesTest<Minitest::Test2 deftest_the_first_verse3 expected="99bottlesofbeeronthewall,"+4 "99bottlesofbeer.\n"+5 "Takeonedownandpassitaround,"+

Page 92: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

6 "98bottlesofbeeronthewall.\n"7 assert_equalexpected,Bottles.new.verse(99)8 end9 10 deftest_another_verse11 expected="3bottlesofbeeronthewall,"+12 "3bottlesofbeer.\n"+13 "Takeonedownandpassitaround,"+14 "2bottlesofbeeronthewall.\n"15 assert_equalexpected,Bottles.new.verse(3)16 end17 18 deftest_verse_219 expected="2bottlesofbeeronthewall,"+20 "2bottlesofbeer.\n"+21 "Takeonedownandpassitaround,"+22 "1bottleofbeeronthewall.\n"23 assert_equalexpected,Bottles.new.verse(2)24 end25 26 deftest_verse_127 expected="1bottleofbeeronthewall,"+28 "1bottleofbeer.\n"+29 "Takeitdownandpassitaround,"+30 "nomorebottlesofbeeronthewall.\n"

Page 93: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

31 assert_equalexpected,Bottles.new.verse(1)32 end33 34 deftest_verse_035 expected="Nomorebottlesofbeeronthewall,"+36 "nomorebottlesofbeer.\n"+37 "Gotothestoreandbuysomemore,"+38 "99bottlesofbeeronthewall.\n"39 assert_equalexpected,Bottles.new.verse(0)40 end41 42 deftest_a_couple_verses43 expected="99bottlesofbeeronthewall,"+44 "99bottlesofbeer.\n"+45 "Takeonedownandpassitaround,"+46 "98bottlesofbeeronthewall.\n"+47 "\n"+48 "98bottlesofbeeronthewall,"+49 "98bottlesofbeer.\n"+50 "Takeonedownandpassitaround,"+51 "97bottlesofbeeronthewall.\n"52 assert_equalexpected,Bottles.new.verses(99,98)53 end54 55 deftest_a_few_verses56 expected="2bottlesofbeeronthewall,

Page 94: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

"+57 "2bottlesofbeer.\n"+58 "Takeonedownandpassitaround,"+59 "1bottleofbeeronthewall.\n"+60 "\n"+61 "1bottleofbeeronthewall,"+62 "1bottleofbeer.\n"+63 "Takeitdownandpassitaround,"+64 "nomorebottlesofbeeronthewall.\n"+65 "\n"+66 "Nomorebottlesofbeeronthewall,"+67 "nomorebottlesofbeer.\n"+68 "Gotothestoreandbuysomemore,"+69 "99bottlesofbeeronthewall.\n"70 assert_equalexpected,Bottles.new.verses(2,0)71 end

2.11.AvoidingtheEcho-ChamberTheoutputof song isastringofonehundredverysimilarverses.Themethoddoesnotyethaveatest.Programmerswhowanttoremedythisomission,butwhoarehyper-alerttoduplication,maybetemptedtotest song likethis:

Listing2.24:WholeSongTestLogic

1 deftest_the_whole_song2 bottles=Bottles.new3 assert_equalbottles.verses(99,0),

Page 95: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

bottles.song4 end

Thetestaboveassertsthat song returnsthesameoutputasdoesverses(99,0) .Onitsface,thisseemslikeagreatidea.Thetestisshort,itpasses,itwaseasytowrite,and(atleastforthemoment,whileyou’reimmersedintheproblem)it’seasytounderstand.However,thistesthasamajorflawthatcancauseittotogglefrom"shortandsweet"to"painfulandcostly"intheblinkofaneye.Thisflawliesdormantuntilsomethingchanges,sothebenefitsofwritingtestslikethisaccruetothewritertoday,whilethecostsarepaidbyanunfortunatemaintainerinthefuture.

Understandingthisflawrequiresbeingclearabout song 'sresponsibilities.Fromthemessagesender’spointofview, song isresponsibleforreturningthelyricsforall100verses.Imaginethatyouweretaskedtotestthismethodbutknewnothingabouthow Bottles wasimplemented.Youwouldbeunawareoftheexistenceofthe verses method,andwouldhavenochoiceotherthantotest song byassertingthatitsoutputmatchedthoselyrics.

Assertingthat song returnstheexpectedlyricsisverydifferentfromassertingthat song returnsthesamethingas verses .Inthefirstcase,the song testisindependentofimplementationdetailsandsotolerateschangestootherpartsoftheclasswithoutbreaking.Inthesecondcase,the song testiscoupledtothecurrent Bottles implementationsuchthatitwillbreakifthesignatureorbehaviorof verses changes,evenif song continuestoreturnthecorrectlyrics.

There’snothingmorefrustratingthanmakingachangethatpreservesthebehaviorofanapplicationbutbreaksapparentlyunrelatedtests.Ifyouchangeanimplementationdetailwhileretainingexistingbehaviorandarethenconfrontedwithaseaofred,youarerighttobeexasperated.Thisiscompletelyavoidable,andasignofteststhataretootightlycoupledtocode.Suchtests

Page 96: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

impedechangeandincreasecosts.

Notonlyistheabove song testtootightly-coupledtothecurrentBottles implementation,itdoesn’tevenforceyoutowritetherightcode.Thefollowingbadly-broken Bottles classpassesthetestsuitewithoutactuallyproducingthecorrectsong.Noticethatthe verses methodbelowcanonlyreturnverses99-98,verses2-0,orthestring"ok."

Listing2.25:BadlyBrokenBottlesSong

1 classBottles2 defsong3 verses(99,0)4 end5 6 defverses(starting,ending)7 ifstarting==99&&ending==988 "99bottlesofbeeronthewall,"+9 "99bottlesofbeer.\n"+10 "Takeonedownandpassitaround,"+11 "98bottlesofbeeronthewall.\n"+12 "\n"+13 "98bottlesofbeeronthewall,"+14 "98bottlesofbeer.\n"+15 "Takeonedownandpassitaround,"+16 "97bottlesofbeeronthewall.\n"17 elsifstarting==218 verse(2)+"\n"+verse(1)+"\n"+verse(0)19 else20 "ok"21 end

Page 97: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

22 end23 24 defverse(number)25 casenumber26 when027 "Nomorebottlesofbeeronthewall,"+28 "nomorebottlesofbeer.\n"+29 "Gotothestoreandbuysomemore,"+30 "99bottlesofbeeronthewall.\n"31 when132 "1bottleofbeeronthewall,"+33 "1bottleofbeer.\n"+34 "Takeitdownandpassitaround,"+35 "nomorebottlesofbeeronthewall.\n"36 when237 "2bottlesofbeeronthewall,"+38 "2bottlesofbeer.\n"+39 "Takeonedownandpassitaround,"+40 "1bottleofbeeronthewall.\n"41 when342 "3bottlesofbeeronthewall,"+43 "3bottlesofbeer.\n"+44 "Takeonedownandpassitaround,"+45 "2bottlesofbeeronthewall.\n"46 else47 "99bottlesofbeeronthewall,"+48 "99bottlesofbeer.\n"+49 "Takeonedownandpassitaround,"+50 "98bottlesofbeeronthewall.\n"51 end

Page 98: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

52 end53 end

Theabovecodeexploitsweaknessesinthetesttogettogreenwithoutactuallyproducingalloftheverses.Tocorrectthis,youmightbetemptedtochangethe song testasfollows:

Listing2.26:WholeSongTestLogicAgain

1 deftest_the_whole_song2 bottles=Bottles.new3 expected=99.downto(0).collect{|i|4 bottles.verse(i)5 }.join("\n")6 assert_equalexpected,bottles.song7 end

Thisnewtestsucceedsinforcing song toproduceeveryverse,butalteringthetestinthiswayjustdigsadeeperhole.Considerwhatjusthappened.Theoriginaltestassertsthatsending songproducesthesameresultasrunningthecodecurrentlycontainedin song .Inotherwords,itassertsthat

and

returnthesameoutput.

Thisnewtestassertsthat song producesthesameresultasrunningthecodecurrentlycontainedin verses .So

song

verses(99,0)

Page 99: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

and

returnthesameoutput.

Noticethatalthoughthissecondvariantforcestheproductionofeveryverse,thetestcontinuestoechocodefrom Bottles .Now,insteadofassertingthattheoutputfrom song islikethecurrentimplementationof song ,itassertsthattheoutputof song islikethecurrentimplementationof verses .Thisdoesn’timprovethetest,butjusttightlycouplesthetesttocodethat’sonestepfartherbackinthestack.Ifthatmore-distantcodechanges,thistestmightbreak.

There’sanobvioussolutiontothistestingproblem,onealludedtoabove.The song testshouldknownothingabouthowtheBottles classproducesthesong.Theclearandunambiguousexpectationhereisthat song returnthecompletesetoflyrics,andthebestandeasiestwaytotest song istoexplicitlyassertthatitdoes.

Here’sthattest:

Listing2.27:SongTest

1 deftest_the_whole_song2 expected=<<-SONG3 99bottlesofbeeronthewall,99bottlesofbeer.4 Takeonedownandpassitaround,98bottlesofbeeronthewall.5

song

99.downto(0).collect{|i|bottles.verse(i)}.join("\n")

Page 100: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

6 98bottlesofbeeronthewall,98bottlesofbeer.7 Takeonedownandpassitaround,97bottlesofbeeronthewall.8 9 97bottlesofbeeronthewall,97bottlesofbeer.10 Takeonedownandpassitaround,96bottlesofbeeronthewall.11 12 #...13 14 4bottlesofbeeronthewall,4bottlesofbeer.15 Takeonedownandpassitaround,3bottlesofbeeronthewall.16 17 3bottlesofbeeronthewall,3bottlesofbeer.18 Takeonedownandpassitaround,2bottlesofbeeronthewall.19 20 2bottlesofbeeronthewall,2bottlesofbeer.21 Takeonedownandpassitaround,1bottleofbeeronthewall.22 23 1bottleofbeeronthewall,1bottleofbeer.24 Takeitdownandpassitaround,nomorebottlesofbeeronthewall.

Page 101: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

25 26 Nomorebottlesofbeeronthewall,nomorebottlesofbeer.27 Gotothestoreandbuysomemore,99bottlesofbeeronthewall.28 SONG29 assert_equalexpected,Bottles.new.song30 end

Inthelistingabove,the expected stringissolongthatverses96through5areelidedonline12.Inreallife,ofcourse,thelyricstoall100verseswouldbeexplicitlydetailedinthistest.

Thetextneededfor100versesisfairlylengthy,andyoumayresistwritingoutthefullstringbecauseofconcernsaboutduplication.

2.12.ConsideringOptionsIfyoufindtheduplicationdistressing,considerthealternatives.Yourchoicesare:

1. Assertthattheexpectedoutputmatchesthatofsomeothermethod.

Thefirsttwo song testvariantsdothis.Thosetestsarecoupledtothecurrent Bottles implementation,andsodependuponcharacteristicsofthatcode.

Thesedependenciesmeanthatchangestothe Bottles codemightbreakthe song test,evenifthereisnothingotherwisewrongwiththeapplication.

2. Assertthattheexpectedoutputmatchesadynamicallygeneratedstring.

Page 102: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Onceyouacceptthatthe song testshouldverifyspecificoutputratherthencoupletothecurrentimplementation,youmustdecidehowtocreatethatoutput.Because song returnsalong,duplicativestring,manyprogrammersfeeltempted,perhapsevenobligated,toreducethisduplicationbydynamicallycreatingtheverseswithinthetests.

However,reducingstringduplicationinsidethe song testwouldofnecessityrequirelogic.Thislogicalreadyexistsinthe Bottles class,sothetestwouldbeforcedtoinvoke,copy,orre-implementit.Regardlessofhowyoudoit,usinganylogicheremeansthatachangeto Bottles mightbreakthesong testinanunexpectedandconfusingway.

3. Assertthattheexpectedoutputmatchesahard-codedstring.

Inthiscase(asinListing2.27:SongTest)notonlyistheexpectedoutputclearlyandunambiguouslystated,butthetesthasnodependencies.Thesequalitiescombinetomakeiteasytounderstandandtotoleratechangesincode.

Ofthesethreechoices,onlythethirdisindependentofthecurrentimplementationandsoguaranteedtosurvivechangestoBottles .Itmaybedifficulttoreconcileyourselftowritingdowntheentirelyricsstring,butremember,DRYingoutthelyricsinthetestwouldforceyoutointroduceanabstraction.Testsarenottheplaceforabstractions—theyaretheplaceforconcretions.Abstractionsbelongincode.Ifyouinsistonreducingduplicationbyaddinglogictoyourtests,thislogicbynecessitymustmirrorthelogicinyourcode.Thisbindstheteststoimplementationdetailsandmakesthemvulnerabletobreakingeverytimeyouchangethecode.

DRYisaverygoodideaincode,butmuchlessusefulintests.Whentesting,thebestchoiceisveryoftenjusttowriteitdown.

Hereagainisthecomplete Bottles listing:

Page 103: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Listing2.28:ShamelessGreen

1 classBottles2 defsong3 verses(99,0)4 end5 6 defverses(starting,ending)7 starting.downto(ending).collect{|i|verse(i)}.join("\n")8 end9 10 defverse(number)11 casenumber12 when013 "Nomorebottlesofbeeronthewall,"+14 "nomorebottlesofbeer.\n"+15 "Gotothestoreandbuysomemore,"+16 "99bottlesofbeeronthewall.\n"17 when118 "1bottleofbeeronthewall,"+19 "1bottleofbeer.\n"+20 "Takeitdownandpassitaround,"+21 "nomorebottlesofbeeronthewall.\n"22 when223 "2bottlesofbeeronthewall,"+24 "2bottlesofbeer.\n"+25 "Takeonedownandpassitaround,"+26 "1bottleofbeeronthewall.\n"27 else

Page 104: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

28 "#{number}bottlesofbeeronthewall,"+29 "#{number}bottlesofbeer.\n"+30 "Takeonedownandpassitaround,"+31 "#{number-1}bottlesofbeeronthewall.\n"32 end33 end34 end

The Bottles testsandcodearenowcomplete.Thetestsarestraightforward,andthecodeiseasytounderstand.

2.13.SummaryTesting,donewell,speedsdevelopmentandlowerscosts.Unfortunatelyit’salsotruethatflawedtestsslowyoudownandcostyoumoney.

Itisworththeeffort,therefore,togetgoodattesting.TDDcanpreventcostlyguesses,butonlyifyoucommittowritingcodeinsmallsteps.Testscanmakeitsafeandeasytorefactor,butonlyiftheyarecarefullyde-coupledfromthecurrentcode.

Goodtestsnotonlytellastory,buttheylead,stepbystep,toawell-organizedsolution.Thetestswritteninthischaptergiverise(assumingproperrestraintonthepartoftheprogrammer)toShamelessGreen.

TheShamelessGreensolutionisneitherclevernorextensible.Itsvalueliesinthefactthatthecodeiseasytounderstand,andcheaptowrite.Ifnothingeverchanges,thissolutionisquitecertainlygoodenough.

Thingsgetmoreinterestingonlyifsomethingneedstochange.

Page 105: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

So,ontoChapter3,whichintroducesanewrequirement,andforcesyoutomakesomeharddecisionsaboutthecode.

Page 106: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

3.UnearthingConceptsTheShamelessGreensolutionvaluesunderstandability,straightforwardnessandefficiency,withlittleregardforchangeability.Itcontainsduplication,andisunapologeticaboutleaningintheproceduraldirection.It’sfast,andcheap,andmaybegoodenough,atleastuntilsomethingchanges.

However,intherealworld,requirementsdochange,andwhenthathappens,thestandardsforcoderise.

Thischapterdefinesanewrequirement,whichtriggersadeeperlookatthestructureofthecode.Itthenintroducesafewstraightforwardrulestoallowyoutosystematicallyandincrementallyimprovecode,withoutfearofgettinglostorintroducingbugs.Therulesaresimple,buttheyallowcomplexbehaviortoemerge.Bytheendofthischapter,you’llhavebeguntounearthconceptsthatarecurrentlyhiddeninthecode.

3.1.ListeningtoChangeCodeisexpensive.Writingitcoststimeormoney.Itthereforebehoovesyoutobeasefficientaspossible.Themostcost-effectivecodeisasgoodasnecessary,butnobetter.

However,programmingisanart,andprogrammersloveelegantcode.Theconundrumisthatonceaninitial,moreprosaic,solutionexists,theproblemissolved,andthechoiceofwhethertodeliveritasis,ortoimproveuponitatthismoment,mustbeweighedcarefully.

Iftheproblemissolved,andyouchoosetorefactornowratherthanlater,youpaytheopportunitycostofnotbeingabletoworkonotherproblems.Spendingtime"improving"codebasedpurelyonaestheticsmaynotbethebestuseofyourprecioustime.

Page 107: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Agoodwaytoknowthatyou’reusinglimitedtimewiselyistobedrivenbychangesinrequirements.Thearrivalofanewrequirementtellsyoutwothings,oneveryspecific,theothermoregeneral.

Specifically,anewrequirementtellsyouexactlyhowthecodeshouldchange.Waitingforthisrequirementavoidstheneedtospeculateaboutthefuture.Therequirementrevealsexactlyhowyoushouldhaveinitiallyarrangedthecode.

Moregenerally,theneedforchangeimposeshigherstandardsontheaffectedcode.Codethatneverchangesobviouslydoesn’tneedtobeverychangeable,butonceanewrequirementarrives,thebarisraised.Codethatneedstobechangedmustbechangeable.Thus,anewrequirementforthe99Bottlesproblemwilldriveyoutoimprovethecode.

Here’sthatnewrequirement:usershaverequestedthatyoualterthe99Bottlescodetooutput"1six-pack"ineachplacewhereitcurrentlysays"6bottles."

Here’sareminderofthecurrentstateofthecode.

Listing3.1:ShamelessGreen

1 classBottles2 defsong3 verses(99,0)4 end5 6 defverses(starting,ending)7 starting.downto(ending).collect{|i|verse(i)}.join("\n")8 end9

Page 108: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

10 defverse(number)11 casenumber12 when013 "Nomorebottlesofbeeronthewall,"+14 "nomorebottlesofbeer.\n"+15 "Gotothestoreandbuysomemore,"+16 "99bottlesofbeeronthewall.\n"17 when118 "1bottleofbeeronthewall,"+19 "1bottleofbeer.\n"+20 "Takeitdownandpassitaround,"+21 "nomorebottlesofbeeronthewall.\n"22 when223 "2bottlesofbeeronthewall,"+24 "2bottlesofbeer.\n"+25 "Takeonedownandpassitaround,"+26 "1bottleofbeeronthewall.\n"27 else28 "#{number}bottlesofbeeronthewall,"+29 "#{number}bottlesofbeer.\n"+30 "Takeonedownandpassitaround,"+31 "#{number-1}bottlesofbeeronthewall.\n"32 end33 end34 end

InthesamewaythatShamelessGreenmakesnoguessesaboutthefuture,youshouldrefrainfrommakinguprequirements.

Page 109: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Noticetherequestisnotto"replaceeverymultipleof6withnnsix-pack(s)"nordoesitmentionspecialhandlingfor"cases"ofbeer.Therequirementissimplytooutput"1six-pack"whereitcurrentlysays"6bottles."Knowledgeofthedomainmaypromptyoutoqueryyourcustomerabouttheseotherpossibilities,andpastexperiencemayoccasionallyleadyoutoinferarequirementotherthantheonespecified.Butgenerallyit’sbesttoclarifyrequirements,andthenwritetheminimumnecessarycode.

Despitethefactthatyoushouldrarelyinfernewrequirements,it’struethatthingsthatchange,do.Nowthatsomeonehasaskedforachange,youhavelicensetoimprovethiscode.ThecodearrangementthatwasacceptableforShamelessGreenisnotnecessarilybestforenablingchange.

ConditionalsarethebaneofOO.ShamelessGreencontainsacasestatement,andwithinitsbranches,muchduplication.Whilethiswasacceptableintheinitialsolution,considertheresultifyoucontinuedowntheconditionalpath.Thefollowingexampleillustratestheproblembyamendingtheexistingcodetomeetthe"six-pack"requirement.

Listing3.2:CompoundingConditionalSins

1 defverse(number)2 casenumber3 when04 "Nomorebottlesofbeeronthewall,"+5 #...6 when17 "1bottleofbeeronthewall,"+8 #...9 when210 "2bottlesofbeeronthewall,"+

Page 110: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

11 #...12 when613 "1six-packofbeeronthewall,"+14 "1six-packofbeer.\n"+15 "Takeonedownandpassitaround,"+16 "5bottlesofbeeronthewall.\n"17 when718 "7bottlesofbeeronthewall,"+19 "7bottlesofbeer.\n"+20 "Takeonedownandpassitaround,"+21 "1six-packofbeeronthewall.\n"22 else23 "#{number}bottlesofbeeronthewall,"+24 #...25 end26 end27 end

The verse casestatementinitiallycontainedfourbranches,andinthecodeabovethenumberofbrancheshasballoonedtosix.Thisisunacceptable.Conditionalsbreed,andnowthatthisonehasstartedreproducing,youmustdosomethingtostopit.

3.2.StartingWiththeOpen/ClosedPrincipleThedecisionaboutwhethertorefactorinthefirstplaceshouldbedeterminedbywhetheryourcodeisalready"open"tothenewrequirement.

"Open"isshortfor"Open/Closed,"whichinturnisshortfor"openforextensionandclosedformodification."The"O"inopensuppliesthe"O"intheacronym"SOLID"(seesidebar).Codeis

Page 111: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

opentoanewrequirementwhenyoucanmeetthatnewrequirementwithoutchangingexistingcode.

SOLIDDesignPrinciplesTheSOLIDacronymwascoinedbyMichaelFeathersandpopularizedbyRobertMartin.Eachletterstandsforawell-knownprincipleinobject-orienteddesign.Here’saformal

definitionofeachone:

S-SingleResponsibility

Themethodsinaclassshouldbecohesivearoundasinglepurpose.

O-Open-Closed

Objectsshouldbeopenforextension,butclosedformodification.

L-LiskovSubstitution

Subclassesshouldbesubstitutablefortheirsuperclasses.

I-InterfaceSegregation

Objectsshouldnotbeforcedtodependonmethodstheydon’tuse.

D-DependencyInversion

Dependonabstractions,notonconcretions.

Ifyoufindtheabovedefinitionslessthenenlightening,don’tdespair.Asprinciplesarereferencedinthisbook,

Page 112: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

plainlanguageexplanations(liketheonebelow)willfollow.

The"open"principlesaysthatyoushouldnotconflatetheprocessofmovingcodearound,ofrefactoring,withtheactofaddingnewfeatures.Youshouldinsteadseparatethesetwooperations.Whenfacedwithanewrequirement,firstrearrangetheexistingcodesuchthatit’sopentothenewfeature,andoncethat’scomplete,thenaddthenewcode.

Thecurrent Bottles classisnotopentothe"6-packs"requirementbecauseaddingnewversevariantsrequireseditingtheconditional.Therefore,whenfacedwiththisnewrequirement,yourfirsttaskistorefactortheexistingcodeintoashapesuchthatyoucanthenimplementthenewrequirementbymerelyaddingcode.Unfortunately,itisquitelikelythatyoudonotknowhowtodothis,andsoareatalossabouthowtoapproachtheproblem.

Fortunately,youdonothavetoknoweverythinginordertochoosetherightplacetostart.Whenfacedwiththissituation,beguidedbythefollowingflowchart.

Page 113: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Figure3.1:OpenClosedFlowchart

Aspertheaboveflowchart,firstaskyourselfiftheexistingcodeisalreadyopentothenewrequirement.Ifso,yourjobissimplytowritethenewcode.

Ifnot,nextaskifyouknowhowtoaltertheexistingcodetomakeitopentothenewrequirement.Thiscaseisalsostraightforward.Ifso,makethealteration,andthenwritethenewcode.

However,thesadtruthisthattheanswertobothofthosequestionsisoften"no."Theexistingcodeisn’topentothenew

Page 114: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

requirement,andyouhavenoideahowtomakeitso.Atthispoint"codesmells"cometotherescue.Ifyoucanidentifysmellsincode,youisolateflawsandcorrectthemonebyone.

3.3.RecognizingCodeSmellsMostcodeisimperfect.Itsflawsaremany,andsothoroughlyentangledthatitisimpossibletocorrectallofthematonce.Ifyou’veevertackledabitofcode,makingchangeafterchangewithoutmanagingtocompletethetask,andeventuallyrollingeverythingback,youknowthisproblem.

Thetricktosuccessfullyimprovingcodethatcontainsmanyflawsistoisolateandcorrectthemoneatatime.InhisRefactoringbook,MartinFowleridentifiesandnamesmanycommonflaws,andprovidesrefactoringrecipestofixthem.Chapter3(whichwasco-writtenbyKentBeck,whocoinedtheterm)callstheflaws"codesmells."ThankstoFowler’sbook,ifyoucanidentifyasmellwithincode,youcanlookupthecurativerefactoring,andapplyittoremovetheflaw.

Ifyou’rewonderingifyouneedtogoreadFowler’sbookrightnow,theansweris,“notnecessarily.”Fowler’sprinciplesareintroducedanddemonstratedhere.However,thisbookexploresonlyafewofthemanyrefactoringrecipeswithwhichyouwouldbewell-servedtobefamiliar.Fowler’sbookisanexcellentinvestment.Also,ifyoupreferyourexamplesinRuby,youmaybeinterestedinJayFields'versionofthebook.

Ifaskedtolistafewcodesmells,youmightsuggest"duplication,"or"classesthataretoobig,"anditisindeedtruethatDuplicatedCodeandLargeClassaretwoofthesmellslistedinMartinFowler’sRefactoringbook.It’sfairlyobvioushowtoremovethesecommonsmells(abstractawaytheduplication,ordivideoneclassintoseveral),andsoitmayappearthatsmellsareageneral,hand-wavykindofthing.

Page 115: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

However,therearemanyothercodesmellswithwhichyoumaynotbeasfamiliar.Youcanprobablyguessthedefinitionof"DivergentChange,"butcanyoudefine"FeatureEnvy?"Canyourecognizeandspecifythecurativerefactoringsfor"PrimitiveObsession,""InappropriateIntimacy,"or"ShotgunSurgery?"

Acompleteexplorationofeverycodesmellisbeyondthescopeofthisbook,especiallysinceMr.Fowlerhascoveredthetopicsothoroughly.However,therefactoringsundertakenherewillbedriven,andguided,bysmells,sothetaskathandistoidentifythesmellsinthecurrent Bottles class.Theeasiestwaytounearththesesmellsistomakealistofthethingsyoudislikeaboutthecode.

3.4.IdentifyingtheBestPointofAttackThecurrent99Bottlescodeisnot"open"tothesix-packrequirement.Ifyouareunclearabouthowtomakeitopen(whichisoftenthecase),thewayforwardistostartremovingcodesmells.Ifthesmellsaren’timmediatelyobvious,startbymakingalistofthethingsyoufindobjectionable.

Considerthe verse method(repeatedbelow).

Listing3.3:ShamelessVerse

1 defverse(number)2 casenumber3 when04 "Nomorebottlesofbeeronthewall,"+5 "nomorebottlesofbeer.\n"+6 "Gotothestoreandbuysomemore,"+7 "99bottlesofbeeronthewall.\n"8 when1

Page 116: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

9 "1bottleofbeeronthewall,"+10 "1bottleofbeer.\n"+11 "Takeitdownandpassitaround,"+12 "nomorebottlesofbeeronthewall.\n"13 when214 "2bottlesofbeeronthewall,"+15 "2bottlesofbeer.\n"+16 "Takeonedownandpassitaround,"+17 "1bottleofbeeronthewall.\n"18 else19 "#{number}bottlesofbeeronthewall,"+20 "#{number}bottlesofbeer.\n"+21 "Takeonedownandpassitaround,"+22 "#{number-1}bottlesofbeeronthewall.\n"23 end24 end

Thismethodcontainsa case statement(theSwitchStatementssmell)whosebranchescontainmanyduplicatedstrings(DuplicatedCode).Ofthesetwosmells,DuplicatedCodeisthemoststraightforwardandsowillbetackledfirst.

Therefore,thecurrenttaskistorefactorthe verse methodtoremovetheduplication,inhopeandexpectationthattheresultingcodewillbemoreopentothesix-packrequirement.

Beforeundertakingthisrefactoring,itmustbeadmittedthatthereisnodirectconnectionbetweenremovingtheduplication,andsucceedinginmakingthecodeopentothesix-packrequirement.That,however,isthebeautyofthistechnique.Youdon’thavetoknowhowtosolvethewholeprobleminadvance.

Page 117: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Theplanistonibbleaway,onecodesmellatatime,infaiththatthepathtoopennesswillberevealed.

3.5.RefactoringSystematicallyHavingbandiedthewordaroundrepeatedly,it’shightimeforaformaldefinitionof"refactoring."AccordingtoFowler:

Refactoringistheprocessofchangingasoftwaresysteminsuchawaythatitdoesnotaltertheexternalbehaviorofthecodeyetimprovesitsinternalstructure.

—MartinFowlerRefactoring

Inshort,refactoringaltersthearrangementofcodewithoutchangingitsbehavior.Recallthatnewrequirementsshouldbeimplementedintwosteps.First,yourearrangeexistingcodesothatitbecomesopentothenewrequirement.Next,youwritenewcodetomeetthatrequirement.Thefirstofthesestepsisrefactoring.

Notethatsaferefactoringreliesupontests.Ifyoutrulyarerearrangingcodewithoutchangingbehavior,ateverystepalongthewaytheexistingtestsshouldcontinuetopass.Testsareasafetyblanketthatjustifiesconfidenceinthenewarrangementofcode.Iftheybegintofail,oneoftwothingsmustbetrue.Eithera)you’veinadvertentlybrokenthecode,orb)theexistingtestsareflawed.

Iftestsfailbecauseyou’vebrokenthecode,thecureissimple.Undothelastchange,makeabetteroneandproceedmerrilyalongyourway.

However,ifyourearrangecodewithoutchangingbehaviorandtestsbegintofail,thentheteststhemselvesareflawed.Teststhatmakeassertionsabouthowthingsaredone,ratherthanwhat

Page 118: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

actuallyhappens,aretheprimecontributorstothispredicament.Forexample,atestthatmakesassertionsabouthowamethodisimplementedwillobviouslybreakifyouchangethatmethod’simplementation,evenifitsoutputisunchanged.Wheninthissituation,there’snoalternativeotherthantoimprovethetestsbeforeembarkinguponarefactoring.

Testsarethewallatyourback.Successfulrefactoringsleanongreen.Therefore,youshouldneverchangetestsduringarefactoring.Ifyourtestsareflawedsuchthattheyinterferewithrefactoring,improvethemfirst,andthenrefactor.

3.6.FollowingtheFlockingRulesRecallthatthecurrenttaskistoremoveduplicationfromthecase statementofthe verse method.

The case statementhasfourbranches,eachofwhichcontainsaversetemplate.Thetemplatesrepresentdistinctversevariants.Thesevariantsobviouslydiffer,butinsomenot-yet-identified,more-abstractway,theyarealsoalike.

Consideredfromahigherviewpoint,eachvariantismerelyaverseinthesong;inthatsensetheyareallthesame.Underlyingeachconcretevariantisageneralizedverseabstraction.Ifyoucouldfindthisabstraction,youcoulduseittoreducethefour-branch case statementtoasinglelineofcode.

Thegoodnewsisthatyoudon’thavetobeabletoseetheabstractioninadvance.Youcanfinditbyiterativelyapplyingasmallsetofsimplerules.Theserulesareknownas"FlockingRules",andareasfollows:

FlockingRules

1. Selectthethingsthataremostalike.

Page 119: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

2. Findthesmallestdifferencebetweenthem.

3. Makethesimplestchangethatwillremovethatdifference.

Changestocodecanbesubdividedintofourdistinctsteps:

1. parsethenewcode

2. parseandexecuteit

3. parse,executeanduseitsresult

4. deleteunusedcode

Makingsmallchangesmeansyougetverypreciseerrormessageswhensomethinggoeswrong,soit’susefultoknowhowtoworkatthislevelofgranularity.Asyougainexperience,you’llbegintotakelargersteps,butifyoutakeabigstepandencounteranerror,youshouldrevertthechangeandmakeasmallerone.

Asyou’refollowingtheflockingrules:

Fornow,changeonlyonelineatatime.

Runthetestsaftereverychange.

Ifthetestsfail,undoandmakeabetterchange.

Why"Flocking"?Birdsflock,fishschool,andinsectsswarm.Aflock’sbehaviorcanappearsosynchronizedandcomplexthatitgivestheimpressionofbeingcentrallycoordinated.Nothingcouldbefurtherfromthetruth.Thegroup’sbehavioristheresultofacontinuousseriesofsmall

Page 120: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

decisionsbeingmadebyeachparticipatingindividual.Thesedecisionsareguidedbythreesimplerules.

1. Alignment-Steertowardstheaverageheadingofneighbors

2. Separation-Don’tgettooclosetoaneighbor

3. Cohesion-Steertowardstheaveragepositionoftheflock

Thus,complexbehavioremergesfromtherepeatedapplicationofsimplerules.Inthesamewaythattherulesinthissidebarallowbirdstoflock,the"FlockingRules"forcodeallowabstractionstoappear.

FlockofStarlingsActingAsASwarm,JohnHolmes,CCBY-SA2.0

Toseeabeautifulexampleofflockinginaction,watchStevenStrogatz’sTheScienceofSyncTEDtalk.

Page 121: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

3.7.ConvergingonAbstractionsTheFlockingRulesaresoatomic,andsogeneral,thattheymaynotyetinspireconfidence.Theremainderofthischapterwillusethemtounearthabstractionsinthe verse method,afterwhichyoumayfindtheprocessmoreconvincing.

3.7.1.FocusingonDi erenceWhileit’struethatthereareproblemsforwhichthesolutionisobvious,thoseofanyinterestingsizearen’ttractabletoinstantunderstanding.They’retoobig,orhavetoomanyparts.

Whenexaminingcomplicatedproblems,theeyeisfirstdrawntowardssameness.However,despitethefactthatsamenessiseasiertoidentify,differenceismoreusefulbecauseithasmoremeaning.DRYingoutsamenesshassomevalue,butDRYingoutdifferencehasmore.

ErichGamma,RichardHelm,RalphJohnsonandJohnVlissidesarecommonlyreferredtoasthe"GangofFour,"inreferencetotheirjointauthorshipofDesignPatterns:ElementsofReusableObject-OrientedSoftware.Thisinfluentialbookdescribestwenty-threepatternsorsolutionstocommonOOprogrammingproblemsanditexplainsthisprocessthusly:

Thefocushereisencapsulatingtheconceptthatvaries,athemeofmanydesignpatterns.

Differenceholdsthekeytounderstanding.Iftwoconcreteexamplesrepresentthesameabstractionandtheycontainadifference,thatdifferencemustrepresentasmallerabstractionwithinthelargerone.Ifyoucannamethedifference,you’veidentifiedthatsmallerabstraction.

Page 122: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Thegoodnewsisthatasystematicapplicationoftherulesofrefactoringconvertsdifferencetosameness,decomposingaproblemintoitsconstituentparts.Theevenbetternewsisthatthishappensautomatically.Youdon’thavetoidentifytheunderlyingabstractionsinadvanceofrefactoring.Ifyoumerelywritethecodedictatedbytherules,theabstractionswillfollow.

Thehabitofbelievingthatyouunderstandtheabstraction,andofjumpingtoaninventedsolution,isdeeplyingrained.Programmersstudyaproblem,decideonasolution,andthenimplementit.Solutionsarecraftedbyintention.

Ifthisdescribesyourentirepastexperience,youmayfindthefollowingcodesurprising.Ittakesmanysmall,iterativesteps,andresultsinasolutionthatisdiscoveredbyrefactoring.

Toreducethe verse case statementtoasinglelineofcode,therulessaytofirstidentifythethingsthataremostalike.Thismeansthatyoushouldselectthetwobranchesthataremostalike,andfocusonmakingthemidentical.

Hereagainisareminderofthe case statement:

Listing3.4:VerseMethodConditional

1 casenumber2 when03 "Nomorebottlesofbeeronthewall,"+4 "nomorebottlesofbeer.\n"+5 "Gotothestoreandbuysomemore,"+6 "99bottlesofbeeronthewall.\n"7 when18 "1bottleofbeeronthewall,"+9 "1bottleofbeer.\n"+

Page 123: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

10 "Takeitdownandpassitaround,"+11 "nomorebottlesofbeeronthewall.\n"12 when213 "2bottlesofbeeronthewall,"+14 "2bottlesofbeer.\n"+15 "Takeonedownandpassitaround,"+16 "1bottleofbeeronthewall.\n"17 else18 "#{number}bottlesofbeeronthewall,"+19 "#{number}bottlesofbeer.\n"+20 "Takeonedownandpassitaround,"+21 "#{number-1}bottlesofbeeronthewall.\n"22 end

Noticethatalthoughverse2containshardcodednumbersfor 2and 1 ,itcouldjustascorrectlysay number and number-1 ,asintheelse branch.Thispartlooksdifferent,butislogicallythesame.Itmayhelptorecallthatverse2hasbutonetest,whichassertsthatthefinallinesays“1bottle”insteadof“1bottles.”Theonlyrealdifferencebetweenthe2andelsecasesistheword“bottle”versustheword“bottles.”Therefore,thesearethelinesthataremostalike.

3.7.2.SimplifyingHardProblemsHavingfoundthestringsthataremostalike,thenexttaskistomakethemidentical.It’simportanttofocusonthisspecificgoalwithoutsuccumbingtothetemptationsoftangents.

Thinkoftheprocessofturningthesetwolinesintooneasbeingonahorizontalpath.[7]Whilewalkingthispath,ifsomethingcatchesyoureyeinanotherpartofthecode(perhapsinthe 0 or

Page 124: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

1 cases),youmaybetemptedtoveeroffinaverticaldirection.However,ifyoubeginmakingchangestootherpartsofthecodebeforeyoucompletelycombinethe 2 and else cases,youstepoffawell-trodpathintoawoodssodarkandsinisterthatyoumightneverreturn.Whileitcanbeusefultointerleavehorizontalandverticalwork,it’sbesttofinishthecurrentjourneywhentheterminusofthehorizontalpathisinsight.

Havealookatthecodebelow,anddecidewhattodonext.

Listing3.5:2andElseCase

1 when22 "2bottlesofbeeronthewall,"+3 "2bottlesofbeer.\n"+4 "Takeonedownandpassitaround,"+5 "1bottleofbeeronthewall.\n"6 else7 "#{number}bottlesofbeeronthewall,"+8 "#{number}bottlesofbeer.\n"+9 "Takeonedownandpassitaround,"+10 "#{number-1}bottlesofbeeronthewall.\n"11 end

Recallthattheselineswerechosenbecausetheonlyrealdifferencebetweenthemisusing"bottle"versus"bottles"inthefinalphrase.Theotherapparentdifferencesareactuallysimilarities.The 2 and 1 inthe 2 casecanbereplacedby #{number} and #{number-1} respectively,whichmeansthatthesepartsarelogicallyidentical.

Thechangeneededtoresolvethedifferencesbetweenthe

Page 125: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

numbersisobvious.Thatpartoftheproblemfeelssolved.It’sboring.The"bottle/bottles"difference,however,ismuchmoreinteresting.Itrequiresmorethought.

Programmerslovehardproblems.Notonlythat,butmanytimesthemostdifficultbitofalargerproblemistheriskiest,andrequiresthemostthought.It’snowonderthatmanyprogrammersgravitatetowardsstartingaproblematitsmostconfusingpart.Itjustsohappensthatsolvingeasyproblems,throughamagicalalchemyofcode,sometimestransmuteshardproblemsintoeasyones.Itiscommontofindthathardproblemsarehardonlybecausetheeasyoneshavenotyetbeensolved.

Therefore,don’tdiscountthevalueofsolvingeasyproblems.Withthatinmind,thefirststeptowardsmakingtheselinesidenticalistoresolvetheveryfirstdifference.Scanninglefttoright,theveryfirstcharacterofthe 2 casecouldbereplacedby #{number} .Proceedingrightwards,thenext 2 cansimilarlybereplaced.Scanningfurtherstill,the 1 canbecome #{number-1} .Theresultisshownbelow:

Listing3.6:ReplaceHardCodedNumber

1 when22 "#{number}bottlesofbeeronthewall,"+3 "#{number}bottlesofbeer.\n"+4 "Takeonedownandpassitaround,"+5 "#{number-1}bottleofbeeronthewall.\n"6 else7 "#{number}bottlesofbeeronthewall,"+8 "#{number}bottlesofbeer.\n"+9 "Takeonedownandpassitaround,"+

Page 126: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

10 "#{number-1}bottlesofbeeronthewall.\n"11 end

Aftermakingtheabovechange(andrunningthetestsbetweeneach,ofcourse),theremainingdifferenceis"bottle/bottles"onthelastline:

Listing3.7:OneDifferenceRemains

1 when22 #...3 "#{number-1}bottleofbeeronthewall.\n"4 else5 #...6 "#{number-1}bottlesofbeeronthewall.\n"7 end

Thisisthefirstinterestingdifference.Nowyoumustdecidewhatthisdifferencemeans.

3.7.3.NamingConceptsPrevioussectionsstatethatifallversesarethesameinsomefundamentalway,thenanunderlyingverseabstractionmustexist.Thegoalofthecurrentrefactoringistofindawaytoexpressthatmoreabstractverse.

Ifanunderlyingverseabstractionexists,thenthissmalldifferencebetweenverse2andverses3-99mustrepresentasmallerabstractionwithinthatlargerone.Tomakethesetwolinesthesame,youmustnamethisconcept,createamethodnamedaftertheconcept,andreplacethetwodifferenceswitha

Page 127: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

commonmessagesend.Therefore,it’stimetodecidewhatthewords"bottle"and"bottles"representinthecontextofthesong.

YoumayrecallfromtheConcretelyAbstractsectionofChapter1that"bottle"isnotunderlyingtheconcept.Ifyoucallthemethod"bottle"youarenamingitafteritscurrentimplementation,andyou’vealreadyseenhowthatcangobadlywrong.

Also,despitethatfactthatthesetwowordsdifferinthatoneissingularandoneisplural,theunderlyingconceptisnot"pluralization."Withinthecontextofthesong,"bottle/bottles"doesnotrepresentpluralization.

Therearetwopiecesofinformationthatcanhelpinthestruggleforaname.Oneisageneralruleandtheotheristhenewrequirement.

First,thenewrequirement.Recallthattheimpetusforthisrefactoringwastheneedtosay"six-pack"insteadof"bottle/bottles"whenthereare6bottles.Thestring"six-pack"isonemoreconcreteexampleoftheunderlyingabstraction.Thissuggeststhatifyounamethemethod"bottle,"youwillregretthisdecisioninshortorder.

Thegeneralruleisthatthenameofathingshouldbeonelevelofabstractionhigherthanthethingitself.Thestrings"bottle/bottles/six-pack"areinstancesofsomecategory,andthetaskistonamethatcategoryandtodosousinglanguageofthedomain.

Onewaytoidentifythecategoryistoimaginetheconcreteexamplesasrowsandcolumnsinaspreadsheet.[8]Thefollowingtableillustratesthisidea.Thistablecontainsthreerows,oneforeachconcreteexample.Eachrowhastwocolumns.Thefirstcolumncontainsanumberofbottles;thenext,thewordusedwiththatnumberinthesong

Page 128: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Table3.1:BottlesColumnHeader

Number xxx?

1 bottle

6 six-pack

n bottles

Column1abovecontainsnumbers,so"Number"makessenseasacolumnheader.Theheader"Number"isalevelofabstractionhigherthantheconcreteexamples."1,""6,"and"n"arenumbers.

Thesecondcolumnhasentriesforbottle,six-pack,andbottles.Bottleisanentityinthisas-yetunnamedcategory,ratherthanthecategoryitself.

Itmightseemasif"Unit"wouldbeagoodheader.Althoughit’struethateveryexampleissomekindofunit,therearetwoproblemswiththisname.First,it’stooabstract.Unitisnotonelevelofabstractionhigherthantheexamples,it’smany.Thereareplentyofgoodnamingalternativesonthecontinuumbetween"bottle"and"unit."Next,unitisnotinthelanguageofthedomain.Thenameyouchoosewillbethenameyouuseinconversationswithyourcustomers.Namingthingsafterdomainconceptsimprovescommunicationbetweenyouandthefolkswhopaythebills.Onlygoodcancomeofthis.

Whenyou’restrugglingtofindagoodnamebuthaveonlyafewconcreteinstancestoguideyou,itcanbeilluminatingtoimagineotherthingsthatwouldalsobeinthesamecategory.[9]Forexample,ifthesongwereaboutwine,thewinemightcomeinacarafe.Juicesometimescomesinsmallboxes.Softdrinkscomeincans.

Page 129: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Ifyouweretoaskyourusers,"Whatkindofthingisabottle?,"theywouldn’treply"It’saunit."Insteadtheymightcallitthecontainer.Inthecontextof99Bottles,containerisagoodnameforthisconcept.Containerismeaningful,understandable,andunambiguous.

Havingnamedtheconcept,it’stimetowritecodetoremovethedifference.

3.7.4.MakingMethodicalTransformationsNowthatyou’vedecidedtocreatea container method,it’stimetoalterthecode.It’stemptingtomakeallofthenecessarychangesinonefellswoop.Doingsorequiresaddinganewmethodandinvokingitintwoplaces.Here’sthenewmethod:

Listing3.8:GuessEntireContainer

1 defcontainer(number)2 ifnumber==13 "bottle"4 else5 "bottles"6 end7 end

Thismethodmustbeinvokedfrombothbranchesofthe versecase statement.Herethecode:

Butwait.Noticethattheabovechangeaddssevennewlinesofcode,changestwoexistingones,andalterscodeinthreeseparateplaces.Anyofthesechangescouldintroduceerrors,whichyouwouldthenbeobligedtounderstandandcorrect.Thissmallexamplestandsinforthemuchbiggerreal-lifeproblemwhere,

"#{number-1}#{container(number-1)}ofbeeronthewall.\n"

Page 130: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

intheprocessofimplementinganewfeature,youaddmanylinesofcode,changemanyothers,andthenrunthetests,onlytobeconfrontedwithaoceanofred.

Realworldproblemsarebig.Realcodehasbugs.Realtestsareoftentightlycoupledtocurrentimplementations.Ifyousimultaneouslychangemanythingsandsomethingbreaks,you’reforcedtounderstandeverythinginordertofixanything.Youcouldendupchasingafterred,withincreasingdesperation,beforeeventuallydiscardingallofthechangesandbeginninganew.

Makingaslewofsimultaneouschangesisnotrefactoring—it’srehacktoring.Itwouldbemuchbettertomakeaseriesoftinychangesandrunthetestsaftereach.Ifthetestsfail,youknowtheexactchangethatcausedthefailure,andcanundobacktogreenandmakeabetterchange.Ifthetestspass,youknowthatthecurrentcodeworks,eveniftherefactoringisonlypartiallycomplete.

Formalrefactoringconferstwoadditionalbenefits.First,becausenochangebreaksthetests,thecodecanbedeployedtoproductionatanyintermediatepoint.Thisallowsyoutoavoidaccumulatingalargesetofchangesandsufferingthroughapainfulmerge.Next,codethatrunsproperlyeveninthemidstofalongrefactoringincreasesthebusfactor.Thiscontributestoahigherlikelihoodofprojectsuccessevenifyou,personally,weretomeetanuntimelyend.

Addingthe container methodbyrefactoringmeanstakingaseriesofsmallsteps.Asareminder,hereagainaretheFlockingRulesandcorollaries:

FlockingRules

1. Selectthethingsthataremostalike.

Page 131: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

2. Findthesmallestdifferencebetweenthem.

3. Makethesimplestchangetoremovethatdifference:

a. parsethenewcode

b. parseandexecuteit

c. parse,executeanduseitsresult

d. deleteunusedcode

Asyou’refollowingtherules:

Ingeneral,changeonlyonelineatatime.

Runthetestsaftereverychange.

Ifyougored,undoandmakeabetterchange.

You’vealreadyfollowedrule1(youchosethe 2 and else cases)andrule2(you’veworkedyourwayacrosstothe"bottle/bottles"difference).Nowyou’reonrule3,readytoremovethisdifference.Asyouintendtochangeonlyonelineatatime,you’llofnecessityhavetomakesmallchangesiteratively.

Thefirststepistocreateanempty container method.

Listing3.9:EmptyContainerMethod

1 defcontainer2 end

Nowrunthetests.

Ifthisadmonitioncomesasasurprise,considerthathavinggreentestsatthispointprovidesaveryusefulpieceoffeedback.

Page 132: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Eventhoughthe container methodisnotyetbeinginvoked,greentestsatthispointprovethatthecodeyoujustwroteissyntacticallycorrect.Thismeansyouarefollowingrule3a,whichcallsforseparatingparsefromexecute.

Nowthatyouhavewrittenthisadmittedlynotveryexcitingcontainer method,thenextstepistomakethesmallestchangethatwilladvancethecodeintheintendeddirection.Here’sareminderofthetargetline:

Listing3.10:OneDifferenceRemainsRedux

1 when22 #...3 "#{number-1}bottleofbeeronthewall.\n"4 else5 #...6 "#{number-1}bottlesofbeeronthewall.\n"7 end

Thecurrent container methodreturns nil .Itwilleventuallybecalledfromtwoplaces.The 2 casewantsthereturntobe"bottle,"andthe else case,"bottles."Thenextincrementalchangeistoalterthemethodtomakeitusableforjustoneofthosecallers.Therefore,youmustnowchoosewhichvaluetoreturnfirst.

Thedefaultcaseisoftenagoodplacetostart,andthere’snoreasonnottodosohere.Inthatspirit,change container toreturnbottles ,likeso:

Listing3.11:SparseContainerMethod

Page 133: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

1 defcontainer2 "bottles"3 end

Fromnowon,itgoeswithoutsayingthatyoushouldrunthetestsaftereverychange.

Nowthat container returnsausablevalue,alterthe else branchtosendthemessageinplaceoftheword"bottles,"asonline8below:

Listing3.12:SparseContainerUsedinElseBranch

1 defverse(number)2 #...3 when24 #...5 "#{number-1}bottleofbeeronthewall.\n"6 else7 #...8 "#{number-1}#{container}ofbeeronthewall.\n"9 end10 end11 12 defcontainer13 "bottles"14 end

Sofar,sogood,butconsiderthenextstep.Tobeusableinboththe 2 and else cases, container musteventuallyreturnthe

Page 134: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

correctchoicebetween bottle or bottles .Thedecisionbetweenthemisbasedonthevalueof number ,which container doesnotyetknow.Therefore, container mustbechangedtotakeanargument.

Justas container doesn’tcurrentlytakeanargument(line12above),itsinvokerdoesn’tcurrentlysendone(line8above).Nowyoufaceaconundrum.Thegoalistomakechangesononelineatatime,butthissituationseemstorequirethatyouchangeboththesenderandthereceiversimultaneously.

Toillustratetheproblem,considerwhathappensifyoumakeeitherofthesechangeswithouttheother.Youcouldaddtheargumenttothemethoddefinitionfirst,likeso:

Inthiscase,themessagesendfailsbecauseitdoesn’tyetsendtheargument:

Ifyoureversetheorderofthechanges,andsendtheargumentfirst,asso:

Thentheoppositefailureoccurs,i.e.anargumentispassedwherenoneisexpected.

Thisproblem,needingtoaddarequiredargument,arisesregularlyintherealworld.Butinsteadofonesenderandonereceiver,asinthiscase,realapplicationsmighthave10or100or1000senders.Itmightbeimpossibletofixeverythingatonce,so

defcontainer(number)

ArgumentError:wrongnumberofarguments(0for1)

"#{number-1}#{container(number-1)}ofbeeronthewall.\n"

ArgumentError:wrongnumberofarguments(1for0)

Page 135: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

it’shandytoknowthetechniqueforworkingaroundthisprobleminanincrementalmanner.

3.7.5.RefactoringGraduallyInhisbookRefactoringtoPatterns,JoshuaKerievskytalksabout"GradualCutoverRefactoring,"astrategyforkeepingthecodeinareleasablestate,graduallyswitchingoverasmallnumberofpiecesatatime.Thistypeofrefactoringcanbedonealongsideotherdevelopmentworksoasnottoaffectthereleaseschedule.

Manyofyourcolleagues,includingyourcustomers,willapplaudthisstrategyandyourwillingnesstokeepthecodereleasable.Butwhenyouarepreventedfromfixingallofthesendersatonce,youmustdosomethingtoallowsometopassthenewargumentwhileothersremainunchanged.Thetrickhere,asyoumayalreadyhaveguessed,istobeginbyaddinganoptionalargumentthatsuppliesitsowndefault,asshownbelow:

Listing3.13:ContainerWithDefaultedArgument

1 defcontainer(number=:FIXME)2 "bottles"3 end

Theabovecodetakesanargumentnamed number ,whichitdefaultstothesymbol :FIXME .Youmayhaveexpectedthedefaulttobe nil ,orattheveryleast,anumericvalue,butinthiscaseitmakessensetosetittosomethingthat’susefullywrong.Thisdefaultisatemporaryshimwhosepurposeistoenableastep-by-steprefactoring.Oncetherefactoriscomplete,thedefaultshouldberemoved.Settingittoavaluelike:FIXMEwillhelpyouremembertodothisclean-up.

Nowthatthe container methodacceptsanargument,considerthenextstep.Youcouldeither:

Page 136: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

alter container tocheckthevalueof number andreturn"bottle"or"bottles,"i.e.changethereceiver,or

alterthe else branchtoaddthe number argumentto containermessage,i.e.changethesender.

Therefactoringrulesprohibityoufrommakingbothofthesechangesatonce,soyoumustchooseoneortheother.

Becausethe container methoddoesnotyetreference number ,changingthe else branchtopassthisargumentchangesalmostnothingaboutthecode.Insteadofpassingtheargument,thebetterchoiceistoexpandthecodein container touse number todecidewhichof"bottle"or"bottles"toreturn,asfollows:

Listing3.14:ContainerWithConditional

1 defcontainer(number=:FIXME)2 ifnumber==13 "bottle"4 else5 "bottles"6 end7 end

Thereareseveralthingstonoteabouttheabovestrategy.

First,noticethataddingtheconditionalwasveryclearlyamultilinechange.Thismayappeartobreakthe"makechangesononlyoneline"rule,butinthiscase,thechangeisobeyingthespiritofthelawwhileslightlyignoringitsletter.Thisconditionalcouldhavebeenexpressedinternaryform,as:

number==1?"bottle":"bottles"

Page 137: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

whichwouldcertainlyhavebeenaone-linechange.Themultiline if formaboveispreferredinthisrefactoringforreasonsthatwillbecomeclearinlaterchapters.Fornow,justthinkofthesetwoformsasbothobeyingthe"oneline"rule.

Next,rememberthatthismethodisbeinginvokedfromonlyoneplace(the else branchofthe case statementin verse ),andthatasyetnoargumentisbeingpassed.Thismeansthatthe numberargumentin container getssetto :FIXME ,whichroutesexecutiontothe false branch.Thenewcodeinthetruebranchisnotyetbeingexecuted,althoughitgetsparsedwhenthetestsrun.

Theactofaddinganewbranchtotheconditionalwhileexecutingonlythepreviouslyexistingcodeisamini-exampleoftheOpen/ClosedPrinciple.Youcanthinkofthischangeasmakingthe container methodopentoanewrequirement—enablingittooccasionallyreturntheword"bottle."Thissplitsthechangeintoseveralsmallsteps,whichmakesiteasiertodebuganyerrors.

Thenexttinystepistochangethesendertoactuallypassthenewargument.Because container isbeinginvokedfromthefourthphraseofthesong,thevalueoftheargumentis number-1 ,asshownonline8below:

Listing3.15:PassinganArgumenttoContainer

1 defverse(number)2 #...3 when24 #...5 "#{number-1}bottleofbeeronthewall.\n"6 else7 #...

Page 138: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

8 "#{number-1}#{container(number-1)}ofbeeronthewall.\n"9 end10 end11 12 defcontainer(number=:FIXME)13 ifnumber==114 "bottle"15 else16 "bottles"17 end18 end

Theabovestepmightseemsotinyastoseempointlesstoisolate,butthere’sarealdifferencebetweenexecutingthe false branchbecauseofthe :FIXME default,andbeingroutedtherebecauseofthevalueofthe number argument.Inthefirstcase,youknowthatifyougotothe false branchthetestspass,andinthesecond,youknowthattheargumentbeingpassedtakesyoutothe falsebranch.Bothofthesethingsmustworkorthetestswillbreak.Changingcodeatthislevelofgranularitymakesiteasiertohandleunexpectedfailures.

Thenextstepistochangethe2branchsothatitalsoinvokesthecontainermethod,asshownbelow:

Listing3.16:2andElseCasesIdentical,NumberDefaultExists

1 defverse(number)2 #...3 when24 "#{number}bottlesofbeeronthewall,"+

Page 139: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

5 "#{number}bottlesofbeer.\n"+6 "Takeonedownandpassitaround,"+7 "#{number-1}#{container(number-1)}ofbeeronthewall.\n"8 else9 "#{number}bottlesofbeeronthewall,"+10 "#{number}bottlesofbeer.\n"+11 "Takeonedownandpassitaround,"+12 "#{number-1}#{container(number-1)}ofbeeronthewall.\n"13 end14 end15 16 defcontainer(number=:FIXME)17 ifnumber==118 "bottle"19 else20 "bottles"21 end22 end

Theabovechangehastwoconsequences.First,allofthecodeincontainer isnowbeingexecuted.Next,thecodeinthe 2 and elsebranchesofthe verse casestatementarenowidentical.

Twotasksremaintocompletethisentirehorizontalrefactoring.First,asallsendersof container nowpass number ,the :FIXMEdefaulthasserveditspurposeandcanberemoved.Next,the 2caseisnowobsolete,andsoitalsocanbedeleted.Thefollowingexampleshowstheresulting,completecode:

Page 140: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Listing3.17:2SubsumedIntoElseCase,NumberDefaultRemoved

1 defverse(number)2 casenumber3 when04 "Nomorebottlesofbeeronthewall,"+5 "nomorebottlesofbeer.\n"+6 "Gotothestoreandbuysomemore,"+7 "99bottlesofbeeronthewall.\n"8 when19 "1bottleofbeeronthewall,"+10 "1bottleofbeer.\n"+11 "Takeitdownandpassitaround,"+12 "nomorebottlesofbeeronthewall.\n"13 else14 "#{number}bottlesofbeeronthewall,"+15 "#{number}bottlesofbeer.\n"+16 "Takeonedownandpassitaround,"+17 "#{number-1}#{container(number-1)}ofbeeronthewall.\n"18 end19 end20 21 defcontainer(number)22 ifnumber==123 "bottle"24 else25 "bottles"

Page 141: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

26 end27 end28 end

Thathorizontalrefactoringrequiredafairamountofexplanation.Here’sareminderofthekeyactions:

1. identified verse 2 and else asthemostsimilarcases

2. workedfromlefttoright

3. changed verse 2 casetoreplacehardcoded 2 with #{number}(twice)

4. changed verse 2 casetoreplacehardcoded 1 with #{number-1}

5. identified"bottle"and"bottles"asthenextdifference

6. chosecontainerforthenameoftheconceptrepresentedbythisdifference

7. createdempty container method

8. changed container toreturn"bottles"

9. changed verse else casetosend container inplaceof"bottles"

10. changed container totake number argumentwithdefault:FIXME

11. addedconditionallogicto container toreturn"bottle"or"bottles"basedon number

12. changed verse else casetopass number-1 to container

13. changed verse 2 casetosend container(number-1) inplaceof

Page 142: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

"bottle"

14. deleted verse 2 case

15. deleted container :FIXME number argumentdefault

Ofthese15steps,12involvechangestocode.Thetestsrunaftereverychange,soitistrivialtofixnewly-introducedflaws.

Thelengthydescriptionabovemayhaveledyoutofearthatworkinginthisfashionwouldbeunbearablyslow.Takeanotherlook.Asyoucansee,there’snotmuchcode,andwithpractice,writingitbecomesveryfast.Thesmallamountoftimelosttomakingincrementalchangesismorethanrecoupedbyavoidinglengthyandfrustratingdebuggingsessions.Thisstyleofcodingisnotonlyfast,it’salsostress-free.

Thisfirstrefactoringwasdeliberatelyperformedusingthesmallestpossiblesteps.Onceyoulearntoworkatthislevelofgranularity,youcanlatercombinestepsifcircumstancesallow.Letredbeyourguide.Ifyoutakeagiantstepandthetestsbegintofail,undoandfallbacktomakingsmallerchanges.

Thereareplentyofhardproblemsinprogramming,butthisisn’toneofthem.Realrefactoringiscomfortinglypredictable,andsavesbrainpowerforpeskierchallenges.

3.8.SummaryWhenfacedwiththeneedtochangecode,veryoftenthehardestdecisioniswheretostart.ThischaptersuggestedthatyoubeguidedbytheOpen-ClosedPrinciple,andsoseparatemostchangesintotwobroadsteps.First,refactortheexistingcodetobeopentothenewrequirement,next,addthenewcode.

Sometimesthefirststep,refactoringtoopenness,requiressuchalargeleapthatitisnotobvioushowtoachieveit.Inthatcase,be

Page 143: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

guidedbycodesmells.Improvecodebyidentifyingandremovingsmellsandhavefaiththatasthecodeimproves,apathtoopennesswillappear.

Makingexistingcodeopentoanewrequirementoftenrequiresidentifyingandnamingabstractions.TheFlockingRulesconcentrateonturningdifferenceintosameness,andthusareusefultoolsforunearthingabstractions.

Thischapterintroducedthesix-packrequirement,andinthesearchforopenness,identifiedtheduplicationofcodeintheverse methodasthefirstpointofattack.Itthendedicatedagoodportionofthechaptertothetaskofmakingthe else and 2 casesidentical.However,nowthatyou’velearnedhowtousetheflockingrulestoidentifyabstractions,resolvingthedifferencesinthe 1 and 0 caseswillgomuchfaster.So,ontoChapter4,andmoreextractingofabstractions.

Page 144: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

4.PracticingHorizontalRefactoringThepreviouschapterintroducedtheFlockingRules,whichitusedtoremovethespecialcaseforverse2.Thechaptercontainedplentyofexplanationabouthowtoapplytherules,butnotmuchnewcode.Fortunately,therefactoringsdictatedbytheFlockingRulesareeasierdonethansaid,andhavingreadthepriorchapter,youarenowequippedtomovebrisklythroughtheotherspecialcases.

ThischapteriterativelyappliestheFlockingRulestotheremainingspecialverses,andresultsinasingle,moreabstract,templatethatproduceseverypossibleverse.

4.1.ReplacingDi erenceWithSamenessTherefactoringrulessaytostartbychoosingthecasesthataremostalike.Nowthatverse2isbeingproducedbythe elsebranch,onlythreedifferentversetemplatesremain.Havealookatthecodebelow,andselectthetwocasesonwhichtoconcentratenext.

Listing4.1:3BranchCaseStatement

1 defverse(number)2 casenumber3 when04 "Nomorebottlesofbeeronthewall,"+5 "nomorebottlesofbeer.\n"+6 "Gotothestoreandbuysomemore,"+7 "99bottlesofbeeronthewall.\n"8 when1

Page 145: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

9 "1bottleofbeeronthewall,"+10 "1bottleofbeer.\n"+11 "Takeitdownandpassitaround,"+12 "nomorebottlesofbeeronthewall.\n"13 else14 "#{number}bottlesofbeeronthewall,"+15 "#{number}bottlesofbeer.\n"+16 "Takeonedownandpassitaround,"+17 "#{number-1}#{container(number-1)}ofbeeronthewall.\n"18 end19 end

The 1 casediffersfromthe else caseinseveralways.Itusesahardcoded 1 asthestartingnumber,ittakes "it" insteadof"one" down,anditendswith "nomore" insteadof number-1bottles.

The 0 caseisevenmoredifferentfromthe else case.Itstartswith "Nomore" ,itsays "Gotothestoreandbuysomemore" ,anditendswith "99" .

Finally,the 1 and 0 casesdifferfromoneanotherinlotsofways.Theybothhavemoreincommonwiththe else casethaneachother.

Ofthesethreeversetemplates,the 1 and else casesaremostalike,sothey’rethenexttoaddress.Startbylookingatthefirstlinesofeach:

Listing4.2:1andElse,1stPhrasesDiffer

1 when1

Page 146: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

2 "1bottleofbeeronthewall,"+3 #...4 else5 "#{number}bottlesofbeeronthewall,"+6 #...7 end

Justaswiththe 2 and else cases,theveryfirstcharacterisdifferent.Removethisdifferencebyinterpolating number inplaceofthehard-coded 1 inthe 1 case,asbelow:

Listing4.3:1andElse,1stPhrasesinProgress

1 when12 "#{number}bottleofbeeronthewall,"+3 #...4 else5 "#{number}bottlesofbeeronthewall,"+6 #...7 end

Asimilarchangewasmadeinthepreviouschapter,wherethehard-coded "2" wasreplacedby #{number} whencombiningthe2 and else cases.Theactofsubstitutingavariableforanexplicitnumberissominorthatitdoesn’tadequatelyreflecttheenormityoftheunderlyingidea,butstepbackandconsiderwhatjusthappened.Replacingdifferingconcretevalueswithareferencetoacommonvariablechangesdifferenceintosameness.

Thefactthattheargumentisknowntoequal 1 doesnotmatter.

Page 147: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Thissubstitutionisimportant,notbecauseitchangestheresultingvalue,butbecauseitincreasesthelevelofabstraction.Itisthisincreaseinabstractionthatmakesthingsthesame.Withoutit,youaredoomedtotheconditional.

Thenextdifferenceis"bottle"versus"bottles."This,conveniently,isthepreviouslyidentified"container"concept.Eachlineischangedtosendthe container message,whichresultsinthefollowingcode:

Listing4.4:1andElse,1stPhrasesIdentical

1 when12 "#{number}#{container(number)}ofbeeronthewall,"+3 #...4 else5 "#{number}#{container(number)}ofbeeronthewall,"+6 #...7 end

Thefirstphrasesarenowidentical.

Thesecondphraseofeachcaseisverysimilartothefirst,asyoucanseehere:

Listing4.5:1andElse,2ndPhrasesDiffer

1 when12 "#{number}#{container(number)}ofbeeronthewall,"+3 "1bottleofbeer.\n"+4 #...

Page 148: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

5 else6 "#{number}#{container(number)}ofbeeronthewall,"+7 "#{number}bottlesofbeer.\n"+8 #...9 end

Thesecondphraseissosimilartothefirstthatrepeatingthesamechangewillmakethemidentical.Here’stheresult:

Listing4.6:1andElse,2ndPhrasesIdentical

1 when12 "#{number}#{container(number)}ofbeeronthewall,"+3 "#{number}#{container(number)}ofbeer.\n"+4 #...5 else6 "#{number}#{container(number)}ofbeeronthewall,"+7 "#{number}#{container(number)}ofbeer.\n"+8 #...9 end

Aftertheabovechanges,thefirsttwophrasesofthe 1 and elsecasesareidentical.

4.2.EquivocatingAboutNamesThename container feelsright.Itwasfairlyeasytofind,inpartbecausetheunderlyingconceptissoobvious.Onceyourealize

Page 149: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

thatyou’retryingtonameacategorythatcontainsbottles,juiceboxes,andcarafes, container naturallyfollows.

However,whenconceptsarefuzzier,findingagoodnamecanbemuchharder.Thissectiondealswithjustsuchaconcept,andoffersseveralsuggestionsforwhattodowhenyoucan’tfindagoodname.

Nowthatphrasesoneandtwoarethesame,it’stimetoconsiderphrasethree.Here’sareminderofthatcode:

Listing4.7:1andElse,3rdPhrasesDiffer

1 when12 #...3 "Takeitdownandpassitaround,"+4 #...5 else6 #...7 "Takeonedownandpassitaround,"+8 #...9 end

Thedifferenceaboveisthat "it" matchesupwith "one" .

Ifallversevariantsarealikeinanunderlying,moreabstract,way,then"it"and"one"mustrepresentasmallerabstractionwithinthatlargerone.Onceyounamethisconcept,youcancreateamethodwiththatname,andthenmaketheselinesalikebysendingamessageinplaceofthedifferentstrings.

Ifthepreviousparagraphgaveyouasenseofdeja-vu,that’sunderstandable.Thisisexactlyhow"bottle"and"bottles"becamecontainer ,andhoweveryfuturedifferencewillberesolved.Theprocessmayseemtoostraightforwardtobelieve,butthe

Page 150: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

mechanismtrulyisthishumble.Therulesofrefactoringaresimple,butwhenfollowed,preciseandcomplexbehavioremerges.

Thechallenge,asalways,isidentifyingthecurrentconceptandcomingupwithagoodname.Thewords "it" and "one" aresoinnatelygenericthatnamingtheunderlyingconceptisparticularlytough.Namesshouldneitherbetoogeneralnortoospecific.Forexample, thing istoobroad,and it_or_one toonarrow.

Ifyouweretoaskyourcustomertonamethiscategory,theywouldlikelyshrugandcallit pronoun .Ifyouobjectto pronoun onthegroundsthatit’soverlygeneral,andinsistthattheygivethecategoryamorespecificname,theymightcomeupwithsomethinglike thing_drunk .

Although pronoun doesfeelabittoogeneral, thing_drunk isjustaboutunbearable.Neitherfeelsperfect.Thissituation,unfortunately,isalltoocommon.Whentheperfectnameforaconceptiselusive,therearethreestrategiesformovingforward.

Somefolksallotthemselvesfivetotenminutestoponder(usuallywiththesaurusinhand),andthenusethebestnametheycancomeupwithduringthatinterval.Theirrationaleisthatthenametheychoosemightbegoodenough,andiftheylaterdiscoverit’snot,theycanalwaysimproveit.Thesefolkshavetheadvantageofworkingwithcodethatcontainsnamesthatareatleastsomewhatuseful,evenifnotentirelycorrect,butmustlivewiththepossibilitythatagood-enoughnamewillpersist,evenafterabetternamebecomesobvioustothehumansinvolved.

Otherfolksfinditmorecosteffectivetoinstantlychooseameaninglessnamelike foo or namethis .Thisstrategyallowsthemtomoveforwardquickly,and(onehopes)insuresthatthenamewillgetimprovedlater.Thesefolksbelievestronglyinthe"You’ll

Page 151: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

neverknowlessthanyouknowrightnow"dictum,[10]andfullyexpectthatabetternamewilloccurastheyworkonthecode.Theybelievethere’snopointinwastingtimethinkingaboutitnow,whenthenamewillbeobviouslater.

Finally,insteadoffollowingoneoftwopreviousstrategiesbyyourself,youcansimplyasksomeoneelseforhelp.Withinanygroupofprogrammers,there’softensomeonewho’sgoodatnamingthings.Ifyourgrouphassuchaperson,youknowwhotheyare.Appointthemthe"nameguru,"andleveragetheirstrengthswhenyouneedaname.

Inthecaseof "it" or "one" herein99Bottles, pronoun isgoodenoughfornow.Ifsomethingbetteroccurslater,youcanalwaysimprovethename.

Theproceduretoturn "it" and "one" into pronoun isidenticaltotheonethattransformed"bottle"and"bottles"into container .Havingpreviouslypracticed,thisnextrefactoringwillgoquickly.Thefollowingexamplesstepthroughthetransitions.Remembertorunthetestsaftereachchange.

First,defineanempty pronoun method.

Listing4.8:EmptyPronounMethod

1 defpronoun2 end

Alter pronoun toreturn"one,"i.e.thevaluefromthe else branch.

Listing4.9:SparsePronounMethod

1 defpronoun2 "one"3 end

Page 152: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Alterthe else branchtosend pronoun inplaceof"one":

Listing4.10:SendPronouninElseBranch

1 when12 #...3 "Takeitdownandpassitaround,"+4 #...5 else6 #...7 "Take#{pronoun}downandpassitaround,"+8 #...9 end

Addadefaultedargumentto pronoun .

Listing4.11:PronounWithDefaultedArgument

1 defpronoun(number=:FIXME)2 "one"3 end

Alter pronoun tobeopentothe 1 case.

Listing4.12:PronounWithConditional

1 defpronoun(number=:FIXME)2 ifnumber==13 "it"4 else5 "one"6 end

Page 153: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

7 end

Alterthe else casetopassthe number argumentto pronoun (line9).

Listing4.13:PassinganArgumenttoPronoun

1 defverse(number)2 #...3 when14 #...5 "Takeitdownandpassitaround,"+6 #...7 else8 #...9 "Take#{pronoun(number)}downandpassitaround,"+10 #...11 end12 end

Alterthe 1 casetosend pronoun(number) inplaceof"it"(line5).

Listing4.14:1andElseCasesSendPronoun

1 defverse(number)2 #...3 when14 #...5 "Take#{pronoun(number)}downandpassitaround,"+6 #...7 else

Page 154: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

8 #...9 "Take#{pronoun(number)}downandpassitaround,"+10 #...11 end12 end

Alterthe pronoun methodtoremovethe :FIXME default:

Listing4.15:FinalPronounMethod

1 defpronoun(number)2 ifnumber==13 "it"4 else5 "one"6 end7 end

Therefactoringstepsthatadded pronoun wereexactlylikethoseusedtoadd container .Ineachcase,differingstringswerereplacedbyacommonmessagesend.The container abstractionreplacedthe"bottle"and"bottles"strings,andthe pronounabstractionreplaced"it"and"one."

Thiscompletestheadditionofthe pronoun method,andmakesphrasethreeofthe 1 and else casesidentical.It’stimetomoveontothefourthandfinalphrase.

4.3.DerivingNamesFromResponsibilitiesAlthough pronoun mayfeeltoogeneral,theconceptitrepresentsisclear.Ifyouhadtodescribetheunderlyingidea,youmightsaysomethinglike"The pronoun messagereturnsthewordthatis

Page 155: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

usedinplaceofthenoun'bottles,'followingtheword'Take,'inphrase3ofeachverse."Pedanticasthatexplanationis,it’sentirelycorrect.Thatis pronoun 'sresponsibility.

Thedifficultynaming pronoun illustrateshowharditcanbetochooseaname,evenwhenyouunderstandtheconcept.Imagine,then,howimpossibleitistochooseanamewhenyoudon’t.Thisnextsectionaddressesthefourthandfinalphrase,andtakesonthechallengeofnamingaconceptthatismuchlessclear-cut.

Thefirstdifferenceinphrasefourlooksabit,well,different,butregardless,itcanberesolvedusingthetechniqueyou’vebeenusing.Thetricktogettingthisnextrefactoringrightistotrusttherules,andtowriteonlythecodethattheyrequire.

Here’salookatphrasefourofthe 1 and else cases:

Listing4.16:1andElse,4thPhrasesDiffer

1 when12 #...3 "nomorebottlesofbeeronthewall.\n"4 else5 #...6 "#{number-1}#{container(number-1)}ofbeeronthewall.\n"7 end

Lookatthecodeaboveandidentifythedifferences.Itmighthelptofirstdecidewhatisnotadifference.Bothphasesendwith"ofbeeronthewall,"sothatpartisclearlythesame.Ifyoudisregardthatsameness,you’releftwith:

"nomorebottles"

Page 156: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

whichmatchesupagainst:

Ifit’snotclearhowtoproceed,lookforawaytomakethelinesmorealike(evenifnotyetidentical),usingcodeyou’vealreadywritten.Rememberthatthegoalistolocatethenextsmalldifference,notthenextclumpofdifferences.

Noticetheword"bottles"inthe 1 case.Theabstractionthatunderlies"bottles"haslongsincebeenidentified.It’sencapsulatedinthe container method,whichisalreadybeingusedbythe else case.

If "bottles" isactuallythesameas container(number-1) ,thenthatpart(atleastlogically),isresolved.Thisleaves:

whichgoeswith:

Untilnow,thedifferencesbetweenphraseshavebothbeenstrings.Here,forthefirsttime,oneisastringandtheotherisinterpolatedcode.However,itdoesn’tmatterwhatformthedifferencetakes.Ifeachversevariantreflectsamoregeneralverseabstraction,thenthedifferencesbetweenthevariantsmustrepresentsmallerconceptswithinthatlargerabstraction.Again,youcanresolvethisdifferencebyfollowingthepatternyoulearnedfrom container and pronoun .Nametheconcept,createthemethod,andreplacethedifferencewithacommonmessagesend.

Tohelpyounamethenewconcept,rememberthe"whatwouldthecolumnheaderbe?"technique.Thefollowingtableshowsa

"#{number-1}#{container(number-1)}"

"nomore"

"#{number-1}"

Page 157: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

samplingofnumbersandassociatedvalues:

Table4.1:NumbertoXXXColumnHeader

Number XXX?

99 '99'

50 '50'

1 '1'

0 'nomore'

Inthetableabove,theleftcolumncontainsanumberbetween99and0,andtherightholdsthestringtobesunginitsplace.Mosttimesthevalueontherightisthedirectstringrepresentationofthenumberontheleft,i.e. 99 becomes "99" , 50 , "50" ,etc.Theexceptionis 0 ,whichbecomes,not "0" asyoumightexpect,but"nomore" .

Phrasefouristhefinalphraseofthesongwherethenumbergetsdecremented,andsotheargumentisalways number-1 .It’stempting,therefore,tothinkof"nomore"and #{number-1} asrepresentingthenumberofbottlesthatremainonceaverseiscomplete.

Youcouldindeednamethisconcept"remainder,"andproceedwiththerefactoring.However,intheinterestofsavingabitofpain,takeabriefpeekforward.You’llsoonbeconsideringthe 0case,whichsays:

Noticethatthe 0 casestartswith "Nomore" ,justasthe 1 case

Nomorebottlesofbeeronthewall,nomorebottlesofbeer.Gotothestoreandbuysomemore,99bottlesofbeeronthewall.

Page 158: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

endswith "nomore" .Thewaythesongworksisthatwheneverthereare 0 bottles,yousing"nomore,"capitalizedappropriately.

When "Nomore" comesatthebeginningofthesong,it’sclearlynottheremainder.Thismeansthatif "nomore" and "Nomore"representthesameidea,then remainder isn’tagoodnamefortheunderlyingconcept.

Ifyoureconsidertheabovetable,therightsideisactuallythename,ordescription,orperhapsquantityofbottlesbeingsungabout.Itisthestringtobesungintheplaceofanynumber.Whilenotperfect, quantity atleastattemptstoindicatetheresponsibilityonthemethodyouplantocreate,andsoisareasonablefirstattemptataname.

Beforeimplementing quantity ,considerwhatwouldhavehappenedhadyounamedthisconcept remainder .Afterfinishingthe 1 case,you’dhaveadvancedtothe 0 caseanddiscoveredthatitstartedwith "Nomore" .Thiswouldhavecausedyoutoreconsider remainder .You’dlikelyhaverevertedtherefactoringtothispoint,andre-startedyoursearchforaname.

Reallifeislikethis,whereyoumakethebestdecisionyoucaninthemoment,andreassesswhenyouknowmore.Hadyoubeendoingthisrefactoringalone,youmightwellhavegonedowntheremainder path,andsufferedtheeventualreversal.There’senoughpaininreallife;hereyou’vebeenlefttoimagineit.

Donottakethisasagenerallicensetothinkfarahead.Whileyouareallowedtousecommonsense,it’susuallybesttostayhorizontalandconcentrateonthecurrentgoal.Whencreatinganabstraction,firstdescribeitsresponsibilityasyouunderstanditatthismoment,thenchooseanamewhichreflectsthatresponsibility.Theeffortyouputintoselectinggoodnamesrightnowpaysoffbymakingiteasiertorecognizeperfectnameslater.

Page 159: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

4.4.ChoosingMeaningfulDefaultsThepreviousfewrefactoringsusedthetechniqueoftemporarilysettinganargumenttoadefault.Ineachcase,thesymbol :FIXMEwasusedasthatdefault. :FIXME ishandybecausethenameitselfremindsyouofitstemporarynature,andactsasaremindertoremoveitattheendoftherefactoring.Helpfulas :FIXME is,however,itwon’tworkineverycase.Sometimescircumstancesconspiretoforceyoutousearealvalueasadefaultduringtheserefactorings.Thisnextsectiondelvesintojustsuchacase.

Rememberthatthedifferencecurrentlybeingaddressedis:

whichgoeswith:

Theunderlyingconceptis quantity .Toremovethisdifference,firstaddthe quantity method:

Listing4.17:InitialQuantityMethod

1 defquantity2 end

Thenextstepistochangethismethodtoreturnoneofthetwodifferences.Untilnow,you’vechosentoreturnthevaluefromthe else branchfirst.Butinthiscase,the else branchcontainsinterpolatedcodethatreferences number .Therefore,youcan’tcopythe else branchdifferenceinto quantity unlessyoufirstalter quantity totake number asanargument.

The 1 branchcontainsthestring"nomore,"whichisasimplerdifference.Thatsimplicitymakesthisagoodplacetoexplore

"nomore"

"#{number-1}"

Page 160: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

whathappensifyouswitchupandreturnthenon-elsevaluefirst.

Becauseofthischangeintactics,proceedingexactlyasyou’vedonepreviouslywilleventuallyleadtoanerror.It’sinstructivetowatchthishappen,asshowninthefollowingcode.

Beginbyreturningthevaluefromthe 1 case:

Listing4.18:QuantityMethodFirstReturn

1 defquantity2 "nomore"3 end

Send quantity inplaceof "nomore" inthe 1 case:

Listing4.19:QuantityMessageFirstSend

1 when12 #...3 "#{quantity}bottlesofbeeronthewall.\n"4 else

Addthenormal :FIXME defaulttothe number argumentinquantity :

Listing4.20:NumberArgumentDefaultedtoFIXME

Ifyou’reconcernedaboutthe :FIXME defaultabove,yourSpidey-sense[11]isworking.Yes,everythingwillgoterriblywrongina

1 defquantity(number=:FIXME)2 "nomore"3 end

Page 161: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

minute,butuntilthen,castyourworriesasideandchargeforward.

Thenextstepistoalter quantity tobeopentothe else case.Rememberthatyou’reworkingonthefinalphraseofverse1,andthatthevalueofthepassedargumentwillbe number-1 ,or 0 .If number is 0 ,theconditionshouldreturn "nomore" ;otherwise,itshouldreturnthenumber.

Here’sthe quantity method,alteredtocontainthatnewconditional:

Listing4.21:QuantityMessageWithConditional

1 defquantity(number=:FIXME)2 ifnumber==03 "nomore"4 else5 number6 end7 end

Ifyounowhaveadditionalconcernsaboutthiscode,hanginthere.Anumberoferrorswillarise,buttheywillsoongetresolved.

Atthispointineachofthepreviousrefactorings,thetestspassedbutinthiscase,notso.Thetestsarenowfailingwith:

Havealookatthe case statementbelow.Examineline3andtry

-Takeitdownandpassitaround,nomorebottlesofbeeronthewall.+Takeitdownandpassitaround,FIXMEbottlesofbeeronthewall.

Page 162: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

toexplainwhatwentwrong.

Listing4.22:UsingtheNumberDefaultFromthe1Case

1 when12 #...3 "#{quantity}bottlesofbeeronthewall.\n"4 else5 #...6 "#{number-1}#{container(number-1)}ofbeeronthewall.\n"

Thisfailureoccursbecauseline3abovecalls quantity withoutpassinganargument.Uponinvocation,the quantity methodsetsnumber to :FIXME ,whichsendsexecutiontothefalsebranchofitsconditional.Thefalsebranchobedientlyreturns number ,whichunfortunatelystillcontains :FIXME .Thisresultthengetsinterpolatedbackintotheverse.Thus, "FIXMEbottlesofbeer" .

Thereasonthe :FIXME defaultworkedinprevioussituationswasbecauseinthosecasesyouwantedtoexecutethefalsebranch.However,nowyouneedthetruebranch,andthereforerequireamuchmorespecificdefault.

Thetestsarefailing,andtherulesdictatethatyoumustundoandreturntogreen.Fortunately,thistakesjustoneundo,whichreverts quantity tothefollowing:

Listing4.23:NumberArgumentDefaultedtoFIXMEReprise

1 defquantity(number=:FIXME)2 "nomore"3 end

Page 163: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Anobviouslywrongandtemporaryvaluelike :FIXME canbeahandydefault,butyoucanonlyusethistechniqueifyoubegintheserefactoringsbyreturningthedifferencefromthe elsebranch.Whileit’sperfectlyacceptabletobeginbyreturning"nomore"(thenon-elsedifference),doingsomeansthatyouhavetothinkmorecarefullyaboutthedefault.So,useadefaultlike:FIXME thoughtfully.

Inthiscase,thedefaultthatwilldriveexecutiontothecorrectbranchis 0 ,asshownbelow:

Listing4.24:NumberArgumentDefaultsto0

Nowthatthedefaultiscorrect,theconditionalcanbere-addedtoquantity ,asfollows:

Listing4.25:DefaultTakestheTrueBranch

1 defquantity(number=0)2 ifnumber==03 "nomore"4 else5 number6 end7 end

Althoughnothingabouttheconditionalhaschangedsincethelastattempt,thedefaultisnowcorrect,sothetestspass.

Takingthedefaultcausedthe true branchtoexecute.Nowit’stimetoensurethatpassinganargumentdoesthesame.Line5belowhasbeenchangedtopass number-1 to quantity :

1 defquantity(number=0)2 "nomore"3 end

Page 164: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Listing4.26:1CasePassesanArgument

1 defverse(number)2 #...3 when14 #...5 "#{quantity(number-1)}bottlesofbeeronthewall.\n"6 else7 #...8 "#{number-1}#{container(number-1)}ofbeeronthewall.\n"9 end10 end

Thetestsstillpass.Thenextstepistouse quantity inthe elsecase,asshownonline8below:

Listing4.27:ElseCaseSendsQuantity

1 defverse(number)2 #...3 when14 #...5 "#{quantity(number-1)}bottlesofbeeronthewall.\n"6 else7 #...8 "#{quantity(number-1)}#{container(number-1)}ofbeeronthewall.\n"9 end10 end

Page 165: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Atthispoint quantity isfullyimplemented.Thedefaultisnolongerneeded,andcanberemoved.Thefinalmethodisshownbelow:

Listing4.28:QuantityMethod

1 defquantity(number)2 ifnumber==03 "nomore"4 else5 number6 end7 end

Afterresolving quantity ,oneminordifferenceremainsbetweenthe 1 and else cases.Thefinalphraseofthe 1 casesays"bottles" (line4below)whereasinthatplacethe else casesendscontainer(number-1) .

Listing4.29:1andElseCasesMoreAlike

1 defverse(number)2 #...3 when14 "#{quantity(number-1)}bottlesofbeeronthewall.\n"5 else6 "#{quantity(number-1)}#{container(number-1)}ofbeeronthewall.\n"7 end8 end

Thisdifferencecanberesolvedbysendingthewell-knowncontainer messageinplaceoftheword "bottles" .Afterthis

Page 166: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

change,the 1 and else casesareidentical,asshownintheirfullglorybelow:

Listing4.30:1andElseCasesIdentical

1 defverse(number)2 #...3 when14 "#{number}#{container(number)}ofbeeronthewall,"+5 "#{number}#{container(number)}ofbeer.\n"+6 "Take#{pronoun(number)}downandpassitaround,"+7 "#{quantity(number-1)}#{container(number-1)}ofbeeronthewall.\n"8 else9 "#{number}#{container(number)}ofbeeronthewall,"+10 "#{number}#{container(number)}ofbeer.\n"+11 "Take#{pronoun(number)}downandpassitaround,"+12 "#{quantity(number-1)}#{container(number-1)}ofbeeronthewall.\n"13 end14 end

Thiscompletelyresolvesthe 1 case,whichcannowbedeleted.

Twonewconceptshavebeenidentified, pronoun and quantity .Althoughtherefactoringthatcreated quantity obedientlyfollows

Page 167: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

theFlockingRules,theorderinwhichcodeiswrittendiffersslightlyfromthatofpreviousmethodextractions.Theearlierexamplesbeganbyreturningthevaluefromthe else branchofthecasestatement,butthe quantity methoddiffersinthatitinitiallyreturnsthevaluefromthe 1 ,ornon- else case.

Alloftheserefactoringsextractamethod.Becausethisisdoneinverysmallsteps,theextractedmethodsstartoutsimpleandthengraduallybecomemorecomplicated.Oneofthecomplicationsisthateachmethodchangestotakeaparameter.Inordertokeepthetestsrunninggreenduringthetransitiontotakingaparameter,theparameterhastobeassignedadefault.Thedefaultistemporary,anditismeanttobedeletedwhenthetransitioniscomplete.

Whenthe else branchisimplementedfirst, :FIXME canalwaysbeusedforthedefault.Thisnotonlysavesyoufromhavingtofigureouttherightvalue,italsoservesasaremindertoremovethistemporarydefaultlater.Ifthenon- else branchisimplementedfirst,thedefaulthastobesettosomethingthatactuallymeetstheconditionandsomakesthe true branchexecute.Therefore,implementingthenon- else branchfirstplacesaslightlygreaterburdenonyou.Youhavetouseaspecific,real,valueforthedefault,andthenmustremembertoremovethedefaultoncethetransitioniscomplete.

Page 168: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

4.5.SeekingStableLandingPointsAtthispoint,the 2 and 1 caseshavebeenremoved,andthreenewconcepts, quantity , pronoun and container ,havebeenidentified.Tosaveyoufromhavingtoremember,thelistingbelowrepeatsthecodefortheseconcepts:

Listing4.31:ThreeAbstractedConcepts

1 defquantity(number)2 ifnumber==03 "nomore"4 else5 number6 end7 end8 9 defpronoun(number)10 ifnumber==111 "it"12 else13 "one"14 end15 end16 17 defcontainer(number)18 ifnumber==119 "bottle"20 else21 "bottles"22 end

Page 169: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

23 end

Noticethesimilaritiesintheabovemethods.Eachhasasingleresponsibility.Theyareidenticalinshape.Alltakethesameargument.Eachcontainsaconditionalandthatconditionalteststheargumentagainstaspecificvalue;itcheckstoseeiftheargumentisequaltosomething,asopposedtogreaterorlessthansomething.

Thesemethodsareincrediblyconsistent,andthisdidnothappenbyaccident--they’readirectresultoftherefactoringrules.Therulesleadtoconsistentcode,andconsistencymattersdeeply.First,itmakescodeeasytounderstand.Codeisreadmanymoretimesthanitiswritten,soanythingthatincreasesunderstandabilitylowerscosts.Next,andjustasimportant,consistentcodeenablesfuturerefactorings.

Imagineyourselfachild,traipsingdownastream,hoppingfromrocktorock.Somerocksarebroadandflatanddry,othersaremossyandwobblyandslick.Imaginealsothatyouarenotallowedtoreturnhomewet.

Thedryrocksarestablelandingpointsonwhichyoucansafelyrest,planningyournextmove.Thewetrocksareriskyinterludesthatgoodsensesuggestsyoutraverseasquicklyaspossible.

Rearrangingcodeislikerockhoppingdownastream.Ifyoufollowtherulesofrefactoring,you’llquicklypassovertheslipperyplaces,andarriveatstable,consistentrestingpoints.Changingcodewilly-nilly,however,canleadtosurprisingandunexpectedbaths.

Theconsistencyinthecodeaboveenablesthenextrefactoring.Fornowyoumusttakethisassertiononfaith,butthatfaithwillberewardedinfuturechapters.

Page 170: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

4.6.ObeyingtheLiskovSubstitutionPrincipleNow,backtothehorizontalrefactoring.Thischapterstartedwithathree-branchcasestatement.Onecase(the 1 case)hasbeenremoved,leavingthe 0 and else casesstilltoberesolved.Here’sareminderofthecurrentstateofthecode:

Listing4.32:0andElseCasesDiffer

1 defverse(number)2 casenumber3 when04 "Nomorebottlesofbeeronthewall,"+5 "nomorebottlesofbeer.\n"+6 "Gotothestoreandbuysomemore,"+7 "99bottlesofbeeronthewall.\n"8 else9 "#{number}#{container(number)}ofbeeronthewall,"+10 "#{number}#{container(number)}ofbeer.\n"+11 "Take#{pronoun(number)}downandpassitaround,"+12 "#{quantity(number-1)}#{container(number-1)}ofbeeronthewall.\n"13 end14 end

Beginthisnextrefactoringbyfocusingonlines4and9above,thefirstphrasesofthetworemainingcases.Lookingforthesmallestdifference,bothlinesendwith "ofbeeronthewall," ,sothisisasimilaritythatcanbeignored.The container methodisusedon

Page 171: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

line9inthe else case.Thismethodcanbesubstitutedonline4fortheword "bottles"` ,andneedsnofurtherconsideration.

Theremainingdifferenceisattheverybeginningoflines4and9,where:

goeswith:

Thisfeelslikethe quantity concept,butasitstands,thatmethodwon’tworktoresolvethisdifference.Ifyouweretochangeline4tosend "#{quantity(number)}" inplaceof "Nomore" ,you’dgetbackanalllowercase"nomore,"andthetestswouldfail.

Thisisaconundrum.Thelowercasevariantof"nomore"isrequiredbyverse1,andnowverse0needsthesametwowords,exceptcapitalizedasthestartofasentence.Theunderlyingconceptisthesameinbothcases("nomore"istobesungwhenthenumberofbottlesis0),butitgetsexpressedinslightlydifferentways,dependingonwhereitfallsinthesong.

Thesewordsareonething,andwhethertheyneedtobecapitalizedisquiteanother.Perhapsknowledgeofthewordsbelongsinoneplace,andknowledgeofthecapitalizationrequirementsbelongsinanother.

Ifthat’sthecase,capitalizationcanreasonablyhappenhereinthe case statement.Replace "Nomore" with "#{quantity(number)}" ,andcapitalizetheresult,asonline4below:

Listing4.33:QuantityCapitalizedin0Case

1 defverse(number)

"Nomore"

"#{number}"

Page 172: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

2 casenumber3 when04 "#{quantity(number).capitalize}bottlesofbeeronthewall,"+5 #...6 else7 "#{number}#{container(number)}ofbeeronthewall,"+8 #...9 end10 end

Theabovechangefollowsthestrategyofgraduallymakingthingsmorealikeinhopesthatitwillthenbecomeclearhowtomakethemidentical.Whennibblingawayattheproblem,youdon’thavetounderstandeverythingbeforeyoucandoanything.Takingcareofthesmallthingsoftencutsthebigonesdowntosize.

Havingmadetheabovechange,itnowseemsreasonabletomakeasimilaroneinthe else case,shownonline7below:

Listing4.34:QuantityCapitalizedinElseCase

1 defverse(number)2 casenumber3 when04 "#{quantity(number).capitalize}bottlesofbeeronthewall,"+5 #...6 else7 "#{quantity(number).capitalize}#{container(number)}ofbeeronthewall,"+

Page 173: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

8 #...9 end10 end

Despiteseemingreasonable,thechangemakesthetestsfailwith:

Becauseyou’reworkinginsuchsmallsteps,youknowthatthepreviouschangecausedthiserror.Havealookatthefollowingcodeandseeifyoucanfigureoutwhat’swrong:

Listing4.35:QuantityMethodReprise

1 defquantity(number)2 ifnumber==03 "nomore"4 else5 number6 end7 end

Themostrecentchangeinvokes quantity withannon-zeroargument.Thiscausesexecutiontoproceedtothefalsebranch.Thetruebranchreturnsastring,butthefalsebranchreturnstheargumentthatwaspassed,whichisindeedaninstanceof Fixnum .String understands capitalize ,but Fixnum doesnot;thusthiserror.

Youmaybeitchingtofixthiserrorbymakingachangeinthequantity method,butit’sinstructivetotryattackingithereinverse .Goaheadandremovetheerrorbyconvertingtheresultintoastringbeforesending capitalize .Line7belowinserts to_sintothemethodchain:

NoMethodError:undefinedmethod`capitalize'for99:Fixnum

Page 174: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Listing4.36:ElseBranchConvertsResult

1 defverse(number)2 casenumber3 when04 "#{quantity(number).capitalize}bottlesofbeeronthewall,"+5 #...6 else7 "#{quantity(number).to_s.capitalize}#{container(number)}ofbeeronthewall,"+8 #...9 end10 end

Theabovechangefixesthefailingtest,butintroducesanewdifferencebetweenthephrases.Toremovethisdifference,youmustalsoinsert to_s intothe 0 case,asonline4below:

Listing4.37:BothBranchesConvertResult

1 defverse(number)2 casenumber3 when04 "#{quantity(number).to_s.capitalize}bottlesofbeeronthewall,"+5 #...6 else7 "#{quantity(number).to_s.capitalize}#{container(number)}ofbeeronthewall,"+8 #...9 end10 end

Page 175: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Nowthatthedifferenceisresolvedandthetestsarerunning,stepbackandconsiderthissolution.Therootoftheproblemisthat quantity returnsthingsthatconformtodifferentAPIs.Sendersof quantity expectthereturntounderstand capitalize ,yet quantity doesn’talwaysoblige;itsometimesreturnsa"capitalizable,"butothertimesdoesnot.Thisinconsistencyofreturntypesforcesthesenderofthemessagetoknowmorethanitshould.

The verse methodaboveknowsthatitcannottrust quantity toreturnsomethingthatunderstands capitalize .The verse methodknowsthatinstancesof String dounderstand capitalize .Itknowsthatanyobjectcanbeconvertedtoastringbysendingto_s .Therefore,itknowsthatitcanconvertanyobjectintosomethingthatunderstands capitalize bysendingitthe to_smessage.

Everypieceofknowledgeisadependency,andthewaythatquantity iswrittenrequires verse toknowtoomanythings.Ifquantity weremoretrustworthy, verse couldknowless.

TheideaofreducingthenumberofdependenciesimposeduponmessagesendersbyrequiringthatreceiversreturntrustworthyobjectsisageneralizationoftheLiskovSubstitutionPrinciple.TheofficialdefinitionofLiskovsaysthat"subtypesmustbesubstitutablefortheirsupertypes."Thisprinciplewasoriginallypostulatedintermsoftypesandsubtypes,butyoucanthinkofitintermsofclassesandsubclasses.

Liskov,inplainterms,requiresthatobjectsbewhattheypromisetheyare.Whenusinginheritance,youmustbeabletofreelysubstituteaninstanceofasubclassforaninstanceofitssuperclass.Subclasses,bydefinition,areallthattheirsuperclassesare,plusmore,sothissubstitutionshouldalwayswork.

Page 176: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

TheLiskovSubstitutionPrinciplealsoappliestoducktypes.Whenrelyingonducktypes,everyobjectthatassertsthatitplaystheduck’srolemustcompletelyimplementtheduck’sAPI.Ducktypesshouldbesubstitutableforoneanother.

Liskovprohibitsyoufromdoinganythingthatwouldforcethesenderofamessagetotestthereturnedresultinordertoknowhowtobehave.Receivershaveacontractwithsenders,anddespitetheimplicitnatureofthiscontractindynamicallytyped,object-orientedlanguages,itmustbefulfilled.

Liskovviolationsforcemessagesenderstohaveknowledgeofthevariousreturntypes,andtoeithertreatthemdifferently,orconvertthemintosomethingconsistent.Inthe quantity methodabove,oneofthereturnshonoredthe"capitalizable"contractandonedidnot.Aninconsistencylikethisveryoftenforcesthesendertoimplementaconditionaltoidentifyandfixtheerrantreturn.Inthiscase,allRubyobjectsunderstand to_s ,soitwasprogrammaticallyconvenienttoblithelyconverteveryreturnintoastring,eventhosethatalreadywere.Thisunconditionalconversionavoidscheckingtoseewhichobjectsneedtobesentto_s ,butaddstheoverheadofsending to_s toeveryobject,evenifit’salreadyastring.

Thesender’sentireburdenisremovedifthereceiverhonorsthecontract,andprovidesaconsistentreturn.Insteadofforcingtheverse methodtosolvethisproblem, quantity shouldreturnatrustworthyobject.

Thisiseasilyaccomplishedbydoingtheconversioninthequantity method,asshownonline5below:

Listing4.38:QuantityObeysLiskov

1 defquantity(number)2 ifnumber==0

Page 177: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

3 "nomore"4 else5 number.to_s6 end7 end

Nowthat quantity alwaysreturnsa"capitalizable,"youcanpretendthatthe to_s dependencyneverexistedinverse,whichreturnsthecodetothestateshownhere:

Listing4.39:VerseTrustsQuantity

1 defverse(number)2 casenumber3 when04 "#{quantity(number).capitalize}bottlesofbeeronthewall,"+5 #...6 else7 "#{quantity(number).capitalize}#{container(number)}ofbeeronthewall,"+8 #...9 end

Havingaltered quantity tomakeitusableinallcases,theremainingdifferenceinthefirstphraseistheword"bottles."Thisiseasilyresolvedbysending container initsplace:

Listing4.40:0CaseSendsContainer

1 defverse(number)2 casenumber3 when0

Page 178: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

4 "#{quantity(number).capitalize}#{container(number)}ofbeeronthewall,"+5 #...6 else7 "#{quantity(number).capitalize}#{container(number)}ofbeeronthewall,"+8 #...9 end10 end

Afterthatchange,thefirstphrasesofthe 0 and else casesareidentical.

4.7.TakingBiggerStepsYou’venowturnedsmalldifferencesintomessagesendsseveraltimes,andhavelikelynoticedthesimilaritybetweenthestepstakenandtheresultingcode.Sofar,theextractedmethodsallhavethesamegeneralshape,andareinvokedinthesameway.

Differencesremain.However,it’sbeginningtofeellikethere’sacommonrefactoringpattern,andonemightreasonablytheorizethatfuturedifferenceswillberesolvedfollowingthesameprocessthatwasusedinthepast.Ifthistheoryiscorrect,itmakessensetospeedupthenextrefactoringbycombiningseveralstepsintoasinglechange.

Thefirstphraseofthe 0 and else casesareidentical,soit’stimetoexaminethesecond.It’srepeatedbelow:

Listing4.41:0andElse,2ndPhrasesDiffer

1 defverse(number)2 casenumber

Page 179: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

3 when04 #...5 "nomorebottlesofbeer.\n"+6 #...7 else8 #...9 "#{number}#{container(number)}ofbeer.\n"+10 #...11 end12 end

Theabovedifferencesreflectthe quantity and containerconcepts,whichhavelongsincebeenidentified.Resolvethembychangingthecodeasfollows:

Listing4.42:2ndPhrasesSendQuantityandContainer

1 defverse(number)2 casenumber3 when04 #...5 "#{quantity(number)}#{container(number)}ofbeer.\n"+6 #...7 else8 #...9 "#{quantity(number)}#{container(number)}ofbeer.\n"+10 end11 end

Page 180: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Nowthatphrases1and2areidentical,here’salookatthewholeverse method.Considerthecode,andidentifythenextdifference:

Listing4.43:Phrases1and2AreIdentical

1 defverse(number)2 casenumber3 when04 "#{quantity(number).capitalize}#{container(number)}ofbeeronthewall,"+5 "#{quantity(number)}#{container(number)}ofbeer.\n"+6 "Gotothestoreandbuysomemore,"+7 "99bottlesofbeeronthewall.\n"8 else9 "#{quantity(number).capitalize}#{container(number)}ofbeeronthewall,"+10 "#{quantity(number)}#{container(number)}ofbeer.\n"+11 "Take#{pronoun(number)}downandpassitaround,"+12 "#{quantity(number-1)}#{container(number-1)}ofbeeronthewall.\n"13 end14 end

Tolocatethenextdifference,itcanagainbehelpfultoscantheversefromtheend.Bothvariantsendwith"ofbeeronthewall."Online7,phrase4ofcase 0 beginswith "99" followedby"bottles" .Theseseemtogowith quantity and container online12.Ignorethisfourthphrasefornowandturnyourthoughtstophrase3,isolatedbelow:

Page 181: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Listing4.44:0andElse,3rdPhrasesDiffer

1 defverse(number)2 casenumber3 when04 #...5 "Gotothestoreandbuysomemore,"+6 #...7 else8 #...9 "Take#{pronoun(number)}downandpassitaround,"+10 #...11 end12 end

Theonlythingtheabovelineshaveincommonisthetrailing "," ,whichmeansthateverythinguptothatpointisadifference.Ifthe 0 and else versevariantsreflectacommonverseabstraction,thisdifferencemustrepresentasmallerconceptwithinthatlargerabstraction.Itdoesn’tmatterhowlongthesestringsare,theirpresencehereinoppositionmeanstheyreflectasingleconcept.

Youmustnametheconcept,createamethodtorepresentit,andthenreplacethisdifferencewithamessagesend.Thefirststepisthereforetonamethecategoryinwhichthesetwophraseareconcreteexamples.

Thispartofthesongisaboutwhathappensasaresultofthecurrentnumberofbeers.Ifbeersexist,youdrinkone.Ifnot,yougoshopping.Theselinesdescribetheactiontotake,sothat’sagoodnameforthisconcept.

Page 182: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Untilnow,you’vebeendoingthisrefactoringinthesmallestpossiblesteps.Asareminder,thosestepsare:

Defineamethodfortheconcept.

Alterittoreturnoneofthedifferences.

Replacethatdifferencewithamessagesend.

Addthe number argumenttothenewmethod,withappropriatedefault.

Implementtheconditional.

Passthe number argumentfromthecurrentsender.

Sendthemessagefromtheotherbranch,thistimeincludingthe number argument.

Cleanup.

Youmayhavenoticedthatthemethodyoucreateduringthisrefactoringcontainscodethatexactlymirrorstheshapeoftheoriginalcasestatement.Oncethisbecomesapparent,itmakessensetobeginextractingthemethodinasinglestep,asshownbelow:

Listing4.45:LeapIntoAction

1 defaction(number)2 ifnumber==03 "Gotothestoreandbuysomemore"4 else5 "Take#{pronoun(number)}downandpassitaround"6 end

Page 183: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

7 end

Thisnew action methodcontainsaconditionalthatreflectsthecasestatementfromwhenceitcame.Justastheoriginal casestatementswitchedon number ,thenew action methodtakesanumber argument,andusesitsvaluetochoosewhattoreturn.Thetrue and false branchesofthenewconditionalcontaincodeextracteddirectlyfromthe 0 and else branchesofthe casestatement.

Once action exists,theoriginalphrasescanbemadeidenticalbyreplacingtheirdifferenceswithacommonmessagesend.Thisresultsinthefollowingcode:

Listing4.46:3rdPhrasesSendAction

1 defverse(number)2 casenumber3 when04 #...5 "#{action(number)},"+6 #...7 else8 #...9 "#{action(number)},"+10 #...11 end

Thepreviouschaptershowedanexamplewheretheentirecontainermethodwascreatedatonce.Thatwasheldupasanexampleofwhatnottodo.The action methodabovelooksalotlikethatoriginal container method,anditmayseemasifyouarenowbeinggivenpermissiontoactinawaythatwaspreviouslyprohibited.

Page 184: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

However,thereisadifference.Backwhentheoriginal containermethodwasfirstintroduced,youhadnotyetlearnedhowtocreateitusingsmallsteps.Sincethattime,you’vepracticedtheFlockingRules,refactoringbitbybit,andonseveraloccasionshaveseendifferencesfromtwobranchesofthecasestatementturnintoasingleconditional.Nowthatyouknowhowtomakethischangeusingsmallsteps,andhaveseenthispattern,itmakessensetostartwritinglargerchunksofcode.

However,ifyoutakebiggerstepsandthetestsbegintofail,there’ssomethingabouttheproblemthatyoudon’tunderstand.Ifthishappens,don’tpushforwardandrefactorunderred.Undo,returntogreen,andmakeincrementalchangesuntilyouregainclarity.

4.8.DiscoveringDeeperAbstractionsSofarthe container , pronoun , quantity ,and action conceptshavebeenidentified,andmethodshavebeenextractedtoberesponsibleforeach.Thishorizontalrefactoringtoremovethecasestatementisalmostcomplete.Thisnextsectionresolvesthefinaldifference,andinsodoingillustratesthedeeppoweroftheFlockingRulestounearthunanticipatedabstractions.

Theremainingdifferencesareinthefourthphrasesofthe 0 andelse cases,onlines7and12below.Here’sthecurrentstateofthecode:

Listing4.47:Phrases1,2,and3AreIdentical

1 defverse(number)2 casenumber3 when04 "#{quantity(number).capitalize}#{container(number)}ofbeeronthewall,"+5 "#{quantity(number)}#

Page 185: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

{container(number)}ofbeer.\n"+6 "#{action(number)},"+7 "99bottlesofbeeronthewall.\n"8 else9 "#{quantity(number).capitalize}#{container(number)}ofbeeronthewall,"+10 "#{quantity(number)}#{container(number)}ofbeer.\n"+11 "#{action(number)},"+12 "#{quantity(number-1)}#{container(number-1)}ofbeeronthewall.\n"13 end

Thetrailing "ofbeeronthewall" inthelinesaboveisasameness,andtheword "bottles" inline7isanexampleofthe containerabstraction,whichisalreadyusedinthisplaceinline12.Ifyouignorethesefornow,theremainingdifferenceisthat:

seemstobesetagainst:

Thismayleadyoutoconcludethat "99" isathirdexampleofthequantity abstraction.Ifso,thenyoushouldalter quantity tosometimesreturn "99" .Theresultingmethodwouldlooklikethis:

Listing4.48:QuantityOverreachestoHandle99

"99"

"#{quantity(number-1)}"

1 defquantity(number)2 casenumber3 when-1

Page 186: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Ifyoumadethealterationshownabove,andthenreplaced "99"with "#{container(number-1)}" ,thetestswouldcontinuetopass.However,justbecausethetestspassdoesn’tmeanthattheabstractioniscorrect.There’ssomethingdeeplywrongwiththissolution,andtherearemanycluestotheproblem.

Thefirstclueisthattheabovechangegives quantity adifferentshapethanthatoftheotherextractedmethods.Here’sareminderofhowthemethodslookedbeforethisalteration:

Listing4.49:ConsistentAbstractions

1 defaction(number)2 ifnumber==03 "Gotothestoreandbuysomemore"4 else5 "Take#{pronoun(number)}downandpassitaround"6 end7 end8 9 defquantity(number)10 ifnumber==011 "nomore"12 else13 number.to_s14 end

4 "99"5 when06 "nomore"7 else8 number.to_s9 end10 end

Page 187: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

15 end16 17 defpronoun(number)18 ifnumber==119 "it"20 else21 "one"22 end23 end24 25 defcontainer(number)26 ifnumber==127 "bottle"28 else29 "bottles"30 end31 end

Theproposedchangealters quantity suchthat:

itsconditionalhas3branchesinsteadof2

itsometimeschecks -1 ,whichisaninvalidnumberofbeers

Theseinconsistenciesdon’tguaranteethatsomethingiswrong,buttheyshouldcertainlymotivateyoutothinkmoredeeplyabouttheunderlyingabstraction.

Askyourselfthesetwoquestions:

1. Whatistheresponsibilityofthe quantity method?

2. Isthereawaytomakethefourthphrasesmorealike,evenif

Page 188: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

notyetidentical?

First,considerresponsibilities.The quantity conceptisresponsibleforknowingwhattosingintheplaceofanumber.Ifthereare 50 beers,thequantityis "50" ,if 5 beers, "5" ,andif 0beers, "nomore" .Thisconceptrepresentsthemappingbetweenthevalueofanumberandthestringthatgetssung.

Asthesongprogresses,theversenumbergetsdecremented.It’sbeenawhilesinceyou’veseenthem,sohere’sareminderofthesong and verses methods:

Listing4.50:SongandVersesReprise

1 defsong2 verses(99,0)3 end4 5 defverses(starting,ending)6 starting.downto(ending).collect{|i|verse(i)}.join("\n")7 end

Line2aboveencodestheknowledgethattheoverallsongstartsonverse99andcountsdownto0.Line6decrementstheversenumber,whichmovesthesongfromoneversetothenext.Butifyouarefamiliarwith99Bottles,youaresurelyawarethatthesongislongerthanthiscodesuggests.Therealsonggoesonforever(oratleastuntilallsingersbecomesufficientlybored).

This"forever"happensinphrase4ofthe 0 case,repeatedbelow:

Listing4.51:Case0HandlesRestart

1 defverse(number)

Page 189: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

2 casenumber3 when04 #...5 "99bottlesofbeeronthewall.\n"6 else7 #...8 "#{quantity(number-1)}#{container(number-1)}ofbeeronthewall.\n"9 end10 end

Line5abovecontainsahard-coded 99 .Thisisnotaspecialcaseofthe quantity concept,whichistheruleforwhattosinginplaceofanumber.

There’ssomethingsubtleaboutthedifferenceabove,suchthattheunderlyingconceptisnotimmediatelyobvious.Andthis,unfortunately,isaconstantofprogramminglife.Ifyouhadperfectunderstanding,you’dwriteperfectapplications.Mostly,however,you’restumblingaround,sufferingfrominsufficientinformation,seeingproblemsthroughaglass,darkly.[12]

Whenyou’reconfused,don’ttrytosolvetheentireproblemstraightaway.Themoreconfusedyouare,themoreimportantitistonibble.Youalreadyknowthatitbecomeseasiertoseehowthingsaredifferentifyoumakethemmorealike.Insteadoftryingtounderstandeverythingatonce,simplysearchforawaytomakeline5abovelookmorelikeline8(evenifnotidentical),usingexistingcode.

Itmayhelptoconsiderthesequestions.Whenthevalueof numberis 5 ,whatdoes quantity return?Howaboutwhennumberis 95?Andfinally,whatwould quantity returnifyoupassedin 99?

Page 190: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Ifyoujustrealizedthatyoucanmaketheselinesalittlebitmorealikebypassingthe 99 into quantity ,you’vegotit.Here’stheresultingcode:

Listing4.52:99IsaQuantity

1 defverse(number)2 casenumber3 when04 #...5 "#{quantity(99)}bottlesofbeeronthewall.\n"6 else7 #...8 "#{quantity(number-1)}#{container(number-1)}ofbeeronthewall.\n"9 end10 end

Asyoucanseefromtheabove,theexisting quantity ruleisfine,anditalreadyapplies.Whenthenumber99appearsinthesong,youshouldsingthestring"99."

Atthispointitmakessensetoscanovertotheword"bottles"andreplaceitwiththe container method.Thisisawell-understooddifference,andtakingitoffthetablenowreducesmentalclutter.Here’stheresultingcode:

Listing4.53:Case0SendsContainer

1 defverse(number)2 casenumber3 when04 #...

Page 191: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

5 "#{quantity(99)}#{container(number-1)}ofbeeronthewall.\n"6 else7 #...8 "#{quantity(number-1)}#{container(number-1)}ofbeeronthewall.\n"9 end10 end

Havingmadetheselinesassimilaraspossible,itisnowobviousthat:

mustrepresentthesameconceptas:

Asalways,youmustnamethisconcept,createamethod,andsendthemessageinplaceofthedifference.

Thisconceptisaboutknowingthatwhen number is 50 ,theresultis 49 ,when 5 , 4 ,when 1 , 0 ,andwhen 0 , 99 .It’swherethesongdeterminesthenextversetobesung."Next"isakeywordinRuby,soitcan’tbeusedasthename,butaconceptlikethisalreadyexistsformanyRubyobjects,anditmakessensetoleverageRuby’sexistingname.

SeveralclassesintheRubystandardlibrarydefinea"successor,"usingtheunfortunatelynamed succ method.Hereareafewexamples:

"99"

number-1

"a".succ#=>"b"9.succ#=>10

Page 192: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Mostversesaresucceededbythenextlowerverse,withtheexceptionofverse0,whichisfollowedbyverse99."Successor"isagoodnameforthisconcept.

The successor conceptwasunearthedusingthesamerefactoringrulesthatledto container , pronoun , quantity ,and action .Asthisideaisabitmoreabstractthantheothers,anabundanceofcautionsuggeststhattherefactoringbedoneinmoderatelysmallsteps.Inthatspirit,firstcreatethemethod,andhaveitreturntheelse branchdifference.Here’sthatcode:

Listing4.54:SuccessorHandlesDefault

1 defsuccessor(number)2 number-13 end

Thecodein successor refersto number ,sotheargumentmustbedefinedfromthefirst.

Nowthat successor exists,useitinthe else branchinplaceofnumber-1 (line8below):

Listing4.55:ElseCaseSendsSuccessor

1 defverse(number)2 casenumber3 when04 #...5 "#{quantity(99)}#{container(number-1)}ofbeeronthewall.\n"6 else7 #...8 "#{quantity(successor(number))}#{container(number-1)}ofbeeronthewall.\n"

Page 193: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

9 end10 end

Thenextstepistomakethesuccessoropentobeingusedinthe0 case,byaddingaconditionaltoreturnthecorrectvalue:

Listing4.56:SuccessorHandlesBothCases

1 defsuccessor(number)2 ifnumber==03 994 else5 number-16 end7 end

Nowthattheconditionalexists,the 99 canbereplacedbyasendof successor ,asshownonline5below:

Listing4.57:BothCasesSendSuccessor

1 defverse(number)2 casenumber3 when04 #...5 "#{quantity(successor(number))}#{container(number-1)}ofbeeronthewall.\n"6 else7 #...8 "#{quantity(successor(number))}#{container(number-1)}ofbeeronthewall.\n"9 end10 end

Page 194: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Afterthischange,the 0 and else casesareidentical.

The successor conceptillustratesthepowerofiterativeapplicationoftheFlockingRules.Thisconceptwasn’tevenhintedatinthesolutionsgiveninChapter1,andifyoufounditwhenyouworkedtheproblemyourself,you’reinaminority.Theconceptissosubtlemostprogrammersdon’tnoticeit,andyetitsimplyappearsifyoufollowthissimplesetofrules.

Successor isimportant,andseparatingitfrom quantity givesbothmethodsasingleresponsibility.Ifyouconflatechoosing-what-to-sing-for-any-number( quantity )withdeciding-what-verse-to-sing-next( successor ),theresultingmethodwouldbehardertounderstand,futurerefactoringswouldbemoredifficult,andattemptstochangethecodeforoneideamightaccidentallybreakitfortheother.

4.9.DependingonAbstractionsAbstractionsarebeneficialinmanyways.Theyconsolidatecodeintoasingleplacesothatitcanbechangedwithease.Theynamethisconsolidatedcode,allowingthenametobeusedasashortcutforanidea,independentofitscurrentimplementation.Thesearevaluablebenefits,butabstractionsalsohelpinanother,moresubtle,way.Inadditiontotheabove,abstractionstellyouwhereyourcodereliesuponanidea.Buttogetthislastbenefit,youmustrefertoanabstractionineveryplacewhereitapplies.

Studythecodeabove,andconsiderthebitsthatsay "#{container(number-1)}" .When container iscalledfromthe 0 case,thevalueofthepassedargumentis -1 .The -1 causestheconditionalin container tofallthroughtothefalsebranchandreturn"bottles."Althoughthiscodepassesthetests,itdoessobyaccident,notbydesign.

Thecodeabovedoesn’twantthe container of number-1 ;itwants

Page 195: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

the container ofthefollowingverse.The successor methodisresponsiblefordeterminingthefollowingverse.Therefore,youshouldnowdefertothatabstraction,andreplacealloccurrencesof number-1 with successor(number) .

Thatfinalchangeresultsinthiscode:

Listing4.58:DeferringtoSuccessor

1 defverse(number)2 casenumber3 when04 #...5 "#{quantity(successor(number))}#{container(successor(number))}ofbeeronthewall.\n"6 else7 #...8 "#{quantity(successor(number))}#{container(successor(number))}ofbeeronthewall.\n"9 end10 end

Here’sthewhole verse method,showingthe 0 and else casestobeidentical:

Listing4.59:Identical0andElseCases

1 defverse(number)2 casenumber3 when04 "#{quantity(number).capitalize}#

Page 196: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

{container(number)}ofbeeronthewall,"+5 "#{quantity(number)}#{container(number)}ofbeer.\n"+6 "#{action(number)},"+7 "#{quantity(successor(number))}#{container(successor(number))}ofbeeronthewall.\n"8 else9 "#{quantity(number).capitalize}#{container(number)}ofbeeronthewall,"+10 "#{quantity(number)}#{container(number)}ofbeer.\n"+11 "#{action(number)},"+12 "#{quantity(successor(number))}#{container(successor(number))}ofbeeronthewall.\n"13 end14 end

Onelastrefactoringtrickprovesthatthiscommontemplateworksforallcases.Copythetemplateandinsertitbelowthecasestatement,asfollows:

Listing4.60:UsingtheSameTemplateforEveryVerse

1 defverse(number)2 casenumber3 when04 #...5 else6 #...7 end

Page 197: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

8 "#{quantity(number).capitalize}#{container(number)}ofbeeronthewall,"+9 "#{quantity(number)}#{container(number)}ofbeer.\n"+10 "#{action(number)},"+11 "#{quantity(successor(number))}#{container(successor(number))}ofbeeronthewall.\n"12 end

Rubymethodsreturntheresultofthelastbitofevaluatedcode,sotheabovechangeletsyoutrythisonetemplateforallcases,whilepreservinganeasyreturntogreenifitfails.Thetestsaregreenafterthischange,andsoyoucansafelydeletetheentirecase statement.

Here’sacompletelistingoftheresultingcode:

Listing4.61:FinalListing

1 classBottles2 defsong3 verses(99,0)4 end5 6 defverses(starting,ending)7 starting.downto(ending).collect{|i|verse(i)}.join("\n")8 end9 10 defverse(number)11 "#{quantity(number).capitalize}#{container(number)}ofbeeronthewall,"+

Page 198: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

12 "#{quantity(number)}#{container(number)}ofbeer.\n"+13 "#{action(number)},"+14 "#{quantity(successor(number))}#{container(successor(number))}ofbeeronthewall.\n"15 end16 17 defquantity(number)18 ifnumber==019 "nomore"20 else21 number.to_s22 end23 end24 25 defcontainer(number)26 ifnumber==127 "bottle"28 else29 "bottles"30 end31 end32 33 defaction(number)34 ifnumber==035 "Gotothestoreandbuysomemore"36 else37 "Take#{pronoun(number)}downandpassitaround"38 end

Page 199: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

39 end40 41 defpronoun(number)42 ifnumber==143 "it"44 else45 "one"46 end47 end48 49 defsuccessor(number)50 ifnumber==051 9952 else53 number-154 end55 end56 end

Thiscompletesthecurrentrefactoring.The verse case statementhasbeenreducedtoasingletemplatethatreferstoaseriesofsmall,consistent,abstractions.

Nowthatyou’redone,it’simportanttoaskwhetherthisnewcodeactuallyimprovesupontheShamelessGreenfromwhenceyoubegan.Mostprogrammersarguethatit’sbetter,soyoumaybedistressedtohearthatFlogthinksit’sworse.FromFlog’spointofview,allyou’veaccomplishedisturnoneconditionalintomany,whilesimultaneouslyadding55%morecode.

However,beofgoodcheer.DespitetheFlogscore,thiscodeisbetter.Animprovementhasbeenmadethatisinvisibletostaticanalysistools.The container , pronoun , quantity , action and

Page 200: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

successor conceptswereinvisibleinShamelessGreen,butarebothrevealedandisolatedinthisnewcode.

4.10.SummaryThischapterfinishedtherefactoringthatbeganinChapter3.ItiterativelyfollowedtheFlockingRulestoremovedifferencesinthe verse method,andasaresultunearthedabstractionsthatweredeeplyhiddenwithinthe99Bottlessong.

ItillustratedthepoweroftheFlockingRulestouncoversophisticatedconcepts,eventhosewhichcastonlydimshadowsintheexistingcode.Youdon’thavetounderstandtheentireprobleminordertofindandexpressthecorrectabstractions—youmerelyapplytheserules,repeatedly,andabstractionswillnaturallyappear.

Onefinalthoughtbeforemovingon.Considerthisquestion:IfseveraldifferentprogrammersstartedfromShamelessGreenandrefactoredthe verse methodaccordingtotheFlockingRules,whatwouldtheresultingcodelooklike?Ifyou’veguessedthateveryone’scodewouldbeidentical,exceptingthenamesusedfortheconcepts,you’dberight.Thishasenormousvalue.

NowontoChapter5,whichreturnstothe"six-pack"problem.

Page 201: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

5.SeparatingResponsibilitiesTheprevioustwochaptersappliedtheFlockingRulestoreduceduplicationinthe verse method.Theresultingcodeisgratifyinglyconsistent,andnowexplicitlyexposesconceptsthatcastonlyfaintshadowsintheoriginalcode.Remember,however,thattheimpetusbehindthatentirerefactoringwasthearrivalofthesix-packrequirement.Withoutthischangeinrequirements,youmightverywellhavestoppedatShamelessGreen.

Thischapterreturnstothesix-packproblem.Codesmellsagainguidethechoiceofthenextrefactoring.Anewclasseventuallygetscreated,andalongthewayanumberofbigideasareexamined.Thischapterexploreswhatitmeanstomodelabstractionsandrelyonmessages;itconsiderstheconsequencesofmutationandtheperilsofprematureperformanceoptimization.

5.1.SelectingtheTargetCodeSmellCodeshouldbeopenforextensionandclosedformodification.It’stimetoreexaminethecurrentcodeinlightoftheongoingsix-packrequirement.Recallthefollowingflowchart(whichoriginallyappearedinChapter3):

Page 202: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Figure5.1:OpenClosedFlowchart

Despitethefactthatyou’vesuccessfullyreplacedafairamountofduplicationwithwell-namedmethodsthatexposeconceptsinthe99Bottlesdomain,theresultingcodeisnotyetopentothesix-packrequirement.Ifanything,thecurrentincarnationislessamenabletothisrequirementthanwasShamelessGreen.WithinShamelessGreen,youcouldhavesimplyamendedthecasestatementtoaddabranchforverses6and7.Thechangesneededtomeetthesix-packrequirementwithinthenewcodearenotnearlysoobvious.Itmayseemasifyouhavecomplicatedthingswithoutmakinganyprogresstowardsmeetingthegoal.

Page 203: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Thetruthaboutrefactoringisthatitsometimesmakesthingsworse,inwhichcaseyoureffortsservegallantlytodisproveanidea.Therefactoringrecipesdon’tpromisetoresultincodethatbetterexpressestheproblem—theymerelymakeiteasytocreatethatnewexpression,andjustaseasytorevertit.Properrefactoringallowsyoutoexploreaproblemdomainsafely.

You’venowcompletedonerefactoring,andtheresultingcodeisnotyetopentothesix-packrequirement.Notonlythat,butitisentirelypossiblethatyoudonotyetknowwhatchangewillmakeitopen.Atthispoint,youmustdecidewhetherit’sbettertoproceedwithadditionalmodificationstothecode,orbettertorevertthepreviouschangeandtakeadifferenttack.

Thecurrentcode,althoughnotopentothenewrequirement,isimproved.Thissuggeststhatit’sreasonabletocontinueforward,inhopesthatmoregoodthingswillcome.

Therefore,havefaith,anditerate.Thismeansyoumustcontinuetobeguidedbycodesmells,anddoingsorequiresthatyouidentifythesmellsinthecurrentcode.

5.1.1.IdentifyingPatternsinCodeOnewaytogetbetteratidentifyingsmellsistopracticedescribingthecharacteristicsofcode.Lookatthe Bottles classbelowandmakenoteofthethingsthatcatchyoureye.Includeanypatternsthatyousee,andthingsyoulike,hate,ordon’tunderstand.Thislistingisfollowedbyaseriesofquestionsintendedtoinspirefurtherthoughts,sotakeaminutetoponderbeforereadingon.

Listing5.1:DRYBottlesClass

1 classBottles2 3 defsong

Page 204: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

4 verses(99,0)5 end6 7 defverses(starting,ending)8 starting.downto(ending).collect{|i|verse(i)}.join("\n")9 end10 11 defverse(number)12 "#{quantity(number).capitalize}#{container(number)}"+13 "ofbeeronthewall,"+14 "#{quantity(number)}#{container(number)}ofbeer.\n"+15 "#{action(number)},"+16 "#{quantity(successor(number))}#{container(successor(number))}"+17 "ofbeeronthewall.\n"18 end19 20 defcontainer(number)21 ifnumber==122 "bottle"23 else24 "bottles"25 end26 end27 28 defquantity(number)29 ifnumber==030 "nomore"

Page 205: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

31 else32 number.to_s33 end34 end35 36 defaction(number)37 ifnumber==038 "Gotothestoreandbuysomemore"39 else40 "Take#{pronoun(number)}downandpassitaround"41 end42 end43 44 defpronoun(number)45 ifnumber==146 "it"47 else48 "one"49 end50 end51 52 defsuccessor(number)53 ifnumber==054 9955 else56 number-157 end58 end59 end

Page 206: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Thefollowingquestionsdrawattentiontoanumberofinterestingcharacteristicsofthecodeasit’swrittensofar:

1. Doanymethodshavethesameshape?

2. Doanymethodstakeanargumentofthesamename?

3. Doargumentsofthesamenamealwaysmeanthesamething?

4. Ifyouweretoaddtheprivatekeywordtothisclass,wherewoulditgo?

5. Ifyouweregoingtobreakthisclassintotwopieces,where’sthedividingline?

ForthosemethodscreatedbytheFlockingRules( container ,quantity , action , pronoun and successor ,hereafterreferredtoasthe"flockedfive"):

6. Dothetestsintheconditionalshaveanythingincommon?

7. Howmanybranchesdotheconditionalshave?

8. Dothemethodscontainanycodeotherthantheconditional?

9. Doeseachmethoddependmoreontheargumentthatgotpassed,orontheclassasawhole?

Theremainderofthissectionexaminestheabovequestions.Ifanydidn’toccurtoyou,lookbackatthecodeandtrytoanswerthembeforeproceeding.

5.1.2.SpottingCommonQualitiesThefirstfivequestionsabovelookattheclassasawholeandexposecommonqualitiesofthecode.Thisnextsectionexamines

Page 207: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

thesequestionsindetail.

Question1:Doanymethodshavethesameshape?

Yes.Theflockedfiveallhavethesameshape.

Youcaneasilyidentifysame-shapedmethodsbydoingtheSquintTest(seesidebar).ThefactthatthesemethodsaresoconsistentisatributetotheFlockingRules.Hadthemethodsbeencreatedatdifferenttimes,bydifferentpeople,fordifferentreasons,theycouldeasilyhavecontainedavarietyofshapes.Forexample,thefollowingthreemethodsarelogicallythesame:

Listing5.2:VariousConditionalForms

1 #verboseconditional2 defcontainer(number)3 ifnumber==14 "bottle"5 else6 "bottles"7 end8 end9 10 #guardclause11 defquantity(number)12 return"nomore"ifnumber==013 number.to_s14 end15 16 #ternaryexpression17 defpronoun(number)

Page 208: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

18 number==1?"it":"one"19 end

Alloftheabovemethodspassthetests.Theproblemisnotthatthecodedoesn’twork;it’sthatthenon-essentialvariationdisguisesacommonshape.Thisunnecessaryvariationmakesthemethodsappeartobedifferentwhentheyareactuallyverymuchthesame.

Programmersnaturallyassumethatdifferenceexistsforareason,butherethereisn’tone.Superfluousdifferenceraisesthecostofreadingcode,andincreasesthedifficultyoffuturerefactorings.

It’snotyetclearwhatitmeansthatthesemethodshavethesameshape,butit’simportanttonoticethattheydo.

SquintTestOneeasywaytojudgecodeisbyperformingaSquintTest.Thistestrequiresnosetup,andcanbeperformedonanycodeatanytime.

Here’showitworks:

1. Putthecodeofinterestonyourscreen.

2. Leanback.*

3. Squintyoureyessuchthatyoucanstillseethecode,butcannolongerreadit.

4. Lookfor:

a. changesinshape,and

Page 209: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

b. changesincolor.

Changesinindentationrevealthepresenceofconditionals.Twoormorelevelsofindentationexposenestedconditionals.Conditionalsresultinmultipleexecutionpathsthroughthecode,whichaddcomplexityandmakecodehardtounderstand.

Changesincolorindicatedifferencesinthelevelofabstraction.Amethodthatintermixesmanycolorstellsastorythatwillbedifficulttofollow.

*Insteadofleaningbackandsquinting,it’sacceptabletozoomoutinyourtexteditoruntilyoucannolongerreadthecode,butcanstillseeitsshapeandcolor.

Question2:Doanymethodstakeanargumentofthesamename?

Sixmethodstake number asanargument—the verse methodandtheflockedfive.

Listing5.3:MethodsWhichTakeanArgumentNamedNumber

1 defverse(number)2 defcontainer(number)3 defquantity(number)4 defaction(number)5 defpronoun(number)6 defsuccessor(number)

Question3:Doargumentsofthesamenamealwaysmeanthe

Page 210: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

samething?

Theeasiestwaytounderstandwhat number representsistofollowitspaththroughthecode,beginningwith song .Here’sareminderofthatmethod:

Listing5.4:SongMethod

1 defsong2 verses(99,0)3 end

When song sends verses(99,0) ,the 99 and 0 representthestartingandendingversenumberstosing.Youcouldarguethatthe 99 and 0 representthestartingnumberofbottlesintheversetobesung,butthatwouldbestretchingitandyou’dbeinaminority.Mostfolksinterpretthe 99 and 0 asversenumbers.

If song issendingversenumbersto verses ,the verses methodmustbereceivingthem.Here’sthatmethod:

Listing5.5:VersesMethod

1 defverses(starting,ending)2 starting.downto(ending).collect{|i|verse(i)}.join("\n")3 end

The starting and ending argumentsareversenumbers.Theverses methoditeratesbetweenthem,so i ,theargumentyieldedtotheblock,mustalsorepresentaversenumber.Therefore,andquitesensiblyso,theargumentwithwhich verseisinvokedmustbetheversenumbertobesung.Asreceivedbyverse ,thisargumentisnamed number .

Page 211: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Torepeat(withnointentiontobelaborthepoint),the numberargumenttakenby verse representsaversenumber.

Nowswitchyourattentiontotheflockedfive,allofwhichalsotakeanargumentnamed number .Here,forexample,is container :

Thequestionathandiswhether number asreceivedby containerrepresentsthesameconceptas number asreceivedby verse .Toanswerthisquestion,considertheentire verse method:

Listing5.6:VerseMethod

1 defverse(number)2 "#{quantity(number).capitalize}#{container(number)}"+3 "ofbeeronthewall,"+4 "#{quantity(number)}#{container(number)}ofbeer.\n"+5 "#{action(number)},"+6 "#{quantity(successor(number))}#{container(successor(number))}"+7 "ofbeeronthewall.\n"8 end

Noticethatline2aboveinvokes container with number ,whileline6invokes container with successor(number) .Withineveryverse,container isinvokedtwice,ontwodifferentvalues.

Thishappensbecauseeachverseknowsabouttwodifferentnumbersofbottles.Verse37,forexample,beginswith37bottles

defverse(number)

defcontainer(number)

Page 212: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

ofbeer,andendswith36.Asyou’vealreadyseen,theincomingnumber argumentto verse representsaversenumber.However,theparameterthat verse thenpassesonto container standsforsomethingelse—abottlenumber.

Thesameistruefortheotherflockedfivemethods—theargumenttheyreceiveisabottlenumberratherthanaversenumber.Thus,the verse methodandtheflockedfivemethodsusethesameargumentnametorepresentdifferentconcepts.

Thisisrarelyagoodidea.

Ifyouhavelongsincenoticedthisissue,congratulations,butyou’reinaminority.Mostfolkswhoworkthisproblemnametheargumenttakenbytheflockedfivemethodsaftertheparameterpassedfrom verse .Initially,thismadeperfectsense.BackinChapter3,whentheFlockingRulesledtotheextractionofthecontainer method,yourgraspoftheproblemwaslessdevelopedthanitisnow.Thenitwasclearonlythat:

thecasestatementin verse switchedon number ,and

container neededanargumentinordertodecidewhethertoreturn"bottle"or"bottles."

Intheinterestsofconsistency,itwasreasonablebackinChapter3tonametheargumenttakenby container aftertheparameterbeingpassedfrom verse .Intheinterimithasn’tmatteredthatnumber standsforaversenumberwithin verse butabottlenumberwithin container .

Now,however,itbeginsto.Havingmultiplemethodsthattakethesameargumentisacodesmell.It’simportant,however,torecognizethatheretheterm"same"meanssameconcept,notidenticalname.Inanidealworld,eachdifferentconceptwouldhaveitsownunique,precisename,andtherewouldbeno

Page 213: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

ambiguity.Unfortunately,realworldcodeoftenfailstomeetthisideal.Inlong-livedapplications,thesameconceptmightgobyseveraldifferentnames,or,asinthiscase,differentconceptsmighthidebehindasinglename.Thesenamingmistakesmakeithardertonoticeunderlyingcodesmells,andnowthatyou’relookingforpatternsinthecode,youmustexaminetheargumentsandclarifytheabstractionsthattheyrepresent.

Havingexaminedtheuseof number in Bottles ,it’snowclearthatthisargumentrepresentsaversenumberto verse ,butabottlenumbertotheflockedfivemethods.

Question4:Ifyouweretoaddtheprivatekeyword,wherewoulditgo?

After verse andbeforetheflockedfivemethods.

Question5:Ifyouweregoingtobreakthisclassintotwopieces,where’sthedividingline?

Sameasabove,i.e.after verse andbeforetheflockedfivemethods.

5.1.3.EnumeratingFlockedMethodCommonalitiesNowthatyou’veconsideredtheclassasawhole,it’stimetomoveontoquestionssixthroughnine,whichapplyonlytotheflockedfivemethods.

Question6:Dothetestsintheconditionalshaveanythingincommon?

Here’sasummaryoftheconditionals:

Page 214: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Listing5.7:FlockedFiveConditionalTests

1 defcontainer(number)2 ifnumber==13 #...4 end5 6 defquantity(number)7 ifnumber==08 #...9 end10 11 defaction(number)12 ifnumber==013 #...14 end15 16 defpronoun(number)17 ifnumber==118 #...19 end20 21 defsuccessor(number)22 ifnumber==023 #...24 end

Inthecodeabove,notonlydoalloftheconditionalstestthevalueof number ,buttheytestfor number tobeexactlyequaltoanothervalue.

Theseconditionalscouldlogicallyhaveusedthelessthan,

Page 215: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

greaterthanornotequaloperators,andstillpassthetests.TheIncomprehensiblyConciseexampleinChapter1managedtouseallfouroftheseoperations,andyourownsolutionmayalsohavehadtestsforsomethingotherthanequality.

Programmerstendtoblithelyinterchangethesedifferentcomparisonoperators,confidentthatifthetestspass,thecodeiscorrect.However,havingteststhatpassdoesn’tguaranteethebestexpressionofcode,andthisisacasewhereyourchoiceofoperatoraffectsfuturecosts.

Testingforequalityhasseveralbenefitsoverthealternatives.Mostobviously,itnarrowstherangeofthingsthatmeetthecondition.Intheaboveexamples,ifunexpectedvaluesof numberarrive,the else branchexecutes.Knowingthattheonlywaytogettothe true branchisbysupplyinganexactvalueof numbermakesiteasierforfuturereaderstounderstandthecode.Thisreducesthedifficultyofdebuggingerrorscausedbyincorrectinputs.Testingforequalityalsomakesthecodemoreprecise,andthisprecision,asyouwillsoonsee,enablesfuturerefactorings.

Question7:Howmanybranchesdotheconditionalshave?

Eachconditionalcontainstwobranches.Thismayornothavemeaning,butit’scertainlyavisiblequalityofthecodeandthusworthnoting.

Question8:Dothemethodscontainanycodeotherthantheconditional?

No.Eachmethodisnamedafteraconcept,andcontainsasingleconditional.Thisconditionalusesthevalueof number tochoosethecorrectconcreteexpressionoftheconcept.Thesemethodsarefiercelycommittedtohavingoneresponsibilityandnever

Page 216: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

conflatingtwoconcepts.

Question9:Doesmethodsthattake number asanargumentdependmoreon number ,ormoreontheclassasawhole?

Theflockedfivedependonlyonthe number argument,ratherthanontherestoftheclass.Thisistrueevenfor action ,ifyouacceptthatalthough action dependson pronoun , pronoun dependsonlyon number .

Inconjunction,theseninequestionsgroupcertainmethodstogether.Thesame-shaped,same-kind-of-conditional-testing,bottlenumber-taking,argument-depending,flockedfivemethodsfallintoonegroup,andthe song , verses ,and verse methodsintoanother.Theanswerstothequestionsaboverevealmanycharacteristicsofthecode,butthere’sonemorequalitytodiscussbeforemovingon.

5.1.4.InsistingUponMessagesThiscodecontainsadeeplynon-object-orientedpattern:theflockedfivemethodstakeanargument,examineit,andthensupplybehaviorforit.

Asyou’veseen,thosefivemethodssharethiscommonshape:

TheabovemethodwascreatedbytheFlockingRules,andsoexhibitsmanydesirablequalities.Despitethat,it’sdeeplyflawed

defcontainer(number)ifnumber==1"bottle"else"bottles"endend

Page 217: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

whenconsideredfromthepointofviewofanindependentOOpractitioner.Whatthatpractitionerwouldseehereisthatsomeonehasgonetothetroubleofinjectingadependency( number ),butthatdependencyistooimpairedtosupplytheneededbehavior.Consequently,notonlydoes container knowabout number ,butit’salsoforcedtounderstandwhatthespecificvaluesof number mean,andtoknowwhattodoineachcase.Thecontainer methoddependsoneachofthesethings.Ifanyofthemchange,the container methodmightbeforcedtochangeinturn.

ItmadesensetotolerateaconditionalbackinShamelessGreen.Thatsolutionoptimizedforunderstandabilitywithoutregardforchangeability.Itsgoalwastogettogreenquickly.Theresultingcodewasmoreproceduralthanobject-oriented,butwouldhavebeengoodenoughifnothingeverchanged.However,nowthatyouhaveanewrequirementandarerearrangingthecode,you’dliketoapplyafull-blownOOmindset,andthatmindsetisdeeplysuspiciousofconditionals.

AsanOOpractitioner,whenyouseeaconditional,thehairsonyourneckshouldstandup.Itsverypresenceoughttooffendyoursensibilities.Youshouldfeelentitledtosendmessagestoobjects,andlookforawaytowritecodethatallowsyoutodoso.Theabovepatternmeansthatobjectsaremissing,andsuggeststhatsubsequentrefactoringsareneededtorevealthem.Beonthelookoutforthiscodeshape,asitimpliesthatthere’smoretobedone.

Thisisnottosaythatyou’llneverhaveaconditionalinanobject-orientedapplication.ThereisaplaceforconditionalsinOO.ManageableOOapplicationsconsistofpoolsofsmallobjectsthatcollaboratetoaccomplishtasks.Collaboratorsmustbebroughttogetherinusefulcombinations,andassemblingthesecombinationsrequiresknowingwhichobjectsaresuitable.Someobject,somewhere,mustchoosewhichobjectstocreate,andthisofteninvolvesaconditional.

Page 218: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

However,there’sabigdifferencebetweenaconditionalthatselectsthecorrectobjectandonethatsuppliesbehavior.Thefirstisacceptableandgenerallyunavoidable.Thesecondsuggeststhatyouaremissingobjectsinyourdomain.

Codeisstrivingforignorance,andpreservingignorancerequiresminimizingdependencies.The container methodyearnstobeinjectedwithasmarterobjecttowhichitcouldmerelyforwardthemessage,asshownhere:

Theexistingcodeisimploringyoutocreatethatsmarterobject.

5.2.ExtractingClassesThequestionsaboveidentifycharacteristicsthatgroupmethodstogether,andmanyofthesegroupsoverlap.Forexample,anumberofmethodstakethesameargument.Mostmethodsthatdosohavethesameshape,containaconditional,couldbeconsideredprivate,anddependmoreontheargumentthanontheclassasawhole.

Eachitemaboveactslikeavote,andthesevotescombinetopointtoPrimitiveObsessionasthedominantcodesmell.Built-indataclasseslike String , Fixnum , Integer (Fixnum 'ssuperclass),Array ,and Hash areexamplesof"primitives."PrimitiveObsessioniswhenyouuseoneofthesedataclassestorepresentaconceptinyourdomain.Obsessingonaprimitiveresultsincodethatpassesbuilt-intypesaround,andsuppliesbehaviorforthem.

ThecureforPrimitiveObsessionistocreateanewclasstouseinplaceoftheprimitive.Forthisoperation,therefactoringrecipeisExtractClass.

defcontainer(smarter_number)smarter_number.containerend

Page 219: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

5.2.1.ModelingAbstractionsHavingdecidedtocurethePrimitiveObsessioncodesmellwiththeExtractClassrefactoring,youmustnowchooseanameforthisnewclass.

Theprimitivethatyou’rereplacingrepresentsabottlenumber.Noticethatitisnotabottle:it’sabottlenumber.Abottleismadeofplastic,orglass,oraluminum,andcontainswater,orsoda,orbeer.Abottlehasashapeandavolume.Itexistsinthephysicalworld.

Unlikebottles,numbersaren’tthings—they’reideas,albeitonessoubiquitousthatyou’velikelyforgottenhowabstractandunlikelytheyare.Numbersaresymbolsusedtodescribequantitiesofthings.Theydon’tphysicallyexist.Youcanpickupabottle,butyoucannotpickupa"six."

Thisnewclassdoesnotrepresentakindofbottle:itrepresentsakindofnumber.Thedistinctionmayseemsubtle,butthedividebetweenthesetwoconceptsischasmic.Abottleisathing,whileanumberisanidea.It’seasytoimaginecreatingobjectsthatstandinforthings,butthepowerofOOisthatitletsyoumodelideas.

Model-ableideasoftenliedormantintheinteractionsbetweenotherobjects.Forexample,aneventmanagementapplicationmightcontain Buyer and Ticket classes. Buyer and Ticket areobviousbecauseyoucanreachoutandtouchthemintherealworld.Theseobjectsinteractinmanyways:buyersbuytickets,perhapsatadiscount,andmaylaterchangetheirmindsandreturntheticketsforrefunds.

Where,insuchanapplication,shouldthelogictomanagepurchases,discounts,andrefundsreside?Youcouldjameverythinginto Buyer and Ticket ,butthepowerofOOisthatitallowsyoutocreateavirtualworldinwhich Purchase , Discount

Page 220: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

and Refund arejustasreal.Embodyingtheseconceptsintodiscreteclassesseparatesresponsibilitiesandmakestheoverallapplicationeasiertounderstand,test,andchange.

ExperiencedOOprogrammersdeftlycreatevirtualworldsinwhichideasareasrealasphysicalthings.Ifyouarenotyetcomfortabledoingso,starttodaybythinkingoftheclassyou’reabouttoextract,notasaphysicalbottle,butasasymbolicnumberwithanaddedbitofbottle-ishbehavior.

Bearingthatideainmind,considerwhattonamethisclass.Thetwomostobviouschoicesare BottleNumber ,or ContainerNumber .

5.2.2.NamingClassesYou’vebeenintroducedtotheruleaboutnamingmethodsatonehigherlevelofabstractionthantheircurrentimplementation.Extrapolatedtoclasses,thatrulesuggeststhisnewclassshouldbenamed ContainerNumber .However,you’vealsoreadfairlylengthydiscoursesaboutnotanticipatingthefuture,andsincetheexistingrequirementsinvolveonlybottles,youmightleantowards BottleNumber .

BottleNumber islessflexiblebutmorestraightforward.ContainerNumber isjusttheopposite;it’sabitmoregeneral,andsowouldworkforabroaderrangeofvessels. BottleNumber ismoreconcrete. ContainerNumber ismoreabstract.

Thetie-breakerhereisthatthe"namethingsatonehigherlevelofabstraction"ruleappliesmoretomethodsthantoclasses.Itwouldbespeculativetocallthisnewclass ContainerNumber .Theruleaboutnamingcanthusbeamended:whileyoushouldcontinuetonamemethodsafterwhattheymean,classescanbenamedafterwhattheyare.

Havingtworequirementsforbottlesfirmlysuggeststhatthisclassrepresentsabottlenumber,andshouldbenamedassuch.

Page 221: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Asalways,youcanrevisitthisdecisionifthingschangelater.

5.2.3.ExtractingBottleNumberThissectionextractsanewclassnamed BottleNumber fromtheexistingcode.ItdoesnotuseTDD.Instead,itcreatesthenewclassbyfollowingaslightlymodifiedversionofMartinFowler’sExtractClassrefactoringrecipe.

Asyoumightrecall,saferefactoringreliesupontestsrunninggreen,sothefactthatthenew BottleNumber classwillcomeintoexistencebeforeitstestsarrivehasacoupleofconsequences.First,theexisting Bottles testsbecomethesafetynetforthisnewclass.Theywereoriginallywrittenasunittests,butusingthemtoindirectlytest BottleNumber transformsthemintoakindofintegrationtest.Thesetestsmustcontinuetorunaftereverychange.

Next,whileextractingtheclass,codethatisknowntoworkiscopiedfrom Bottles into BottleNumber .It’simportanttoputthisnewclassfullyintousebeforeeditinganyofthecopiedcode.Safetyisbeingprovidedbythe Bottles tests,sotheymustexercisethenewcodeasquicklyaspossible.

Inthepreviouschapters,theprocessofchangingcodewassubdividedintofoursteps.

1. parsethenewcode

2. parseandexecuteit

3. parse,executeanduseitsresult

4. deleteunusedcode

Thesestepsstillapply.Starttheclassextractionbycreatinganempty BottleNumber class,asshownbelow:

Page 222: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Listing5.8:BottleNumberClassDefinition

1 classBottles2 #...3 end4 5 classBottleNumber6 end

Asyougothroughthisrefactoring,remembertosavethecodeaftereverychange,andtorunthetestsaftereverysave.

Next,copythemethodsthatobsessonbottle number intothenewclass.

Listing5.9:ObsessiveMethodsCopiedtoBottleNumber

1 classBottles2 #...3 defcontainer(number)4 ifnumber==15 "bottle"6 else7 "bottles"8 end9 end10 11 defquantity(number)12 #...13 end14 15 defaction(number)16 #...

Page 223: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

17 end18 19 defpronoun(number)20 #...21 end22 23 defsuccessor(number)24 #...25 end26 end27 28 classBottleNumber29 defcontainer(number)30 ifnumber==131 "bottle"32 else33 "bottles"34 end35 end36 37 defquantity(number)38 ifnumber==039 "nomore"40 else41 number.to_s42 end43 end44 45 defaction(number)46 ifnumber==047 "Gotothestoreandbuysomemore"

Page 224: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

48 else49 "Take#{pronoun(number)}downandpassitaround"50 end51 end52 53 defpronoun(number)54 ifnumber==155 "it"56 else57 "one"58 end59 end60 61 defsuccessor(number)62 ifnumber==063 9964 else65 number-166 end67 end68 end

Rememberthatthe verse methodshouldnotbeextracted.Eventhoughitsargumentisalsonamed number ,inthiscasetheargumentrepresentsaversenumber,notabottlenumber.

Noticethattheaboveexamplecopiedmethodsfrom Bottle toBottleNumber .Themethodsweren’tmoved—theywereduplicated,sonothingabout Bottle hasyetbeenchanged.Thismeansthattheoldcodecontinuestoworkasisandthenewcodeisnotyetbeingexecuted.Runningthetestsatthispointmerely

Page 225: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

parsesthenewcode,provingthatit’ssyntacticallycorrect.

Asmentionedearlier,therecipebeingfollowedherewasinspiredbyonefromMartinFowler.The"official"ExtractClassrecipebeginsbylinkingtheoldclasstothenew.Then,oneatatime,therecipemovesfields,andthenmethods,ofinterest.Incontrast,theexampleabovestartswithFowlersfinalstep,andcombinesallofthemethodmoveswithinasinglechange.

Thismayseemlikealargeleap,buthereyoucanbeconfidentthatyou’removingtherightgroupofmethods.ThesemethodswerecreatedbytheFlockingRules,sotheyvisiblyshareacommonpattern.Thiscommonpatternmakesiteasytorecognizethattheybelongtogetherintheextractedclass.Thisvisualsimilarityisatributetotherules,andanillustrationofthevalueofstablelandingpoints(rememberthestreamandtherocks?)Thepriorrefactoringresultedindeeplyconsistentcode,andhere’smoreproofthatconsistentcodemakesthecurrentrefactoringeasy.

The BottleNumber classneedstoknowthevalueof number ,soaddan attr_reader for :number ,andan initialize methodtosetthevariable.Here’sthecode:

Listing5.10:BottleNumberHoldingOntoNumber

1 classBottleNumber2 attr_reader:number3 definitialize(number)4 @number=number5 end6 #...7 end

Online2above, attr_reader isaclassmethod.Invokingitwith

Page 226: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

thesymbol :number effectivelydefinesanewinstancemethodonBottleNumber thatactslikethis:

Becauseofthe attr_reader , BottleNumber respondstothe numbermessagebyreturningthevalueheldinthe @number instancevariable.Thisvariableissetwithinthe initialize methodonline4above.That initialize methodgetsinvokedwhen new issenttoBottleNumber .

The BottleNumber classnowcontainsallofthenecessarycode,butasyetthiscodeisonlybeingparsed.Thenextsmallstepistoexecuteabitofthenewclasswithoutusingtheresult.

Thefollowingexampledoesthisbyalteringthe container methodof Bottles toinvokethecontainermethodof BottleNumber :

Listing5.11:ParseandExecuteaBitofNewCode

1 classBottles2 #...3 defcontainer(number)4 BottleNumber.new(number).container(number)5 ifnumber==16 "bottle"7 else8 "bottles"9 end10 end11 #...12 end

defnumber@numberend

Page 227: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Line4aboveexecutesthenewmethod,butthendiscardstheresultinfavorofexistingcode.Thisprovesthatthenewcodecanexecutewithoutblowingup,butdoesnotprovethatitreturnsthecorrectresult.

Itmustnowbeadmittedthattheaddedlineofcodeis,byanystandard,ugly.

Intheabovecode,both new and container requirethe numberargument,soitmustpassedtwice.Youmayfindthisannoyinglyredundant.Inthenewly-created BottleNumber class,the containermethodcouldeasilymakedowithoutanargument.Itcangettherightnumberbysimplysendingthe number messagetoitself.Insteadofthecodeabove,you’dprefer:

However,aspreviouslymentioned,youshouldrefrainfromalteringthecodeinthesecopiedmethodsuntilthenewclassisfullywiredintotheold.Regardlessofhowmuchyouhatepassingtheparametertwice,atthispointyoushouldresisttheurgetomakethechangeshownabove.First,fullyconnectBottleNumber to Bottles .Oncethat’scomplete,youcanreturnandimprovethemethodsin BottleNumber .

So,settingthatunpleasantcodetemporarilyaside,thenextsmallstepinthecurrentrefactoringistousetheresultofthe containermessagewithinthe Bottle class.Theeasiestwaytoaccomplishthisistomoveline4tothebottomofthemethod,likeso:

Listing5.12:Parse,ExecuteandUseResult

1 classBottles2 #...

BottleNumber.new(number).container(number)

BottleNumber.new(number).container

Page 228: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

3 defcontainer(number)4 ifnumber==15 "bottle"6 else7 "bottles"8 end9 BottleNumber.new(number).container(number)10 end11 #...12 end

Thetestspass,sonowyoucandeletetheoldimplementationfrom container (lines4-8above).Thisleavesthefollowingcode:

Listing5.13:ResultingContainerMethod

1 classBottles2 #...3 defcontainer(number)4 BottleNumber.new(number).container(number)5 end6 #...7 end

Repeattheaboveprocedureforeachofthemethodscopiedfromthe Bottles class.Thisisanextremelymechanical,wonderfullyboring,anddeeplycomfortingrefactoringprocess.

Here’stheresulting Bottles class:

Listing5.14:ForwardingMessagestoBottleNumber

1 classBottles

Page 229: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

2 #...3 defcontainer(number)4 BottleNumber.new(number).container(number)5 end6 7 defquantity(number)8 BottleNumber.new(number).quantity(number)9 end10 11 defaction(number)12 BottleNumber.new(number).action(number)13 end14 15 defpronoun(number)16 BottleNumber.new(number).pronoun(number)17 end18 19 defsuccessor(number)20 BottleNumber.new(number).successor(number)21 end22 end

Thesemethodsin Bottles nowmerelyforwardmessagesalongtoBottleNumber .

5.2.4.RemovingArgumentsNowthattheold Bottles classfullyuses BottleNumber ,theexistingtestsserveasasafetynetforchangestothenewclass.Thismeansthatyoucannowundertakeimprovementsinthenewcode.

Although BottleNumber works,partsofitareannoyingly

Page 230: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

redundant.TheproblemisthateventhoughinstancesofBottleNumber knowtheir number ,itsmethodscontinuetorequirenumber asanargument.Toillustrate,herearethetwo containermethods:

Listing5.15:RedundantArguments

1 classBottles2 #...3 defcontainer(number)4 BottleNumber.new(number).container(number)5 end6 #...7 end8 9 classBottleNumber10 attr_reader:number11 definitialize(number)12 @number=number13 end14 15 defcontainer(number)16 ifnumber==117 "bottle"18 else19 "bottles"20 end21 #...22 end

Line4abovegetsanew BottleNumber andasksforits container .Doingsorequirestworeferencesto number .The initialize

Page 231: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

method(invokedby new anddefinedonline11)andthecontainer method(line15)bothrequirea number argument.

ThepointofthePrimitiveObsession/ExtractClassrefactoringistocreateasmarterobjecttostandinfortheprimitive.Thissmarterobject,bydefinition,knowsboththevalueoftheprimitiveanditsassociatedbehavior.Becausethenew BottleNumber classholdstherightnumber,themethodsin BottleNumber don’tneedtotakeanargument,andinvokersofthesemethodscouldberelievedoftheirobligationtopassaparameter.

Nowthat BottleNumber isfullyconnectedto Bottles ,it’ssafetostartmakingtheseimprovements.Noticethatifyou’rewillingtosimultaneouslyalterboththesendersandthereceiverseverymessage,it’seasytomakethischange.Forexample,youcouldfixthe container methodbychangingline4abovetoremovetheparameterbeingpassedto container ,whilesimultaneouslydeletingtheargumentfromline15.Ifyoumakebothofthesechangesatonce,andthensaveandrunthetests,thetestswillpass.

Keepinmindthatisamulti-linechange.Someproblemsaresosimplethatit’seasiestjustleapinandmakesuchachange,butothersaresocomplexthatitisn’tfeasibletofixeverythingatonce.Inreal-worldapplications,thesamemethodnameisoftendefinedseveraltimes,andamessagemightgetsentfrommanydifferentplaces.Learningtheartoftransformingcodeonelineatatime,whilekeepingthetestspassingateverypoint,letsyouundertakeenormousrefactoringspiecemeal.Thissmallproblemisagoodplacetopracticethistechnique,inpreparationforlatertacklingbiggerones.

BackinChapter3,youhadtoaddanargumenttoamethodthatwasalreadybeingcalledwithoutone.Thisistheoppositeproblem:hereyouneedtoremoveanargumentfromamethodthat’scurrentlybeinginvokedwithone.Whetherargumentsarebeingaddedorremoved,thetrickisthesame;youmustchange

Page 232: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

themethoddefinitiontotemporarilysettheargumenttoadefault.

Thereareaseveralwaystoaccomplishthis.Thefollowingtechniqueisthemostdirect,butrequiresashortrefresheronRubysyntax.

Consider container ,repeatedagainbelow.Thismethodtakesanumber argument.Remember,however,thatthe BottleNumberclassitselfrespondstothe number message.Nowanswerthisquestion:Online4below,does number refertotheargument,ortothemessage?

Listing5.16:BottleNumberContainerRedoux

1 classBottleNumber2 #...3 defcontainer(number)4 ifnumber==15 "bottle"6 else7 "bottles"8 end9 end10 #...11 end

Rubyisperfectlyhappytoallowthesamenametobeusedfordifferentthings,andtoinferwhichyoumeanbasedoncontext.Inthecodeabove,theprogrammerclearlyintendsfor number online4torefertothe number argumentfromline3,andthat’sexactlywhatRubydoes.The number online4isinterpretedasareferencetothemethod’sargumentratherthanasasendofthenumber message.

Page 233: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Armedwiththisknowledge,youcanguessthatremovingtheargumentfromthemethoddefinitionwouldcauseRubytointerpretline4asasendofthe number message.Thisisyourgoal,butunfortunately,the Bottles classisstillsendingcontainer(number) ,sothischangebreaksthetests.

Thetricktoworkingyourwayforwardundergreen,whilemakingonlyone-linechanges,istoalterthenameoftheargumenttosomethingotherthan number ,andsimultaneouslygiveitadefault.Line3belowcontainsthatchange:

Listing5.17:RenamedArgument

1 classBottleNumber2 #...3 defcontainer(delete_me=nil)4 ifnumber==15 "bottle"6 else7 "bottles"8 end9 end10 #...11 end

Above,the number argumentfor container hasbeenrenamedtodelete_me andassignedadefaultof nil .Thatchangeturnsthenumber referenceonline4intoamessagesend,whichallowsthismethodtodependuponamessagesenttoitselfratherthananargumentpassedbysomeoneelse.

Nowthattheargumentisoptional,turnyourattentiontosendersof container .Inthisapplicationthere’sonlytheonein Bottles ,shownhere:

Page 234: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Listing5.18:ForwardWithRedundantArguments

1 classBottles2 #...3 defcontainer(number)4 BottleNumber.new(number).container(number)5 end6 #...7 end

Removingthe number parameterfromthe container messageinvocationonline4resultsinthiscode:

Listing5.19:ForwardWithoutRedundancy

1 classBottles2 #...3 defcontainer(number)4 BottleNumber.new(number).container5 end6 #...7 end

Onceyouhavelocatedandremovedtheparameterfromallofitssenders,the container methoddefinitionnolongerneedstotakeanargument.Youcannowreturnto BottleNumber andremovethedelete_me argumentanddefault,asonline3below:

Listing5.20:BottleNumberContainerMethodWithoutArgument

1 classBottleNumber2 #...3 defcontainer4 ifnumber==1

Page 235: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

5 "bottle"6 else7 "bottles"8 end9 end10 #...11 end

Here’sarecapofthestepsforremovinganargumentusingone-linechanges.

1. Alterthemethoddefinitiontochangetheargumentnameandprovideadefault.

Startbychangingtheexistingargumentnametoanythingotherthanwhatitcurrentlyis.Using delete_me willhelpyouremembertodeletetheargumentwhenyou’veupdatedallofthesenders.Thevalueofthedefaultdoesnotmatter,soit’scommontouse nil .Intheexampleabove:

became:

2. Changeeverysenderofthemessagetoremovetheparameter.Intheexample:

became:

defcontainer(number)

defcontainer(delete_me=nil)

BottleNumber.new(number).container(number)

BottleNumber.new(number).container

Page 236: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Finally,deletetheargumentfromthemethoddefinition.So,finally:

became:

Asyoucansee,despitethelengthoftheexplanation,thetechniqueissimple,andinvolvesonlythreesteps.Havingpracticedon container ,theothermethodswilleasilybendtoyourwill.Youcannowfollowthisprocesstoremovethe numberargumentfromtheremainingmethodsin BottleNumber .

Ifyoudothisrefactoringyourself,you’llfindthat quantity andaction workasexpected,butthatwhenyouchange pronoun ,thetestsbeginfail.

5.2.5.TrustingtheProcessRefactoringsthatleadtoerrorscanshakeyourfaithinthevalidityofthecorrespondingrecipes.However,theserecipeshaveproventhemselvesreliableinmanycircumstances,formanypeople,inmanysituations.Ifyouadheretoarecipeandtestsstartfailing,it’slikelythatthere’ssomethingabouttheproblemthatyoudon’tyetunderstand.

Inthiscase,you’vebeenusingthe"removeargumentsviaone-linechanges"process.Itworksfor container , quantity ,andaction butcausestheteststofailwhenappliedto pronoun .

Specifically,ifyougotothe pronoun definitionin BottleNumber :

defcontainer(delete_me=nil)

defcontainer

1 classBottleNumber2 #...

Page 237: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

andchange number to delete_me ,andsupplyadefault:

Thengotothe pronoun methodin Bottles :

andremovetheparameterfromtheforwardof pronoun toBottleNumber :

Finally,youreturntothe pronoun methoddefinitioninBottleNumber anddeletetheentireargument:

Thenthetestsbegintofailwith:

Theprocessthatworkedforothermethodsisnowfailingforpronoun .Whilethiserrormightleadyoutodoubtthevalidityofthetechnique,itdoesn’tpointoutaflawintheprocess.Instead,it

3 defpronoun(number)

1 classBottleNumber2 #...3 defpronoun(delete_me=nil)

1 classBottles2 #...3 defpronoun(number)4 BottleNumber.new(number).pronoun(number)

1 classBottles2 #...3 defpronoun(number)4 BottleNumber.new(number).pronoun

1 classBottleNumber2 #...3 defpronoun

ArgumentError:wrongnumberofarguments(given1,expected0)

Page 238: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

exposesaslightlymorecomplexbitofcode.

Recallthestepsneededtoremoveparameters:

1. Alterthemethoddefinitiontochangetheargumentname,andprovideadefault.

2. Changeeverysenderofthemessagetoremovetheparameter.

3. Deletetheargumentfromthemethoddefinition.

Thefailureappearedafterstep3.Theerrormessageindicatesthatsomecallerisstillpassingaparameterto pronoun .Thismeansstep2isn’tcomplete;i.e.somesenderhasnotbeenfixed.Thisshouldtriggeryoutoexaminethesourcecodewherethefailureoccurred.Whenyoudoso,you’llseethefollowing:

Itturnsoutthat pronoun isinvokedonlyfromthe action methodof BottleNumber ,wherethemessageissentto self .The pronounmethoddefinedbackin Bottles isnolongerused(asyoucanconfirmbycavalierlydeletingitandrunningthetests).

Insteadofchangingtheunused pronoun methodin Bottles ,step2shouldhaveremovedthe number argumentfromthecalltopronoun inthe action methodof BottleNumber ,leaving:

1 classBottleNumber2 #...3 defaction(number)4 ifnumber==05 "Gotothestoreandbuysomemore"6 else7 "Take#{pronoun(number)}downandpassitaround"8 end9 end

Page 239: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Onceyoumakethatchangeandthencompletethesteps,thecodepassesthetests.

Thelessonhereisthattheprocessworks,andthatencounteringerrorswhilefollowingitsuggeststhatacloserlookatthecodeisinorder.Agreatbenefitoftheserefactoringtechniquesisthatyoucanaccomplishquiteabitwhilethinkingverylittle.Sometimes,however,thoughtjustcan’tbeavoided.Theblessingofthesetechniquesisthatalteringcodeinsuchsmallincrementsseverelyconstrainsthenumberoferrorsanychangecanintroduce.Whenforcedtothink,youcanbeconfidentthatyoureffortswillbenarrowlyfocusedonanopportunetopic.

Nowthat pronoun works,onlythe successor methodremains.Itsuccumbstothisrefactoringwithnosurprises.ThiscompletestheremovalofextraneousargumentstomethodsintheBottleNumber class,andleavesthecodeatthefollowingrestingpoint.

Listing5.21:ForwardMessagestoSmarterNumber

1 classBottles2 3 defsong4 verses(99,0)5 end6

1 classBottleNumber2 #...3 defaction(number)4 ifnumber==05 "Gotothestoreandbuysomemore"6 else7 "Take#{pronoun}downandpassitaround"8 end9 end

Page 240: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

7 defverses(starting,ending)8 starting.downto(ending).collect{|i|verse(i)}.join("\n")9 end10 11 defverse(number)12 "#{quantity(number).capitalize}#{container(number)}"+13 "ofbeeronthewall,"+14 "#{quantity(number)}#{container(number)}ofbeer.\n"+15 "#{action(number)},"+16 "#{quantity(successor(number))}#{container(successor(number))}"+17 "ofbeeronthewall.\n"18 end19 20 defcontainer(number)21 BottleNumber.new(number).container22 end23 24 defquantity(number)25 BottleNumber.new(number).quantity26 end27 28 defaction(number)29 BottleNumber.new(number).action30 end31 32 defsuccessor(number)33 BottleNumber.new(number).successor

Page 241: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

34 end35 end36 37 classBottleNumber38 attr_reader:number39 definitialize(number)40 @number=number41 end42 43 defcontainer44 ifnumber==145 "bottle"46 else47 "bottles"48 end49 end50 51 defquantity52 ifnumber==053 "nomore"54 else55 number.to_s56 end57 end58 59 defaction60 ifnumber==061 "Gotothestoreandbuysomemore"62 else63 "Take#{pronoun}downandpassitaround"

Page 242: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

5.3.AppreciatingImmutabilityTomutateistochange.Stateis"theparticularconditionofsomethingataspecifictime."Avariableis"thatwhichvaries,"or,inmaths,"aquantitywhichadmitsaninfinitenumberofvaluesinthesame

64 end65 end66 67 defpronoun68 ifnumber==169 "it"70 else71 "one"72 end73 end74 75 defsuccessor76 ifnumber==077 9978 else79 number-180 end81 end82 end

Thecompletestheextractionofthe BottleNumber class.Despiteitsmanyconditionals,thecodehasaregular,orderlyaspectthatfeelspleasing,andbodeswellforfuturerefactorings.

It’salmosttimetoreturnyourfocustothe Bottles class,butbeforedoingso,thereareafewbroadideastoconsider.

Page 243: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

expression."

Inthephysicalworld,conditionsvaryovertime.Yourcoffeecupwasfull,butnowisempty.You’vebeenexercising,andnowyou’remorefit.TheHimalayasarerising.

It’sthesamecup,you,andmountainrange,buttheirconditionshavechanged.Therealworldispervadedbythisidea—whatexists,willchange.

Humanagreementaboutthenecessityandrightnessofchangeisreflectedinthechoiceofthewordvariableforusewithincomputerprogramminglanguages.Whatpurposeavariableotherthantovary?Mostobject-orientedprogrammerswritecodethatbothexpectsandreliesuponobjectmutation.Objectsareconstructed,used,mutated,andthenusedagain.

Regardlessofhowintuitiveandnaturalitmayseem,mutationisnotanabsoluterequirement.Itisperfectlypossible(asprogrammersoffunctionallanguageswillhappilyinformyou)toconstructapplicationsfromimmutableobjects,i.e.objectsthatdonotchange.Forthoseunusedtothisidea,itcanbedisorientingtoimaginerealityasconstructedbythefunctionalprogrammer.Insteadofrefillingyourexistingcup,youdiscarditinfavorofanewonethatlooksidenticalbutisfullofcoffee.Ratherthanchangingyourselftobemorefit,youswapyourselfforthenew,fitter,you.AstheHimalayasrise,youreplaceyourexistingcopywithabrandnewmountainrangethat’satinybittaller.

Iftheideaofimmutabilityisnewtoyou,theexamplesinthepriorparagraphmayseempositivelyalarming.Thefirstconcernmostfolkshaveisforperformance.Theconsequencesofgettingawholenewcupwhenallyouwantismorecoffeedon’tseemsobad,butreplacinganentiremountainrangetohandleafive-millimeterannualheightchangemayfeelexcessive.

Thenextsectionwilldelveintothoseconsiderations,sodefer

Page 244: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

performanceconcernsforamoment.Fornow,ponderthebenefitsofworkingwithobjectsthatdonotchange.Whatvirtuemightimmutabilityprovide,andwhattroublemightitavoid?

Oneofthebestthingsaboutimmutableobjectsisthattheyareeasytounderstandandreasonabout.Theseobjectsneverstartoutonewayandthensecretlymorphintosomethingelse.Youcanbeconfidentthatwhatyouseeatcreationtimeisalwayswhatyougetlater.

Becausetheyareeasytoreasonabout,immutableobjectsarealsoeasytotest.Objectsthatchangeneedtestsfortheaffectedbehavior.Thechangemightbecausedbyacollaboratingobject,ortriggeredbyadistantevent,sotestscouldneedadditionalcollaborators,oractionstriggeredbyapparentlyunrelatedpartsofyourapp.Testsforimmutableobjectsavoidthisextrasetup,whichmakesthetestscheapertowriteandeasiertounderstand.

Anotherkeyvirtueofimmutableobjectsisthattheyarethreadsafe.Someofthemostperniciousbugsinmulti-threadedsystemsinvolvetheinadvertentchangingofsharedstatebydifferentthreads.Thesebugsareoftenrelatedtothetimingofthreadexecution,andsoarenotoriouslydifficulttoreproduce,aswellascostlyandfrustratingtodebug.Thisclassofproblemisentirelyavoidedbyimmutableobjects.Youcan’tbreaksharedstateifsharedstatedoesn’tchange.

Therefore,therearemanygoodreasonstopreferobjectsthatdonotmutate.Youarerestrainedfromcreatingthemonlybythehabitofmutability,andthe(oftenunquestioned)assumptionthatinstantiatingnewobjectswillbeunacceptablymorecostlythanreusingexistingones.

Havingreadthissection,lookbackatthenew BottleNumber classinListing5.21:ForwardMessagestoSmarterNumber.Thequestionofmutabilityappliesdirectlytothisnewclass.Imaginethatyou’reholdingontoaninstanceof BottleNumber whose @number variablecontainsthevalue 99 .Theverseprogressessuchthatitnowneeds

Page 245: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

bottlenumber98.Isitbettertomutatethevalueof @number inthecurrentinstanceof BottleNumber ,orshouldthatobjectbediscardedinfavorof BottleNumber.new(98) ?

Ifyouleantowardsmutatingtheexisting BottleNumber ratherthanmakinganother,it’spossiblethatyouarebiasedagainstcreatingnewobjects.Thisbiasisoftenunexamined,andhasitsrootsintheassumptionthatifyouroutinelycreatemanynewobjects,yourapplicationwillbetooslow.

5.4.AssumingFastEnoughThebenefitsofimmutabilityaresogreatthat,ifitwerefree,you’dchooseiteverytime.Immutability’soffsettingcostsaretwofold.First,youmustbecomereconciledtotheidea,whichformanyprogrammersisnosmallthing.Next,achievingimmutabilityrequiresthecreationofmore(sometimesmanymore)newobjects.

Gettinghabituatedtoanewwayofthinkingneedhappenonlyonce,sothiscostisnotanpermanentconcern;drinkingtheimmutabilityKool-Aidtodaysufficesforforever.Theongoingcostsofimmutabilityarethereforemostlyinthecreationofnewobjects,andthat’sthetopicofthissection.

YoumaybefamiliarwithPhilKarlton’sfamoussaying"ThereareonlytwohardthingsinComputerScience:cacheinvalidationandnamingthings."You’vealreadyreadagreatdealaboutnamingthings,andit’sfinallytimetodiscusscaching.

Acache,incomputerscience,isalocalcopyofsomethingstoredelsewhere.Savingalocalcopyoftheresultsofanexpensiveoperation,orcachingit,isassumedtoincreasethespeedofyourapplication,andsolowercosts.

Thepresumptionsintheabovestatementaretwofold.First,itassumesthatcachingwillmakeapplicationsfaster,andnext,itassumesthatcachingwilllowercosts.Thesestatementsare

Page 246: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

sometimestrue,butnotalways.

Whenyousendamessageandsavetheresultintoavariable,you’vecreatedasimplecache.Ifthevalueinyourvariablebecomesobsolete,youmustinvalidatethiscache,eitherbydiscardingit,orbyresendingthemessageandsavingthenewresult.

Cachingiseasy.However,figuringoutthatacacheneedstobeupdatedcanbehard.Thecodetodosoisoftencomplicatedandconfusing.Thisadditionalcodemustbetested,andinevitably,whenitturnsoutthatthetestsareinsufficient,debugged.Theextracodeneededtomanageacachecanbesodifficulttowrite,hardtounderstand,andexpensivetorunthatitoffsetstheoriginalbenefits.

Noticethatthecostsofcachingandmutationareinterrelated.Ifthethingyoucachedoesn’tmutate,yourlocalcopyisgoodforever.Ifyoucachesomethingthatchanges,youmustwriteadditionalcodetorecognizethatyourcopyisstale,andtore-runtheinitialoperationtoupdatethecache.

Ifyou’veeverworkedoncodethathandlescomplicatedcacheinvalidation,itwillcomeasnosurprisethattheworditselfcomesfromtheFrenchcacher,whichmeanstoconcealorhide.Outdatedcachescanbeasourceofopaque,expensive,andfrustratingbugs.Thenetcostofcachingcanbecalculatedonlybycomparingthebenefitofincreasesinspeedtothecostofcreatingandmaintainingthecache.Ifyourequirethisspeedincrease,anycostischeap.Ifyoudon’t,everycostistoomuch.

Mutationandcachingcomplicatecode.Thiscomplicationisoftenacceptedasnecessaryandjustifiedbythebeliefthatitwillimproveperformance.However,theunfortunatetruthisthathumansareverybadatpredicting,inadvance,whetheraprogramwillbefastenoughoverall,and,ifnot,whichpartsofitwillbetooslow.

Complicatingcodeinordertosolveperformanceproblems,inadvanceofactualdataaboutwherethoseproblemsare,raisescosts

Page 247: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

andveryoftenpaysnothinginreturn.Theseguessesarealmostcertaintobewrong,andmerelyservetoharmreadabilityandimpedechange.

Giventhis,thebestprogrammingstrategyistowritethesimplestcodepossibleandmeasureitsperformanceonceyou’redone.Ifthewholeisnotacceptablyfast,profiletheperformance,andspeeduptheslowestparts.Increasingspeedmayrequirecaching,butmanyproblemscanbefixedbysubstitutingmoreefficientcodeinspecific,narrowplaces.Onceyouunderstandpreciselywhat’swrong,itmaybepossibletofixitwithoutcachingatall.

Yourgoalistooptimizeforeaseofunderstandingwhilemaintainingperformancethat’sfastenough.Don’tsacrificereadabilityinadvanceofhavingsolidperformancedata.Thefirstsolutiontoanyproblemshouldavoidcaching,useimmutableobjects,andtreatobjectcreationasfree.Thisresultsinspeedydevelopmentofsimplecode,whichleavesplentyoftimetoidentifyandcorrecttherealperformanceproblems.

Nowthatthissomewhattheoreticaldiscussioniscomplete,it’stimereturntothe Bottles class,andapplyideastoactualcode.

5.5.CreatingBottleNumbersEvenforthosecomfortablewithobjectcreation,thecodein Bottlesconstructsanotablenumberof BottleNumber s.Examinethemethodsbelow,andcountthenumberoftimesanew BottleNumber iscreatedby verse .

Listing5.22:LotsofNewBottleNumbers

1 classBottles2 #...3 defverse(number)4 "#{quantity(number).capitalize}#

Page 248: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

{container(number)}"+5 "ofbeeronthewall,"+6 "#{quantity(number)}#{container(number)}ofbeer.\n"+7 "#{action(number)},"+8 "#{quantity(successor(number))}#{container(successor(number))}"+9 "ofbeeronthewall.\n"10 end11 12 defcontainer(number)13 BottleNumber.new(number).container14 end15 16 defquantity(number)17 BottleNumber.new(number).quantity18 end19 20 defaction(number)21 BottleNumber.new(number).action22 end23 24 defsuccessor(number)25 BottleNumber.new(number).successor26 end27 end

Inthecodeabove,anewinstanceof BottleNumber iscreatedeachtimecontainer , quantity , action ,or successor areinvoked.The versemethodsendsthosemessagesatotalofninetimes.Therefore,overthecourseofthesong,900newinstancesof BottleNumber arecreated

Page 249: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

(nineeachin100verses).

Thismayfeelexcessive.

Thisplethoraofobjectcreationistheresultofthepriorrefactoring.Therecipereplacesthebodyofeachoriginalmethodwithcodethatforwardsthemessagetoanewinstanceofthenewly-extractedclass.

Within Bottles , verse istheonlymethodthatsendsthe container ,quantity , action ,or successor messages,sothepresenceoftheseforwardingmethodsmayseemlikeoverkill.Inthissimpleexample,theyprobablyare.Inmorecomplicatedproblems,however,itwouldnotbesurprisingtoperformanExtractClassrefactoringandfindthattheresultingforwardingmessageswereinvokedmanytimes,frommanyothermethodswithintheoriginalclass.Theseforwardingmethodsexisttoprovideasingleplacefortheoriginalclasstocatchthesemessageswhensenttoitself,andfunnelthemalongtothenewclass.

Thepreviousrefactoringrecipemakesnoattempttominimizethenumberofnewobjects,andcreatesasetofforwardingmethodsthatunabashedlycreatenewinstancesoftheextractedclass.Theupshotis900new BottleNumber s.

Thiscodeworks,andifyoufinditdistressing,it’slikelybecauseitfeelswasteful.Therearealternatives.Ifunconstrainedbytherecipe,thereareanumberofwaystoavoidsuchprofligateobjectcreation,andit’sinstructivetoconsiderthem.

Forexample,thefirstthreephrasesofthefirstverseofthesongsendquantity and container twice,and action once.Thiscreatesfiveinstancesof BottleNumber forthe number 99.Ifthefirstinstanceweretobecached,itcouldbere-usedfourtimes.

Thefourthphraseofverse99sends quantity and container once,andsuccessor twice,creatingfourinstancesof BottleNumber on number 98.Cachingthefirstinstancewouldsavethreefurtherobjectcreations

Page 250: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

withinthisverse.Additionally,thecachedcopycouldalsobeusedwithinthefirstthreephrasesofthefollowingverse,savingfivemoreobjectcreations,foratotalofeightaltogether.Overthecourseofthesong,thiswouldreducethenumberofnew BottleNumber instancesfrom900to100.

Forthosewhofeeltheneedtobeevenmoreparsimonious,it’spossibletocreateasingleinstanceof BottleNumber andreuseit900times.Toaccomplishthis,onewouldcreatea BottleNumber forthenumber 99,andthen,whentheneedforbottlenumber98arose,changethevalueof number from99to98inthatoneexistingobject.Andjustlikethat,you’veaddedcachingplusmutation.

So,youcanavoidcreatingnew BottleNumber sbycachingexistingones,anddecreasethisnumberfurtherifyou’rewillingtomutatethem.Doingeitherofthesethingsmaylowersomecosts,butwillcertainlyraiseothers.Thesethingsarenotfree.

Asathoughtexercise,takeaminutebeforereadingonandimaginealteringtheexistingcodetouseasingleinstanceof BottleNumber .Ifyoufindthatexerciseeasy,tryanother,thistimepretendingthatcontainer , quantity , action ,and successor aresentfrommultiplemethodswithin Bottles .Pauseamoment,ifyoucareto,andgowritethecode.You’llfindthatthechangesneededtodothisaddcomplexity.Thiscomplexitymaycostmorethanthebenefitgainedbyfasterperformance.

Havingdonethatexperiment,returntotheproblemathand.Inthisexample,theforwardingmethodsareinvokedfromonlyonemethodof Bottles .Thismeansthatit’spossibletoreduceobjectcreationbyaddingasimple,automatically-invalidating,low-costcache.Thefollowingexampleshowsa BottleNumber beingcachedonline4:

Listing5.23:CachingaBottleNumber

1 classBottles2 #...

Page 251: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

3 defverse(number)4 bottle_number=BottleNumber.new(number)5 6 "#{quantity(number).capitalize}#{container(number)}"+7 "ofbeeronthewall,"+8 "#{quantity(number)}#{container(number)}ofbeer.\n"+9 "#{action(number)},"+10 "#{quantity(successor(number))}#{container(successor(number))}"+11 "ofbeeronthewall.\n"12 end13 #...14 end

Line4abovecreatesanewinstanceof BottleNumber andcachesitinatemporaryvariable(thisistheTemporaryVariablecodesmell)withinthe verse method.Thiscachereducesobjectcreationwithoutaddingmuchadditionalcomplexity,sothebenefitsoutweighthecosts.

Nowthatthiscachedobjectexists,youcangraduallyaltertheversetemplatetosendmessagestothenewobjectratherthantoself.Thenextexamplebeginsthetransitionwiththesimplestchangepossible.Line4belowasksthisnewobjectforits action :

Listing5.24:AskingtheCachedObjectforItsAction

1 defverse(number)2 bottle_number=BottleNumber.new(number)3 #...4 "#{bottle_number.action},"+5 #...

Page 252: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

6 end

Inthecodeabove, action(number) hasbeenreplacedbybottle_number.action .Thissendsthe action messagedirectlytothenew BottleNumber ,entirelybypassingthelocalimplementation.

Asimilarchangecanbemadeinthefirstandsecondphrasesoftheverse template,asshownbelow:

Listing5.25:UsingtheCachedObjectinPhrases1and2

Inlines4and5ofthecodeabove, quantity and container arenowsentdirectlyto bottle_number .This,again,bypassesthelocalimplementationsinfavorofsendingmessagestothecachedobject.

NowthefirstthreephrasesoftheversetemplatesendmessagestoaBottleNumber ratherthanto self .Onlyphrasefourremainstobeupdated.

5.6.RecognizingLiskovViolationsPhrases1through3oftheversetemplaterefertothesamebottlenumber,andsocansharethecurrently-cached BottleNumber instance.Phrase4,however,usesadifferentbottlenumber.Here’sareminderofthecode:

1 defverse(number)2 bottle_number=BottleNumber.new(number)3 4 "#{bottle_number.quantity.capitalize}#{bottle_number.container}"+5 "ofbeeronthewall,"+6 "#{bottle_number.quantity}#{bottle_number.container}ofbeer.\n"+7 "#{bottle_number.action},"+8 #...9 end

Page 253: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Listing5.26:CurrentPhrase4

Theplanistochangephrase4tosendmessagestoinstancesofBottleNumber ratherthanto self .Previously,whenmakingasimilarchangetophrase1and2,

wasreplacedwith

Online4above,phrase4alsoinvokes quantity ,butitpassesadifferentargumentthandoesphrase1:

The quantity methodaboveispassed successor(number) becausephrase4isaboutthenextnumber.Forexample,inaversewherephrase1isaboutnumber99,thenphrase4isaboutnumber98.

Thegoalhereistosendthe quantity messagetoanobjectthatcananswercorrectly,andtheproblemisthatyoudonotyethaveaccesstosuchanobject.

BottleNumber simplement successor ,anditfeelsasif successor shouldreturntheobjectyouneed.Yourobject-orientedintuitionisbangon[13]ifyouexpectthe successor ofa BottleNumber tobeanotherBottleNumber .Ifthisweretrue,youcouldreplace:

1 defverse(number)2 bottle_number=BottleNumber.new(number)3 #...4 "#{quantity(successor(number))}#{container(successor(number))}"+5 "ofbeeronthewall.\n"6 end

quantity(number)

bottle_number.quantity

quantity(successor(number))

Page 254: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

with:

Unfortunately,asis,thiscodedoesn’twork.Ifyoumaketheabovechangeandrunthetests,you’llsee:

Theproblemisthat successor stillreturnsanumber,whenlogicallyitshouldnowreturnthesucceeding BottleNumber . BottleNumber sknowquantity ,but Fixnum sdonot.

Backwhen successor wasfirstcreated,itwascorrectforittoreturnanumber.ThisabstractionwasidentifiedbytheFlockingRules,whichcalledforcopyingcodefromtheold verse casestatementintothenew successor method.Thecasestatementoriginallyreturnednumbers,thusthe successor methoddidthesame.Atthatpoint,successor wasanumber.

However,the successor methodhasmovedtoanewclass,andtheconceptoncerepresentedbyanumberisnowrepresentedbyaBottleNumber .Thetypeoftheobjecthaschanged,butthe successormethodstillreturnstheoldtype.Youhaveeveryrighttoexpectanymethodnamed successor toreturnanobjectthatimplementsthesameAPIasthereceiver,butalas,this successor methoddoesnot.

ThisinconsistencyisanotherviolationofthegeneralizedLiskovSubstitutionPrinciple.Amethodnamed successor implicitlypromisesthatthethingitreturnswillbehaveliketheobjecttowhichyousentthemessage.Butthis successor methodlies.Itbreaksitspromise,whichforcesthesendertoknowthatthereturnisuntrustworthy,andtotakestepstohandletheviolation.

quantity(successor(number))

bottle_number.successor.quantity

NoMethodError:undefinedmethod`quantity'for99:Fixnum

Page 255: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Asannoyingasthisis,youareinthemiddleofalteringthe versetemplatetosendmessagestoobjects.Thiscurrentrefactoringisalmostcomplete,anditisoftenbettertofinishhorizontalrefactoringsbeforeundertakingverticaltangents.YoucouldveerfromthepathandfixtheLiskovviolation,butinthespiritofcompletingthecurrentthoughtbeforeundertakinganewtask,staythecourse.You’vealreadydeclaredatemporaryvariabletoholdbottlenumber99.Thecurrentproblemcanbesolvedbydeclaringanothervariabletoholdbottlenumber98,andwritingsomeshamelesscode.Online3below,thefollowingexamplebravelydoesjustthat:

Listing5.27:CachingtheSuccessor

1 defverse(number)2 bottle_number=BottleNumber.new(number)3 next_bottle_number=BottleNumber.new(bottle_number.successor)4 5 "#{bottle_number.quantity.capitalize}#{bottle_number.container}"+6 "ofbeeronthewall,"+7 "#{bottle_number.quantity}#{bottle_number.container}ofbeer.\n"+8 "#{bottle_number.action},"+9 "#{next_bottle_number.quantity}#{next_bottle_number.container}"+10 "ofbeeronthewall.\n"11 end

Line3abovecreatesanew BottleNumber onthesuccessoroftheexisting BottleNumber .Ultimately,you’dliketoimprovethislineofcode,butatpresentitsufficestomovethecurrentrefactoringforward.Nowthat next_bottle_number exists,line9canaskitforits

Page 256: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

quantity and container .

Afterthatchange,the verse methodcontainstwodistinctparts.Lines5-10abovedefineatemplatewhichqueriesinstancesof BottleNumberfordetails.Lines2and3createnewinstancesof BottleNumber .Line2seemsreasonable,butline3isawkwardbecausetheLiskovviolationforcesyoutoinvoke successor andthenconvertitsreturnintoaBottleNumber yourself.

Thiscompletesthecachingof BottleNumber sinthe verse method,butthere’sonefinalchangetomake.Nowthat verse talksdirectlytoobjectscachedintemporaryvariables,theforwardingmethodsarenolongerneeded.Deletingthemreducesthecodetothefollowing:

Listing5.28:ObsessionCured

1 classBottles2 3 defsong4 verses(99,0)5 end6 7 defverses(starting,ending)8 starting.downto(ending).collect{|i|verse(i)}.join("\n")9 end10 11 defverse(number)12 bottle_number=BottleNumber.new(number)13 next_bottle_number=BottleNumber.new(bottle_number.successor)14 15 "#{bottle_number.quantity.capitalize}#

Page 257: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

{bottle_number.container}"+16 "ofbeeronthewall,"+17 "#{bottle_number.quantity}#{bottle_number.container}ofbeer.\n"+18 "#{bottle_number.action},"+19 "#{next_bottle_number.quantity}#{next_bottle_number.container}"+20 "ofbeeronthewall.\n"21 end22 end23 24 classBottleNumber25 attr_reader:number26 definitialize(number)27 @number=number28 end29 30 defcontainer31 ifnumber==132 "bottle"33 else34 "bottles"35 end36 end37 38 defquantity39 ifnumber==040 "nomore"41 else42 number.to_s43 end

Page 258: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

44 end45 46 defaction47 ifnumber==048 "Gotothestoreandbuysomemore"49 else50 "Take#{pronoun}downandpassitaround"51 end52 end53 54 defpronoun55 ifnumber==156 "it"57 else58 "one"59 end60 end61 62 defsuccessor63 ifnumber==064 9965 else66 number-167 end68 end69 end

Thiscompletestheextractionofthe BottleNumber class,resolvesthePrimitiveObsessioncodesmell,andheraldstheendofChapter5.

5.7.Summary

Page 259: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Thischaptercontinuedthequesttomake Bottles opentothesix-packrequirement.Itrecognizedthatmanymethodsin Bottlesobsessedon number ,andundertooktheExtractClassrefactoringtocurethisobsession.TherefactoringcreatedanewclassnamedBottleNumber .

Duringthecourseoftherefactoring,conditionalswereexaminedfromanexperiencedOOpractitioners'pointofview.Thischapteralsoexploredtherewardsofmodelingabstractions,thetrade-offsofcaching,theadvantagesofimmutability,andthebenefitsofdeferringperformancetuning.

MostprogrammersarehappierwiththecurrentcodethantheywerewithShamelessGreen,butthisversionisfarfromperfect.ThetotalFlogscore,forexample,hasgoneupagain.FromFlog’spointofview,afterturningoneconditionalintomanybackinChapter4,you’venowcompoundedyoursinsbyintroducinganewclasswhichaddsnonewbehaviorbutincreasesthelengthofthecode.

Also,therearenounittestsfor BottleNumber .ItreliesentirelyonBottle 'stests.

Thecodestillexudesmanysmells(duplication,conditionals,andtemporaryfield,tonameafew).And,finally,itcommitsaLiskovviolationinthe successor method.

Therefactoringsinthisandthepriorchapterwereundertakeninhopesofmakingthecodeopentothesix-packrequirement,butthishasnotyetsucceeded.You’vebeenactinginfaiththatremovingcodesmellswouldeventuallyleadtoopenness.It’spossiblethatyourfaithisbeingtested.

Despitetheimperfectionslistedabove,therearewaysinwhichthecodeisbetter.Therearenowtwoclasses,buteachhasfocusedresponsibilities.Whileit’struethatthewholeisbigger,eachpartiseasytounderstandandreasonabout.

Page 260: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

Thecodeisconsistentandregular,andembodiesanextremelystablelandingpointthatsplendidlyenablesthenextrefactoring.

Withthat,ontoChapter6.

Page 261: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

6.ReplacingConditionalswithObjectsThischapterispending.

Thisultimatechaptertransformsthecodetobeopentothesix-packrequirement.Alongthewayitresolvesafewmorecodesmells,delvesintomonkey-patchingandmetaprogramming,andutilizesinheritanceandcomposition.

Thecodeendsupbeingsosimpleandexpressivethatthesix-packrequirementismetbyaddingjustafewlinesofnewcode.It’sawondertobehold.

Page 262: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

AppendixA:Prerequisites

A.1.RubyThecodeiscompatiblewithanyRubyversionstartingat1.9.CheckwhichversionofRubyyouhavewiththefollowingcommand:

Ifyoudon’thaveRuby1.9orhigherinstalledfollowtheinstructionsonruby-lang.orgtoinstallit.

A.2.MinitestThecodeexamplesincludeaMinitesttestsuite.TocheckwhichversionsofMinitestyouhave,usethe gemlist command.

IfMinitestisnotinstalled,orifnoneoftheversionslistedareinthe5.xseries,installitwith geminstall .

ruby--version

gemlistminitest

geminstallminitest--version"~>5.4"

Page 263: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

AppendixB:InitialExercise

B.1.GettingtheexerciseThecodeinthisbookisonGithub.Thesimplestwaytogettheexerciseistoclonetherepositoryandcheckoutthecorrectbranch,asfollows:

Thedirectorystructurefortheexerciseshouldlooklikethis:

Ifyoudon’thavegitinstalled,createtheexpecteddirectorystructure,andthencopyandpastethecontentsoftherawfileonGitHubinto bottles_test.rb .

Finally,ifyoudon’thaveaninternetconnection,youcanfindthefullcodelistingforthetestsuitebelow,intheTestSuitesection.

B.2.DoingtheexerciseThetestsuiteandexercisearewritteninRuby.Ifyou’reunfamiliarwiththelanguage,ruby-lang.orghasinstallationinstructions,agentletutorial(RubyinTwentyMinutes),and

gitclone--depth=1--branch=exercisehttps://github.com/sandimetz/99bottles.git

├──lib│└──bottles.rb

└──test

└──bottles_test.rb

Page 264: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

furtherreferences.

Torunthetestsuite,invokeRubywiththepathtothetestfile.

Thetestsuitecontainsonefailingtest,andmanyskippedtests.Yourgoalistowritecodethatpassesallofthetests.Followthisprotocol:

runthetestsandexaminethefailure

writeonlyenoughcodetopassthefailingtest

unskipthenexttest(thissimulateswritingityourself)

Repeattheaboveuntilnotestsisskipped,andyou’vewrittencodetopasseachone.

Workonthistaskfor30minutes.Thevastmajorityoffolksdonotfinishin30minutes,butit’suseful,forlatercomparisonpurposes,torecordhowfaryougot.Evenifyoucan’tforceyourselftostopatthatpoint,takeabreakat30minutesandsaveyourcode.

ReturntoPreface.

ReturntoChapter1.

B.3.TestSuite

rubytest/bottles_test.rb

1 classBottlesTest<Minitest::Test2 deftest_the_first_verse3 expected=<<-VERSE4 99bottlesofbeeronthewall,99bottlesofbeer.5 Takeonedownandpassitaround,98bottlesofbeeronthe

Page 265: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

wall.6 VERSE7 assert_equalexpected,::Bottles.new.verse(99)8 end9

10 deftest_another_verse11 skip12 expected=<<-VERSE13 89bottlesofbeeronthewall,89bottlesofbeer.14 Takeonedownandpassitaround,88bottlesofbeeronthewall.15 VERSE16 assert_equalexpected,::Bottles.new.verse(89)17 end18

19 deftest_verse_220 skip21 expected=<<-VERSE22 2bottlesofbeeronthewall,2bottlesofbeer.23 Takeonedownandpassitaround,1bottleofbeeronthewall.24 VERSE25 assert_equalexpected,::Bottles.new.verse(2)26 end27

28 deftest_verse_129 skip30 expected=<<-VERSE31 1bottleofbeeronthewall,1bottleofbeer.32 Takeitdownandpassitaround,nomorebottlesofbeeronthewall.33 VERSE34 assert_equalexpected,::Bottles.new.verse(1)35 end36

Page 266: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

37 deftest_verse_038 skip39 expected=<<-VERSE40 Nomorebottlesofbeeronthewall,nomorebottlesofbeer.41 Gotothestoreandbuysomemore,99bottlesofbeeronthewall.42 VERSE43 assert_equalexpected,::Bottles.new.verse(0)44 end45

46 deftest_a_couple_verses47 skip48 expected=<<-VERSES49 99bottlesofbeeronthewall,99bottlesofbeer.50 Takeonedownandpassitaround,98bottlesofbeeronthewall.51

52 98bottlesofbeeronthewall,98bottlesofbeer.53 Takeonedownandpassitaround,97bottlesofbeeronthewall.54 VERSES55 assert_equalexpected,::Bottles.new.verses(99,98)56 end57

58 deftest_a_few_verses59 skip60 expected=<<-VERSES61 2bottlesofbeeronthewall,2bottlesofbeer.62 Takeonedownandpassitaround,1bottleofbeeronthewall.63

Page 267: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

64 1bottleofbeeronthewall,1bottleofbeer.65 Takeitdownandpassitaround,nomorebottlesofbeeronthewall.66

67 Nomorebottlesofbeeronthewall,nomorebottlesofbeer.68 Gotothestoreandbuysomemore,99bottlesofbeeronthewall.69 VERSES70 assert_equalexpected,::Bottles.new.verses(2,0)71 end72

73 deftest_the_whole_song74 skip75 expected=<<-SONG76 99bottlesofbeeronthewall,99bottlesofbeer.77 Takeonedownandpassitaround,98bottlesofbeeronthewall.78

79 98bottlesofbeeronthewall,98bottlesofbeer.80 Takeonedownandpassitaround,97bottlesofbeeronthewall.81

82 97bottlesofbeeronthewall,97bottlesofbeer.83 Takeonedownandpassitaround,96bottlesofbeeronthewall.84

85 96bottlesofbeeronthewall,96bottlesofbeer.86 Takeonedownandpassitaround,95bottlesofbeeronthewall.87

Page 268: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

88 95bottlesofbeeronthewall,95bottlesofbeer.89 Takeonedownandpassitaround,94bottlesofbeeronthewall.90

91 94bottlesofbeeronthewall,94bottlesofbeer.92 Takeonedownandpassitaround,93bottlesofbeeronthewall.93

94 93bottlesofbeeronthewall,93bottlesofbeer.95 Takeonedownandpassitaround,92bottlesofbeeronthewall.96

97 92bottlesofbeeronthewall,92bottlesofbeer.98 Takeonedownandpassitaround,91bottlesofbeeronthewall.99

100 91bottlesofbeeronthewall,91bottlesofbeer.101 Takeonedownandpassitaround,90bottlesofbeeronthewall.102

103 90bottlesofbeeronthewall,90bottlesofbeer.104 Takeonedownandpassitaround,89bottlesofbeeronthewall.105

106 89bottlesofbeeronthewall,89bottlesofbeer.107 Takeonedownandpassitaround,88bottlesofbeeronthe

Page 269: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

wall.108

109 88bottlesofbeeronthewall,88bottlesofbeer.110 Takeonedownandpassitaround,87bottlesofbeeronthewall.111

112 87bottlesofbeeronthewall,87bottlesofbeer.113 Takeonedownandpassitaround,86bottlesofbeeronthewall.114

115 86bottlesofbeeronthewall,86bottlesofbeer.116 Takeonedownandpassitaround,85bottlesofbeeronthewall.117

118 85bottlesofbeeronthewall,85bottlesofbeer.119 Takeonedownandpassitaround,84bottlesofbeeronthewall.120

121 84bottlesofbeeronthewall,84bottlesofbeer.122 Takeonedownandpassitaround,83bottlesofbeeronthewall.123

124 83bottlesofbeeronthewall,83bottlesofbeer.125 Takeonedownandpassitaround,82bottlesofbeeronthewall.126

Page 270: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

127 82bottlesofbeeronthewall,82bottlesofbeer.128 Takeonedownandpassitaround,81bottlesofbeeronthewall.129

130 81bottlesofbeeronthewall,81bottlesofbeer.131 Takeonedownandpassitaround,80bottlesofbeeronthewall.132

133 80bottlesofbeeronthewall,80bottlesofbeer.134 Takeonedownandpassitaround,79bottlesofbeeronthewall.135

136 79bottlesofbeeronthewall,79bottlesofbeer.137 Takeonedownandpassitaround,78bottlesofbeeronthewall.138

139 78bottlesofbeeronthewall,78bottlesofbeer.140 Takeonedownandpassitaround,77bottlesofbeeronthewall.141

142 77bottlesofbeeronthewall,77bottlesofbeer.143 Takeonedownandpassitaround,76bottlesofbeeronthewall.144

145 76bottlesofbeeronthewall,76bottlesofbeer.146 Takeonedownandpassitaround,75bottlesofbeeronthewall.147

Page 271: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

148 75bottlesofbeeronthewall,75bottlesofbeer.149 Takeonedownandpassitaround,74bottlesofbeeronthewall.150

151 74bottlesofbeeronthewall,74bottlesofbeer.152 Takeonedownandpassitaround,73bottlesofbeeronthewall.153

154 73bottlesofbeeronthewall,73bottlesofbeer.155 Takeonedownandpassitaround,72bottlesofbeeronthewall.156

157 72bottlesofbeeronthewall,72bottlesofbeer.158 Takeonedownandpassitaround,71bottlesofbeeronthewall.159

160 71bottlesofbeeronthewall,71bottlesofbeer.161 Takeonedownandpassitaround,70bottlesofbeeronthewall.162

163 70bottlesofbeeronthewall,70bottlesofbeer.164 Takeonedownandpassitaround,69bottlesofbeeronthewall.165

166 69bottlesofbeeronthewall,69bottlesofbeer.167 Takeonedownandpassitaround,68bottlesofbeeronthe

Page 272: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

wall.168

169 68bottlesofbeeronthewall,68bottlesofbeer.170 Takeonedownandpassitaround,67bottlesofbeeronthewall.171

172 67bottlesofbeeronthewall,67bottlesofbeer.173 Takeonedownandpassitaround,66bottlesofbeeronthewall.174

175 66bottlesofbeeronthewall,66bottlesofbeer.176 Takeonedownandpassitaround,65bottlesofbeeronthewall.177

178 65bottlesofbeeronthewall,65bottlesofbeer.179 Takeonedownandpassitaround,64bottlesofbeeronthewall.180

181 64bottlesofbeeronthewall,64bottlesofbeer.182 Takeonedownandpassitaround,63bottlesofbeeronthewall.183

184 63bottlesofbeeronthewall,63bottlesofbeer.185 Takeonedownandpassitaround,62bottlesofbeeronthewall.186

Page 273: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

187 62bottlesofbeeronthewall,62bottlesofbeer.188 Takeonedownandpassitaround,61bottlesofbeeronthewall.189

190 61bottlesofbeeronthewall,61bottlesofbeer.191 Takeonedownandpassitaround,60bottlesofbeeronthewall.192

193 60bottlesofbeeronthewall,60bottlesofbeer.194 Takeonedownandpassitaround,59bottlesofbeeronthewall.195

196 59bottlesofbeeronthewall,59bottlesofbeer.197 Takeonedownandpassitaround,58bottlesofbeeronthewall.198

199 58bottlesofbeeronthewall,58bottlesofbeer.200 Takeonedownandpassitaround,57bottlesofbeeronthewall.201

202 57bottlesofbeeronthewall,57bottlesofbeer.203 Takeonedownandpassitaround,56bottlesofbeeronthewall.204

205 56bottlesofbeeronthewall,56bottlesofbeer.206 Takeonedownandpassitaround,55bottlesofbeeronthewall.207

Page 274: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

208 55bottlesofbeeronthewall,55bottlesofbeer.209 Takeonedownandpassitaround,54bottlesofbeeronthewall.210

211 54bottlesofbeeronthewall,54bottlesofbeer.212 Takeonedownandpassitaround,53bottlesofbeeronthewall.213

214 53bottlesofbeeronthewall,53bottlesofbeer.215 Takeonedownandpassitaround,52bottlesofbeeronthewall.216

217 52bottlesofbeeronthewall,52bottlesofbeer.218 Takeonedownandpassitaround,51bottlesofbeeronthewall.219

220 51bottlesofbeeronthewall,51bottlesofbeer.221 Takeonedownandpassitaround,50bottlesofbeeronthewall.222

223 50bottlesofbeeronthewall,50bottlesofbeer.224 Takeonedownandpassitaround,49bottlesofbeeronthewall.225

226 49bottlesofbeeronthewall,49bottlesofbeer.227 Takeonedownandpassitaround,48bottlesofbeeronthe

Page 275: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

wall.228

229 48bottlesofbeeronthewall,48bottlesofbeer.230 Takeonedownandpassitaround,47bottlesofbeeronthewall.231

232 47bottlesofbeeronthewall,47bottlesofbeer.233 Takeonedownandpassitaround,46bottlesofbeeronthewall.234

235 46bottlesofbeeronthewall,46bottlesofbeer.236 Takeonedownandpassitaround,45bottlesofbeeronthewall.237

238 45bottlesofbeeronthewall,45bottlesofbeer.239 Takeonedownandpassitaround,44bottlesofbeeronthewall.240

241 44bottlesofbeeronthewall,44bottlesofbeer.242 Takeonedownandpassitaround,43bottlesofbeeronthewall.243

244 43bottlesofbeeronthewall,43bottlesofbeer.245 Takeonedownandpassitaround,42bottlesofbeeronthewall.246

Page 276: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

247 42bottlesofbeeronthewall,42bottlesofbeer.248 Takeonedownandpassitaround,41bottlesofbeeronthewall.249

250 41bottlesofbeeronthewall,41bottlesofbeer.251 Takeonedownandpassitaround,40bottlesofbeeronthewall.252

253 40bottlesofbeeronthewall,40bottlesofbeer.254 Takeonedownandpassitaround,39bottlesofbeeronthewall.255

256 39bottlesofbeeronthewall,39bottlesofbeer.257 Takeonedownandpassitaround,38bottlesofbeeronthewall.258

259 38bottlesofbeeronthewall,38bottlesofbeer.260 Takeonedownandpassitaround,37bottlesofbeeronthewall.261

262 37bottlesofbeeronthewall,37bottlesofbeer.263 Takeonedownandpassitaround,36bottlesofbeeronthewall.264

265 36bottlesofbeeronthewall,36bottlesofbeer.266 Takeonedownandpassitaround,35bottlesofbeeronthewall.267

Page 277: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

268 35bottlesofbeeronthewall,35bottlesofbeer.269 Takeonedownandpassitaround,34bottlesofbeeronthewall.270

271 34bottlesofbeeronthewall,34bottlesofbeer.272 Takeonedownandpassitaround,33bottlesofbeeronthewall.273

274 33bottlesofbeeronthewall,33bottlesofbeer.275 Takeonedownandpassitaround,32bottlesofbeeronthewall.276

277 32bottlesofbeeronthewall,32bottlesofbeer.278 Takeonedownandpassitaround,31bottlesofbeeronthewall.279

280 31bottlesofbeeronthewall,31bottlesofbeer.281 Takeonedownandpassitaround,30bottlesofbeeronthewall.282

283 30bottlesofbeeronthewall,30bottlesofbeer.284 Takeonedownandpassitaround,29bottlesofbeeronthewall.285

286 29bottlesofbeeronthewall,29bottlesofbeer.287 Takeonedownandpassitaround,28bottlesofbeeronthe

Page 278: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

wall.288

289 28bottlesofbeeronthewall,28bottlesofbeer.290 Takeonedownandpassitaround,27bottlesofbeeronthewall.291

292 27bottlesofbeeronthewall,27bottlesofbeer.293 Takeonedownandpassitaround,26bottlesofbeeronthewall.294

295 26bottlesofbeeronthewall,26bottlesofbeer.296 Takeonedownandpassitaround,25bottlesofbeeronthewall.297

298 25bottlesofbeeronthewall,25bottlesofbeer.299 Takeonedownandpassitaround,24bottlesofbeeronthewall.300

301 24bottlesofbeeronthewall,24bottlesofbeer.302 Takeonedownandpassitaround,23bottlesofbeeronthewall.303

304 23bottlesofbeeronthewall,23bottlesofbeer.305 Takeonedownandpassitaround,22bottlesofbeeronthewall.306

Page 279: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

307 22bottlesofbeeronthewall,22bottlesofbeer.308 Takeonedownandpassitaround,21bottlesofbeeronthewall.309

310 21bottlesofbeeronthewall,21bottlesofbeer.311 Takeonedownandpassitaround,20bottlesofbeeronthewall.312

313 20bottlesofbeeronthewall,20bottlesofbeer.314 Takeonedownandpassitaround,19bottlesofbeeronthewall.315

316 19bottlesofbeeronthewall,19bottlesofbeer.317 Takeonedownandpassitaround,18bottlesofbeeronthewall.318

319 18bottlesofbeeronthewall,18bottlesofbeer.320 Takeonedownandpassitaround,17bottlesofbeeronthewall.321

322 17bottlesofbeeronthewall,17bottlesofbeer.323 Takeonedownandpassitaround,16bottlesofbeeronthewall.324

325 16bottlesofbeeronthewall,16bottlesofbeer.326 Takeonedownandpassitaround,15bottlesofbeeronthewall.327

Page 280: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

328 15bottlesofbeeronthewall,15bottlesofbeer.329 Takeonedownandpassitaround,14bottlesofbeeronthewall.330

331 14bottlesofbeeronthewall,14bottlesofbeer.332 Takeonedownandpassitaround,13bottlesofbeeronthewall.333

334 13bottlesofbeeronthewall,13bottlesofbeer.335 Takeonedownandpassitaround,12bottlesofbeeronthewall.336

337 12bottlesofbeeronthewall,12bottlesofbeer.338 Takeonedownandpassitaround,11bottlesofbeeronthewall.339

340 11bottlesofbeeronthewall,11bottlesofbeer.341 Takeonedownandpassitaround,10bottlesofbeeronthewall.342

343 10bottlesofbeeronthewall,10bottlesofbeer.344 Takeonedownandpassitaround,9bottlesofbeeronthewall.345

346 9bottlesofbeeronthewall,9bottlesofbeer.347 Takeonedownandpassitaround,8bottlesofbeeronthe

Page 281: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

wall.348

349 8bottlesofbeeronthewall,8bottlesofbeer.350 Takeonedownandpassitaround,7bottlesofbeeronthewall.351

352 7bottlesofbeeronthewall,7bottlesofbeer.353 Takeonedownandpassitaround,6bottlesofbeeronthewall.354

355 6bottlesofbeeronthewall,6bottlesofbeer.356 Takeonedownandpassitaround,5bottlesofbeeronthewall.357

358 5bottlesofbeeronthewall,5bottlesofbeer.359 Takeonedownandpassitaround,4bottlesofbeeronthewall.360

361 4bottlesofbeeronthewall,4bottlesofbeer.362 Takeonedownandpassitaround,3bottlesofbeeronthewall.363

364 3bottlesofbeeronthewall,3bottlesofbeer.365 Takeonedownandpassitaround,2bottlesofbeeronthewall.366

Page 282: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

367 2bottlesofbeeronthewall,2bottlesofbeer.368 Takeonedownandpassitaround,1bottleofbeeronthewall.369

370 1bottleofbeeronthewall,1bottleofbeer.371 Takeitdownandpassitaround,nomorebottlesofbeeronthewall.372

373 Nomorebottlesofbeeronthewall,nomorebottlesofbeer.374 Gotothestoreandbuysomemore,99bottlesofbeeronthewall.375 SONG376 assert_equalexpected,::Bottles.new.song377 end378 end

Page 283: 99 Bottles of OOP: A Practical Guide to Object-Oriented Design

AcknowledgementsWe’regratefultoeveryone,andwilltellyouallaboutitverysoon.

1.FromthenovelbyJosephHeller,acatch-22isaparadoxicalsituationfromwhichyoucannotescapebecauseofcontradictoryrules.

2.Forthoseunfamiliarwiththefairytale,thisisareferencetoeverythingownedbytheLittle,Small,WeeBearinGoldilocks(Goldenlocks)andtheThreeBears

3.ThisquotewashistoricallythoughttooriginatewithMarkTwainbutisnowwidelyattributedtoCharlesDudleyWarner.TwainandWarnerwereneighborsandtheformerapparentlyhearditfromthelatter.

4.AquotefromRobertMartin’sTransformationPriorityPremiseblogpost.5.Aredherringissomethingthatmisleadsordistractsfromarelevantor

importantissue.6.Ahairshirt,orcilice,isanundergarmentmadeofanimalhair,worntoinduce

discomfortasasignofrepentanceoratonement.7.SeeKentBeckDon’tCrosstheBeams:AvoidingInterferenceBetweenHorizontal

andVerticalRefactorings8.ThankstoAvdiGrimmforthesuggestionofusingrowsandcolumnsinan

imaginaryspreadsheettohelpfindnamesforunderlyingconcepts.9.ThankstoTomStuartforthesuggestionthat,whenyou’restrugglingtonamea

conceptforwhichyouhaveonlyafewexamples,itcanhelptoimagineotherconcretethingsthatmightalsofallintothesamecategory.

10."You’llneverknowlessthanyouknowrightnow"isaquotefromKentBeck.11.Spidey(orspider)senseisatinglingfeelingatthebaseofMarvelComics

superheroSpider-Man'sskullthatalertshimtodanger.12.Aquotefrom1Corinthians13:12oftheKingJamesVersionoftheChristian

Bible.13.MerriamWebsterdefinesbangonas"exactlycorrectorappropriate"

0.3Lastupdated2016-10-0611:48:16EDT