Whitepaper Kwaliteit binnen Agile Paul Meek en Henri ter Steeg pm@linkitprojects.nl en hts@linkitprojects.nl Versie 1.0 (09-09-2010) Web linkit-projects.nl
Inleiding Agile is hot. Agile projecten beloven sneller software te leveren, die na elke iteratie onmiddellijk in productie kan. Daarnaast zou de software beter moeten zijn. Dus sneller en beter, daar moeten de klanten toch wel tevreden mee zijn? Business managers zijn tegenwoordig ook meer aware en vragen soms letterlijk aan hun ICT-afdeling om een meer agile aanpak. Toch lossen agile projecten in de praktijk vaak deze beloftes niet in. Het één op één toepassen van de regels van bijvoorbeeld Scrum blijkt vaak onvoldoende. Het goed begrijpen van de principes van agile software development kan al een hoop ellende voorkomen. Theoretische kennis (opgedaan in een eenmalige cursus al dan niet met certificering) en het dogmatisch toepassen van de agile procedures en regeltjes alleen is onvoldoende garantie voor succes. Elk project kan weliswaar putten uit dezelfde pot met agile methoden, technieken en tools, maar wat als de ingezette set in de context van een specifiek project onvoldoende blijken te werken? In agile software development projecten wordt een applicatie iteratief gebouwd. Hierbij wordt gestuurd op business value : de functionaliteit (en ook non-functionals) die voor de business de meeste waarde opleveren, worden als eerste gebouwd. Uiteraard kan een applicatie pas in productie worden gegeven als de applicatie van voren naar achteren werkt. Vandaar dat we ons in de eerste iteraties richten op de architectural baseline. Meer hierover kunt U vinden in de eerste whitepaper van dit drieluik. Het iteratief opzetten van een applicatie houdt in, dat de kans groot is, dat we bepaalde stukken functionaliteit, die we in eerdere iteraties hebben gebouwd in latere iteraties zullen raken. Ook zal het ontwerp van de applicatie gedurende de uitvoering van de iteraties voortdurend evolueren. De snelheid van het team wordt gemeten in nieuwe opgeleverde werkende software (ofwel functionaliteit). We kunnen alleen voldoende snelheid behouden, indien we niet te veel tijd kwijt zijn aan bug fixing vanuit eerdere iteraties. Daarnaast moet het integreren van nieuwe functionaliteit in de bestaande software zonder al te veel extra inspanning mogelijk blijven. Dit kan alleen als de bestaande basis (i.e. reeds gebouwde software) van voldoende kwaliteit is en blijft. Dus moet er gedurende de uitvoering van de iteraties voortdurend aandacht te worden besteed aan kwaliteit van de software. Lean development kent als één van haar principes build quality into the system. Door dit principe op een goede wijze toe te passen, kunnen we waarborgen dat de software van voldoende kwaliteit blijft gedurende alle iteraties in een project, een ook daarna in het onderhoud. Deze whitepaper is het tweede deel in een drieluik over kwaliteit binnen agile sofware development. Hoe kunnen we ervoor zorgen dat de sofware, die een agile project elke iteratie opnieuw oplevert, inderdaad van hoge kwaliteit is. Immers, pas dan heeft de business lead (product owner in Scrum terminologie) werkelijk de keuze om na elke iteratie in productie te gaan met de opgeleverde software. Daarnaast dient een team snelheid te kunnen vasthouden door elke iteratie weer voldoende nieuwe functionaliteit op te leveren. In deze whitepaper gaan we verder in op het kwaliteitsaspect building the thing right. De vorige whitepaper behandelde het aspect building the right thing. De derde en laatste whitepaper zal ingaan op fitness for change. 2
Terminologie Alvorens in te gaan op het antwoord hoe we kwaliteit kunnen garanderen in een agile project omgeving, dienen we eerst de volgende termen nader te verklaren: agile en kwaliteit. Voor een korte introductie in agile ontwikkelen, zie de bijlage van de vorige whitepaper (Building the right thing). Kwaliteit in het heden is geen garantie voor kwaliteit in de toekomst Wat is kwaliteit? Een simpele, algemene definitie van kwaliteit is de mate waarin het geleverde aan de verwachtingen van de klant voldoet. Volgens ISO 8402 is kwaliteit: het geheel van eigenschappen en kenmerken van een product of dienst dat van belang is voor het voldoen aan vastgestelde of vanzelfsprekende behoeften. De auteur Joseph Juran beschrijft kwaliteit als fitness for use en Prince2 hanteert de definitie fitness for purpose of conforms to requirements. Kwaliteit ( Fitness for purpose ) kan worden gesplitst in: Building the right thing In de eerste plaats moet je de juiste requirements ontwikkelen ( building the right thing ), en deze moet je op de juiste wijze implementeren ( buidling the thing right ). Binnen Linkit projects is fitness for purpose alleen niet voldoende. Het is mooi, dat de software nu precies doet wat de klant ervan verwacht. Maar wat als de verwachtingen van de klant wijzigen, bijvoorbeeld als gevolg van veranderingen in de markt? Iedereen kent de uitspraak resultaten behaald in het verleden bieden geen garantie voor de toekomst. Met kwaliteit is dit net zo: kwaliteit in het heden is geen garantie voor kwaliteit in de toekomst. De software dient te kunnen meebewegen met de eisen en wensen van de klant. En dan wel in het tempo van die klant! Om software van blijvende waarde te laten zijn voor de klant, dient de software mee te kunnen bewegen met de eisen en wensen van de klant. En dan wel in het tempo van de klant! En niet alleen na het project, maar ook al gedurende het project Dit houdt in dat we naast fitness for purpose ook fitness for change als een belangrijke pijler van kwaliteit zien. 3
Binnen agile software development bestaan diverse feedback cylci op verschillende niveau s. Feedback cycles Hét middel om te bepalen of we blijvend op de goede weg zijn is terugkoppeling. Terugkoppeling van gebruikers, beheerders, testers, mede-ontwikkelaars, etc. Een belangrijk onderscheid tussen agile en traditionele ontwikkelprocessen, zoals waterval en het V-model, zijn de korte iteraties (sprints in Scrum) binnen agile. We kunnen de feedback cycli binnen agile presenteren als schillen, zoals bij een ui. Meer naar buiten zijn de feedback cycli langer en komt de feedback voornamelijk van business community, gebruikers en product owner. Dit is voornamelijk feedback op het kwaliteitsaspect building the right thing. Meer naar binnen toe zijn de feedback cycli korter en komt de feedback meer vanuit het team zelf en komt daardoor het kwaliteitsaspect building the thing right meer in beeld. In dit stuk zullen de feedback cycli pair programming, TDD (test driven development), continuous integration en de daily standup ter sprake komen. Vanuit een technische invalshoek komen achtereenvolgens test driven development en continuous integration aan bod. Het uitgangspunt hierbij is de veranderingsbehoefte ten opzichte van een eenmalige oplevering. Vervolgens wordt de rol van pair programming en de daily standup belicht vanuit het building the thing right perspectief. 4
Hoe zorgen we ervoor dat (continue) veranderingen geen negatieve invloed hebben op de kwaliteit van de bestaande werkende software? Eén van de belangrijkste uitdagingen bij building the thing right is het combineren van werkende software met het reageren op veranderingen. Bij iedere iteratie dient immers werkende software opgeleverd te worden. Bovendien kan de klant besluiten (delen van) de reeds opgeleverde software in een volgende iteratie te wijzigen of uit te breiden. Voor ontwikkelaars en de te bouwen software heeft dit grote gevolgen. Software gebouwd voor de ene functie staat immers niet los van de software voor de andere functie. Er is vaak sprake van code die gedeeld wordt door verschillende functies. Dit kan variëren van code voor het domein model, herbruikbare componenten in de presentatie laag tot utility functies voor database toegang. Dit hergebruik van code zorgt zowel voor reductie van de kosten voor de ontwikkeling als voor consistent gedrag van de applicatie. Kortom, een goede zaak. Echter, als hergebruikte code wordt aangepast voor de ene functie, heeft dit soms onbedoelde (en ongewenste) consequenties voor andere functies. Dit is de belangrijkste oorzaak voor het ontstaan van defects (bugs), want de code van een applicatie wordt al snel complexer dan de beste ontwikkelaar kan bevatten. Bij de waterval methodiek is het lastig om correct werkende applicatie op te leveren, door wijzigingen tijdens het ontwikkelproces en integratie van diverse componenten. Bij agile werken wordt dit probleem verveelvoudigd, aangezien we na iedere iteratie werkende software op willen leveren! Hier zullen we dus structureel een oplossing voor moeten vinden. Een veel gebruikte oplossing is het zo veel mogelijk automatiseren van testen. Testen Bij de waterval methodiek was de oplossing simpel: testen! De gebruikers en hiertoe opgeleide testers testen de applicatie door na oplevering alle functies door te testen. Dit zouden we natuurlijk ook kunnen doen na iedere iteratie. Zoals echter uit bovenstaande relaas blijkt, is het testen van de functies die in de laatste iteratie zijn opgeleverd echter niet voldoende. De gehele applicatie zal opnieuw doorgetest moeten worden. Een kostbare en tijdrovende operatie. Een veel gebruikte oplossing voor dit probleem is het automatiseren van het testen. Ok, probleem opgelost! We gaan alle functionele tests gewoon automatiseren. Tools genoeg tegenwoordig voor het testen van (bijvoorbeeld) web applicaties: Selenium, Watir, WatiN, WebDriver, etc. Al snel komen we er dan achter, dat geautomatiseerd testen via de user interface zo z n eigen issues heeft. Het uitvoeren van de testen duurt vaak lang (bij enkele honderden testen hebben we het vaak al over een doorlooptijd die uitgedrukt wordt in uren). Bovendien falen deze tests vrij eenvoudig door simpele veranderingen in de layout van de pagina. De vraag is: kunnen we op een ander niveau stabielere en snellere tests definieren? 5
Middels een unit test wordt het verwachte gedrag van een klein stukje software gedocumenteerd en getest. Unit tests Dit zijn testen die door de ontwikkelaar worden gemaakt door het schrijven van een testfunctie. Hiermee wordt een klein stukje software getest. Of eigenlijk: het verwachte gedrag van een functie wordt gedocumenteerd en gecontroleerd. Het gebruiken van unit testen heeft als voordeel dat deze snel kunnen worden uitgevoerd en dat ze stabieler zijn dan testen op de user interface. Nadeel is, dat ondanks (vele) unit tests de applicatie als geheel alsnog niet hoeft te werken. Daarom wordt vaak een combinatie van unit tests en functionele tests op de user interface gebruikt. Op de user interface wordt dan het happy path getest, terwijl de uitzonderingssituaties d.m.v. unit tests worden uitgevoerd. De term Test Driven Development staat voor een vorm van ontwikkelen, waarbij eerst de (falende) testen worden geschreven. Daarna wordt de minimale functionaliteit toegevoegd om de test te laten slagen. Het toevoegen van functionaliteit vereist dat er eerst een test wordt toegevoegd. Het technisch ontwerp van de code wordt vervolgens afgeleid van de benodigde functionaliteit. Voordeel van deze manier van werken is, dat de code ook goed bruikbaar is. Het werd immers al vanuit de testen gebruikt! Wanneer zijn we volledig, wanneer zijn we klaar? Volledigheid De volgende vraag die we onszelf kunnen stellen is, hoe volledig we zijn met bovenstaande testen. Hoeveel van de functionaliteit wordt eigenlijk geraakt door onze testen? Als we dit kunnen bepalen, weten we ook waar we het grootste risico lopen op het ontstaan van fouten. Dit noemen we code coverage. Er zijn tools om dit te meten. Tijdens het uitvoeren van de testen wordt gemonitored welke statements zijn uitgevoerd. Het resultaat wordt uitgedrukt in een percentage. 85% code coverage betekent dus dat 85% van alle statements in onze (productie) code is uitgevoerd door onze testen. De vraag is of 100% code coverage een doel moet zijn. Code coverage is een maatstaf om inzicht te krijgen in de mate waarin de applicatie is getest. Het schrijven van tests kost echter ook tijd en deze moeten bovendien worden onderhouden. Er dient een afweging te worden gemaakt tussen de hoeveelheid testen en het risico dat men loopt. Ook al is 100% code coverage vereist, het is niet de holy grail. Code coverage gaat namelijk alleen over de zelf geschreven code en niet over de executiepaden in de aangeroepen framework- of library code. Een ander executie pad in de aangeroepen code, kan namelijk ook nog tot ongewenste resultaten leiden. Een voorbeeld hiervan is het statement: return x / y; Als we dit in de testfunctie alleen testen met x=1 en y=2, dan is er 100% coverage voor dit statement. Wordt de functie in productie uitgevoerd met x=1 en y=0, dan treedt er in de meeste talen een divide by zero fout op. Deze fout wordt immers vanuit het onderliggende framework gegenereerd. De afhandeling van deze fout is niet getest, ondanks de 100% coverage van de eigen code! 6
Hoe snel zijn aanpassingen doorgevoerd? Onderhoudbaarheid De mate waarin de applicatie geautomatiseerd wordt getest, bepaalt hoe snel nieuwe bugs boven water komen. Het bepaalt echter niet, hoe snel aanpassingen op de code kunnen worden doorgevoerd. In een agile project is dat zeer relevant, het onderhoud op bestaande code begint immers al bij de 2e iteratie! Natuurlijk is het erg moeilijk om een abstract concept als onderhoudbaarheid te bepalen van de code. Er zijn wel een aantal software metrics die een correlatie hebben met de onderhoudbaarheid. De meest bruikbare voor ons is de Cyclomatic Complexity. Dit is een mooie naam voor een eenvoudig principe. De cyclomatic complexity is (bij benadering) gelijk aan het aantal mogelijke executiepaden door een functie, dus ieder control statement (if, for, while) hoogt de complexity met 1 op. Er is een duidelijk verband gevonden tussen de complexiteit en het aantal fouten. Onderzoek wijst uit dat bij een complexiteit van 11 de kans op fouten 28% is, dit 50% wordt bij een complexiteit van 38 en de kans op fouten 98% is bij een complexiteit van 74. Door het SEI (Software Engineering Institute) worden de volgende richtlijnen aangehouden omtrent cyclomatic complexity: Complexity Risico 1-10 eenvoudige functie, weinig risico 11-20 meer complexiteit, matig risico 21-50 complexe functie, hoog risico >= 51 niet te testen functie, erg hoog risico Zelf hanteren we bij nieuwe projecten een harde limiet op de complexiteit van 10. Dit levert soms andere technische keuzes op (bijv. bij case statements). Dit incidentele extra werk weegt echter ruimschoots op tegen de beter onderhoudbare code. Het toepassen van alleen deze metric om de onderhoudbaarheid te borgen, is zeer effectief. Dit komt doordat de relatie tussen oorzaak (de code) en gevolg (de complexiteit) eenduidig is. Bovendien is er een duidelijke grens te stellen. In het begin vraagt dit enige creativiteit van ontwikkelaars, daarna zijn de patronen duidelijk. 7
Hoe krijgen we voldoende en op tijd feedback op de kwaliteitseisen die we stellen aan de software? Continuous Integration Nu we onze software testen met behulp van unit tests en de kwaliteit van de code kunnen bepalen middels metrics, wordt het tijd om dit te valideren. Als we het aan de individuele ontwikkelaars overlaten om de testen te draaien, komt deze er al snel achter dat er testen falen waar zijn eigen wijzigingen niet de oorzaak van zijn. En dan de moeite nemen om uit te zoeken welke testen wel falen als gevolg van de eigen wijzigingen is vaak een te hoge drempel. Wat maakt het nog uit of er 16 of 17 testen falen? We kunnen dit beter op een structurele wijze oplossen. Hiervoor kunnen we een zgn build server gebruiken. Dit is een proces dat meestal op een aparte (virtuele) server draait. Dit proces monitort de centrale software repository op wijzigingen. Zodra er een wijziging is aangebracht, wordt op de build server de software gecompileerd en worden de testen gedraaid. De test coverage en complexity worden bepaald. Het resultaat is dat de build ofwel is geslaagd, ofwel is gefaald. Een build is alleen geslaagd als de software met succes kan worden gecompileerd, alle testen succesvol zijn uitgevoerd, de test coverage boven de afgesproken threshold zit en de complexity kleiner is dan de afgesproken threshold. Als een van de stappen faalt, wordt de volledige build beschouwd als gefaald. Hiernaast een voorbeeld van een setup zoals die voor een.net project is gehanteerd. De feedback vanuit de build server werd hier gegeven door Homer Simpson sounds op de developer machines: Geslaagde build: Woohoo! Gefaalde build: WAAAH! Het is aan te raden het volledige team direct feedback te geven als een build faalt d.m.v. een auditief of visueel signaal. De consequenties voor het team dienen duidelijk te zijn: degene die de betreffende wijziging in de repository heeft aangebracht, zorgt ervoor dat dit zo snel mogelijk wordt hersteld. De overige teamleden voeren geen wijzigingen door in de repository, totdat de build weer is geslaagd. Het is bovendien goed gebruik om in het ontwikkelteam af te spreken, dat men pas naar huis gaat als de build weer geslaagd is. Er is niks zo frustrerend als s ochtends binnenkomen en constateren dat degene die gisteren de build falend heeft achtergelaten, vandaag pas tegen de middag binnenkomt. Veel ontwikkelaars draaien om deze reden ditzelfde build proces vaak op hun eigen ontwikkel PC, voordat de centrale repository wordt bijgewerkt. 8
Nuke and Pave Een van de eisen aan een build server, waarbij testen worden uitgevoerd, is dat er geen afhankelijkheden zijn. We willen dat het resultaat van de testen voorspelbaar is. Als bijvoorbeeld getest wordt met gegevens uit een database, mag het niet zo zijn dat testen falen omdat iemand iets in deze database wijzigt. Een oplossing hiervoor is de zogenaamde nuke-and-pave strategie. Hierbij wordt als onderdeel van het build proces de database (structuur) verwijderd (nuke) en vervolgens weer opgebouwd (pave), inclusief de data die nodig is voor het draaien van de testen. Van belang is ook dat deze database alleen wordt gebruikt voor het build proces. Ook als dit build proces lokaal op de machines van ontwikkelaars wordt gedraaid, is het van belang dat iedere ontwikkelaar zijn eigen database omgeving heeft. Hoe testen we koppelingen met externe systemen? Er kunnen natuurlijk ook afhankelijkheden van externe systemen zijn. Om deze systemen in een geautomatiseerde (integratie)test mee te kunnen nemen, moet hiervan gebruik gemaakt worden op een voorspelbare manier. Als dit niet mogelijk is, zullen we naar andere oplossingen moeten kijken. Er wordt dan vaak gekozen voor het gebruiken van een stub. Dit is een stukje dummy software, dat dezelfde (software) interface implementeert en waarvan het gedrag wel voorspelbaar gemaakt kan worden. Door het aanpassen van de configuratie wordt tijdens de geautomatiseerde tests de stub gebruikt in plaats van het echte externe systeem. Het belang van oplossingen voor externe afhankelijkheden is erg groot. Wanneer de geautomatiseerde testen niet reproduceerbaar zijn, wordt een falende build al snel niet meer serieus genomen door de ontwikkelaars. En dan wordt er al snel ook geen actie meer ondernomen op echte fouten... Refactoring Als de build server draait met de bijbehorende unit testen, dan kan er ook serieus begonnen worden met refactor werkzaamheden. Vaak is het zo, dat naarmate er meer functionaliteit wordt gebouwd, er een andere (technische) structuur wenselijk wordt. Refactoring is het herstructureren van de code, zonder dat er nieuwe functionaliteit wordt geïmplementeerd. Met een goede suite aan unit tests kan dit veilig worden uitgevoerd. Achterstallig technisch onderhoud wordt ook wel Technical debt genoemd. Zonder refactoring krijgen we al snel een situatie waarin niemand nog structurele aanpassingen in de code durft te maken. Er worden dan steeds meer puisten aan het systeem gebouwd. Dit wordt ook wel legacy code genoemd. Het tijdig uitvoeren van refactoring zorgt ervoor, dat het ontwikkelteam ook op langere termijn met dezelfde snelheid kan doorontwikkelen. 9
Pair programming lijkt in eerste instantie inefficiënt, maar levert op termijn tijd op. Pair Programming Een andere techniek die de kwaliteit van de software kan verhogen is Pair Programming. Hierbij zitten twee ontwikkelaars achter een scherm en ontwikkelen samen een stuk software. Dit lijkt inefficiënt, immers nu kost dezelfde software tijd van 2 mensen in plaats van 1. Als de snelheid van ontwikkelen zou worden bepaald door de snelheid van typen, zou pair programming een slecht idee zijn. Het blijkt echter, dat pair programming verschillende voordelen heeft. Ontwikkelaars maken tijdens het bouwen van software ieder uur veel keuzes. Veel van deze keuzes zijn impliciet en intuïtief. Pair programming helpt ontwikkelaars om deze keuzes expliciet te maken, doordat de 2e persoon deze keuzes ter discussie stelt. Hierdoor wordt een bewustere en vaak kwalitatief betere ontwerp keuze gemaakt. Daarnaast zitten ontwikkelaars vaak in een bepaalde frame of mind, waardoor ze mogelijke bugs of andere implementatie mogelijkheden niet (kunnen) zien. Ook zijn ontwikkelaars bij pair programming veel meer gefocussed bezig. Houd er rekening mee dat de eerste dagen pairen vaak als zeer intensief en vermoeiend worden ervaren. Tevens is pair programming een prima manier om kennis binnen het ontwikkelteam te verspreiden. Zeker als er dagelijks van partner gewisseld wordt, verspreid de kennis omtrent de implementatie en de ontwerp beslissingen zich erg snel. Daily standup De daily scrum is een dagelijkse stand-up meeting, waarin ieder projectlid de volgende vragen beantwoordt: Wat heb je gisteren gedaan? Wat ga je vandaag doen? Welke problemen ondervind je? De dagelijkse standup meeting is ook belangrijk voor het faciliteren van een hoge kwaliteit. De uitgevoerde en de nog uit te voeren werkzaamheden worden benoemd, alsmede de punten waar men technisch niet uitkomt. In de standup komen overeenkomstige activiteiten van anderen aan het licht en kunnen afspraken over samenwerking en/of pair programming worden gemaakt. Ook komt de noodzaak tot inhoudelijke en ontwerp discussies vaak aan het licht tijdens de standup. De daily standup is een goed forum on technische kenlpunten aan de orde te stellen Door gebruik te maken van de diversiteit van de groep, kunnen technische blokkades door ideeën van anderen snel worden opgelost. Uiteraard is hiervoor van groot belang dat de groep elkaar vertrouwd, zodat er geen schroom is om technische knelpunten te benoemen... Volgende aflevering In de volgende aflevering gaan we verder in op het kwaliteitsaspect fitness for change. 10
Paul Meek is managing director van LinkiT projects. Hij heeft meer dan 20 jaren ervaring in de software ontwikkeling in het algemeen en agile software ontwikkeling in het bijzonder. Hij werkt in projecten graag nauw samen met klanten om voor hen software op te leveren, die echt waarde toevoegt. Henri ter Steeg is agile coach, scrum master en development teamlead bij LinkiT projects. Hij heeft meer dan 20 jaar ervaring in hands-on software ontwikkeling. Het leiden en coachen van agile development teams is zijn grote passie. LinkiT projects voert software ontwikkelingsprojecten uit van A tot Z. Hierbij maken we gebruik van agile projectmanagement methoden en ontwikkeltechnieken (Scrum, Lean, XP, Kanban). Daarmee garanderen we onze klanten, dat in onze projecten snel resultaten kunnen worden getoond en maximale business value wordt opgeleverd. We doen dit al vanaf de oprichting in 2001, en kunnen daarmee worden gezien als een van de pioniers op het gebied van agile software ontwikkeling in Nederland. Projecten voeren wij uit op basis van "time & materials", maar ook op basis van fixed date en/of fixed price. Hierbij maken wij bij voorkeur gebruik van OO-technologieën, zoals Java,.NET en Ruby/RAILS, waarin we ons de afgelopen jaren hebben gespecialiseerd. Uiteraard blijven wij continu op de hoogte van nieuwe ontwikkelingen in de markt. Naast projecten doen wij ook coaching en consultancy op het gebied van agile project management, agile methoden en technieken en software ontwikkeling in het algemeen. We helpen hierbij klanten te transformeren van de meer traditionele methoden naar een agile werkwijze. Tevens geven wij trainingen/workshops, en verzorgen wij presentaties op (internationale) conferenties op het gebied van agile projectmanagement en software ontwikkeling. LinkiT projects is onderdeel van de LinkiT enterprise. 11