You are on page 1of 79

FoundationsofProgramming

BuildingBetterSoftware
ByKarlSeguin

WWW.CODEBETTER.COM

FoundationsofProgramming

Thispagewasintentionallyleftblank

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

FoundationsofProgramming

License
TheFoundationsofProgrammingbookislicensedundertheAttributionNonCommercialNoDerivs3.0 Unportedlicense. Youarebasicallyfreetocopy,distributeanddisplaythebook.However,Iaskthatyoualwaysattribute thebooktome,KarlSeguin,donotuseitforcommercialpurposesanddonotalterthebookinanyway. Youcanseethefulltextofthelicenseat: http://creativecommons.org/licenses/byncnd/3.0/legalcode

DownloadableLearningApplication
Readingaboutcodeisagoodwaytolearn,butifyou'reanythinglikeme,nothingbeatsareal application.That'swhyIcreatedtheFoundationsofProgrammingLearningApplicationasimple(yet complete)ASP.NETMVCwebsitewhichleveragesmanyoftheideasandtoolscoveredinthisbook.The applicationisaVisualStudio2008solutionwithhelpfulinlinedocumentationmeanttohelpyoubridge thegapbetweentheoryandpractice.TheLearningApplicationandthisbookareprettyindependent, soyoucanapproachthisjourneyhoweveryouprefer. http://codebetter.com/blogs/karlseguin/archive/2008/07/18/foundationsofprogramminglearning application.aspx

Acknowledgement
Therearecountlessofpeoplewhodeservethanks.Thisbookisbutasmallcontributiontothe incalculabletimedonatedandknowledgesharedbythesoftwarecommunityatlarge.Withoutthe qualitybooks,forums,newsgroupposts,blogs,librariesandopensourceprojects,Iwouldstillbetrying tofigureoutwhymyASPscriptwastimingoutwhileloopingthrougharecordset(stupidMoveNext). It'snosurprisethatthesoftwarecommunityhasleveragedtheopennessoftheinternetmorethanany otherprofessioninordertoadvanceourcause.Whatissurprisingishowthephenomenonappearsto havegoneunnoticed.Good! Ofcourse,thereisonespecialpersonwithoutwhomthiscouldn'thavehappened. ToWendy, Peoplecallmeluckyforbeingwithsomeoneasbeautifulandintelligentasyou.Theydon'tknowthe halfofit.Youarenotonlybeautifulandintelligent,butyouletmespendfartoomuchtimeonmy computer,eitherworking,learning,writingorplaying.You'realsomorethanhappytoreadovermy stufforlistentomeblabonaboutnonsense.Idon'tappreciateyounearlyasmuchasIshould.

FoundationsofProgramming

CopyrightKarlSeguin www.codebetter.com

FoundationsofProgramming

TableofContents
AbouttheAuthor...............................................................................................................................6 ALT.NET.............................................................................................................................................7 Goals..............................................................................................................................................8 Simplicity........................................................................................................................................8 YAGNI.............................................................................................................................................8 LastResponsibleMoment................................................................................................................9 DRY................................................................................................................................................9 ExplicitnessandCohesion................................................................................................................9 Coupling.........................................................................................................................................9 UnitTestsandContinuousIntegration.............................................................................................9 InThisChapter..............................................................................................................................10 DomainDrivenDesign.......................................................................................................................11 Domain/DataDrivenDesign...........................................................................................................11 Users,ClientsandStakeholders.....................................................................................................12 TheDomainObject.......................................................................................................................13 UI.................................................................................................................................................15 TricksandTips..............................................................................................................................16 FactoryPattern.........................................................................................................................16 AccessModifiers.......................................................................................................................17 Interfaces.................................................................................................................................17 InformationHidingandEncapsulation........................................................................................18 InThisChapter..............................................................................................................................19 Persistence.......................................................................................................................................20 TheGap........................................................................................................................................20 DataMapper.................................................................................................................................20 Wehaveaproblem...................................................................................................................23 Limitations................................................................................................................................24 InThisChapter..............................................................................................................................25 DependencyInjection.......................................................................................................................26 SneakPeakatUnitTesting.............................................................................................................27 DontavoidCouplinglikethePlague..............................................................................................28 DependencyInjection....................................................................................................................28 ConstructorInjection.................................................................................................................28 Frameworks..............................................................................................................................30 AFinalImprovement.................................................................................................................32 InThisChapter..............................................................................................................................33 UnitTesting......................................................................................................................................34 WhyWasn'tIUnitTesting3YearsAgo?..........................................................................................35 TheTools......................................................................................................................................36 nUnit........................................................................................................................................36

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

FoundationsofProgramming

WhatisaUnitTest........................................................................................................................38 Mocking.......................................................................................................................................38 MoreonnUnitandRhinoMocks.....................................................................................................41 UIandDatabaseTesting................................................................................................................42 InThisChapter..............................................................................................................................42 ObjectRelationalMappers................................................................................................................43 InfamousInlineSQLvs.StoredProcedureDebate...........................................................................43 NHibernate...................................................................................................................................46 Configuration............................................................................................................................46 Relationships............................................................................................................................49 Querying..................................................................................................................................50 LazyLoading.............................................................................................................................51 Download.....................................................................................................................................52 InThisChapter..............................................................................................................................52 BacktoBasics:Memory....................................................................................................................53 MemoryAllocation........................................................................................................................53 TheStack..................................................................................................................................53 TheHeap..................................................................................................................................54 Pointers....................................................................................................................................55 MemoryModelinPractice.............................................................................................................57 Boxing......................................................................................................................................57 ByRef........................................................................................................................................58 ManagedMemoryLeaks...........................................................................................................61 Fragmentation..........................................................................................................................61 Pinning.....................................................................................................................................62 Settingthingstonull.................................................................................................................63 DeterministicFinalization..............................................................................................................63 InThisChapter..............................................................................................................................63 BacktoBasics:Exceptions.................................................................................................................64 HandlingExceptions......................................................................................................................64 Logging.....................................................................................................................................65 CleaningUp..............................................................................................................................65 ThrowingExceptions.....................................................................................................................67 ThrowingMechanics.................................................................................................................67 WhenToThrowExceptions.......................................................................................................68 CreatingCustomExceptions..........................................................................................................69 InThisChapter..............................................................................................................................72 BacktoBasics:ProxyThisandProxyThat...........................................................................................73 ProxyDomainPattern...................................................................................................................74 Interception..................................................................................................................................75 InThisChapter..............................................................................................................................77 WrappingItUp.................................................................................................................................78 FoundationsofProgramming CopyrightKarlSeguin www.codebetter.com

FoundationsofProgramming

AbouttheAuthor
KarlSeguinisadeveloperatFuelIndustries,aformerMicrosoftMVP,amemberoftheinfluential CodeBetter.comcommunityandaneditorforDotNetSlackers.Hehaswrittennumerousarticlesandis anactivememberofvariousMicrosoftpublicnewsgroups.HelivesinOttawa,OntarioCanada. Hispersonalwebpageis:http://www.openmymind.net/ Hisblog,alongwiththatofanumberofdistinguishedprofessionals,islocatedat: http://www.codebetter.com/

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter1ALT.NET

ALT.NET
IFTHEREISDISSATISFACTIONWITHTHESTATUSQUO,GOOD.IFTHEREISFERMENT , SOMUCHTHEBETTER.IFTHEREISRESTLESSNESS ,IAMPLEASED.T HENLETTHERE BEIDEAS ,ANDHARDTHOUGHT ,ANDHARDWORK.IFMANFEELSSMALL,LETMAN MAKEHIMSELFBIGGER.HUBERTHHUMPHREY

fewyearsagoIwasfortunateenoughtoturnacornerinmyprogrammingcareer.The opportunityforsolidmentoringpresenteditself,andItookfulladvantageofit.Withinthespace ofafewmonths,myprogrammingskillsgrewexponentiallyandoverthelastcoupleyears,Ive continuedtorefinemyart.DoubtlessIstillhavemuchtolearn,andfiveyearsfromnowIlllookbackon thecodeIwritetodayandfeelembarrassed.Iusedtobeconfidentinmyprogrammingskill,butonly onceIacceptedthatIknewverylittle,andlikelyalwayswould,didIstarttoactuallyunderstand. MyFoundationsofProgrammingseriesisacollectionofpostswhichfocusonhelpingenthusiastic programmershelpthemselves.Throughouttheserieswelllookatanumberoftopicstypically discussedinfartoomuchdepthtobeofmuchusetoanyoneexceptthosewhoalreadyknowabout them.Ivealwaysseentwodominantforcesinthe.NETworld,oneheavilydrivenbyMicrosoftasa naturalprogressionofVB6andclassicASP(commonlyreferredtoasTheMSDNWay)andtheother heavilydrivenbycoreobjectorientedpracticesandinfluencedbysomeofthebestJava projects/concepts(knownasALT.NET). Inreality,thetwoarentreallycomparable.TheMSDNWaylooselydefinesaspecificwaytobuilda systemdowntoeachindividualmethodcall(afterall,isnttheAPIreferencedocumentationtheonly reasonanyofusvisitMSDN?)WhereasALT.NETfocusesonmoreabstracttopicswhileprovidingspecific implementation.AsJeremyMillerputsit:the.NetcommunityhasputtoomuchfocusonlearningAPI andframeworkdetailsandnotenoughemphasisondesignandcodingfundamentals.Forarelevantand concreteexample,TheMSDNWayheavilyfavorstheuseofDataSetsandDataTablesforalldatabase communication.ALT.NEThowever,focusesondiscussionsaboutpersistencedesignpatterns,object relationalimpendencemismatchaswellasspecificimplementationssuchasNHibernate(O/RMapping), MonoRail(ActiveRecord)aswellasDataSetsandDataTables.Inotherwords,despitewhatmanypeople think,ALT.NETisntaboutALTernativestoTheMSDNWay,butratherabeliefthatdevelopersshould knowandunderstandalternativesolutionsandapproachesofwhichTheMSDNWayispartof. Ofcourse,itsplainfromtheabovedescriptionthatgoingtheALT.NETrouterequiresafargreater commitmentaswellasawiderbaseofknowledge.Thelearningcurveissteepandhelpfulresourcesare justnowstartingtoemerge(whichisthereasonIdecidedtostartthisseries).However,therewardsare worthwhile;forme,myprofessionalsuccesshasresultedingreaterpersonalhappiness.

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter1ALT.NET

Goals
Althoughsimplistic,everyprogrammingdecisionImakeislargelybasedonmaintainability. Maintainabilityisthecornerstoneofenterprisedevelopment.FrequentCodeBetterreadersarelikely sickofhearingaboutit,butthere'sagoodreasonwetalkaboutmaintainabilitysooftenitsthekeyto beingagreatsoftwaredeveloper.Icanthinkofacouplereasonswhyitssuchanimportantdesign factor.First,bothstudiesandfirsthandexperiencetellusthatsystemsspendaconsiderableamountof time(over50%)inamaintenancestatebeitchanges,bugfixesorsupport.Second,thegrowing adoptionofiterativedevelopmentmeansthatchangesandfeaturesarecontinuouslymadetoexisting code(andevenifyouhaventadoptediterativedevelopmentsuchasAgile,yourclientsarelikelystill askingyoutomakealltypesofchanges.)Inshort,amaintainablesolutionnotonlyreducesyourcost, butalsoincreasesthenumberandqualityoffeaturesyoullbeabletodeliver. Evenifyou'rerelativelynewtoprogramming,there'sagoodchanceyou'vealreadystartedforming opinionsaboutwhatisandisn'tmaintainablefromyourexperienceworkingwithothers,takingover someone'sapplication,oreventryingtofixsomethingyouwroteacouplemonthsago.Oneofthemost importantthingsyoucandoisconsciouslytakenotewhensomethingdoesntseemquiterightand googlearoundforbettersolutions.Forexample,thoseofuswhospentyearsprogramminginclassic ASPknewthatthetightintegrationbetweencodeandHTMLwasn'tideal. Creatingmaintainablecodeisntthemosttrivialthing.Asyougetstarted,youllneedtobeextra diligentuntilthingsstarttobecomemorenatural.Asyoumighthavesuspected,wearentthefirststo putsomethoughtintocreatingmaintainablecode.Tothisend,therearesomesoundideologiesyou oughttofamiliarizeyourselfwith.Aswegothroughthem,taketimetoconsidereachoneindepth, googlethemforextrabackgroundandinsight,and,mostimportantly,trytoseehowtheymightapply toarecentprojectyouworkedon.

Simplicity
Theultimatetoolinmakingyourcodemaintainableistokeepitassimpleaspossible.Acommonbelief isthatinordertobemaintainable,asystemneedstobeengineeredupfronttoaccommodateany possiblechangerequest.Iveseensystemsbuiltonmetarepositories(tableswithaKeycolumnanda Valuecolumn),orcomplexXMLconfigurations,thataremeanttohandleanychangesaclientmight throwattheteam.Notonlydothesesystemstendtohaveserioustechnicallimitation(performance canbeordersofmagnitudeslower),buttheyalmostalwaysfailinwhattheysetouttodo(welllookat thismorewhenwetalkaboutYAGNI).Inmyexperience,thetruepathtoflexibilityistokeepasystem assimpleaspossible,sothatyou,oranotherdeveloper,caneasilyreadyourcode,understandit,and makethenecessarychange.Whybuildaconfigurablerulesenginewhenallyouwanttodoischeckthat ausernameisthecorrectlength?Inalaterchapter,wellseehowTestDrivenDevelopmentcanhelpus achieveahighlevelofsimplicitybymakingsurewefocusonwhatourclientispayingustodo.

YAGNI
YouArentGoingtoNeedItisanExtremeProgrammingbeliefthatyoushouldntbuildsomethingnow becauseyouthinkyouregoingtoneeditinthefuture.Experiencetellsusthatyouprobablywont actuallyneedit,oryoullneedsomethingslightlydifferent.Youcanspendamonthbuildingan FoundationsofProgramming CopyrightKarlSeguin www.codebetter.com

Chapter1ALT.NET

amazinglyflexiblesystemjusttohaveasimple2lineemailfromaclientmakeittotallyuseless.Justthe otherdayIstartedworkingonanopenendedreportingenginetolearnthatIhadmisunderstoodan emailandwhattheclientreallywantedwasasingledailyreportthatendeduptaking15minutesto build.

LastResponsibleMoment
TheideabehindLastResponsibleMomentisthatyoudeferbuildingsomethinguntilyouabsolutelyhave to.Admittedly,insomecases,thelatestresponsiblemomentisveryearlyoninthedevelopmentphase. ThisconceptistightlycoupledwithYAGNI,inthatevenifyoureallyDOneedit,youshouldstillwaitto writeituntilyoucantwaitanylonger.Thisgivesyou,andyourclient,timetomakesureyoureallyDO needitafterall,andhopefullyreducesthenumberofchangesyoullhavetomakewhileandafter development.

DRY
Codeduplicationcancausedevelopersmajorheadaches.Theynotonlymakeithardertochangecode (becauseyouhavetofindalltheplacesthatdothesamething),butalsohavethepotentialtointroduce seriousbugsandmakeitunnecessarilyhardfornewdeveloperstojumponboard.Byfollowingthe DontRepeatYourself(DRY)principalthroughoutthelifetimeofasystem(userstories,design,code, unittestsanddocumentation)youllendupwithcleanerandmoremaintainablecode.Keepinmind thattheconceptgoesbeyondcopyandpasteandaimsateliminatingduplicatefunctionality/behavior inallforms.Objectencapsulationandhighlycohesivecodecanhelpusreduceduplication.

ExplicitnessandCohesion
Itsoundsstraightforward,butitsimportanttomakesurethatyourcodedoesexactlywhatitsaysits goingtodo.Thismeansthatfunctionsandvariablesshouldbenamedappropriatelyandusing standardizedcasingand,whennecessary,adequatedocumentationbeprovided.AProducerclassought todoexactlywhatyou,otherdevelopersintheteamandyourclientthinkitshould.Additionally,your classesandmethodsshouldbehighlycohesivethatis,theyshouldhaveasingularityofpurpose.Ifyou findyourselfwritingaCustomerclasswhichisstartingtomanageorderdata,theresagoodchanceyou needtocreateanOrderclass.Classesresponsibleforamultitudeofdistinctcomponentsquickly becomeunmanageable.Inthenextchapter,welllookatobjectorientedprogrammingscapabilities whenitcomestocreatingexplicitandcohesivecode.

Coupling
Couplingoccurswhentwoclassesdependoneachother.Whenpossible,youwanttoreducecouplingin ordertominimizetheimpactcausedbychanges,andincreaseyourcodestestability.Reducingoreven removingcouplingisactuallyeasierthanmostpeoplethink;therearestrategiesandtoolstohelpyou. Thetrickistobeabletoidentifyundesirablecoupling.Wellcovercoupling,indetail,inalaterchapter.

UnitTestsandContinuousIntegration
UnitTestingandContinuousIntegration(commonlyreferredtoasCI)areyetanothertopicwehaveto deferforalatertime.Therearetwothingsthatareimportantforyoutoknowbeforehand.First,both FoundationsofProgramming CopyrightKarlSeguin www.codebetter.com

Chapter1ALT.NET

10

areparamountinordertoachieveourgoalofhighlymaintainablecode.Unittestsempowerdevelopers withanunbelievableamountofconfidence.Theamountofrefactoringandfeaturechangesyoure able/willingtomakewhenyouhavesafetynetofhundredsorthousandsofautomatedteststhat validateyouhaventbrokenanythingisunbelievable.Secondly,ifyouarentwillingtoadopt,oratleast try,unittesting,yourewastingyourtimereadingthis.Muchofwhatwellcoverissquarelyaimedat improvingthetestabilityofourcode.

InThisChapter
Eventhoughthischapterdidn'thaveanycode,wedidmanagedtocoverquiteafewitems.SinceIwant thistobemorehandsonthantheoretical,welldiveheadfirstintoactualcodefromhereonin. Hopefullywevealreadymanagedtoclearupsomeofthebuzzwordsyouvebeenhearingsomuch aboutlately.ThenextcouplechapterswilllaythefoundationfortherestofourworkbycoveringOOP andpersistenceatahighlevel.Untilthen,Ihopeyouspendsometimeresearchingsomeofthekey wordsIvethrownaround.Sinceyourownexperienceisyourbesttool,thinkaboutyourrecentand currentprojectsandtrytolistthingsthatdidntworkoutwellaswellasthosethatdid.

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter2DomainDrivenDesign

11

DomainDrivenDesign
WHATISDESIGN?IT'SWHEREYOUSTANDWITHAFOOTINTWOWORLDSTHE WORLDOFTECHNOLOGYANDTHEWORLDOFPEOPLEANDHUMANPURPOSESAND YOUTRYTOBRINGTHETWOTOGETHER.MITCHELLKAPOR

t'spredictabletostartoffbytalkingaboutdomaindrivendesignandobjectorientedprogramming. AtfirstIthoughtIcouldavoidthetopicforatleastacoupleposts,butthatwoulddobothyouand meagreatdisservice.Therearealimitednumberofpracticalwaystodesignthecoreofyour system.Averycommonapproachfor.NETdevelopersistouseadatacentricmodel.Theresagood chancethatyourealreadyanexpertwiththisapproachhavingmasterednestedrepeaters,theever usefulItemDataBoundeventandskillfullynavigatingDataRelations.Anothersolution,whichisthe normforJavadevelopersandquicklygainingspeedinthe.NETcommunity,favorsadomaincentric approach.

Domain/DataDrivenDesign
WhatdoImeanbydataanddomaincentricapproaches?Datacentricgenerallymeansthatyoubuild yoursystemaroundyourunderstandingofthedatayoullbeinteractingwith.Thetypicalapproachisto firstmodelyourdatabasebycreatingallthetables,columnsandforeignkeyrelationships,andthen mimickingthisinC#/VB.NET.Thereasonthisissopopularamongst.NETdevelopersisthatMicrosoft spentalotoftimeautomatingthemimickingprocesswithDataAdapters,DataSetsand DataTables.Weallknowthatgivenatablewithdatainit,wecanhaveawebsiteorwindows applicationupandrunninginlessthan5minuteswithjustafewlinesofcode.Thefocusisallaboutthe datawhichinalotofcasesisactuallyagoodidea.Thisapproachissometimescalleddatadriven development. Domaincentricdesignor,asitsmorecommonlycalled,domaindrivendesign(DDD),focusesonthe problemdomainasawholewhichnotonlyincludesthedata,butalsothebehavior.Sowenotonly focusonthefactthatanemployeehasaFirstName,butalsoonthefactthatheorshecangeta Raise.TheProblemDomainisjustafancywayofsayingthebusinessyourebuildingasystemfor.The toolweuseisobjectorientedprogramming(OOP)andjustbecauseyoureusinganobjectoriented languagelikeC#orVB.NETdoesntmeanyourenecessarilydoingOOP. TheabovedescriptionsaresomewhatmisleadingitsomehowimpliesthatifyouwereusingDataSets youwouldntcareabout,orbeabletoprovide,thebehaviorofgivingemployeesaraise.Ofcoursethat isntatallthecaseinfactitdbeprettytrivialtodo.Adatacentricsystemisntvoidofbehaviornor doesittreatthemasanafterthought.DDDissimplybettersuitedathandlingcomplexsystemsina moremaintainablewayforanumberofreasonsallofwhichwellcoverinfollowingchapters.This doesntmakedomaindrivenbetterthandatadrivenitsimplymakesdomaindrivenbetterthandata driveninsomecasesandthereverseisalsotrue.Youveprobablyreadallofthisbefore,andintheend, yousimplyhavetomakealeapoffaithandtentativelyacceptwhatwepreachatleastenoughsothat youcanjudgeforyourself. FoundationsofProgramming CopyrightKarlSeguin www.codebetter.com

Chapter2DomainDrivenDesign

12

(ItmaybecrudeandalittlecontradictorytowhatIsaidinmyintroduction,butthedebatebetweenThe MSDNWayandALT.NETcouldbesummedupasabattlebetweendatadrivenanddomaindriven design.TrueALT.NETersthough,oughttoappreciatethatdatadrivenisindeedtherightchoiceinsome situations.IthinkmuchofthehostilitybetweenthecampsisthatMicrosoftdisproportionatelyfavors datadrivendesigndespitethefactthatitdoesntfitwellwithwhatmost.NETdevelopersaredoing (enterprisedevelopment),and,whenimproperlyused,resultsinlessmaintainablecode.Many programmers,bothinsideandoutsidethe.NETcommunity,areprobablyscratchingtheirheadstrying tounderstandwhyMicrosoftinsistsongoingagainstconventionalwisdomandclumsilyhavingto alwayscatchup.)

Users,ClientsandStakeholders
SomethingwhichItakeveryseriouslyfromAgile developmentisthecloseinteractionthedevelopmentteam haswithclientsandusers.Infact,wheneverpossible,I dontseeitasthedevelopmentteamandtheclient,buta singleentity:theteam.Whetheryourefortunateenoughor nottobeinsuchasituation(sometimeslawyersgetinthe way,sometimesclientsarentavailableforthatmuch commitment,etc.)itsimportanttounderstandwhat everyonebringstothetable.Theclientisthepersonwho paysthebillsandassuch,shouldmakethefinaldecisions aboutfeaturesandpriorities.Usersactuallyusethesystem. Clientsareoftentimesusers,butrarelyaretheytheonly user.Awebsiteforexamplemighthaveanonymoususers, registeredusers,moderatorsandadministrators.Finally, stakeholdersconsistofanyonewithastakeinthesystem. Thesamewebsitemighthaveasisterorparentsite, advertisers,PRordomainexperts.

Inthepast,I'dfrequentlyget pissedoffwithclients.Theywere annoying,didn'tknowwhatthey wantedandalwaysmadethe wrongchoice. OnceIactuallythoughtaboutit though,IrealizedthatIwasn't nearlyassmartasIthoughtIwas. Theclientknewfarmoreabouthis orherbusinessthanIdid.Notonly that,butitwastheirmoneyand myjobwastomakethemgetthe mostoutofit. Astherelationshipturnedmore collaborativeandpositive,notonly didtheendresultgreatlyimprove, butprogrammingbecamefun again.

Clientshaveaveryhardjob.Theyhavetoobjectively prioritizethefeatureseveryonewants,includingtheirown anddealwiththeirfinitebudget.Obviouslytheyllmake wrongchoices,maybebecausetheydontfullyunderstand ausersneed,maybebecauseyoumadeamistakeinthe informationyouprovided,ormaybebecausetheyimproperlygivehigherprioritytotheirownneeds overeveryoneelse's.Asadeveloper,itsyourjobtohelpthemoutasmuchaspossibleanddeliveron theirneeds.

Whetheryourebuildingacommercialsystemornot,theultimatemeasureofitssuccesswilllikelybe howusersfeelaboutit.Sowhileyoureworkingcloselywithyourclient,hopefullybothofyouare workingtowardsyourusersneeds.Ifyouandyourclientareseriousaboutbuildingsystemsforusers,I

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter2DomainDrivenDesign

13

stronglyencourageyoutoreaduponUserStoriesagoodplacetostartisMikeCohnsexcellentUser StoriesApplied1. Finally,andthemainreasonthislittlesectionexists,aredomainexperts.Domainexpertsarethepeople whoknowalltheinsandoutsabouttheworldyoursystemwilllivein.Iwasrecentlypartofaverylarge developmentprojectforafinancialinstituteandtherewereliterallyhundredsofdomainexpertsmost ofwhichbeingeconomistsoraccountants.Thesearepeoplewhoareasenthusiasticaboutwhattheydo asyouareaboutprogramming.Anyonecanbeadomainexpertaclients,auser,astakeholderand, eventually,evenyou.Yourrelianceondomainexpertsgrowswiththecomplexityofasystem.

TheDomainObject
AsIsaidearlier,objectorientedprogrammingisthetoolwellusetomakeourdomaincentricdesign cometolife.Specifically,wellrelyonthepowerofclassesandencapsulation.Inthischapterwellfocus onthebasicsofclassesandsometrickstogetstartedmanydeveloperswillalreadyknoweverything coveredhere.Wewontcoverpersistence(talkingtothedatabase)justyet.Ifyourenewtothiskindof design,youmightfindyourselfconstantlywonderingaboutthedatabaseanddataaccesscode.Trynot toworryaboutittoomuch.Inthenextchapterwellcoverthebasicsofpersistence,andinfollowing chapters,welllookatpersistenceinevengreaterdepth. Theideabehinddomaindrivendesignistobuildyoursysteminamannerthatsreflectiveoftheactual problemdomainyouaretryingtosolve.Thisiswheredomainexpertscomeintoplaytheyllhelpyou understandhowthesystemcurrentlyworks(evenifitsamanualpaperprocess)andhowitoughtto work.Atfirstyoullbeoverwhelmedbytheirknowledgetheylltalkaboutthingsyouveneverheard aboutandbesurprisedbyyourdumbfoundedlook.Theyllusesomanyacronymsandspecialwords thatllyoullbegintoquestionwhetherornotyoureuptothetask.Ultimately,thisisthetruepurpose ofanenterprisedevelopertounderstandtheproblemdomain.Youalreadyknowhowtoprogram,but doyouknowhowtoprogramthespecificinventorysystemyourebeingaskedtodo?Someonehasto learnsomeoneelsesworld,andifdomainexpertslearntoprogram,werealloutofjobs. Anyonewhosgonethroughtheaboveknowsthatlearninganewbusinessisthemostcomplicatedpart ofanyprogrammingjob.Forthatreason,therearerealbenefitstomakingourcoderesemble,asmuch aspossible,thedomain.EssentiallywhatImtalkingaboutiscommunication.Ifyourusersaretalking aboutStrategicOutcomes,whichamonthagomeantnothingtoyou,andyourcodetalksabout StrategicOutcomesthensomeoftheambiguityandmuchofthepotentialmisinterpretationis cleanedup.Manypeople,myselfincluded,believethatagoodplacetostartiswithkeynounwords thatyourbusinessexpertsandusersuse.Ifyouwerebuildingasystemforacardealershipandyou talkedtoasalesman(whoislikelybothauserandadomainexpert),hellundoubtedlytalkabout Clients,Cars,Models,PackagesandUpgrades,Paymentsandsoon.Asthesearethecoreofhis business,itslogicalthattheybethecoreofyoursystem.Beyondnounwordsistheconvergenceonthe languageofthebusinesswhichhascometobeknownastheubiquitouslanguage(ubiquitousmeans

AmazonLink:http://www.amazon.com/UserStoriesAppliedDevelopmentAddisonWesley/dp/0321205685

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter2DomainDrivenDesign

14

presenteverywhere).Theideabeingthatasinglesharedlanguagebetweenusersandsystemiseasier tomaintainandlesslikelytobemisinterpreted. Exactlyhowyoustartisreallyuptoyou.Doingdomaindrivendesigndoesntnecessarilymeanyouhave tostartwithmodelingthedomain(althoughitsagoodidea!),butratheritmeansthatyoushouldfocus onthedomainandletitdriveyourdecisions.Atfirstyoumayverywellstartwithyourdatamodel, whenweexploretestdrivendevelopmentwelltakeadifferentapproachtobuildingasystemthatfits verywellwithDDD.Fornowthough,letsassumewevespokentoourclientandafewsalespeople, weverealizedthatamajorpainpointiskeepingtrackoftheinterdependencybetweenupgrade options.Thefirstthingwelldoiscreatefourclasses:


publicclassCar{} publicclassModel{} publicclassPackage{} publicclassUpgrade{}

Nextwelladdabitofcodebasedonsomeprettysafeassumptions:
publicclassCar { privateModel_model privateList<Upgrade>_upgrades publicvoidAdd(Upgradeupgrade){//todo} } publicclassModel { privateint_id privateint_year privatestring_name publicReadOnlyCollection<Upgrade>GetAvailableUpgrades() { returnnull//todo } } publicclassUpgrade { privateint_id privatestring_name publicReadOnlyCollection<Upgrade>RequiredUpgrades { get{returnnull//todo} } }

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter2DomainDrivenDesign

15

Thingsarequitesimple.Weveaddedsomeprettytraditionalfields(id,name),somereferences(both CarsandModelshaveUpgrades),andanAddfunctiontotheCarclass.Nowwecanmakeslight modificationsandstartwritingabitofactualbehavior.


publicclassCar { privateModel_model //todowheretoinitializethis? privateList<Upgrade>_upgrades publicvoidAdd(Upgradeupgrade) { _upgrades.Add(upgrade) } publicReadOnlyCollection<Upgrade>MissingUpgradeDependencies() { List<Upgrade>missingUpgrades=newList<Upgrade>() foreach(Upgradeupgradein_upgrades) { foreach(UpgradedependentUpgradeinupgrade.RequiredUpgrades) { if(!_upgrades.Contains(dependentUpgrade) &&!missingUpgrades.Contains(dependentUpgrade)) { missingUpgrades.Add(dependentUpgrade) } } } returnmissingUpgrades.AsReadOnly() } }

First,weveimplementedtheAddmethod.Nextweveimplementedamethodthatletsusretrieveall missingupgrades.Again,thisisjustafirststep;thenextstepcouldbetotrackwhichupgradesare responsibleforcausingmissingupgrades,i.e.Youmustselect4WheelDrivetogowithyourTraction Control;however,wellstopfornow.Thepurposewasjusttohighlighthowwemightgetstartedand whatthatstartmightlooklike.

UI
YoumighthavenoticedthatwehaventtalkedaboutUIsyet.Thatsbecauseourdomainisindependent ofthepresentationlayeritcanbeusedtopowerawebsite,awindowsapplicationorawindows service.Thelastthingyouwanttodoisintermixyourpresentationanddomainlogic.Doingsowont onlyresultinhardtochangeandhardtotestcode,butitllalsomakeitimpossibletoreuseourlogic acrossmultipleUIs(whichmightnotbeaconcern,butreadabilityandmaintainabilityalwaysis).Sadly though,thatsexactlywhatmanyASP.NETdevelopersdointermixtheirUIanddomainlayer.Ideven sayitscommontoseebehaviorthroughoutASP.NETbuttonclickhandlersandpageloadevents.The ASP.NETpageframeworkismeanttocontroltheASP.NETUInottoimplementbehavior.Theclick eventoftheSavebuttonshouldntvalidatecomplexbusinessrules(orworse,hitthedatabasedirectly),

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter2DomainDrivenDesign

16

ratheritspurposeistomodifytheASP.NETpagebasedontheresultsonthedomainlayermaybeit oughttoredirecttoanotherpage,displaysomeerrormessagesorrequestadditionalinformation. Remember,youwanttowritecohesivecode.YourASP.NETlogicshouldfocusondoingonethingand doingitwellIdoubtanyonewilldisagreethatithastomanagethepage,whichmeansitcantdo domainfunctionality.Also,logicplacedincodebehindwilltypicallyviolatetheDontRepeatYourself principal,simplybecauseofhowdifficultitistoreusethecodeinsideanaspx.csfile. Withthatsaid,youcantwaittoolongtostartworkingonyourUI.Firstofall,wewanttogetclientand userfeedbackasearlyandoftenaspossible.Idoubttheyllbeveryimpressedifwesendthemabunch of.cs/.vbfileswithourclasses.Secondly,makingactualuseofyourdomainlayerisgoingtorevealsome flawsandawkwardness.Forexample,thedisconnectednatureofthewebmightmeanwehavetomake littlechangestoourpureOOworldinordertoachieveabetteruserexperience.Inmyexperience,unit testsaretoonarrowtocatchthesequirks. YoullalsobehappytoknowthatASP.NETandWinFormsdealwithdomaincentriccodejustaswellas withdatacentricclasses.Youcandatabindtoany.NETcollection,usesessionsandcacheslikeyou normallydo,andanythingelseyoureusedtodoing.Infact,outofeverything,theimpactontheUIis probablytheleastsignificant.Ofcourse,itshouldntsurpriseyoutoknowthatALT.NETersalsothink youshouldkeepyourmindopenwhenitcomestoyourpresentationengine.TheASP.NETPage Frameworkisntnecessarilythebesttoolforthejobalotofusconsideritunnecessarilycomplicated andbrittle.Welltalkaboutthismoreinalaterchapter,butifyoureinterestedtofindoutmore,I suggestyoulookatMonoRails(whichisaRailsframeworkfor.NET)ortherecentlyreleasedMVC frameworkbyMicrosoft.ThelastthingIwantisforanyonetogetdiscouragedwiththevastnessof changes,sofornow,letsgetbackontopic.

TricksandTips
Wellfinishoffthischapterbylookingatsomeusefulthingswecandowithclasses.Wellonlycoverthe tipoftheiceberg,buthopefullytheinformationwillhelpyougetoffontherightfoot. FactoryPattern WhatdowedowhenaClientbuysanewCar?ObviouslyweneedtocreateanewinstanceofCarand specifythemodel.Thetraditionalwaytodothisistouseaconstructorandsimplyinstantiateanew objectwiththenewkeyword.Adifferentapproachistouseafactorytocreatetheinstance:
publicclassCar { privateModel_model privateList<Upgrade>_upgrades privateCar() { _upgrades=newList<Upgrade>() } publicstaticCarCreateCar(Modelmodel) {

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter2DomainDrivenDesign

17

Carcar=newCar() car._model=model returncar } }

Therearetwoadvantagestothisapproach.Firstly,wecanreturnanullobject,whichisimpossibletodo withaconstructorthismayormaynotbeusefulinyourparticularcase.Secondly,iftherearealotof differentwaystocreateanobject,itgivesyouthechancetoprovidemoremeaningfulfunctionnames. ThefirstexamplethatcomestomindiswhenyouwanttocreateaninstanceofaUserclass,youll likelyhaveUser.CreateByCredentials(stringusername,stringpassword) , User.CreateById(intid) andUser.GetUsersByRole(stringrole) .Youcanaccomplishthe samefunctionalitywithconstructoroverloading,butrarelywiththesameclarity.Truthbetold,Ialways haveahardtimedecidingwhichtouse,soitsreallyamatteroftasteandgutfeeling. AccessModifiers Asyoufocusonwritingclassesthatencapsulatethebehaviorofthebusiness,arichAPIisgoingto emergeforyourUItoconsume.ItsagoodideatokeepthisAPIcleanandunderstandable.Thesimplest methodistokeepyourAPIsmallbyhidingallbutthemostnecessarymethods.Somemethodsclearly needtobepublicandothersprivate,butifeveryouarentsure,pickamorerestrictiveaccess modifierandonlychangeitwhennecessary.Imakegooduseoftheinternalmodifieronmanyofmy methodsandproperties.Internalmembersareonlyvisibletoothermemberswithinthesameassembly soifyourephysicallyseparatingyourlayersacrossmultipleassemblies(whichisgenerallyagood idea),youllgreatlyminimizeyourAPI. Interfaces Interfaceswillplayabigpartinhelpinguscreatemaintainablecode.Wellusethemtodecoupleour codeaswellascreatemockclassesforunittesting.Aninterfaceisacontractwhichanyimplementing classesmustadhereto.Letssaythatwewanttoencapsulateallourdatabasecommunicationinsidea classcalledSqlServerDataAccess suchas:
internalclassSqlServerDataAccess { internalList<Upgrade>RetrieveAllUpgrades() { returnnull//todoimplement } } publicvoidASampleMethod() { SqlServerDataAccessda=newSqlServerDataAccess() List<Upgrade>upgrades=da.RetrieveAllUpgrades() }

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter2DomainDrivenDesign

18

YoucanseethatthesamplecodeatthebottomhasadirectreferencetoSqlServerDataAccess as wouldthemanyothermethodsthatneedtocommunicatewiththedatabase.Thishighlycoupledcode isproblematictochangeanddifficulttotest(wecanttestASampleMethodwithouthavingafully functionalRetrieveAllUpgradesmethod).Wecanrelievethistightcouplingbyprogrammingagainst aninterfaceinstead:


internalinterfaceIDataAccess { List<Upgrade>RetrieveAllUpgrades() } internalclassDataAccess { internalstaticIDataAccessCreateInstance() { returnnewSqlServerDataAccess() } } internalclassSqlServerDataAccess:IDataAccess { publicList<Upgrade>RetrieveAllUpgrades() { returnnull//todoimplement } } publicvoidASampleMethod() { IDataAccessda=DataAccess.CreateInstance() List<Upgrade>upgrades=da.RetrieveAllUpgrades() }

Weveintroducedtheinterfacealongwithahelperclasstoreturnaninstanceofthatinterface.Ifwe wanttochangeourimplementation,saytoanOracleDataAccess,wesimplycreatethenewOracle class,makesureitimplementstheinterface,andchangethehelperclasstoreturnitinstead.Rather thanhavingtochangemultiple(possiblyhundreds),wesimplyhavetochangeone. Thisisonlyasimpleexampleofhowwecanuseinterfacestohelpourcause.Wecanbeefupthecode bydynamicallyinstantiatingourclassviaconfigurationdataorintroducingaframeworkspecially tailoredforthejob(whichisexactlywhatweregoingtodo).Welloftenfavorprogrammingagainst interfacesoveractualclasses,soifyouarentfamiliarwiththem,Idsuggestyoudosomeextrareading. InformationHidingandEncapsulation Informationhidingistheprinciplethatdesigndecisionsshouldbehiddenfromothercomponentsof yoursystem.Itsgenerallyagoodideatobeassecretiveaspossiblewhenbuildingclassesand componentssothatchangestoimplementationdontimpactotherclassesandcomponents. EncapsulationisanOOPimplementationofinformationhiding.Essentiallyitmeansthatyourobject's data(thefields)andasmuchastheimplementationshouldnotbeaccessibletootherclasses.Themost

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter2DomainDrivenDesign

19

commonexampleismakingfieldsprivatewithpublicproperties.Evenbetteristoaskyourselfifthe_id fieldevenneedsapublicpropertytobeginwith.

InThisChapter
Thereasonenterprisedevelopmentexistsisthatnosingleofftheshelfproductcansuccessfullysolve theneedsofacomplexsystem.Therearesimplytoomanyoddorintertwinedrequirementsand businessrules.Todate,noparadigmhasbeenbettersuitedtothetaskthanobjectoriented programming.Infact,OOPwasdesignedwiththespecificpurposeoflettingdevelopersmodelreallife things.Itmaystillbedifficulttoseethelongtermvalueofdomaindrivendesign.Sharingacommon languagewithyourclientandusersinadditiontohavinggreatertestabilitymaynotseemnecessary. Hopefullyasyougothroughtheremainingchaptersandexperimentonyourown,youllstartadopting someoftheconceptsandtweakingthemtofityoursandyourclientsneeds.

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter3Persistence

20

Persistence
CODD'SAIMWASTOFREEPROGRAMMERSFROMHAVINGTOKNOWTHEPHYSICAL STRUCTUREOFDATA.O URAIMISTOFREETHEMINADDITIONFROMHAVINGTO KNOWITSLOGICALSTRUCTURE.LAZYSOFTWARE

nthepreviouschapterwemanagedtohaveagooddiscussionaboutDDDwithouttalkingmuch aboutdatabases.IfyoureusedtoprogrammingwithDataSets,youprobablyhavealotof questionsabouthowthisisactuallygoingtowork.DataSetsaregreatinthatalotistakencareof foryou.InthischapterwellstartthediscussionaroundhowtodealwithpersistenceusingDDD.Well manuallywritecodetobridgethegapbetweenourC#objectsandourSQLtables.Inlatersectionswe willlookatmoreadvancedalternatives(twodifferentO/Rmappingapproaches)which,likeDataSets, domuchoftheheavyliftingforus.Thischapterismeanttobringsomeclosuretotheprevious discussionwhileopeningthediscussiononmoreadvancedpersistencepatterns.

TheGap
Asyouknow,yourprogramrunsinmemoryandrequiresaplacetostore(orpersist)information.These days,thesolutionofchoiceisarelationaldatabase.Persistenceisactuallyaprettybigtopicinthe softwaredevelopmentfieldbecause,withoutthehelpofpatternsandtools,itisnttheeasiestthingto successfullypulloff.Withrespecttoobjectorientedprogramming,thechallengehasbeengivenafancy name:theObjectRelationalImpedanceMismatch.Thatprettymuchmeansthatrelationaldatadoesnt mapperfectlytoobjectsandobjectsdontmapperfectlytorelationalstores.Microsoftbasicallytriedto ignorethisproblemandsimplymadearelationalrepresentationwithinobjectorientedcodeaclever approach,butnotwithoutitsflawssuchaspoorperformance,leakyabstractions,poortestability, awkwardness,andpoormaintainability.(Ontheothersideareobjectorienteddatabaseswhich,tothe bestofmyknowledge,haventtakenoffeither.) Ratherthantrytoignoretheproblem,wecan,andshouldfaceitheadon.Weshouldfaceitsothatwe canleveragethebestofbothworldscomplexbusinessrulesimplementedinOOPanddatastorage andretrievalviarelationaldatabases.Ofcourse,thatisprovidingthatwecanbridgethegap.Butwhat gapexactly?WhatisthisImpedanceMismatch?Youreprobablythinkingthatitcantbethathardto pumprelationaldataintoobjectsandbackintotables.Ifyouare,thanyoureabsolutelyright(mostly rightanywaysfornowletsassumethatitsalwaysasimpleprocess).

DataMapper
Forsmallprojectswithonlyahandfulofsmalldomainclassesanddatabasetables,mypreferencehas generallybeentomanuallywritecodethatmapsbetweenthetwoworlds.Letslookatasimple example.ThefirstthingwelldoisexpandonourUpgradeclass(wereonlyfocusingonthedata portionsofourclass(thefields)sincethatswhatgetspersisted):

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter3Persistence

21

publicclassUpgrade { privateint_id privatestring_name privatestring_description privatedecimal_price privateList<Upgrade>_requiredUpgrades publicintId { get{return_id} internalset{_id=value} } publicstringName { get{return_name} set{_name=value} } publicstringDescription { get{return_description} set{_description=value} } publicdecimalPrice { get{return_price} set{_price=value} } publicList<Upgrade>RequiredUpgrades { get{return_requiredUpgrades} } }

Weveaddedthebasicfieldsyoudlikelyexpecttoseeintheclass.Nextwellcreatethetablethat wouldhold,orpersist,theupgradeinformation.
CREATETABLEUpgrades ( IdINTIDENTITY(1,1)NOTNULLPRIMARYKEY, [Name]VARCHAR(64)NOTNULL, DescriptionVARCHAR(512)NOTNULL, PriceMONEYNOTNULL, )

Nosurprisesthere.Nowcomestheinterestingpart(well,relativelyspeaking),wellstarttobuildupour dataaccesslayer,whichsitsbetweenthedomainandrelationalmodels(interfacesleftoutforbrevity)

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter3Persistence

22

internalclassSqlServerDataAccess { privatereadonlystaticstring_connectionString="FROM_CONFIG" internalList<Upgrade>RetrieveAllUpgrades() { //useasprocifyouprefer stringsql="SELECTId,Name,Description,PriceFROMUpgrades" using(SqlCommandcommand=newSqlCommand(sql)) using(SqlDataReaderdataReader=ExecuteReader(command)) { List<Upgrade>upgrades=newList<Upgrade>() while(dataReader.Read()) { upgrades.Add(DataMapper.CreateUpgrade(dataReader)) } returnupgrades } } privateSqlDataReaderExecuteReader(SqlCommandcommand) { SqlConnectionconnection=newSqlConnection(_connectionString) command.Connection=connection connection.Open() returncommand.ExecuteReader(CommandBehavior.CloseConnection) } }

ExecuteReader isahelpermethodtoslightlyreducetheredundantcodewehavetowrite.

RetrieveAllUpgradesismoreinterestingasitselectsalltheupgradesandloadsthemintoalistviathe DataMapper.CreateUpgrade function.CreateUpgrade,shownbelow,isthereusablecodeweuse tomapupgradeinformationstoredinthedatabaseintoourdomain.Itsstraightforwardbecausethe domainmodelanddatamodelaresosimilar.


internalstaticclassDataMapper { internalstaticUpgradeCreateUpgrade(IDataReaderdataReader) { Upgradeupgrade=newUpgrade() upgrade.Id=Convert.ToInt32(dataReader["Id"]) upgrade.Name=Convert.ToString(dataReader["Name"]) upgrade.Description=Convert.ToString(dataReader["Description"]) upgrade.Price=Convert.ToDecimal(dataReader["Price"]) returnupgrade } }

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter3Persistence

23

Ifweneedto,wecanreuseCreateUpgradeasmuchasnecessary.Forexample,wedlikelyneedthe abilitytoretrieveupgradesbyidorpricebothofwhichwouldbenewmethodsinthe SqlServerDataAccess class. Obviously,wecanapplythesamelogicwhenwewanttostoreUpgradeobjectsbackintothestore. Heresonepossiblesolution:


internalstaticSqlParameter[]ConvertUpgradeToParameters(Upgradeupgrade) { SqlParameter[]parameters=newSqlParameter[4] parameters[0]=newSqlParameter("Id",SqlDbType.Int) parameters[0].Value=upgrade.Id parameters[1]=newSqlParameter("Name",SqlDbType.VarChar,64) parameters[1].Value=upgrade.Name parameters[2]=newSqlParameter("Description",SqlDbType.VarChar,512) parameters[2].Value=upgrade.Description parameters[3]=newSqlParameter("Price",SqlDbType.Money) parameters[3].Value=upgrade.Price returnparameters }

Wehaveaproblem Despitethefactthatwevetakenaverysimpleandcommonexample,westillranintothedreaded impedancemismatch.Noticehowourdataaccesslayer(eithertheSqlServerDataAccess or DataMapper)doesnthandlethemuchneededRequiredUpgrades collection.Thatsbecauseoneof thetrickiestthingstohandlearerelationships.Inthedomainworldthesearereferences(oracollection ofreferences)tootherobjects;whereastherelationalworldusesforeignkeys.Thisdifferenceisa constantthorninthesideofdevelopers.Thefixisnttoohard.Firstwelladdamanytomanyjointable whichassociatesanupgradewiththeotherupgradesthatarerequiredforit(couldbe0,1ormore).


CREATETABLEUpgradeDepencies ( UpgradeIdINTNOTNULL, RequiredUpgradeIdINTNOTNULL, )

NextwemodifyRetrieveAllUpgradetoloadinrequiredupgrades:

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter3Persistence

24

internalList<Upgrade>RetrieveAllUpgrades() { stringsql=@"SELECTId,Name,Description,PriceFROMUpgrades SELECTUpgradeId,RequiredUpgradeIdFROMUpgradeDepencies" using(SqlCommandcommand=newSqlCommand(sql)) using(SqlDataReaderdataReader=ExecuteReader(command)) { List<Upgrade>upgrades=newList<Upgrade>() Dictionary<int,Upgrade>localCache=newDictionary<int,Upgrade>() while(dataReader.Read()) { Upgradeupgrade=DataMapper.CreateUpgrade(dataReader) upgrades.Add(upgrade) localCache.Add(upgrade.Id,upgrade) } dataReader.NextResult() while(dataReader.Read()) { intupgradeId=dataReader.GetInt32(0) intrequiredUpgradeId=dataReader.GetInt32(1) Upgradeupgrade Upgraderequired if(!localCache.TryGetValue(upgradeId,outupgrade) ||!localCache.TryGetValue(requiredUpgradeId,outrequired)) { //probablyshouldthrowanexception //sinceourdbisinaweirdstate continue } upgrade.RequiredUpgrades.Add(requiredUpgrade) } returnupgrades } }

Wepulltheextrajointableinformationalongwithourinitialqueryandcreatealocallookupdictionary toquicklyaccessourupgradesbytheirid.Nextweloopthroughthejointable,gettheappropriate upgradesfromthelookupdictionaryandaddthemtothecollections. Itisntthemostelegantsolution,butitworksratherwell.Wemaybeabletorefactorthefunctionabit tomakeitlittlemorereadable,butfornowandforthissimplecase,itlldothejob. Limitations Althoughwereonlydoinganinitiallookatmapping,itsworthittolookatthelimitationsweveplaced onourselves.Onceyougodownthepathofmanuallywritingthiskindofcodeitcanquicklygetoutof hand.Ifwewanttoaddfiltering/sortingmethodsweeitherhavetowritedynamicSQLorhavetowrite alotofmethods.WellendupwritingabunchofRetrieveUpgradeByX methodsthatllbepainfully similarfromonetoanother.

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter3Persistence

25

Oftentimesyoullwanttolazyloadrelationships.Thatis,insteadofloadingalltherequiredupgrades upfront,maybewewanttoloadthemonlywhennecessary.Inthiscaseitisntabigdealsinceitsjust anextra32bitreference.AbetterexamplewouldbetheModelsrelationshiptoUpgrades.Itis relativelyeasytoimplementlazyloads,itsjust,yetagain,alotofrepetitivecode. Themostsignificantissuethoughhastodowithidentity.IfwecallRetrieveAllUpgrades twice,well gettodistinctinstancesofeveryupgrade.Thiscanresultininconsistencies,given:


SqlServerDataAccessda=newSqlServerDataAccess() Upgradeupgrade1a=da.RetrieveAllUpgrades()[0] Upgradeupgrade1b=da.RetrieveAllUpgrades()[0] upgrade1b.Price=2000 upgrade1b.Save()

Thepricechangetothefirstupgradewontbereflectedintheinstancepointedtobyupgrade1a.In somecasesthatwontbeaproblem.However,inmanysituations,youllwantyourdataaccesslayerto tracktheidentityofinstancesitcreatesandenforcesomecontrol(youcanreadmorebygooglingthe IdentifyMappattern). Thereareprobablymorelimitations,butthelastonewelltalkabouthastodowithunitsofwork (again,youcanreadmorebygooglingtheUnitofWorkpattern).Essentiallywhenyoumanuallycode yourdataaccesslayer,youneedtomakesurethatwhenyoupersistanobject,youalsopersist,if necessary,updatedreferencedobjects.Ifyoureworkingontheadminportionofourcarsalessystem, youmightverywellcreateanewModelandaddanewUpgrade.IfyoucallSaveonyourModel,you needtomakesureyourUpgradeisalsosaved.Thesimplestsolutionistocallsaveoftenforeach individualactionbutthisisbothdifficult(relationshipscanbeseverallevelsdeep)andinefficient. Similarlyyoumaychangeonlyafewpropertiesandthenhavetodecidebetweenresavingallfields,or somehowtrackingchangedpropertiesandonlyupdatingthose.Again,forsmallsystems,thisisntmuch ofaproblem.Forlargersystems,itsanearimpossibletasktomanuallydo(besides,ratherthanwasting yourtimebuildingyourownunitofworkimplementation,maybeyoushouldbewritingfunctionality theclientaskedfor).

InThisChapter
Intheend,wewontrelyonmanualmappingitjustisntflexibleenoughandweendupspendingtoo muchtimewritingcodethatsuselesstoourclient.Nevertheless,itsimportanttoseemappinginaction andeventhoughwepickedasimpleexample,westillranintosomeissues.Sincemappinglikethisis straightforward,themostimportantthingisthatyouunderstandthelimitationsthisapproachhas.Try thinkingwhatcanhappeniftwodistinctinstancesofthesamedataarefloatingaroundinyourcode,or justhowquicklyyourdataaccesslayerwillballoonasnewrequirementscomein.Wewontrevisit persistenceforatleastacouplechaptersbutwhenwedoreaddressitwellexaminefullblown solutionsthatpackquiteapunch.

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter4DependencyInjection

26

DependencyInjection
IWOULDSAYTHATMODERNSOFTWAREENGINEERINGISTHEONGOINGREFINEMENT OFTHEEVERINCREASINGDEGREESOFDECOUPLING.Y ET ,WHILETHEHISTORYOF SOFTWARESHOWSTHATCOUPLINGISBAD,ITALSOSUGGESTSTHATCOUPLINGIS UNAVOIDABLE.A NABSOLUTELYDECOUPLEDAPPLICATIONISUSELESSBECAUSEIT ADDSNOVALUE.DEVELOPERSCANONLYADDVALUEBYCOUPLINGTHINGS TOGETHER.T HEVERYACTOFWRITINGCODEISCOUPLINGONETHINGTOANOTHER. T HEREALQUESTIONISHOWTOWISELYCHOOSEWHATTOBECOUPLEDTO.JUVAL LWY

tscommontoheardeveloperspromotelayeringasameanstoprovideextensibility.Themost commonexample,andoneIusedinChapter2whenwelookedatinterfaces,istheabilitytoswitch outyourdataaccesslayerinordertoconnecttoadifferentdatabase.Ifyourprojectsareanything likemine,youknowupfrontwhatdatabaseyouregoingtouseandyouknowyouarentgoingtohave tochangeit.Sure,youcouldbuildthatflexibilityupfrontjustincasebutwhataboutkeepingthings simpleandYouArentGoingToNeedIT(YAGNI)? Iusedtowriteabouttheimportanceofdomainlayersinordertohavereuseacrossmultiple presentationlayers:website,windowsapplicationsandwebservices.Ironically,Iverarelyhadtowrite multiplefrontendsforagivendomainlayer.Istillthinklayeringisimportant,butmyreasoninghas changed.Inowseelayeringasanaturalbyproductofhighlycohesivecodewithatleastsomethought putintocoupling.Thatis,ifyoubuildthingsright,itshouldautomaticallycomeoutlayered. Therealreasonwerespendingawholechapterondecoupling(whichlayeringisahighlevel implementationof)isbecauseitsakeyingredientinwritingtestablecode.ItwasntuntilIstartedunit testingthatIrealizedhowtangledandfragilemycodewas.Iquicklybecamefrustratedbecausemethod XreliedonafunctioninclassYwhichneededadatabaseupandrunning.Inordertoavoidthe headachesIwentthrough,wellfirstcovercouplingandthenlookatunittestinginthenextchapter. (ApointaboutYAGNI.Whilemanydevelopersconsideritahardrule,Iratherthinkofitasageneral guideline.TherearegoodreasonswhyyouwanttoignoreYAGNI,themostobviousisyourown experience.Ifyouknowthatsomethingwillbehardtoimplementlater,itmightbeagoodideatobuild itnow,oratleastputhooksinplace.ThisissomethingIfrequentlydowithcaching,buildingan ICacheProvider andaNullCacheProviderimplementationthatdoesnothing,exceptprovidethe necessaryhooksforarealimplementationlateron.Thatsaid,ofthenumerousguidelinesoutthere, YAGNI,DRYandSustainablePaceareeasilythethreeIconsiderthemostimportant.)

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter4DependencyInjection

27

SneakPeakatUnitTesting
Talkingaboutcouplingwithrespecttounittestingissomethingofachickenandeggproblemwhichto talkaboutfirst.Ithinkitsbesttomoveaheadwithcoupling,providedwecoversomebasicsaboutunit testing.Mostimportantlyisthatunittestsareallabouttheunit.Youarentfocusingonendtoend testingbutratheronindividualbehavior.Theideaisthatifyoutesteachbehaviorofeachmethod thoroughlyandtesttheirinteractionwithoneanother,yourewholesystemissolid.Thisistrickygiven thatthemethodyouwanttounittestmighthaveadependencyonanotherclasswhichcantbeeasily executedwithinthecontextofatest(suchasadatabase,orawebbrowserelement).Forthisreason, unittestingmakesuseofmockclassesorpretendclass. Letslookatanexample,savingacarsstate:
publicclassCar { privateint_id publicvoidSave() { if(!IsValid()) { //todo:comeupwithabetterexception thrownewInvalidOperationException("Thecarmustbeinavalid state") } if(_id==0) { _id=DataAccess.CreateInstance().Save(this) } else { DataAccess.CreateInstance().Update(this) } } privateboolIsValid() { //todo:makesuretheobjectisinavalidstate returntrue } }

ToeffectivelytesttheSavemethod,therearethreethingswemustdo: 1. Makesurethecorrectexceptionisthrownwhenwetrytosaveacarwhichisinaninvalidstate, 2. MakesurethedataaccessSavemethodiscalledwhenitsanewcar,and 3. MakesuretheUpdatemethodiscalledwhenitsanexistingcar. Whatwedontwanttodo(whichisjustasimportantaswhatwedowanttodo),istestthefunctionality ofIsValidorthedataaccessSaveandUpdatefunctions(othertestswilltakecareofthose).Thelast pointisimportantallwewanttodoismakesurethesefunctionsarecalledwiththeproper FoundationsofProgramming CopyrightKarlSeguin www.codebetter.com

Chapter4DependencyInjection

28

parametersandtheirreturnvalue(ifany)isproperlyhandled.Itshardtowrapyourheadaround mockingwithoutaconcreteexample,butmockingframeworkswillletusintercepttheSaveand Updatecalls,ensurethattheproperargumentswerepassed,andforcewhateverreturnvaluewewant. Mockingframeworksarequitefunandeffective....unlessyoucantusethembecauseyourcodeis tightlycoupled.

DontavoidCouplinglikethePlague
IncaseyouforgotfromChapter1,couplingissimplywhatwecallitwhenoneclassrequiresanother classinordertofunction.Itsessentiallyadependency.Allbutthemostbasiclinesofcodeare dependentonotherclasses.Heck,ifyouwritestringsite=CodeBetter ,yourecoupledtothe System.String classifitchanges,yourcodecouldverywellbreak.Ofcoursethefirstthingyou needtoknowisthatinthevastmajorityofcases,suchasthesillystringexample,couplingisntabad thing.Wedontwanttocreateinterfacesandprovidersfor eachandeveryoneofourclasses.ItsokforourCarclassto LikeJuvalisquotedatthestartof holdadirectreferencetotheUpgradeclassatthispoint thischapter,I'mtemptedtothrow itdbeoverkilltointroduceanIUpgradeinterface.What myvoteinfordecouplingasthe isntokisanycouplingtoanexternalcomponent(database, singlegreatestnecessityfor stateserver,cacheserver,webservice),anycodethat modernapplications. requiresextensivesetup(databaseschemas)and,asIlearnt onmylastproject,anycodethatgeneratesrandomoutput Infact,inadditiontotheoftcited (passwordgeneration,keygenerators).Thatmightbea benefitsofunittesting,I'vefound somewhatvaguedescription,butafterthisandthenext thatthemostimmediate chapter,andonceyouplaywithunittestingyourself,youll advantageistohelpdevelopers getafeelforwhatshouldandshouldntbeavoided. learnthepatternsofgoodandbad

coupling. Whohasn'tlookedbackatcode theywrotetwoorthreeyearsago andbeenalittleembarrassed?I cancategoricallysaythatthe singlegreatestreasonmycode stunkwasbecauseofhorrible coupling.I'mreadytobetyourold codehastheexactsamestench!

Sinceitsalwaysagoodideatodecoupleyourdatabasefrom yourdomain,wellusethatastheexamplethroughoutthis chapter.

DependencyInjection

InChapter2wesawhowinterfacescanhelpourcause however,thecodeprovideddidntallowustodynamically provideamockimplementationofIDataAccessforthe DataAccessfactoryclasstoreturn.Inordertoachievethis, wellrelyonapatterncalledDependencyInjection(DI).DIis specificallytailoredforthesituationbecause,asthename implies,itsapatternthatturnsahardcodeddependencyintosomethingthatcanbeinjectedat runtime.WelllookattwoformsofDI,onewhichwemanuallydo,andtheotherwhichleveragesathird partylibrary. ConstructorInjection ThesimplestformofDIisconstructorinjectionthatis,injectingdependenciesviaaclassconstructor. First,letslookatourDataAccessinterfaceagainandcreateafake(ormock)implementation(dont

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter4DependencyInjection

29

worry,youwontactuallyhavetocreatemockimplementationsofeachcomponent,butfornowit keepsthingsobvious):
internalinterfaceIDataAccess { intSave(Carcar) voidUpdate(Carcar) } internalclassMockDataAccess:IDataAccess { privatereadonlyList<Car>_cars=newList<Car>() publicintSave(Carcar) { _cars.Add(car) return_cars.Count } publicvoidUpdate(Carcar) { _cars[_cars.IndexOf(car)]=car } }

Althoughourmocksupgradefunctioncouldprobablybeimproved,itlldofornow.Armedwiththis fakeclass,onlyaminorchangetotheCarclassisrequired:
publicclassCar { privateint_id privateIDataAccess_dataProvider publicCar():this(newSqlServerDataAccess()) { } internalCar(IDataAccessdataProvider) { _dataProvider=dataProvider } publicvoidSave() { if(!IsValid()) { //todo:comeupwithabetterexception thrownewInvalidOperationException("Thecarmustbeinavalid state") } if(_id==0) {

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter4DependencyInjection

30

_id=_dataProvider.Save(this) } else { _dataProvider.Update(this) } } }

Takeagoodlookatthecodeaboveandfollowitthrough.Noticethecleveruseofconstructor overloadingmeansthattheintroductionofDIdoesnthaveanyimpactonexistingcodeifyouchoose nottoinjectaninstanceofIDataAccess,thedefaultimplementationisusedforyou.Ontheflipside, ifwedowanttoinjectaspecificimplementation,suchasaMockDataAccessinstance,wecan:


publicvoidAlmostATest() { Carcar=newCar(newMockDataAccess()) car.Save() if(car.Id!=1) { //somethingwentwrong } }

ThereareminorvariationsavailablewecouldhaveinjectedanIDataAccessdirectlyintheSave methodorcouldsettheprivate_dataAccessfieldviaaninternalpropertywhichyouuseismostlya matteroftaste. Frameworks DoingDImanuallyworksgreatinsimplecases,butcanbecomeunrulyinmorecomplexsituations.A recentprojectIworkedonhadanumberofcorecomponentsthatneededtobeinjectedonefor caching,oneforlogging,oneforadatabaseaccessandanotherforawebservice.Classesgotpolluted withmultipleconstructoroverloadsandtoomuchthoughthadtogointosettingupclassesforunit testing.SinceDIissocriticaltounittesting,andmostunittesterslovetheiropensourcetools,itshould comeasnosurprisethatanumberofframeworksexisttohelpautomateDI.Therestofthischapterwill focusonStructureMap,anopensourceDependencyInjectionframeworkcreatedbyfellowCodeBetter bloggerJeremyMiller.(http://structuremap.sourceforge.net/) BeforeusingStructureMapyoumustconfigureitusinganXMLfile(calledStructureMap.config)orby addingattributestoyourclasses.TheconfigurationessentiallysaysthisistheinterfaceIwantto programagainstandheresthedefaultimplementation.Thesimplestofconfigurationstoget StructureMapupandrunningwouldlooksomethinglike:

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter4DependencyInjection

31

<StructureMap> <DefaultInstance PluginType="CodeBetter.Foundations.IDataAccess,CodeBetter.Foundations" PluggedType="CodeBetter.Foundations.SqlDataAccess,CodeBetter.Foundations"/> </StructureMap>

WhileIdontwanttospendtoomuchtimetalkingaboutconfiguration,itsimportanttonotethatthe XMLfilemustbedeployedinthe/binfolderofyourapplication.YoucanautomatethisinVS.NETby selectingthefiles,goingtothepropertiesandsettingtheCopyToOuputDirectoryattributetoCopy Always.(Thereareavarietyofmoreadvancedconfigurationoptionsavailable.Ifyoureinterestedin learningmore,IsuggesttheStructureMapwebsite). Onceconfigured,wecanundoallthechangeswemadetotheCarclasstoallowconstructorinjection (removethe_dataProviderfield,andtheconstructors).TogetthecorrectIDataAccess implementation,wesimplyneedtoaskStructureMapforit,theSavemethodnowlookslike:


publicclassCar { privateint_id publicvoidSave() { if(!IsValid()) { //todo:comeupwithabetterexception thrownewInvalidOperationException("Thecarmustbeinavalid state") } IDataAccessdataAccess=ObjectFactory.GetInstance<IDataAccess>() if(_id==0) { _id=dataAccess.Save(this) } else { dataAccess.Update(this) } } }

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter4DependencyInjection

32

Touseamockratherthanthedefaultimplementation,wesimplyneedtoinjectthemockinto StructureMap:
publicvoidAlmostATest() { ObjectFactory.InjectStub(typeof(IDataAccess),newMockDataAccess()) Carcar=newCar() car.Save() if(car.Id!=1) { //somethingwentwrong } ObjectFactory.ResetDefaults() }

WeuseInjectStubsothatsubsequentcallstoGetInstancereturnourmock,andmakesureto reseteverythingtonormalviaResetDefaults. DIframeworkssuchasStructureMapareaseasytouseastheyareuseful.Withacouplelinesof configurationandsomeminorchangestoourcode,wevegreatlydecreasedourcouplingwhichhas increasedourtestability.Inthepast,IveintroducedStructureMapintoexistinglargecodebasesina matterofminutestheimpactisminor. AFinalImprovement WiththeintroductionoftheIDataAccessclassaswellastheuseofourDIframework,we'vemanaged toremovemuchofthebadcouplingpresentinoursimpleexample.Wecouldprobablytakeitacouple stepsfurther,eventoapointwhereitmightdomoreharmthangood.Thereishoweveronelast dependencythatI'dliketohideawayourbusinessobjectsareprobablybetteroffnotknowingabout ourspecificDIimplementation.RatherthancallingStructureMap'sObjectFactorydirectly,we'lladd onemorelevelofindirection:
publicstaticclassDataFactory { publicstaticIDataAccessCreateInstance { get { returnObjectFactory.GetInstance<IDataAccess>() } } }

Again,thankstoasimplechange,we'reabletomakemassivechanges(choosingadifferentDI framework)wellintothedevelopmentofourapplicationwithease.

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter4DependencyInjection

33

InThisChapter
Reducingcouplingisoneofthosethingsthatsprettyeasytodoyetyieldsgreatresultstowardsour questforgreatermaintainability.Allthatsrequiredisabitofknowledgeanddisciplineandofcourse, toolsdonthurteither.Itshouldbeobviouswhyyouwanttodecreasethedependencybetweenthe componentsofyourcodeespeciallybetweenthosecomponentsthatareresponsiblefordifferent aspectsofthesystem(UI,DomainandDatabeingtheobviousthree).Inthenextchapterwelllookat unittestingwhichwillreallyleveragethebenefitsofdependencyinjection.Ifyourehavingproblems wrappingyourheadaroundDI,takealookatmymoredetailedarticleonthesubjectlocatedon DotNetSlackers.

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter5UnitTesting

34

UnitTesting
WECHOOSENOTTODOU NITT ESTINGSINCEITGIVESUSHIGHERPROFITNOTTO! (RANDOMCONSULTANCYCOMPANY)

hroughoutthisbookwevetalkedabouttheimportanceoftestabilityandwe'velookedat techniquestomakeiteasiertotestoursystem.Itgoeswithoutsayingthatamajorbenefitof writingtestsforoursystemistheabilitytodeliverabetterproducttoourclient.Althoughthisis trueforunittestsaswell,themainreasonIwriteunittestsisthatnothingcomesclosetoimprovingthe maintainabilityofasystemasmuchasaproperlydevelopedsuiteofunittests.Youlloftenhearunit testingadvocatesspeakofhowmuchconfidenceunittestsgivethemandthatswhatitsreallyall about.OnaprojectImcurrentlyworkingon,werecontinuouslymakingchangesandtweaksto improvethesystem(functionalimprovements,performance,refactoring,younameit).Beingthatitsa fairlylargesystem,weresometimesaskedtomakeachangethatoughttoflatoutscaresus.Isit doable?Willithavesomeweirdsideeffect?Whatbugswillbeintroduced?Withoutunittests,wed likelyrefusetomakethehigherriskchanges.Butweknow,andourclientknows,thatitsthehighrisk changesthathavethemostpotentialforsuccess.Itturnsoutthathaving700+unittestswhichrun withinacoupleminutesletsusripcomponentsapart,reorganizecode,andbuildfeatureswenever thoughtaboutayearago,withoutworryingtoomuchaboutit.Becauseweareconfidentinthe completenessoftheunittests,weknowthatwearentlikelytointroducebugsintoourproduction environmentourchangesmightstillcausebugsbutwellknowaboutthemrightaway. Unittestsarentonlyaboutmitigatinghighriskchanges.Inmyprogramminglife,Ivebeenresponsible formajorbugscausedfromseeminglylowriskchangesaswell.ThepointisthatIcanmakea fundamentalorminorchangetooursystem,rightclickthesolution,selectRunTestsandwithin2 minutesknowwherewestand. Isimplycan'tstresshowimportantunittestsare.Surethey'rehelpfulinfindingbugsandvalidatingthat mycodedoeswhatitshould,butfarmoreimportantistheirseeminglymagicalabilitytouncoverfatal flaws,orpricelessgemsinthedesignofasystem.IgetexcitedwheneverIrunacrossamethodor behaviorwhichismindblowinglydifficulttotest.ItmeansI'velikelyfoundaflawinafundamentalpart ofthesystemwhichlikelywouldhavegoneunnoticeduntilsomeunforeseenchangewasaskedfor. Similarly,wheneverIputtogetheratestinacouplesecondsforsomethingwhichIwasprettysurewas goingtobedifficult,Iknowsomeoneontheteamwrotecodethat'llbereusableinotherprojects.

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter5UnitTesting

35

WhyWasn'tIUnitTesting3YearsAgo?
Forthoseofuswhovediscoveredthejoyofunittesting,itshardtounderstandwhyeveryoneisnt doingit.Forthosewhohaventadoptedit,youprobablywishwedshutupaboutitalready.Formany yearsIdreadblogsandspeaktocolleagueswhowerereallyintounittesting,butdidntpracticemyself. Lookingback,hereswhyittookmeawhiletogetonthebandwagon: 1. Ihadmisconceptionaboutthegoalsofunittesting.AsIvealreadysaid,unittestingdoes improvethequalityofasystem,butitsreallyallaboutmakingiteasiertochange/maintainthe systemlateron.Furthermore,ifyougotothenextlogicalstepandadoptTestDriven Development,unittestingreallybecomesaboutdesign.ToparaphraseScottBellware,TDDisn't abouttestingbecauseyou'renotthinkingasatesterwhendoingTDDyourethinkingasa designer. 2. Likemany,Iusedtothinkdevelopersshouldntwritetests!Idontknowthehistorybehindthis belief,butInowthinkthisisjustanexcuseusedbybadprogrammers.Testingistheprocessof bothfindingbugsinasystemaswellasvalidatingthatitworksasexpected.Maybedevelopers arentgoodatfindingbugsintheirowncode,buttheyarethebestsuitedtomakesureitworks thewaytheyintendeditto(andclientsarebestsuitedtotestthatitworkslikeitshould(if youreinterestedtofindoutmoreaboutthat,IsuggestyouresearchAcceptanceTestingand FitNess).Eventhoughunittestingisntallthatmuchabouttesting,developerswhodont believetheyshouldtesttheirowncodesimplyarentaccountable. 3. Testingisntfun.Sittinginfrontofamonitor,inputtingdataandmakingsureeverythingsok sucks.Butunittestingiscoding,whichmeanstherearealotofchallengesandmetricstogauge yoursuccess.Sometimes,likecoding,itsalittlemundane,butallinallitsnodifferentthanthe otherprogrammingyoudoeveryday. 4. Ittakestime.Advocateswilltellyouthatunittestingdoesnttaketime,itSAVEStime.Thisis trueinthatthetimeyouspendwritingunittestsislikelytobesmallcomparedtothetimeyou saveonchangerequestsandbugfixes.ThatsalittletooIvoryTowerforme.Inallhonesty,unit testingDOEStakealotoftime(especiallywhenyoujuststartout).Youmayverywellnothave enoughtimetounittestoryourclientmightnotfeeltheupfrontcostisjustified.Inthese situationsIsuggestyouidentifythemostcriticalcodeandtestitasthoroughlyaspossible evenacouplehoursspentwritingunittestscanhaveabigimpact. Ultimately,unittestingseemedlikeacomplicatedandmysteriousthingthatwasonlyusedinedge cases.Thebenefitsseemedunattainableandtimelinesdidntseemtoallowforitanyways.Itturnsoutit tookalotofpractice(Ihadahardtimelearningwhattounittestandhowtogoaboutit),butthe benefitswerealmostimmediatelynoticeable.

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter5UnitTesting

36

TheTools
WithStructureMapalreadyinplacefromthelastchapter,weneedonlyadd2frameworksand1toolto ourunittestingframework:nUnit,RhinoMocksandTestDriven.NET. TestDriven.NETisanaddonforVisualStudiothatbasicallyaddsaRunTestoptiontoourcontext (rightclick)menubutwewontspendanytimetalkingaboutit.ThepersonallicenseofTestDriven.NET isonlyvalidforopensourceandtrialusers.Howeverdontworrytoomuchifthelicensingdoesntsuite you,nUnithasitsowntestrunnertool,justnotintegratedinVS.NET.(Resharperuserscanalsouseits builtinfunctionality). nUnitisthetestingframeworkwellactuallyuse.Therearealternatives,suchasmbUnit,butIdont knownearlyasmuchaboutthemasIoughtto. RhinoMocksisthemockingframeworkwelluse.Inthelastpartwemanuallycreatedourmockwhich wasbothratherlimitedandtimeconsuming.RhinoMockswillautomaticallygenerateamockclassfrom aninterfaceandallowustoverifyandcontroltheinteractionwithit. nUnit Thefirstthingtodoistoaddareferencetothenunit.framework.dll andtheRhino.Mocks.dll. Myownpreferenceistoputmyunittestsintotheirownassembly.Forexample,ifmydomainlayerwas locatedinCodeBetter.Foundations ,Idlikelycreateanewassemblycalled CodeBetter.Foundations.Tests .Thisdoesmeanthatwewontbeabletotestprivatemethods (moreonthisshortly).In.NET2.0+wecanusetheInternalsVisibleToAttribute toallowtheTest assemblyaccesstoourinternalmethod(openProperties/AssemblyInfo.cs andadd[assembly: InternalsVisibleTo(CodeBetter.Foundations.Tests)]whichissomethingItypicallydo. TherearetwothingsyouneedtoknowaboutnUnit.First,youconfigureyourtestsviatheuseof attributes.TheTestFixtureAttribute isappliedtotheclassthatcontainsyourtests,setupand teardownmethods.TheSetupAttributeisappliedtothemethodyouwanttohaveexecutedbefore eachtestyouwontalwaysneedthis.Similarly,theTearDownAttributeisappliedtothemethod youwantexecutedaftereachtest.Finally,theTestAttributeisappliedtoyouractualunittests. (Thereareotherattributes,butthese4arethemostimportant).Thisiswhatitmightlooklike:
usingNUnit.Framework [TestFixture] publicclassCarTests { [SetUp] publicvoidSetUp(){//todo} [TearDown] publicvoidTearDown(){//todo} [Test]

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter5UnitTesting

37

publicvoidSaveThrowsExceptionWhenInvalid(){//todo} [Test] publicvoidSaveCallsDataAccessAndSetsId(){//todo} //moretests }

Noticethateachunittesthasaveryexplicitnameitsimportanttostateexactlywhatthetestisgoing todo,andsinceyourtestshouldneverdotoomuch,youllrarelyhaveobscenelylongnames. ThesecondthingtoknowaboutnUnitisthatyouconfirmthatyourtestexecutedasexpectedviathe useoftheAssertclassanditsmanymethods.Iknowthisislame,butifwehadamethodthattooka paramint[]numbers andreturnedthesum,ourunittestwouldlooklike:


[Test] publicvoidMathUtilityReturnsZeroWhenNoParameters() { Assert.AreEqual(0,MathUtility.Add()) } [Test] publicvoidMathUtilityReturnsValueWhenPassedOneValue() { Assert.AreEqual(10,MathUtility.Add(10)) } [Test] publicvoidMathUtilityReturnsValueWhenPassedMultipleValues() { Assert.AreEqual(29,MathUtility.Add(10,2,17)) } [Test] publicvoidMathUtilityWrapsOnOverflow() { Assert.AreEqual(2,MathUtility.Add(int.MaxValue,int.MaxValue)) }

Youwouldntknowitfromtheaboveexample,buttheAssertclasshasmorethanonefunction,such asAssert.IsFalse,Assert.IsTrue,Assert.IsNull,Assert.IsNotNull,Assert.AreSame, Assert.AreNotEqual ,Assert.Greater ,Assert.IsInstanceOfType andsoon.

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter5UnitTesting

38

WhatisaUnitTest
Unittestsaremethodsthattestbehaviorataverygranularlevel.Developersnewtounittestingoften letthescopeoftheirtestsgrow.Mostunittestsfollowthesamepattern:executesomecodefromyour systemandassertthatitworkedasexpected.Thegoalofaunittestistovalidateaspecificbehavior.If weweretowritetestsforourCar'sSavemethod,wewouldn'twriteanallencompassingtest,but ratherwanttowriteatestforeachofthebehavioritcontainsfailingwhentheobjectisinaninvalid state,callingourdataaccesssSavemethodandsettingtheid,andcallingthedataaccesssUpdate method.Itsimportantthatourunittestpinpointafailureasbestpossible. Imsuresomeofyouwillfindthe4testsusedtocovertheMathUtility.Addmethodalittle excessive.Youmaythinkthatall4testscouldbegroupedintothesameoneandinthisminorcaseId saywhateveryouprefer.However,whenIstartedunittesting,Ifellintothebadhabitoflettingthe scopeofmyunittestsgrow.Idhavemytestwhichcreatedanobject,executedsomeofitsmembers andassertedthefunctionality.ButIdalwayssay,wellaslongasImhere,Imightaswellthrowina coupleextraassertstomakesurethesefieldsaresetthewaytheyoughttobe.Thisisverydangerous becauseachangeinyourcodecouldbreaknumerousunrelatedtestsdefinitelyasignthatyouve givenyourteststoolittlefocus. Thisbringsusbacktothetopicabouttestingprivatemethods.Ifyougoogleyoullfindanumberof discussionsonthetopic,butthegeneralconsensusseemstobethatyoushouldnttestprivate methods.Ithinkthemostcompellingreasonnottotestprivatemethodsisthatourgoalisnottotest methodsorlinesofcode,butrathertotestbehavior.Thisissomethingyoumustalwaysremember.If youthoroughlytestyourcodespublicinterface,thenprivatemethodsshouldautomaticallygettested. Anotherargumentagainsttestingprivatemethodsisthatitbreaksencapsulation.Wetalkedaboutthe importanceofinformationhidingalready.Privatemethodscontainimplementationdetailthatwewant tobeabletochangewithoutbreakingcallingcode.Ifwetestprivatemethodsdirectly,implementation changeswilllikelybreakourtests,whichdoesntbodewellforhighermaintainability.

Mocking
Togetstarted,itsagoodideatotestsimplepiecesoffunctionality.Beforelongthough,youllwantto testamethodthathasadependencyonanoutsidecomponentsuchasthedatabase.Forexample, youmightwanttocompleteyourtestcoverageoftheCarclassbytestingtheSavemethod.Sincewe wanttokeepourtestsasgranularaspossible(andaslightaspossibletestsshouldbequicktorunso wecanexecutethemoftenandgetinstantfeedback)wereallydontwanttofigureouthowwellsetup atestdatabasewithfakedataandmakesureitskeptinapredictablestatefromtesttotest.Inkeeping withthisspirit,allwewanttodoismakesurethatSaveinteractspropertywiththeDAL.Lateronwe canunittesttheDALonitsown.IfSaveworksasexpectedandtheDALworksasexpectedandthey interactproperlywitheachother,wehaveagoodbasetomovetomoretraditionaltesting. Inthepreviouschapterwesawthebeginningsoftestingwithmocks.Wewereusingamanuallycreated mockclasswhichhadsomeprettymajorlimitations.Themostsignificantofwhichwasourinabilityto confirmthatcallstoourmockobjectswereoccurringasexpected.That,alongwitheaseofuse,is exactlytheproblemRhinoMockismeanttosolve.UsingRhinoMockscouldntbesimpler,tellitwhat FoundationsofProgramming CopyrightKarlSeguin www.codebetter.com

Chapter5UnitTesting

39

youwanttomock(aninterfaceoraclasspreferablyaninterface),tellitwhatmethod(s)youexpectto becalled,alongwiththeparameters,executethecall,andhaveitverifythatyourexpectationswere met. Beforewecangetstarted,weneedtogiveRhinoMocksaccesstoourinternaltypes.Thisisquickly achievedbyadding[assembly:InternalsVisibleTo("DynamicProxyGenAssembly2")] toour Properties/AssemblyInfo.csfile. NowwecanstartcodingbywritingatesttocovertheupdatepathofourSavemethod:


[TestFixture] publicclassCarTest { [Test] publicvoidSaveCarCallsUpdateWhenAlreadyExistingCar() { MockRepositorymocks=newMockRepository() IDataAccessdataAccess=mocks.CreateMock<IDataAccess>() ObjectFactory.InjectStub(typeof(IDataAccess),dataAccess) Carcar=newCar() dataAccess.Update(car) mocks.ReplayAll() car.Id=32 car.Save() mocks.VerifyAll() ObjectFactory.ResetDefaults() } }

Onceamockobjectiscreated,whichtook1lineofcodetodo,weinjectitintoourdependency injectionframework(StructureMapinthiscase).Whenamockiscreated,itentersrecordmode,which meansanysubsequentoperationsagainstit,suchasthecalltodataAccess.UpdateCar(car) ,is recordedbyRhinoMocks(nothingreallyhappenssincedataAccesissimplyamockobjectwithnoreal implementation).WeexitrecordmodebycallingReplayAll,whichmeanswearenowreadyto executeourrealcodeandhaveitverifiedagainsttherecordedsequence.Whenwethencall VerifyAllafterhavingcalledSaveonourCarobject,RhinoMockswillmakesurethatouractualcall behavedthesameaswhatweexpected.Inotherwords,youcanthinkofeverythingbeforeReplayAll asstatingourexpectations,everythingafteritasouractualtestcodewithVerifyAlldoingthefinal check.

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter5UnitTesting

40

Wecantestallofthisbyforcingourtesttofail(noticetheextradataAccess.Update call):
[Test] publicvoidSaveCarCallsUpdateWhenAlreadyExistingCar() { MockRepositorymocks=newMockRepository() IDataAccessdataAccess=mocks.CreateMock<IDataAccess>() ObjectFactory.InjectStub(typeof(IDataAccess),dataAccess) Carcar=newCar() dataAccess.Update(car) dataAccess.Update(car) mocks.ReplayAll() car.Id=32 car.Save() mocks.VerifyAll() ObjectFactory.ResetDefaults() }

OurtestwillfailwithamessagefromRhinoMockssayingtwocallstoUpdatewereexpected,butonly oneactuallyoccurred. FortheSavebehavior,theinteractionisslightlymorecomplexwehavetomakesurethereturnvalue isproperlyhandledbytheSavemethod.Heresthetest:


[Test] publicvoidSaveCarCallsSaveWhenNew() { MockRepositorymocks=newMockRepository() IDataAccessdataAccess=mocks.CreateMock<IDataAccess>() ObjectFactory.InjectStub(typeof(IDataAccess),dataAccess) Carcar=newCar() Expect.Call(dataAccess.Save(car)).Return(389) mocks.ReplayAll() car.Save() mocks.VerifyAll() Assert.AreEqual(389,car.Id) ObjectFactory.ResetDefaults() }

UsingtheExpect.Callmethodallowsustospecifythereturnvaluewewant.Alsonoticethe Assert.Equalweveaddedwhichisthelaststepinvalidatingtheinteraction.Hopefullythe possibilitiesofhavingcontroloverreturnvalues(aswellasoutput/refvalues)letsyouseehoweasyitis totestforedgecases.

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter5UnitTesting

41

IfwechangedourSavefunctiontothrowanexceptionifthereturnedidwasinvalid,ourtestwould looklike:
[TestFixture] publicclassCarTest { privateMockRepository_mocks privateIDataAccess_dataAccess [SetUp] publicvoidSetUp() { _mocks=newMockRepository() _dataAccess=_mocks.CreateMock<IDataAccess>() ObjectFactory.InjectStub(typeof(IDataAccess),_dataAccess) } [TearDown] publicvoidTearDown() { _mocks.VerifyAll() } [Test,ExpectedException("CodeBetter.Foundations.PersistenceException")] publicvoidSaveCarCallsSaveWhenNew() { Carcar=newCar() Expect.Call(_dataAccess.Save(car)).Return(0) _mocks.ReplayAll() car.Save() } }

Inadditiontoshowinghowyoucantestforanexception(viatheExpectedException attribute), wevealsoextractedtherepetitivecodethatcreates,setsupandverifiesthemockobjectintothe SetUpandTearDownmethods.

MoreonnUnitandRhinoMocks
SofarweveonlylookedatthebasicfeaturesofferedbynUnitandRhinoMocks,buttheresalotmore thatcanactuallybedonewiththem.Forexample,RhinoMockscanbesetuptoignoretheorderof methodcalls,instantiatemultiplemocksbutonlyreplay/verifyspecificones,ormocksomebutnot othermethodsofaclass(apartialmock). CombinedwithautilitylikeNCover,youcanalsogetreportsonyourtestscoverage.Coveragebasically tellsyouwhatpercentageofanassembly/namespace/class/methodwasexecutedbyyourtests.NCover hasavisualcodebrowserthatllhighlightanyunexecutedlinesofcodeinred.Generallyspeaking,I dislikecoverageasameansofmeasuringthecompletenessofunittests.Afterall,justbecauseyouve executedalineofcodedoesnotmeanyouveactuallytestedit.WhatIdolikeNCoverforistohighlight anycodethathasnocoverage.Inotherwords,justbecausealineofcodeormethodhasbeenexecuted

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter5UnitTesting

42

byatest,doesntmeanyouretestisgood.Butifalineofcodeormethodhasntbeenexecuted,then youneedtolookataddingsometests. WevementionedTestDrivenDevelopmentbrieflythroughoutthisbook.Ashasalreadybeen mentioned,TestDrivenDevelopment,orTDD,isaboutdesign,nottesting.TDDmeansthatyouwrite yourtestfirstandthenwritecorrespondingcodetomakeyourtestpass.InTDDwedwriteourSave testbeforehavinganyfunctionalityintheSavemethod.Ofcourse,ourtestwouldfail.Wedthenwrite thespecificbehaviorandtestagain.Thegeneralmantrafordevelopersisredgreenrefactor. Meaningthefirststepistogetafailingunittesting,thentomakeitpass,thentorefactorthecodeas required. Inmyexperience,TDDgoesverywellwithDomainDrivenDesign,becauseitreallyletsusfocusonthe businessrulesofthesystem.Ifourclientsaystrackingdependenciesbetweenupgradeshasbeena majorpainpointforthem,thenwesetoffrightawaywithwritingteststhatlldefinethebehaviorand APIofthatspecificfeature.Irecommendthatyoufamiliarizeyourselfwithunittestingingeneralbefore adoptingTDD.

UIandDatabaseTesting
UnittestingyourASP.NETpagesprobablyisntworththeeffort.TheASP.NETframeworkiscomplicated andsuffersfromverytightcoupling.MoreoftenthannotyoullrequireanactualHTTPContext,which requiresquiteabitofworktosetup.IfyouremakingheavyuseofcustomHttpHandlers,youshould beabletotestthoselikeanyotherclass(dependingonexactlywhatitisyourdoingofcourse). Ontheotherhand,testingyourDataAccessLayerispossibleandIwouldrecommendit.Theremaybe bettermethods,butmyapproachhasbeentomaintainallmyCREATETables/CREATESprocsintext filesalongwithmyproject,createatestdatabaseonthefly,andtousetheSetupandTeardown methodstokeepthedatabaseinaknownstate.Thetopicmightbeworthofafutureblogpost,butfor now,Illleaveituptoyourcreativity.

InThisChapter
UnittestingwasntnearlyasdifficultasIfirstthoughtitwasgoingtobe.Suremyinitialtestswerentthe bestsometimesIwouldwritenearmeaninglesstests(liketestingthataplainoldpropertywas workingasitshould)andsometimestheywerefartoocomplexandwelloutsideofawelldefined scope.Butaftermyfirstproject,Ilearntalotaboutwhatdidanddidntwork.Onethingthat immediatelybecameclearwashowmuchcleanermycodebecame.Iquicklycametorealizethatif somethingwashardtotestandIrewroteittomakeitmoretestable,theentirecodebecamemore readable,betterdecoupledandoveralleasiertoworkwith.ThebestadviceIcangiveistostartsmall, experimentwithavarietyoftechniques,dontbeafraidtofailandlearnfromyourmistakes.Andof course,dontwaituntilyourprojectiscompletetounittestwritethemasyougo!

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter6ObjectRelationalMappers

43

ObjectRelationalMappers
T HEOTHEROPTIONINVOLVEDWRITINGWAYTOOMUCHSQL.CHRISKOCH

nchapter3wetookourfirststabatbridgingthedataandobjectworldbyhandwritingourown dataaccesslayerandmapper.Theapproachturnedouttoberatherlimitedandrequiredquiteabit ofrepetitivecode(althoughitwasusefulindemonstratingthebasics).Addingmoreobjectsand morefunctionalitywouldbloatourDALintoanenormouslyunmaintainableviolationofDRY(dont repeatyourself).InthischapterwelllookatanactualO/RMappingframeworktodoalltheheavylifting forus.Specifically,welllookatthepopularopensourceNHibernateframework. Thesinglegreatestbarrierpreventingpeoplefromadoptingdomaindrivendesignistheissueof persistence.MyownadoptionofO/Rmapperscamewithgreattrepidationanddoubt.Youllessentially beaskedtotradeinyourknowledgeofatriedandtruemethodforsomethingthatseemsalittletoo magical.Aleapoffaithmayberequired. ThefirstthingtocometotermswithisthatO/RmappersgenerateyourSQLforyou.Iknow,itsounds likeitsgoingtobeslow,insecureandinflexible,especiallysinceyouprobablyfiguredthatitllhaveto useinlineSQL.Butifyoucanpushthosefearsoutofyourmindforasecond,youhavetoadmitthatit couldsaveyoualotoftimeandresultinalotlessbugs.Remember,wewanttofocusonbuilding behavior,notworryaboutplumbing(andifitmakesyoufeelanybetter,agoodO/Rmapperwillprovide simplewaysforyoutocircumventtheautomatedcodegenerationandexecuteyourownSQLorstored procedures).

InfamousInlineSQLvs.StoredProcedureDebate
Overtheyears,theresbeensomedebatebetweeninlineSQLandstoredprocedures.Thisdebatehas beenverypoorlyworded,becausewhenpeoplehearinlineSQL,theythinkofbadlywrittencodelike:
stringsql=@"SELECTUserIdFROMUsers WHEREUserName='"+userName+"' ANDPassword='"+password+"'" using(SqlCommandcommand=newSqlCommand(sql)) { return0//todo }

Ofcourse,phrasedthisway,inlineSQLreallydoessuck.However,ifyoustopandthinkaboutitand actuallycompareapplestoapples,thetruthisthatneitherisparticularlybetterthantheother.Let's examinesomecommonpointsofcontention.

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter6ObjectRelationalMappers

44

StoredProceduresareMoreSecure InlineSQLshouldbewrittenusingparameterizedqueriesjustlikeyoudowithstoredprocedures.For example,thecorrectwaytowritetheabovecodeinordertoeliminatethepossibilityofanSQLinjection attackis:


stringsql=@"SELECTUserIdFROMUsers WHEREUserName=@UserNameANDPassword=@Password" using(SqlCommandcommand=newSqlCommand(sql)) { command.Parameters.Add("@UserName",SqlDbType.VarChar).Value=userName command.Parameters.Add("@Password",SqlDbType.VarChar).Value=password return0//todo }

Fromthereon,there'snotmuchdifferenceviewscanbeusedordatabaseroles/userscanbesetup withappropriatepermissions. Storedproceduresprovideanabstractiontotheunderlyingschema WhetheryoureusinginlineSQLorstoredprocedures,whatlittleabstractionyoucanputinaSELECT statementisthesame.Ifanysubstantialchangesaremade,yourstoredproceduresaregoingtobreak andtheresagoodchanceyoullneedtochangethecallingcodetodealwiththeissue.Essentially,it's thesamecode,simplyresidinginadifferentlocation,thereforeitcannotprovidegreaterabstraction. O/RMappersontheotherside,generallyprovidemuchbetterabstractionbybeingconfigurable,and implementingtheirownquerylanguage. IfImakeachange,Idonthavetorecompilethecode Somewhere,somehow,peoplegotitintheirheadthat codecompilationsshouldbeavoidedatallcost(maybe thiscomesfromthedayswhereprojectscouldtakedays tocompile).Ifyouchangeastoredprocedure,youstill havetorerunyourunitandintegrationtestsanddeploy achangetoproduction.Itgenuinelyscaresandpuzzles methatdevelopersconsiderachangetoastored procedureorXMLtrivialcomparedtoasimilarchangein code. StoredProceduresreducenetworktraffic Whocares?Inmostcasesyourdatabaseissittingona GigEconnectionwithyourserversandyouarentpaying forthatbandwidth.Youreliterallytalkingfractionsof nanoseconds.Ontopofthat,awellconfiguredO/R mappercansaveroundtripsviaidentifymap implementations,cachingandlazyloading.

AsI'vesaidbefore,Ithinkit's generallybettertoerrontheside ofsimplicitywheneverpossible. Writingabunchofmindlessstored procedurestoperformevery databaseoperationyouthinkyou mayneedisdefinitelynotwhatI'd callsimple...I'mcertainlynot rulingouttheuseofstored procedures,buttostartwith procs?Thatseemslikeafairly extremecaseofpremature optimizationtome.JeffAtwood, codinghorror.com

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter6ObjectRelationalMappers

45

Storedproceduresarefaster ThisistheexcuseIheldontothelongest.Writeareasonable/commonSQLstatementinlineandthen writethesamethinginastoredprocedureandtimethem.Goahead.Inmostcasesthereslittleorno difference.Insomecases,storedprocedureswillbeslowerbecauseacachedexecutionplanwillnotbe efficientgivenacertainparameter.JeffAtwoodcalledusingstoredproceduresforthesakeofbetter performanceafairlyextremecaseofprematureoptimization.Hesright.Theproperapproachistotake thesimplestpossibleapproach(letatoolgenerateyourSQLforyou),andoptimizespecificqueries when/ifbottlenecksareidentified. Ittookawhile,butafteracoupleyears,Irealizedthatthedebatebetweeninlineandstoredprocedures wasastrivialastheoneaboutC#andVB.NET.Ifitwasjustamatterofoneortheother,thenpick whicheveryoupreferandmoveontoyournextchallenge.Iftherewasnothingmoretosayonthe topic,I'dpickstoredprocedures.However,whenyouaddanO/Rmapperintothemix,yousuddenly gainsignificantadvantages.Youstopparticipatinginstupidflamewars,andsimplysay"Iwantthat!". Specifically,therearethreemajorbenefitstobehadwithO/Rmappers: 1. Youendupwritingalotlesscodewhichobviouslyresultsinamoremaintainablesystem, 2. Yougainatruelevelofabstractionfromtheunderlyingdatasourcebothbecauseyoure queryingtheO/Rmapperforyourdatadirectly(anditconvertsthatintotheappropriateSQL), andbecauseyoureprovidingmappinginformationbetweenyourtableschemasanddomain objects, 3. Yourcodebecomessimplerifyourimpedancemismatchislow,you'llwritefarlessrepetitive code.Ifyourimpedancemismatchishighyouwon'thavetocompromiseyourdatabasedesign andyourdomaindesignyoucanbuildthembothinanoptimizedway,andlettheO/Rmapper managethemismatch. Intheend,thisreallycomesdowntobuildingthesimplestsolutionupfront.Optimizationsoughttobe leftuntilafteryou'veprofiledyourcodeandidentifiedactualbottlenecks.Likemostthings,itmightnot soundthatsimplebecauseofthefairlycomplexlearningtodoupfront,butthatstherealityofour profession.

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter6ObjectRelationalMappers

46

NHibernate
Oftheframeworksandtoolswevelookedatsofar,NHibernateisthemostcomplex.Thiscomplexityis certainlysomethingyoushouldtakeintoaccountwhendecidingonapersistencesolution,butonceyou dofindaprojectthatallowsforsomeR&Dtime,thepayoffwillbewellworthitinfutureprojects.The nicestthingaboutNHibernate,andamajordesigngoaloftheframework,isthatitscompletely transparentyourdomainobjectsarentforcedtoinheritaspecificbaseclassandyoudonthaveto useabunchofdecoratorattributes.Thismakesunittestingyourdomainlayerpossibleifyoureusing adifferentpersistentmechanism,saytypeddatasets,thetightcouplingbetweendomainanddata makesithard/impossibletoproperlyunittest Ataveryhighlevel,youconfigureNHibernatebytellingithowyourdatabase(tablesandcolumns)map toyourdomainobjects,usetheNHibernateAPIandNHibernateQueryLanguagetotalktoyour database,andletitdothelowlevelADO.NETandSQLwork.Thisnotonlyprovidesseparationbetween yourtablestructureanddomainobjects,butalsodecouplesyourcodefromaspecificdatabase implementation. Inpreviouschapterswefocusedonasystemforacar dealershipspecificallyfocusingoncarsandupgrades.In thischapterwellchangeperspectiveslightlyandlookatcar sales(sales,modelsandsalespeople).Thedomainmodelis simpleaSalesPersonhaszeroormoreSaleswhich referenceaspecificModel. AlsoincludedisaVS.NETsolutionthatcontainssamplecode andannotationsyoucanfindalinkattheendofthis chapter.Allyouneedtodotogetitrunningiscreateanew database,executetheprovideSQLscript(ahandfulofcreate tables),andconfiguretheconnectionstring.Thesample, alongwiththerestofthischapter,aremeanttohelpyouget startedwithNHibernateatopictoooftenoverlooked.

Remember,ourgoalistowiden ourknowledgebasebylookingat differentwaystobuildsystemsin ordertoprovideourclientswith greatervalue.Whilewemaybe specificallytalkingabout NHibernate,thegoalisreallyto introducetoconceptofO/R mappers,andtrytocorrectthe blindfaith.NETdevelopershave putintostoredproceduresand ADO.NET.

Finally,you'llfindtheNHibernatereferencemanualtobeof exceptionalquality,bothasahelpfultooltogetstarted,andasareferencetolookupspecifictopics. There'salsoabookbeingpublishedbyManning,NHibernateinAction,that'llbeavailableinJune.Inthe meantime,youcanpurchaseaprereleaseversionofthebookinelectronicformat. Configuration ThesecrettoNHibernatesamazingflexibilityliesinitsconfigurability.Initiallyitcanberatherdaunting tosetitup,butafteracoupeprojectitbecomesrathernatural.ThefirststepistoconfigureNHibernate itself.Thesimplestconfiguration,whichmustbeaddedtoyourapp.configorweb.config,lookslike:

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter6ObjectRelationalMappers

47

<configuration> <configSections> <sectionname="hibernateconfiguration" type="NHibernate.Cfg.ConfigurationSectionHandler,NHibernate"/> </configSections> <hibernateconfigurationxmlns="urn:nhibernateconfiguration2.2"> <sessionfactory> <propertyname="hibernate.dialect"> NHibernate.Dialect.MsSql2005Dialect </property> <propertyname="hibernate.connection.provider"> NHibernate.Connection.DriverConnectionProvider </property> <propertyname="hibernate.connection.connection_string"> Server=SERVERInitialCatalog=DBUserId=USERPassword=PASSWORD </property> <mappingassembly="CodeBetter.Foundations"/> </sessionfactory> </hibernateconfiguration> </configuration>

Ofthefourvalues,dialectisthemostinteresting.ThistellsNHibernatewhatspecificlanguageour databasespeaks.If,inourcode,weaskNHibernatetoreturnapagedresultofCarsandourdialectis settoSQLServer2005,NHibernatewillissueanSQLSELECTutilizingtheROW_NUMBER()ranking function.However,ifthedialectissettoMySQL,NHibernatewillissueaSELECTwithaLIMIT.Inmost cases,youllsetthisonceandforgetaboutit,butitdoesprovidesomeinsightintothecapabilities providebyalayerthatgeneratesallofyourdataaccesscode. Inourconfiguration,wealsotoldNHibernatethatourmappingfileswerelocatedinthe CodeBetter.Foundations assembly.MappingfilesareembeddedXMLfileswhichtellNHibernate howeachclassispersisted.Withthisinformation,NHibernateiscapableofreturningaCarobjectwhen youaskforone,aswellassavingit.Thegeneralconventionistohaveamappingfileperdomainobject, andforthemtobeplacedinsideaMappingsfolder.ThemappingfileforourModelobject,named Model.hbm.xml ,lookslike:
<hibernatemappingxmlns="urn:nhibernatemapping2.2" assembly="CodeBetter.Foundations" namespace="CodeBetter.Foundations"> <classname="Model"table="Models"lazy="true"proxy="Model"> <idname="Id"column="Id"type="int"access="field.lowercaseunderscore"> <generatorclass="native"/> </id> <propertyname="Name"column="Name" type="string"notnull="true"length="64"/> <propertyname="Description"column="Description" type="string"notnull="true"/> <propertyname="Price"column="Price" type="double"notnull="true"/> </class>

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter6ObjectRelationalMappers

48

</hibernatemapping>

(itsimportanttomakesuretheBuildActionforallmappingfilesissettoEmbeddedResources ) ThisfiletellsNHibernatethattheModelclassmapstorowsintheModelstable,andthatthe4 propertiesId,Name,DescriptionandPricemaptotheId,Name,DescriptionandPrice columns.TheextrainformationaroundtheIdpropertyspecifiesthatthevalueisgeneratedbythe database(asopposedtoNHibernateitself(forclusteredsolutionsforexample),orourownalgorithm) andthattheresnosetter,soitshouldbeaccessedbythefieldwiththespecifiednamingconvention (wesuppliedIdasthename,andlowercaseunderscoreasthenamingstrategy,soitlluseafield named_id). Withthemappingfilesetup,wecanstartinteractingwiththedatabase:


privatestaticISessionFactory_sessionFactory publicvoidSample() { //Let'saddanewcarmodel Modelmodel=newModel() model.Name="Hummbee" model.Description="Greathandling,builtinGPStoalwaysfindyour waybackhome,Hummbee2Hummbe(tm)communication" model.Price=50000.00 ISessionsession=_sessionFactory.OpenSession() session.Save(model) //Let'sdiscountthex149model IQueryquery=session.CreateQuery("fromModelmodelwheremodel.Name=?") Modelmodel=query.SetString(0,"X149").UniqueResult<Model>() model.Price=5000 ISessionsession=_sessionFactory.OpenSession() session.Update(model) }

Theaboveexampleshowshoweasyitistopersistnewobjectstothedatabase,retrievethemand updatethemallwithoutanyADO.NETorSQL. Youmaybewonderingwherethe_sessionFactoryobjectcomesfrom,andexactlywhatan ISessionis.The_sessionFactory (whichimplementsISessionFactory )isaglobalthreadsafe objectthatyoudlikelycreateonapplicationstart.Youlltypicallyneedoneperdatabasethatyour applicationisusing(whichmeansyoulltypicallyonlyneedone),anditsjob,likemostfactories,isto createapreconfiguredobject:anISession.TheISessionhasnoADO.NETequivalent,butitdoes maplooselytoadatabaseconnection.However,creatinganISessiondoesntnecessarilyopenupa connection.Instead,ISessionssmartlymanageconnectionsandcommandobjectsforyou.Unlike connectionswhichshouldbeopenedlateandclosedearly,youneedntworryabouthavingISessions stickaroundforawhile(althoughtheyarentthreadsafe).IfyourebuildinganASP.NETapplication,you FoundationsofProgramming CopyrightKarlSeguin www.codebetter.com

Chapter6ObjectRelationalMappers

49

couldsafelyopenanISessiononBeginRequestandcloseitonEndRequest(orbetteryet,lazy loaditincasethespecificrequestdoesntrequireanISession).
ITransaction isanotherpieceofthepuzzlewhichiscreatedbycallingBeginTransaction onan ISession.Itscommonfor.NETdeveloperstoignoretheneedfortransactionswithintheir

applications.Thisisunfortunatebecauseitcanleadtounstableandevenunrecoverablestatesinthe data.AnITransactionisusedtokeeptrackoftheunitofworktrackingwhatschanged,been addedordeleted,figuringoutwhatandhowtocommittothedatabase,andprovidingthecapabilityto rollbackshouldanindividualstepfail. Relationships Inoursystem,itsimportantthatwetracksalesspecificallywithrespecttosalespeople,sothatwecan providesomebasicreports.Weretoldthatasalecanonlyeverbelongtoasinglesalesperson,andthus setupaonetomanyrelationshipthatis,asalespersoncanhavemultiplesales,andasalescanonly belongtoasinglesalesperson.Inourdatabase,thisrelationshipisrepresentedasaSalesPersonId columnintheSalestable(aforeignkey).Inourdomain,theSalesPersonclasshasaSales collectionandtheSalesclasshasaSalesPersonproperty(reference). Bothendsoftherelationshipneedstobesetupintheappropriatemappingfile.OntheSalesend, whichmapsasingleproperty,weuseaglorifiedpropertyelementcalledmanytoone:
... <manytoonename="SalesPerson" class="SalesPerson" column="SalesPersonId" notnull="true"/> ...

Werespecifyingthenameoftheproperty,thetype/class,andtheforeignkeycolumnname.Werealso specifyinganextraconstraint,thatis,whenweaddanewSalesobject,theSalesPersonproperty cantbenull. Theothersideoftherelationship,thecollectionofsalesasalespersonhas,isslightlymorecomplicated namelybecauseNHibernatesterminologyisntstandard.NETlingo.Tosetupacollectionweusea set,list,map,bagorarrayelement.Yourfirstinclinationmightbetouselist,butNHibernate requiresthatyouhaveacolumnthatspecifiestheindex.Inotherwords,theNHibernateteamseesalist asacollectionwheretheindexisimportant,andthusmustbespecified.Whatmost.NETdevelopers thinkofasalist,NHibernatecallsabag.Confusingly,whetheryouusealistorabagelement,your domaintypemustbeanIList(oritsgenericIList<T>equivalent).Thisisbecause.NETdoesnthave anIBagobject.Inshort,foryoureverydaycollection,youusethebagelementandmakeyourproperty typeanIList.

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter6ObjectRelationalMappers

50

Theotherinterestingcollectionoptionistheset.Asetisacollectionthatcannotcontainduplicatesa commonscenarioforenterpriseapplication(althoughitisrarelyexplicitlystated).Oddly,.NETdoesnt haveasetcollection,soNHibernateusestheIesi.Collection.ISetinterface.Therearefour specificimplementations,theListSetwhichisreallyfastforverysmallcollections(10orlessitems), theSortedSetwhichcanbesorted,theHashSetwhichisfastforlargercollectionsandthe HybridSetwhichinitiallyusesaListSetandautomaticallyswitchesitselftoaHashSetasyour collectiongrows. Foroursystemwelluseabag(eventhoughwecanthaveduplicatesales,itsjustalittlemore straightforwardrightnow),sowedeclareourSales collectionasanIList:
privateIList<Sale>_sales publicIList<Sale>Sales { get{return_sales} }

andaddour<bag>elementtotheSalesPersonmapping file:
<bagname="Sales"access="field.lowercase underscore" table="Sales"inverse="true" cascade="all"> <keycolumn="SalesPersonId"/> <onetomanyclass="Sale"/> </bag>

Withthereleaseof.NET3.5,a HashSetcollectionhasfinallybeen addedtotheframework.Hopefully, futureversionswilladdothertypes ofsets,suchasanOrderedSet.Sets areveryusefulandefficient collections,soconsideradding themtoyourarsenaloftools!You canlearnmorebyreadingJason Smith'sarticledescribesets.

Again,ifyoulookateachelement/attribute,itisntascomplicatedasitfirstmightseem.Weidentify thenameofourproperty,specifytheaccessstrategy(wedonthaveasetter,sotellittousethefield withournamingconvention),thetableandcolumnholdingtheforeignkey,andthetype/classofthe itemsinthecollection. WevealsosetthecascadeattributetoallwhichmeansthatwhenwecallUpdateona SalesPersonobject,anychangesmadetohisorherSalescollection(additions,removals,changesto existingsales)willautomaticallybepersisted.Cascadingcanbearealtimesaverasyoursystemgrows incomplexity. Querying NHibernatesupportstwodifferentqueryingapproaches:HibernateQueryLanguage(HQL)andCriteria Queries(youcanalsoqueryinactualSQL,butloseportabilitywhendoingso).HQListheeasieroftwo asitlooksalotlikeSQLyouusefrom,where,aggregates,orderby,groupby,etc.However, ratherthanqueryingagainstyourtables,youwritequeriesagainstyourdomainwhichmeansHQL

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter6ObjectRelationalMappers

51

supportsOOprincipleslikeinheritanceandpolymorphism.Eitherquerymethodsareabstractionson topofSQL,whichmeansyougettotalportabilityallyouneedtodototargetadifferentdatabaseis changeyourdialectconfiguration. HQLworksoffoftheIQueryinterface,whichiscreatedbycallingCreateQueryonyoursession.With IQueryyoucanreturnindividualentities,collections,substituteparametersandmore.Herearesome examples:


stringlastName="allen" ISessionsession=_sessionFactory.OpenSession() //retrieveasalespersonbylastname IQueryquery=s.CreateQuery("fromSalesPersonpwherep.LastName= 'allen'") SalesPersonp=query.UniqueResult<SalesPerson>() //sameasabovebutin1line,andwiththelastnameasavariable SalesPersonp=session.CreateQuery("fromSalesPersonpwherep.LastName= ?").SetString(0,lastName).UniqueResult<SalesPerson>() //peoplewithfewsales IList<SalesPerson>slackers=session.CreateQuery("fromSalesPersonperson wheresize(person.Sales)<5").List<SalesPerson>()

ThisisjustasubsetofwhatcanbeaccomplishedwithHQL(thedownloadablesamplehasslightlymore complicatedexamples). LazyLoading Whenweloadasalesperson,saybydoing:SalesPersonperson= session.Get<SalesPerson>(1) theSalescollectionwontbeloaded.Thatsbecause,bydefault, collectionsarelazilyloaded.Thatis,wewonthitthedatabaseuntiltheinformationisspecifically requested(i.e.,weaccesstheSalesproperty).Wecanoverridethebehaviorbysetting lazy=false onthebagelement. Theother,moreinteresting,lazyloadstrategyimplementedbyNHibernateisonentitiesthemselves. Youlloftenwanttoaddareferencetoanobjectwithouthavingtoloadtheactualobjectfromthe database.Forexample,whenweaddaSalestoaSalesPerson,weneedtospecifytheModel,but dontwanttoloadeverypropertyallwereallywanttodoisgettheIdsowecanstoreitinthe ModelIdcolumnoftheSalestable.Whenyouusesession.Load<T>(id) NHibernatewillloada proxyoftheactualobject(unlessyouspecifylazy=falseintheclasselement).Asfarasyoure concerned,theproxybehavesexactlyliketheactualobject,butnoneofthedatawillberetrievedfrom thedatabaseuntilthefirsttimeyouaskforit.Thismakesitpossibletowritethefollowingcode:
Salesale=newSale(session.Load<Model>(1),DateTime.Now,46000.00) salesPerson.AddSales(sale)

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter6ObjectRelationalMappers

52

session.SaveOrUpdate(salesPerson)

withouteverhavingtoactuallyhitthedatabasetoloadtheModel.

Download
YoucandownloadasampleprojectwithmoreexamplesofNHibernateusageat: http://codebetter.com/files/folders/codebetter_downloads/entry172562.aspx.Thecodeisheavily documentedtoexplainvariousaspectsofusingNHibernate.(Iftheabovelinkdoesnotworkforyou, youcantrythisalternativedownloadlocation:http://openmymind.net/CodeBetter.Foundations.zip).

InThisChapter
WeveonlytouchedthetipofwhatyoucandowithNHibernate.WehaventlookedatitsCriteria Queries(whichisaqueryAPItiedevenclosertoyourdomain),itscachingcapabilities,filteringof collections,performanceoptimizations,logging,ornativeSQLabilities.BeyondNHibernatethetool, hopefullyyouvelearntmoreaboutobjectrelationalmapping,andalternativesolutionstothelimited toolsetbakedinto.NET.ItishardtoletgoofhandwrittenSQLbut,steppingbeyondwhat's comfortable,it'simpossibletoignorethebenefitsofO/Rmappers. You'remorethanhalfwaythrough!Ihopeyou'reenjoyingyourselfandlearningalot.This mightbeagoodtimetotakeabreakfromreadingandgetalittlemorehandsonwiththefree FoundationsofProgrammingLearningApplication.

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter7BacktoBasics:Memory

53

BacktoBasics:Memory
NOT'GETTING'ALGEBRAISNOTACCEPTABLEFORAMATHEMATICIAN,ASNOT 'GETTING'POINTERSISNOTACCEPTABLEFORPROGRAMMERS .T OOFUNDAMENTAL. WARDCUNNINGHAM

ryastheymight,modernprogramminglanguagecan'tfullyabstractfundamentalaspectsof computersystems.Thisismadeevidentbythevariousexceptionsthrownbyhighlevel languages.Forexample,it'ssafetoassumethatyou'velikelyfacedthefollowing.NETexceptions: NullReferneceException ,OutOfMemoryException ,StackOverflowExceptionand ThreadAbortException .Asimportantasitisfordeveloperstoembracevarioushighlevelpatterns andtechniques,it'sequallyimportanttounderstandtheecosysteminwhichyourprogramruns. LookingpastthelayersprovidedbytheC#(orVB.NET)compiler,theCLRandtheoperatingsystem,we findmemory.Allprogramsmakeextensiveuseofsystemmemoryandinteractwithitinmarvelous ways,it'sdifficulttobeagoodprogrammerwithoutunderstandingthisfundamentalinteraction. MuchoftheconfusionaboutmemorystemsfromthefactthatC#andVB.NETaremanagedlanguages andthattheCLRprovidesautomaticgarbagecollection.Thishascausedmanydevelopersto erroneouslyassumethattheyneednotworryaboutmemory.

MemoryAllocation
In.NET,aswithmostlanguages,everyvariableyoudefineiseitherstoredonthestackorintheheap. Thesearetwoseparatespacesallocatedinsystemmemorywhichserveadistinct,yetcomplimentary purpose.Whatgoeswhereispredetermined:valuetypesgoonthestack,whileallreferencetypesgo ontheheap.Inotherwords,allthesystemtypes,suchaschar,int,long,byte,enumandany structures(eitherdefinedin.NETordefinedbyyou)goonthestack.Theonlyexceptiontothisruleare valuetypesbelongingtoreferencetypesforexampletheIdpropertyofaUserclassgoesontheheap alongwiththeinstanceoftheUserclassitself. TheStack Althoughwe'reusedtomagicalgarbagecollection,valuesonthestackareautomaticallymanagedeven inagarbagecollectionlessworld(suchasC).That'sbecausewheneveryouenteranewscope(suchasa methodoranifstatement)valuesarepushedontothestack Ifyou'veeverwonderedwhya andwhenyouexitthestackthevaluesarepoppedoff.Thisis variabledefinedinaforlooporif whyastackissynonymouswithaLIFOlastinfirstout.You canthinkofitthisway:wheneveryoucreateanewscope, statementwasn'tavailableoutside sayamethod,amarkerisplacedonthestackandvaluesare thatscope,it'sbecausethestack addedtoitasneeded.Whenyouleavethatscope,allvalues hasunwounditselfandthevalueis arepoppedoffuptoandincludingthemethodmarker.This lost. workswithanylevelofnesting.

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter7BacktoBasics:Memory

54

Untilwelookattheinteractionbetweentheheapandthestack,theonlyrealwaytogetintroublewith thestackiswiththeStackOverflowException .Thismeansthatyou'veusedupallthespace availableonthestack.99.9%ofthetime,thisindicatesanendlessrecursivecall(afunctionwhichcalls itselfadinfinitum).Intheoryitcouldbecausedbyavery,verypoorlydesignedsystem,thoughI've neverseenanonrecursivecalluseupallthespaceonthestack. TheHeap Memoryallocationontheheapisn'tasstraightforwardasonthestack.Mostheapbasedmemory allocationoccurswheneverwecreateanewobject.Thecompilerfiguresouthowmuchmemorywe'll need(whichisn'tthatdifficult,evenforobjectswithnestedreferences),carvesupanappropriatechunk ofmemoryandreturnsapointertotheallocatedmemory(moreonthisinmoments).Thesimplest exampleisastring,ifeachcharacterinastringtakesup2bytes,andwecreateanewstringwiththe valueof"HelloWorld",thentheCLRwillneedtoallocate22bytes(11x2)pluswhateveroverheadis needed. Speakingofstrings,you'venodoubtheardthatstringareimmutablethatis,onceyou'vedeclareda stringandassigneditavalue,ifyoumodifythatstring(bychangingitsvalue,orconcatenatinganother stringontoit),thenanewstringiscreated.Thiscanactuallyhavenegativeperformanceimplications, andsothegeneralrecommendationistouseaStringBuilderforanysignificantstringmanipulation. Thetruththoughisthatanyobjectstoredontheheapisimmutablewithrespecttosizeallocation,and anychangestotheunderlyingsizewillrequirenewallocation.TheStringBuilder,alongwithsome collections,partiallygetaroundthisbyusinginternalbuffers.Oncethebufferfillsupthough,thesame reallocationoccursandsometypeofgrowthalgorithmisusedtodeterminedthenewsize(thesimplest beingoldSize*2).Wheneverpossibleit'sagoodideatospecifytheinitialcapacityofsuchobjectsin ordertoavoidthistypeofreallocation(theconstructorforboththeStringBuilderandthe ArrayList(amongstmanyothercollections)allowyoutospecifyaninitialcapacity). Garbagecollectingtheheapisanontrivialtask.Unlikethestackwherethelastscopecansimplybe poppedoff,objectsintheheaparen'tlocaltoagivenscope.Instead,mostaredeeplynestedreferences ofotherreferencedobjects.InlanguagessuchasC,wheneveraprogrammercausesmemorytobe allocatedontheheap,heorshemustalsomakesuretoremoveitfromtheheapwhenhe'sfinished withit.Inmanagedlanguages,theruntimetakescareofcleaningupresources(.NETusesa GenerationalGarbageCollectorwhichisbrieflydescribedonWikipedia). Therearealotofnastyissuesthatcanstingdeveloperswhileworkingwiththeheap.Memoryleaks aren'tonlypossiblebutverycommon,memoryfragmentationcancausealltypesofhavoc,andvarious performanceissuescanariseduetostrangeallocationbehaviororinteractionwithunmanagedcode (which.NETdoesalotunderthecovers).

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter7BacktoBasics:Memory

55

Pointers Formanydevelopers,learningpointersinschoolwasapainfulexperience.Theyrepresenttheveryreal indirectionwhichexistsbetweencodeandhardware.Manymoredevelopershaveneverhadthe experienceoflearningthemhavingjumpedintoprogrammingdirectlyfromalanguagewhichdidn't exposethemdirectly.ThetruththoughisthatanyoneclaimingthatC#orJavaarepointerlesslanguages issimplywrong.Sincepointersarethemechanismbywhichalllanguagesmanagevaluesontheheap,it seemsrathersillynottounderstandhowtheyareused. Pointersrepresentthenexusofasystem'smemorymodelthatis,pointersarethemechanismby whichthestackandtheheapworktogethertoprovidethememorysubsystemrequiredbyyour program.Aswediscussedearlier,wheneveryouinstantiateanewobject,.NETallocatesachunkof memoryontheheapandreturnsapointertothestartofthismemoryblock.Thisisallapointeris:the startingaddressfortheblockofmemorycontaininganobject.Thisaddressisreallynothingmorethan anuniquenumber,generallyrepresentedinhexadecimalformat.Therefore,apointerisnothingmore thanauniquenumberthattells.NETwheretheactualobjectisinmemory.Whenyouassigna referencetypetoavariable,yourvariableisactuallyapointertotheobject.Thisindirectionis transparentinJavaor.NET,butnotinCorC++whereyoucanmanipulatethememoryaddressdirectly viapointerarithmetic.InCorC++youcouldtakeapointerandadd1toit,hencearbitrarilychanging whereitpointsto(andlikelycrashingyourprogrambecauseofit). Whereitgetsinterestingiswherethepointerisactuallystored.Theyactuallyfollowthesamerules outlinedabove:asintegerstheyarestoredonthestackunlessofcoursetheyarepartofareference objectandthentheyareontheheapwiththerestoftheirobject.Itmightnotbeclearyet,butthis meansthatultimately,allheapobjectsarerootedonthestack(possiblythroughnumerouslevelsof references).Let'sfirstlookatasimpleexample:
staticvoidMain(string[]args) { intx=5 stringy="codebetter.com" }

Fromtheabovecode,we'llendupwith2valuesonthestack,theinteger5andthepointertoourstring, aswellastheactualstringontheheap.Here'sagraphicalrepresentation:

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter7BacktoBasics:Memory

56

Whenweexitourmainfunction(forgetthefactthattheprogramwillstop),ourstackpopsoffalllocal values,meaningboththexandyvaluesarelost.Thisissignificantbecausethememoryallocatedonthe heapstillcontainsourstring,butwe'velostallreferencestoit(there'snopointerpointingbacktoit).In CorC++thisresultsinamemoryleakwithoutareferencetoourheapaddresswecan'tfreeupthe memory.InC#orJava,ourtrustygarbagecollectorwilldetecttheunreferencedobjectandfreeitup. We'lllookatamorecomplexexamples,butasidefromhavingmorearrows,it'sbasicallythesame.


publicclassEmployee { privateint_employeeId privateEmployee_manager publicintEmployeeId { get{return_employeeId} set{_employeeId=value} } publicEmployeeManager { get{return_manager} set{_manager=value} } publicEmployee(intemployeeId) { _employeeId=employeeId } } publicclassTest { privateEmployee_subordinate voidDoSomething() { Employeeboss=newEmployee(1) _subordinate=newEmployee(2) _subordinate.Manager=_boss } }

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter7BacktoBasics:Memory

57

Someprefertocallthetypeof pointersfoundinC#,VB.NETand JavaReferencesorSafePointers, becausetheyeitherpointtoavalid objectornull.Thereisnosuch guaranteeinlanguagessuchasC orC++sincedevelopersarefreeto manipulatepointersdirectly.

Interestingly,whenweleaveourmethod,thebossvariable willpopoffthestack,butthesubordinate,whichisdefinedin aparentscope,won't.Thismeansthegarbagecollectorwon't haveanythingtocleanupbecausebothheapvalueswillstill bereferenced(onedirectlyfromthestack,andtheother indirectlyfromthestackthroughareferencedobject). Asyoucansee,pointersmostdefinitelyplayasignificantpart inbothC#andVB.NET.Sincepointerarithmeticisn'tavailable ineitherlanguage,pointersaregreatlysimplifiedand hopefullyeasilyunderstood.

MemoryModelinPractice
We'llnowlookattheactualimpactthishasonourapplications.Keepinmindthoughthat understandingthememorymodelinplaywon'tonlyhelpyouavoidpitfalls,butitwillalsohelpyou writebetterapplications. Boxing Boxingoccurswhenavaluetype(storedonthestack)iscoercedontotheheap.Unboxinghappens whenthesevaluetypesareplacedbackontothestack.Thesimplestwaytocoerceavaluetype,suchas aninteger,ontotheheapisbycastingit:
intx=5 objecty=x

Amorecommonscenariowhereboxingoccursiswhenyousupplyavaluetypetoamethodthat acceptsanobject.Thiswascommonwithcollectionsin.NET1.xbeforetheintroductionofgenerics.The nongenericcollectionclassesmostlyworkwiththeobjecttype,sothefollowingcoderesultsinboxing andunboxing:


ArrayListuserIds=newArrayList(2) userIds.Add(1) userIds.Add(2) intfirstId=(int)userIds[0]

Therealbenefitofgenericsistheincreaseintypesafety,buttheyalsoaddresstheperformancepenalty associatedwithboxing.Inmostcasesyouwouldn'tnoticethispenalty,butinsomesituations,suchas largecollections,youverywellcould.Regardlessofwhetherornotit'ssomethingyououghttoactually concernyourselfwith,boxingisaprimeexampleofhowtheunderlyingmemorysystemcanhavean impactonyourapplication.

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter7BacktoBasics:Memory

58

ByRef Withoutagoodunderstandingofpointers,it'svirtuallyimpossibletounderstandpassingavalueby referenceandbyvalue.Developersgenerallyunderstandtheimplicationofpassingavaluetype,suchas aninteger,byreference,butfewunderstandwhyyou'dwanttopassareferencebyreference.ByRef andByValaffectreferenceandvaluetypesthesameprovidedyouunderstandthattheyalwayswork againsttheunderlyingvalue(whichinthecaseofareferencetypemeanstheyworkagainstthepointer andnotthevalue).UsingByRefistheonlycommonsituationwhere.NETwon'tautomaticallyresolve thepointerindirection(passingbyreferenceorasanoutputparameterisn'tallowedinJava). Firstwe'lllookathowByVal/ByRefaffectsvaluetypes.Giventhefollowingcode:


publicstaticvoidMain() { intcounter1=0 SeedCounter(counter1) Console.WriteLine(counter1) intcounter2=0 SeedCounter(refcounter2) Console.WriteLine(counter2) } privatestaticvoidSeedCounter(intcounter) { counter=1 } privatestaticvoidSeedCounter(refintcounter) { counter=1 }

Wecanexpectanoutputof0proceededby1.Thefirstcalldoesnotpasscounter1byreference, meaningacopyofcounter1ispassedintoSeedCounterandchangesmadewithinarelocaltothe function.Inotherwords,we'retakingthevalueonthestackandduplicatingitontoanotherstack location. Inthesecondcasewe'reactuallypassingthevaluebyreferencewhichmeansnocopyiscreatedand changesaren'tlocalizedtotheSeedCounterfunction. Thebehaviorwithreferencetypesistheexactsame,althoughitmightnotappearsoatfirst.We'lllook attwoexamples.ThefirstoneusesaPayManagementclasstochangethepropertiesofanEmployee. Inthecodebelowweseethatwehavetwoemployeesandinbothcaseswe'regivingthema$2000 raise.Theonlydifferenceisthatonepassestheemployeebyreferencewhiletheotherispassedby value.Canyouguesstheoutput?

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter7BacktoBasics:Memory

59

publicclassEmployee { privateint_salary publicintSalary { get{return_salary} set{_salary=value} } publicEmployee(intstartingSalary) { _salary=startingSalary } } publicclassPayManagement { publicstaticvoidGiveRaise(Employeeemployee,intraise) { employee.Salary+=raise } publicstaticvoidGiveRaise(refEmployeeemployee,intraise) { employee.Salary+=raise } } publicstaticvoidMain() { Employeeemployee1=newEmployee(10000) PayManagement.GiveRaise(employee1,2000) Console.WriteLine(employee1.Salary) Employeeemployee2=newEmployee(10000) PayManagement.GiveRaise(refemployee2,2000) Console.WriteLine(employee2.Salary) }

Inbothcases,theoutputis12000.Atfirstglance,thisseemsdifferentthanwhatwejustsawwithvalue types.What'shappeningisthatpassingareferencetypebyvaluedoesindeedpassacopyofthevalue, butnottheheapvalue.Instead,we'repassingacopyofourpointer.Andsinceapointerandacopyof thepointerpointtothesamememoryontheheap,achangemadebyoneisreflectedintheother. Whenyoupassareferencetypebyreference,you'repassingtheactualpointerasopposedtoacopyof thepointer.Thisbegsthequestion,whenwouldweeverpassareferencetypebyreference?Theonly reasontopassbyreferenceiswhenyouwanttomodifythepointeritselfasinwhereitpointsto.This canactuallyresultinnastysideeffectswhichiswhyit'sagoodthingfunctionswantingtodosomust specificallyspecifythattheywanttheparameterpassedbyreference.Let'slookatoursecondexample.

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter7BacktoBasics:Memory

60

publicclassEmployee { privateint_salary publicintSalary { get{return_salary} set{_salary=value} } publicEmployee(intstartingSalary) { _salary=startingSalary } } publicclassPayManagement { publicstaticvoidTerminate(Employeeemployee) { employee=null } publicstaticvoidTerminate(refEmployeeemployee) { employee=null } } publicstaticvoidMain() { Employeeemployee1=newEmployee(10000) PayManagement.Terminate(employee1) Console.WriteLine(employee1.Salary) Employeeemployee2=newEmployee(10000) PayManagement.Terminate(refemployee2) Console.WriteLine(employee2.Salary) }

Trytofigureoutwhatwillhappenandwhy.I'llgiveyouahint:anexceptionwillbethrown.Ifyou guessedthatthecalltoemployee1.Salaryoutputted10000whilethe2ndonethrewa NullReferenceException thenyou'reright.Inthefirstcasewe'resimplysettingacopyofthe originalpointertonullithasnoimpactwhatsoeveronwhatemployee1ispointingto.Inthesecond case,wearen'tpassingacopybutthesamestackvalueusedbyemployee2.Thussettingtheemployee tonullisthesameaswritingemployee2=null . It'squiteuncommontowanttochangetheaddresspointedtobyavariablefromwithinaseparate methodwhichiswhytheonlytimeyou'relikelytoseeareferencetypepassedbyreferenceiswhen youwanttoreturnmultiplevaluesfromafunctioncall(inwhichcaseyou'rebetteroffusinganout parameter,orusingapurerOOapproach).Theaboveexampletrulyhighlightsthedangersofplayingin anenvironmentwhoserulesaren'tfullyunderstood.

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter7BacktoBasics:Memory

61

ManagedMemoryLeaks WealreadysawanexampleofwhatamemoryleaklookslikeinC.Basically,ifC#didn'thaveagarbage collector,thefollowingcodewouldleak:


privatevoidDoSomething() { stringname="dune" }

Ourstackvalue(apointer)willbepoppedoff,andwithitwillgotheonlywaywehavetoreferencethe memorycreatedtoholdourstring.Leavinguswithnomethodoffreeingitup.Thisisn'taproblemin .NETbecauseitdoeshaveagarbagecollectorwhichtracksunreferencedmemoryandfreesit.However, atypeofmemoryleakisstillpossibleifyouholdontoreferencesindefinitely.Thisiscommoninlarge applicationswithdeeplynestedreferences.Theycanbehardtoidentifybecausetheleakmightbevery smallandyourapplicationmightnotrunforlongenough Ultimatelywhenyourprogramterminatestheoperatingsystemwillreclaimallmemory,leakedor otherwise.However,ifyoustartseeingOutOfMemoryException andaren'tdealingwithabnormally largedata,there'sagoodchanceyouhaveamemoryleak..NETshipswithtoolstohelpyouout,but you'lllikelywanttotakeadvantageofacommercialmemoryprofilersuchasdotTraceorANTSProfiler. Whenhuntingformemoryleaksyou'llbelookingforyourleakedobject(whichisprettyeasytofindby taking2snapshotsofyourmemoryandcomparingthem),tracingthroughalltheobjectswhichstillhold areferencetoitandcorrectingtheissue. There'sonespecificsituationworthmentioningasacommoncauseofmemoryleaks:events.If,ina class,youregisterforanevent,areferenceiscreatedtoyourclass.Unlessyouderegisterfromthe eventyourobjectslifecyclewillultimatelybedeterminedbytheeventsource.Inotherwords,ifClassA (thelistener)registersforaneventinClassB(theeventsource)areferenceiscreatedfromClassBto ClassA.Twosolutionsexists:deregisteringfromeventswhenyou'redone(theIDisposablepatternis theidealsolution),orusetheWeakEventPatternorasimplifiedversion. Fragmentation AnothercommoncauseforOutOfMemoryException hastodowithmemoryfragmentation.When memoryisallocatedontheheapit'salwaysacontinuousblock.Thismeansthattheavailablememory mustbescannedforalargeenoughchunk.Asyourprogramrunsitscourse,theheapbecomes increasinglyfragmented(likeyourharddrive)andyoumightendupwithplentyofspace,butspreadout inamannerwhichmakesitunusable.Undernormalcircumstances,thegarbagecollectorwillcompact theheapasit'sfreeingmemory.Asitcompactsmemory,addressesofobjectschangeand.NETmakes suretoupdateallyourreferencesaccordingly.Sometimesthough,.NETcan'tmoveanobject:namely whentheobjectispinnedtoaspecificmemoryaddress.

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter7BacktoBasics:Memory

62

Pinning Pinningoccurswhenanobjectislockedtoaspecificaddressontheheap.Pinnedmemorycannotbe compactedbythegarbagecollectorresultinginfragmentation.Whydovaluesgetpinned?Themost commoncauseisbecauseyourcodeisinteractingwithunmanagedcode.Whenthe.NETgarbage collectorcompactstheheap,itupdatesallreferencesinmanagedcode,butithasnowaytojumpinto unmanagedcodeanddothesame.Therefore,beforeinteropingitmustfirstpinobjectsinmemory. Sincemanymethodswithinthe.NETframeworkrelyonunmanagedcode,pinningcanhappenwithout youknowingaboutit(thescenarioI'mmostfamiliarwitharethe.NETSocketclasseswhichrelyon unmanagedimplementationsandpinbuffers). Acommonwayaroundthistypeofpinningistodeclarelargeobjectswhichdon'tcauseasmuch fragmentationasmanysmallones(thisisevenmoretrueconsideringlargeobjectsareplacedina specialheap(calledtheLargeObjectHeap(LOH)whichisn'tcompactedatall).Forexample,ratherthan creatinghundredsof4KBbuffers,youcancreate1largebufferandassignchunksofityourself.Foran exampleaswellasmoreinformationonpinning,IsuggestyoureadGregYoung'sadvancedposton pinningandasynchronoussockets. There'sasecondreasonwhyanobjectmightbepinnedwhenyouexplicitlymakeithappen.InC#(not inVB.NET)ifyoucompileyourassemblywiththeunsafeoption,youcanpinanobjectviathefixed statement.Whileextensivepinningcancausememorypressuresonthesystem,judicialuseofthe fixedstatementcangreatlyimproveperformance.Why?Becauseapinnedobjectcanbemanipulated directlywithpointerarithmeticthisisn'tpossibleiftheobjectisn'tpinnedbecausethegarbage collectormightreallocateyourobjectsomewhereelseinmemory. TakeforexamplethisefficientASCIIstringtointegerconversionwhichrunsover6timesfasterthan usingint.Parse.
publicunsafestaticintParse(stringstringToConvert) { intvalue=0 intlength=stringToConvert.Length fixed(char*characters=stringToConvert) { for(inti=0i<length++i) { value=10*value+(characters[i]48) } } returnvalue }

Unlessyou'redoingsomethingabnormal,thereshouldneverbeaneedtomarkyourassemblyas unsafeandtakeadvantageofthefixedstatement.Theabovecodewilleasilycrash(passnullasthe stringandseewhathappens),isn'tnearlyasfeaturerichasint.Parse,andinthescaleofthingsis extremelyriskywhileprovidingnobenefits.

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter7BacktoBasics:Memory

63

Settingthingstonull So,shouldyousetyourreferencetypestonullwhenyou'redonewiththem?Ofcoursenot.Oncea variablefallsoutofscope,it'spoppedofthestackandthereferenceisremoved.Ifyoucan'twaitforthe scopetoexit,youlikelyneedtorefactoryourcode.

DeterministicFinalization
Despitethepresenceofthegarbagecollector,developersmuststilltakecareofmanagingsomeoftheir references.That'sbecausesomeobjectsholdontovitalorlimitedresources,suchasfilehandlesor databaseconnectionswhichshouldbereleasedassoonaspossible.Thisisproblematicsincewedon't knowwhenthegarbagecollectorwillactuallyrunbynaturethegarbagecollectoronlyrunswhen memoryisinshortsupply.Tocompensate,classeswhichholdontosuchresourcesshouldmakeuseof theDisposablepattern.All.NETdevelopersarelikelyfamiliarwiththispattern,alongwithitsactual implementation(theIDisposableinterface),sowewon'trehashwhatyoualreadyknow.With respecttothischapter,it'ssimplyimportantthatyouunderstandtheroledeterministicfinalization takes.Itdoesn'tfreethememoryusedbytheobject.Itreleasesresources.Inthecaseofdatabase connectionsforexample,itreleasestheconnectionbacktothepoolinordertobereused. IfyouforgettocallDisposeonanobjectwhichimplementsIDisposable,thegarbagecollectorwill doitforyou(eventually).Youshouldn'trelyonthisbehavior,however,astheproblemoflimited resourcesisveryreal(it'srelativelytrivialtotryitoutwithaloopthatopensconnectionstoadatabase). YoumaybewonderingwhysomeobjectsexposebothaCloseandDisposemethod,andwhichyou shouldcall.InallthecasesI'veseenthetwoaregenerallyequivalentsoit'sreallyamatteroftaste.I wouldsuggestthatyoutakeadvantageoftheusingstatementandforgetaboutClose.PersonallyI finditfrustrating(andinconsistent)thatbothareexposed. Finally,ifyou'rebuildingaclassthatwouldbenefitfromdeterministicfinalizationyou'llfindthat implementingtheIDisposablepatternissimple.AstraightforwardguideisavailableonMSDN.

InThisChapter
Stacks,heapsandpointerscanseemoverwhelmingatfirst.Withinthecontextofmanagedlanguages though,thereisn'treallymuchtoit.Thebenefitsofunderstandingtheseconceptsaretangibleindayto dayprogramming,andinvaluablewhenunexpectedbehavioroccurs.Youcaneitherbetheprogrammer whocausesweirdNullReferenceExceptions andOutOfMemoryExceptions ,ortheonethatfixes them.

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter8BacktoBasics:Exceptions

64

BacktoBasics:Exceptions
FAILFASTJIMSHORE

xceptionsaresuchpowerfulconstructsthatdeveloperscangetalittleoverwhelmedandfartoo defensivewhendealingwiththem.Thisisunfortunatebecauseexceptionsactuallyrepresenta keyopportunityfordeveloperstomaketheirsystemconsiderablymorerobust.Inthischapter we'lllookatthreedistinctaspectsofexceptions:handling,creatingandthrowingthem.Since exceptionsareunavoidableyoucanneitherrunnorhide,soyoumightaswellleverage.

HandlingExceptions
Yourstrategyforhandlingexceptionsshouldconsistoftwogoldenrules: 1. Onlyhandleexceptionsthatyoucanactuallydosomethingabout,and 2. Youcan'tdoanythingaboutthevastmajorityofexceptions Mostnewdevelopersdotheexactoppositeofthefirstrule,andfighthopelesslyagainstthesecond. Whenyourapplicationdoessomethingdeemedexceptionallyoutsideofitsnormaloperationthebest thingtodoisfailitrightthenandthere.Ifyoudon'tyouwon'tonlylosevitalinformationaboutyour mysterybug,butyouriskplacingyourapplicationinanunknownstate,whichcanresultinfarworse consequences. Wheneveryoufindyourselfwritingatry/catchstatement,askyourselfifyoucanactuallydosomething aboutaraisedexception.Ifyourdatabasegoesdown,canyouactuallywritecodetorecoverorareyou betteroffdisplayingafriendlyerrormessagetotheuserandgettinganotificationabouttheproblem? It'shardtoacceptatfirst,butsometimesit'sjustbettertocrash,logtheerrorandmoveon.Evenfor missioncriticalsystems,ifyou'remakingtypicaluseofadatabase,whatcanyoudoifitgoesdown?This trainofthoughtisn'tlimitedtodatabaseissuesorevenjustenvironmentalfailures,butalsoyourtypical everydayruntimebug.IfconvertingaconfigurationvaluetoanintegerthrowsaFormatException doesitmakesensecontinuingasifeverything'sok?Probablynot. Ofcourse,ifyoucanhandleanexceptionyouabsolutelyoughttobutdomakesuretocatchonlythe typeofexceptionyoucanhandle.Catchingexceptionsandnotactuallyhandlingthemiscalled exceptionswallowing(Iprefertocallitwishfulthinking)andit'sabadcode.AcommonexampleIsee hastodowithinputvalidation.Forexample,let'slookathownottohandleacategoryIdbeing passedfromtheQueryStringofanASP.NETpage.
intcategoryId try { categoryId=int.Parse(Request.QueryString["categoryId"]) } catch(Exception)

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter8BacktoBasics:Exceptions

65

{ categoryId=1 }

Theproblemwiththeabovecodeisthatregardlessofthetypeofexceptionthrown,it'llbehandledthe sameway.ButdoessettingthecategoryIdtoadefaultvalueof1actuallyhandlean OutOfMemoryException ?Instead,theabovecouldshouldcatchaspecificexception:


intcategoryId try { categoryId=int.Parse(Request.QueryString["categoryId"]) } catch(FormatException) { categoryId=1 }

(anevenbetterapproachwouldbetheusetheint.TryParsefunctionintroducedin.NET2.0 especiallyconsideringthatint.Parsecanthrowtwoothertypesofexceptionsthatwe'dwantto handlethesameway,butthat'sbesidesthepoint). Logging Eventhoughmostexceptionsaregoingtogounhandled,youshouldstilllogeachandeveryoneof them.Ideallyyou'llcentralizeyourlogginganHttpModule'sOnErroreventisyourbestchoiceforan ASP.NETapplicationorwebservice.I'veoftenseendeveloperscatchexceptionswheretheyoccuronly tologandrethrow(moreonrethrowinginabit).Thiscauses alotofunnecessaryandrepetitivecodebettertolet Awordofwarningbasedonabad exceptionsbubbleupthroughyourcodeandlogall personalexperience:sometypesof exceptionsattheouteredgeofyoursystem.Exactlywhich exceptionstendtocluster.Ifyou loggingimplementationyouuseisuptoyouandwilldepend choosetosendoutemails onthecriticalnessofyoursystem.Maybeyou'llwanttobe wheneveranexceptionoccursyou notifiedbyemailassoonasexceptionsoccur,ormaybeyou caneasilyfloodyourmailserver.A cansimplylogittoafileordatabaseandeitherreviewit smartloggingsolutionshould dailyorhaveanotherprocesssendyouadailysummary. probablyimplementsometypeof Manydevelopersleveragerichloggingframeworkssuchas bufferingoraggregation. log4netorMicrosoft'sLoggingApplicationBlock. CleaningUp Inthepreviouschapterwetalkedaboutdeterministicfinalizationwithrespecttothelazynatureofthe garbagecollector.Exceptionsprovetobeanaddedcomplexityastheirabruptnaturecancause Disposenottobecalled.Afaileddatabasecallisaclassicexample:

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter8BacktoBasics:Exceptions

66

SqlConnectionconnection=newSqlConnection(FROM_CONFIGURATION) SqlCommandcommand=newSqlCommand("SomeSQL",connection) connection.Open() command.ExecuteNonQuery() command.Dispose() connection.Dispose()

IfExecuteNonQuerythrowsanexception,neitherourcommandnorourconnectionwillgetdisposed of.ThesolutionistouseTry/Finally:
SqlConnectionconnection SqlCommandcommand try { connection=newSqlConnection(FROM_CONFIGURATION) command=newSqlCommand("SomeSQL",connection) connection.Open() command.ExecuteNonQuery() } finally { if(command!=null){command.Dispose()} if(connection!=null){connection.Dispose()} }

orthesyntacticallynicerusingstatement(whichgetscompiledtothesametry/finallyabove):
using(SqlConnectionconnection=newSqlConnection(FROM_CONFIGURATION)) using(SqlCommandcommand=newSqlCommand("SomeSQL",connection)) { connection.Open() command.ExecuteNonQuery() }

Thepointisthatevenifyoucan'thandleanexception,andyoushouldcentralizeallyourlogging,youdo needtobemindfulofwhereexceptionscancropupespeciallywhenitcomestoclassesthat implementIDiposable.

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter8BacktoBasics:Exceptions

67

ThrowingExceptions
Thereisn'tonemagicruletothrowingexceptionslikethereisforcatchingthem(again,thatruleisdon't catchexceptionsunlessyoucanactuallyhandlethem).Nonethelessthrowingexceptions,whetheror nottheybeyourown(whichwe'llcovernext),isstillprettysimple.Firstwe'lllookattheactual mechanicsofthrowingexceptions,whichreliesonthethrowstatement.Thenwe'llexaminewhenand whyyouactuallywanttothrowexceptions. ThrowingMechanics Youcaneitherthrowanewexception,orrethrowacaughtexception.Tothrowanewexception,simply createanewexceptionandthrowit.
thrownewException("somethingbadhappened!") //or Exceptionex=newException("somethingbadhappened") throwex

Iaddedthesecondexamplebecausesomedevelopersthinkexceptionsaresomespecial/uniquecase butthetruthisthattheyarejustlikeanyotherobject(excepttheyinheritfromSystem.Exception whichinturninheritsfromSystem.Object).Infact,justbecauseyoucreateanewexception doesn'tmeanyouhavetothrowitalthoughyouprobablyalwayswould. Onoccasionyou'llneedtorethrowanexceptionbecause,whileyoucan'thandletheexception,youstill needtoexecutesomecodewhenanexceptionoccurs.Themostcommonexampleishavingtorollback atransactiononfailure:


ITransactiontransaction=null try { transaction=session.BeginTransaction() //dosomework transaction.Commit() } catch { if(transaction!=null){transaction.Rollback()} throw } finally { //cleanup }

Intheaboveexampleourvanillathrowstatementmakesourcatchtransparent.Thatis,ahandlerup thechainofexecutionwon'thaveanyindicationthatwecaughttheexception.Inmostcases,thisis

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter8BacktoBasics:Exceptions

68

whatwewantrollingbackourtransactionreallydoesn'thelpanyoneelsehandletheexception. However,there'sawaytorethrowanexceptionwhichwillmakeitlookliketheexceptionoccurred withinourcode:


catch(HibernateExceptionex) { if(transaction!=null){transaction.Rollback()} throwex }

Byexplicitlyrethrowingtheexception,thestacktraceismodifiedsothattherethrowinglineappearsto bethesource.Thisisalmostalwayscertainlyabadidea,asvitalinformationislost.Sobecarefulhow yourethrowexceptionsthedifferenceissubtlebutimportant. Ifyoufindyourselfinasituationwhereyouthinkyouwanttorethrowanexceptionwithyourhandler asthesource,abetterapproachistouseanestedexception:


catch(HibernateExceptionex) { if(transaction!=null){transaction.Rollback()} thrownewException("Emailalreadyinuse",ex) }

ThiswaytheoriginalstacktraceisstillaccessibleviatheInnerExceptionpropertyexposedbyall exceptions. WhenToThrowExceptions It'simportanttoknowhowtothrowexceptions.Afarmoreinterestingtopicthoughiswhenandwhy youshouldthrowthem.Havingsomeoneelse'sunrulycodebringdownyourapplicationisonething. Writingyourowncodethat'lldothesamethingjustseemsplainsilly.However,agooddeveloperisn't afraidtojudiciallyuseexceptions. Thereareactuallytwolevelsofthoughtonhowexceptionsshouldbeused.Thefirstlevel,whichis universallyaccepted,isthatyoushouldn'thesitatetoraiseanexceptionwheneveratrulyexceptional situationoccurs.Myfavoriteexampleistheparsingofconfigurationfiles.Manydevelopersgenerously usedefaultvaluesforanyinvalidentries.Thisisokinsomecases,butinothersitcanputthesystemin anunreliableorunexpectedstate.AnotherexamplemightbeaFacebookapplicationthatgetsan unexpectedresultfromanAPIcall.Youcouldignoretheerror,oryoucouldraiseanexception,logit(so thatyoucanfixit,sincetheAPImighthavechanged)andpresentahelpfulmessagetoyourusers. Theotherbeliefisthatexceptionsshouldn'tjustbereservedforexceptionalsituations,butforany situationinwhichtheexpectedbehaviorcannotbeexecuted.Thisapproachisrelatedtothedesignby

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter8BacktoBasics:Exceptions

69

contractapproachamethodologythatI'madoptingmoreandmoreeveryday.Essentially,ifthe SaveUsermethodisn'tabletosavetheuser,itshouldthrowanexception. InlanguagessuchasC#,VB.NETandJava,whichdon'tsupportadesignbycontractmechanism,this approachcanhavemixedresults.AHashtablereturnsnullwhenakeyisn'tfound,butaDictionary throwsanexceptiontheunpredictablebehaviorsucks(ifyou'recuriouswhytheyworkdifferently checkoutBradAbramsblogpost).There'salsoalinebetweenwhatconstitutescontrolflowandwhat's consideredexceptional.Exceptionsshouldn'tbeusedtocontrolanif/elselikelogic,butthebiggera parttheyplayinalibrary,themorelikelyprogrammerswillusethemassuch(the int.Parsemethod isagoodexampleofthis). Generallyspeaking,Ifinditeasytodecidewhatshouldandshouldn'tthrowanexception.Igenerallyask myselfquestionslike: Isthisexceptional, Isthisexpected, CanIcontinuedoingsomethingmeaningfulatthispointand IsthissomethingIshouldbemadeawareofsoIcanfixit,oratleastgiveitasecondlook

Perhapsthemostimportantthingtodowhenthrowingexceptions,ordealingwithexceptionsin general,istothinkabouttheuser.Thevastmajorityofusersarenaivecomparedtoprogrammersand caneasilypanicwhenpresentedwitherrormessages.JeffAtwoodrecentlybloggedaboutthe importanceofcrashingresponsibly.

ITISNOTTHEUSER'SJOBTOTELLYOUABOUTERRORSINYOURSOFTWARE! DON'TEXPOSEUSERSTOTHEDEFAULTSCREENOFDEATH. HAVEADETAILEDPUBLICRECORDOFYOURAPPLICATION'SERRORS .

It'sprobablysafetosaythatWindows'BlueScreenofDeathisexactlythetypeoferrormessageusers shouldn'tbeexposedto(anddon'tthinkjustbecausethebarhasbeensetsolowthatit'soktobeas lazy).

CreatingCustomExceptions
Oneofthemostoverlookedaspectofdomaindrivendesignarecustomexceptions.Exceptionsplaya seriouspartofanybusinessdomain,soanyseriousattemptatmodelingabusinessdomainincode mustincludecustomexceptions.Thisisespeciallytrueifyoubelievethatexceptionsshouldbeused wheneveramethodfailstodowhatitsaysitwill.Ifaworkflowstateisinvaliditmakessensetothrow yourowncustomWorkflowExceptionexceptionandevenattachsomespecificinformationtoit whichmightnotonlyhelpyouidentifyapotentialbug,butcanalsobeusedtopresentmeaningful informationtotheuser.

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter8BacktoBasics:Exceptions

70

ManyoftheexceptionsIcreatearenothingmorethanmarkerexceptionsthatis,theyextendthebase System.Exception classanddon'tprovidefurtherimplementation.Ilikenthistomarkerinterfaces (ormarkerattributes),suchastheINamingContainerinterface.Theseareparticularlyusefulin allowingyoutoavoidswallowingexceptions.Takethefollowingcodeasanexample.IftheSave() methoddoesn'tthrowacustomexceptionwhenvalidationfails,wereallyhavelittlechoicebutto swallowallexceptions:


try { user.Save() } catch { Error.Text=user.GetErrors() Error.Visible=true } //versus try { user.Save() } catch(ValidationExceptionex) { Error.Text=ex.GetValidationMessage() Error.Visible=true }

Theaboveexamplealsoshowshowwecanextendexceptionstoprovidefurthercustombehavior specificallyrelatedtoourexceptions.ThiscanbeassimpleasanErrorCode,tomorecomplex informationsuchasaPermissionException whichexposestheuser'spermissionandthemissing requiredpermission. Ofcourse,notallexceptionsaretiedtothedomain.It'scommontoseemoreoperationaloriented exceptions.Ifyourelyonawebservicewhichreturnsanerrorcode,youmayverywrapthatintoyour owncustomexceptiontohaltexecution(remember,failfast)andleverageyourlogginginfrastructure. Actuallycreatingacustomexceptionisatwostepprocess.First(andtechnicallythisisallyoureally need)createaclass,withameaningfulname,whichinheritsfromSystem.Exception.
publicclassUpgradeException:Exception { }

YoushouldgotheextrastepandmarkyourclasswiththeSerializeAttributeandalwaysprovide atleast4constructors: FoundationsofProgramming CopyrightKarlSeguin www.codebetter.com

Chapter8BacktoBasics:Exceptions

71

publicYourException() publicYourException(stringmessage) publicYourException(stringmessage,ExceptioninnerException) protectedYourException(SerializationInfoinfo,StreamingContextcontext)

Thefirstthreeallowyourexceptiontobeusedinanexpectedmanner.Thefourthisusedtosupport serializationincase.NETneedstoserializeyourexceptionwhichmeansyoushouldalsoimplementthe GetObjectData method.Thepurposeofsupportserializationisinthecasewhereyouhavecustom properties,whichyou'dliketohavesurvivebeingserialized/deserialized.Here'sthecompleteexample:


[Serializable] publicclassUpgradeException:Exception { privateint_upgradeId publicintUpgradeId{get{return_upgradeId}} publicUpgradeException(intupgradeId) { _upgradeId=upgradeId } publicUpgradeException(intupgradeId,stringmessage,Exceptioninner) :base(message,inner) { _upgradeId=upgradeId } publicUpgradeException(intupgradeId,stringmessage):base(message) { _upgradeId=upgradeId } protectedUpgradeException(SerializationInfoinfo,StreamingContextc) :base(info,c) { if(info!=null) { _upgradeId=info.GetInt32("upgradeId") } } publicoverridevoidGetObjectData(SerializationInfoi,StreamingContextc) { if(i!=null) { i.AddValue("upgradeId",_upgradeId) } base.GetObjectData(i,c) } }

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter8BacktoBasics:Exceptions

72

InThisChapter
Itcantakequiteafundamentalshiftinperspectivetoappreciateeverythingexceptionshavetooffer. Exceptionsaren'tsomethingtobefearedorprotectedagainst,butrathervitalinformationaboutthe healthofyoursystem.Don'tswallowexceptions.Don'tcatchexceptionsunlessyoucanactuallyhandle them.Equallyimportantistomakeuseofbuiltin,oryourownexceptionswhenunexpectedthings happenwithinyourcode.Youmayevenexpandthispatternforanymethodthatfailstodowhatitsays itwill.Finally,exceptionsareapartofthebusinessyouaremodeling.Assuch,exceptionsaren'tonly usefulforoperationalpurposesbutshouldalsobepartofyouroveralldomainmodel.

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter9BacktoBasics:ProxyThisandProxyThat

73

BacktoBasics:ProxyThisandProxyThat
O NEOFTHEBEAUTIESOFOO PROGRAMMINGISCODEREUSETHROUGH INHERITANCE,BUTTOACHIEVEITPROGRAMMERSHAVETOACTUALLYLETOTHER PROGRAMMERSREUSETHECODE.GARYSHORT

ewkeywordsareassimpleyetamazinglypowerfulasvirtualinC#( overridableinVB.NET). Whenyoumarkamethodasvirtualyouallowaninheritingclasstooverridethebehavior. Withoutthisfunctionalityinheritanceandpolymorphismwouldn'tbeofmuchuse.Asimple example,slightlymodifiedfromProgrammingRuby(ISBN:9780974514055),whichhasa KaraokeSongoverridesaSong'sto_s(ToString)functionlookslike:


classSong defto_s returnsprintf("Song:%s,%s(%d)",@name,@artist,@duration) end end classKaraokeSong<Song defto_s returnsuper+""@lyrics end end

TheabovecodeshowshowtheKaraokeSongisableto buildontopofthebehaviorofitsbaseclass.Specialization isn'tjustaboutdata,it'salsoaboutbehavior! Evenifyourrubyisalittlerusty,youmighthavepickedup thatthebaseto_smethodisn'tmarkedasvirtual.That's becausemanylanguages,includingJava,makemethods virtualbydefault.Thisrepresentsafundamentaldifferingof opinionbetweentheJavalanguagedesignersandthe C#/VB.NETlanguagedesigners.InC#methodsarefinalby defaultanddevelopersmustexplicitlyallowoverriding(via thevirtualkeyword).InJava,methodsarevirtualby defaultanddevelopersmustexplicitlydisallowoverriding (viathefinalkeyword).

Thevirtualbydefaultvsfinalby defaultdebateisinteresting,but outsidethescopeofthischapter.If you'reinterestedinlearningmore,I suggestyoureadthisinterview withAndersHejlsberg(C#'sLead Architect),aswellasthesetwo blogpostsbyEricGunnerson.As well,checkoutMichaelFeathers pointofview.

Typicallyvirtualmethodsarediscussedwithrespecttoinheritanceofdomainmodels.Thatis,a KaraokeSongwhichinheritsfromaSong,oraDogwhichinheritsfromaPet.That'saveryimportant concept,butit'salreadywelldocumentedandwellunderstood.Therefore,we'llexaminevirtual methodsforamoretechnicalpurpose:proxies.

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter9BacktoBasics:ProxyThisandProxyThat

74

ProxyDomainPattern
Aproxyissomethingactingassomethingelse.Inlegalterms,aproxyissomeonegivenauthoritytovote oractonbehalfofsomeoneelse.Suchaproxyhasthesamerightsandbehavesprettymuchlikethe personbeingproxied.Inthehardwareworld,aproxyserversitsbetweenyouandaserveryou're accessing.Theproxyservertransparentlybehavesjustliketheactualserver,butwithadditional functionalitybeitcaching,loggingorfiltering.Insoftware,theproxydesignpatternisaclassthat behaveslikeanotherclass.Forexample,ifwewerebuildingatasktrackingsystem,wemightdecideto useaproxytotransparentlyapplyauthorizationontopofataskobject:
publicclassTask { publicstaticTaskFindById(intid) { returnTaskRepository.Create().FindById(id) } publicvirtualvoidDelete() { TaskRepository.Create().Delete(this) } } publicclassTaskProxy:Task { publicoverridevoidDelete() { if(User.Current.CanDeleteTask()) { base.Delete() } else { thrownewPermissionException(...) } } }

Thankstopolymorphism,FindByIdcanreturneitheraTaskoraTaskProxy.Thecallingclient doesn'thavetoknowwhichwasreturneditdoesn'tevenhavetoknowthataTaskProxyexists.Itjust programsagainsttheTask'spublicAPI. Sinceaproxyisjustasubclassthatimplementsadditionalbehavior,youmightbewonderingifaDogis aproxytoaPet.Proxiestendtoimplementmoretechnicalsystemfunctions(logging,caching, authorization,remoting,etc)inatransparentway.Inotherwords,youwouldn'tdeclareavariableas TaskProxybutyou'dlikelydeclareaDogvariable.Becauseofthis,aproxywouldn'taddmembers (sinceyouaren'tprogrammingagainstitsAPI),whereasaDogmightaddaBarkmethod.

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter9BacktoBasics:ProxyThisandProxyThat

75

Interception
Thereasonwe'reexploringamoretechnicalsideofinheritanceisbecausetwoofthetoolswe'velooked atsofar,RhinoMocksandNHibernate,makeextensiveuseofproxieseventhoughyoumightnothave noticed.RhinoMocksusesproxiestosupportitscorerecord/playbackfunctionality.NHibernaterelieson proxiesforitsoptionallazyloadingcapabilities.We'llonlylookatNHibernate,sinceit'seasierto understandwhat'sgoingonbehindthecovers,butthesamehighlevelpatternappliestoRhinoMocks. (AsidenoteaboutNHibernate.It'sconsideredafrictionlessortransparentO/Rmapperbecauseit doesn'trequireyoutomodifyyourdomainclassesinordertowork.However,ifyouwanttoenablelazy loading,allmembersmustbevirtual.Thisisstillconsideredfrictionless/transparentsinceyouaren't addingNHibernatespecificelementstoyourclassessuchasinheritingfromanNHibernatebaseclass orsprinklingNHibernateattributeseverywhere.) UsingNHibernatetherearetwodistinctopportunitiestoleveragelazyloading.Thefirst,andmost obvious,iswhenloadingchildcollections.Forexample,youmaynotwanttoloadallofaModel's Upgradesuntiltheyareactuallyneeded.Here'swhatyourmappingfilemightlooklike:
<classname="Model"table="Models"> <idname="Id"column="Id"type="int"> <generatorclass="native"/> </id> ... <bagname="Upgrades"table="Upgrades"lazy="true"> <keycolumn="ModelId"/> <onetomanyclass="Upgrade"/> </bag> </class>

Bysettingthelazyattributetotrueonourbagelement,wearetellingNHibernatetolazilyloadthe Upgradescollection.NHibernatecaneasilydothissinceitreturnsitsowncollectiontypes(whichall implementstandardinterfaces,suchasIList,sotoyou,it'stransparent). Thesecond,andfarmoreinteresting,usageoflazyloadingisforindividualdomainobjects.Thegeneral ideaisthatsometimesyou'llwantwholeobjectstobelazilyinitialized.Why?Well,saythatasalehas justbeenmade.Salesareassociatedwithbothasalespersonandacarmodel:


Salesale=newSale() sale.SalesPerson=session.Get<SalesPerson>(1) sale.Model=session.Get<Model>(2) sale.Price=25000 session.Save(sale)

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter9BacktoBasics:ProxyThisandProxyThat

76

Unfortunately,we'vehadtogotothedatabasetwicetoloadtheappropriateSalesPersonandModel eventhoughwearen'treallyusingthem.ThetruthisallweneedistheirID(sincethat'swhatgets insertedintoourdatabase),whichwealreadyhave. Bycreatingaproxy,NHibernateletsusfullylazyloadanobjectforjustthistypeofcircumstance.The firstthingtodoischangeourmappingandenablelazyloadingofbothModelsandSalesPeoples:


<classname="Model"table="Models"lazy="true"proxy="Model">...</class> <classname="SalesPerson"table="SalesPeople" lazy="true"proxy="SalesPerson">...</class>

TheproxyattributetellsNHibernatewhattypeshouldbeproxied.Thiswilleitherbetheactualclass youaremappingto,oraninterfaceimplementedbytheclass.Sinceweareusingtheactualclassasour proxyinterface,weneedtomakesureallmembersarevirtualifwemissany,NHibernatewillthrowa helpfulexceptionwithalistofnonvirtualmethods.Nowwe'regoodtogo:


Salesale=newSale() sale.SalesPerson=session.Load<SalesPerson>(1) sale.Model=session.Load<Model>(2) sale.Price=25000 session.Save(sale)

Noticethatwe'reusingLoadinsteadofGet.Thedifferencebetweenthetwoisthatifyou'reretrieving aclassthatsupportslazyloading,Loadwillgettheproxy,whileGetwillgettheactualobject.Withthis codeinplacewe'renolongerhittingthedatabasejusttoloadIDs.Instead,calling Session.Load<Model>(2) returnsaproxydynamicallygeneratedbyNHibernate.Theproxywill haveanidof2,sincewesupplieditthevalue,andallotherpropertieswillbeuninitialized.Anycallto anothermemberofourproxy,suchassale.Model.Namewillbetransparentlyinterceptedandthe objectwillbejustintimeloadedfromthedatabase. Justanote,NHibernate'slazyloadbehaviorcanbehardtospotwhendebuggingcodeinVisualStudio. That'sbecauseVS.NET'swatch/local/tooltipactuallyinspectstheobject,causingtheloadtohappen rightaway.Thebestwaytoexaminewhat'sgoingonistoaddacouplebreakpointsaroundyourcode andcheckoutthedatabaseactivityeitherthroughNHibernate'slog,orSQLprofiler. HopefullyyoucanimaginehowproxiesareusedbyRhinoMocksforrecording,replayingandverifying interactions.Whenyoucreateapartialyou'rereallycreatingaproxytoyouractualobject.Thisproxy interceptsallcalls,anddependingonwhichstateyouare,doesitsownthing.Ofcourse,forthistowork, youmusteithermockaninterface,oravirtualmembersofaclass.

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

Chapter9BacktoBasics:ProxyThisandProxyThat

77

InThisChapter
Inchapter6webrieflycoveredNHibernate'slazyloadingcapabilities.Inthischapterweexpandedon thatdiscussionbylookingmoredeeplyattheactualimplementation.Theuseofproxiesiscommon enoughthatyou'llnotonlyfrequentlyrunintothem,butwillalsolikelyhavegoodreasontoimplement someyourself.IstillfindmyselfimpressedattherichfunctionalityprovidedbyRhinoMockand NHibernatethankstotheproxydesignpattern.Ofcourse,everythinghingesonyouallowingthemto overrideorinserttheirbehavioroveryourclasses.Hopefullythischapterwillalsomakeyouthinkabout whichofyourmethodsshouldandwhichshouldn'tbevirtual.Istronglyrecommendthatyoufollowthe linksprovidedonthefirstpageofthischaptertounderstandtheprosandconsofvirtualandfinal methods.

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

WrappingItUp

78

WrappingItUp
WEALLTENDTOTIEOURSELFESTEEMSTRONGLYTOTHEQUALITYOFTHE PRODUCTWEPRODUCENOTTHEQUANTITYOFPRODUCT,BUTTHEQUALITY.T OM DEMARCO&T IMOTHYLISTER(PEOPLEWARE)

ormany,programmingisachallengingandenjoyablejobthatpaysthebills.However,giventhat you'vemanagedtoreadthroughallofthis,there'sagoodchancethat,likeme,programmingis somethingmuchmoreimportanttoyou.It'sacraft,andwhatyoucreatemeansmoretoyouthan probablyanynonprogrammercanunderstand.Itakegreatpleasureandprideisbuildingsomething thatstandsuptomylevelofquality,andlearningfromthepartsthatIknowneedtobeimproved. Itisn'teasytobuildqualitysoftware.Anewfeatureshereoramisunderstandingthere,andourhard workstarts,eversoslightly,toshowweakness.That'swhyit'simportanttohaveatrulysolid understandingofthefoundationsofgoodsoftwaredesign.Wemanagedtocoveralotofreal implementationdetails,butatahighlevel,herearemycoreprinciplesofgoodsoftwareengineering: Themostflexiblesolutionisthesimplest.I'veworkedonprojectswithflexibilitybuiltintothe systemupfront.It'salwaysbeenadisaster.AtthecoreofthisbelieflieinYAGNI,DRY,KISSand explicitness. Couplingisunavoidable,butmustbeminimized.Themoredependentaclassisonanother class,oralayeronanotherlayer,theharderyourcodeistochange.Istronglybelievethatthe pathtomasteringlowcouplingisviaunittests.Badlycoupledcodewillbeimpossibletotest andplainlyobvious. Thenotionthatdevelopersshouldn'ttesthasbeentheantisilverbulletofourtime.Youare responsible(andhopefullyaccountable)forthecodethatyouwrite,andthinkingthatamethod orclassdoeswhatit'ssupposedtoisn'tgoodenough.Thepursuitofperfectionshouldbegood enoughreasontowritetests,but,thereareevenbetterreasonstodoso.Testinghelpsyou identifybadcoupling.TestinghelpsyoufindawkwardnessinyourAPI.Testinghelpsyou documentbehaviorandexpectations.Testingletsyoumakesmallandradicalchangeswithfar greaterconfidenceandfarlessrisk. It'spossibletobuildsuccessfulsoftwarewithoutbeingAgilebutit'sfarlesslikelyandalotless fun.Myjoyswouldbeshortlivedwithoutongoingcustomercollaboration.Iwouldquitthisfield withoutiterativedevelopment.IwouldbelivinginadreamifIrequiredsignedoffspecifications beforestartingtodevelop. Questionthestatusquoandalwaysbeonthelookoutforalternatives.Spendagoodamountof timelearning.Learndifferentlanguagesanddifferentframeworks.LearningRubyandRailshas mademeafarbetterprogrammer.Icanpinpointthebeginningofmyjourneyinbecominga betterprogrammertoafewyearsagowhenIwasdeeplyentrenchedinthesourcecodeforan opensourceproject,tryingtomakeheadsoftailsofit.

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

WrappingItUp

79

Mylastpieceofadviceisnottogetdiscouraged.I'maslowlearner,andthemoreIlearn,themoreI realizehowlittleIknow.Istilldon'tunderstandhalfofwhatmypeersonCodeBetter.comblog about(seriously).Ifyou'rewillingtotakethetimeandtryitout,youwillseetheprogress.Picka simpleprojectforyourselfandspendaweekendbuildingitusingnewtoolsandprinciples(justpick oneortwoatatime).Mostimportantly,ifyouaren'thavingfun,don'tdoit.Andifyouhavethe opportunitytolearnfromamentororaproject,takeitevenifyoulearnmorefromthemistakes thanthesuccesses. Isincerelyhopeyoufoundsomethingvaluableinhere.Ifyouwish,youcanthankmebydoing somethingniceforsomeonespecialyoucareabout,somethingkindforastranger,orsomething meaningfulforourenvironment. RemembertodownloadthefreeFoundationsofProgrammingLearningApplicationforamore handsonlookattheideasandtoolsetsrepresentedinthisbook.

FoundationsofProgramming

CopyrightKarlSeguin

www.codebetter.com

You might also like