Just nu sitter jag och kodar på ett litet tillägg till kod som importerar filer in i det system som jag jobbar med. Kunden behöver importera filer även i ett nytt format som de inte har gjort tidigare. Att skriva koden för att importera det nya filformatet är enkelt och smidigt då jag bara behöver ärva av ett interface, implementera logiken och sedan använda mig av dependency injection i den klass som har hand om importering av filer. Allt detta är redan tänkt på innan så inga ändringar behöver göras.
Däremot så har den klass som har hand om själva importeringen även idag hand om validering. Detta är något som så klart bryter mot principen om separation of concerns men detta har inte varit till några problem hitills. Min kollega föreslog däremot att samtidigt som vi implementerade importeringen av det nya filformatet skulle vi kanske refaktorisera om och ta ut valideringen från den klass som har hand om importeringen av filer. Något jag tyckte var en ypperlig idé då detta är en ganska enkel refaktorisering som ändå gör väldigt mycket för att förbättra koden.
Min första tanke var att ta ut koden till en egen klass för att sköta valideringen av den inkommande datan. Ett ganska självklart val som nog skulle fungera bra. Vi har idag även varit ganska defensiva i koden och använt oss av design by contract mycket vilket man skulle kunna utöka för mer ren validering då de i mycket kompletterar varandra. Design by contract är en sorts validering. Detta kändes dock inte bra då den klass som har hand om att importera filer även i fortsättningen skulle ha hand om valideringen också. Jag tillät mig därför att utforska ämnet lite mer för att se ifall det fanns några bra idéer och saker man kanske bör tänka mer på.
Jag började att söka lite på Google angående data-validering i allmänhet samt i dotnet och till sist även i Java. Inget av det lilla jag hittade var direkt något som gav några bra resultat som kunde tillföra något nytt för att förbättra på hur jag tänkt lägga upp data-valideringen. Jag tog dock sen till mitt hemliga knep när den allsmäktige Google inte vill ge resultat, böcker! :-)
Det finns speciellt en bok som anses vara bland de man måste ha som utvecklare, Code Complete av Steve McConnell. Hyllad som bland det bästa man kan läsa av de mest kända i branschen (bl a Martin Fowler och Martin Booch med fler ger sina lovord i början av boken). Så har du den inte i bokhyllan än rekommenderar jag att på något sätt fixa fram den med en gång så du slipper skämmas. Den är också väldigt lättläst och riktar sig till alla utvecklare oavsett vilket språk man för tillfället råkar utveckla i med källor som skulle få den mest kräsne akademiker att åtminstonde känna sig nöjd och tillfreds med tillvaron. Men nu svävar jag ut och kommer ifrån ämnet.
I kapitlet med överskriften Defensive Programming (8) skriver Steve McConnell om just vikten av validering. Då jag inte vill göra en parafras citerar jag här på engelska:
For production software, garbage in, garbage out isn’t good enough. A good program never puts out garbage, regardless of what it takes in. A good program uses “garabage in, nothing out,” “garbage in, error message out,” or “no garbage allowed in” instead. By today’s standards, “garbage in, garbage out” is the mark of a sloppy, nonsecure program. [Code Complete, 2nd Edition. Page 188]
Inget nytt här egentligen vi vill inte ha skräp in, skräp ut vilket är ganska självklart. Kanske så självklart att man ibland glömmer av det. Men hur gör vi då för att gå tillväga med detta. Generellt sett så finns det tre saker att tänka på enligt Steve McConnell. Det första är att kolla alla värden på data som kommer utifrån. Detta kan vara allt ifrån att ett värde är inom en viss räckvidd, säg 1 till 10 endast eller att en sträng inte får innehålla SQL-kommandon eller kanske HTML. Det andra är att även kolla alla argument till metoder. Detta är egentligen samma sak men med skillnaden att vi inte alltid faktiskt får data utifrån utan från interna klasser och därför behövs det. Det tredje är att bestämma hur man tar hand om det skräp som kommer in. Alltifrån att inte göra någonting till att returnera ett felmeddelanden till användaren eller kanske använda sig av ett standardvärde för argumentet.
För koden som jag pratade om ovan behöver alltid användaren få tillbaka ett felmeddelande om något gått fel då filerna kommer in från webben. I Code Complete skriver också Steve McConnell att man bör använda sig av assertions vid saker som man anser aldrig skall hända och annan felhantering annars. I dotnet finns en Assert-metod i klassen Debug i System.Diagnostics som man kan använda sig av för detta. Den kollar ett villkor och spottar ut call stack om värdet är falskt. T ex om vi tar in en Stream till en FileImport-metod kan vi kolla efter ett null-värde:
Debug.Assert(stream != null, “File stream can not be null.”);
Det här fungerar mycket bra för att kolla villkor på argument till en metod före och efter. Ett annat sätt som vi med framgång använt är design by contract där vi har en klass Check som är sealed och innehåller fyra statiska metoder. Require, Ensure, Invariant och Assert, dessa kastar sedan olika exceptions om vilkoret inte uppfylls. T ex kastar Require ett PreconditionException och Ensure ett PostConditionException. Vi skulle t ex kunna kolla att ett argument till en metod är högre än noll:
Check.Require(argument > 0, “Argument must be higher than zero.”);
Och sedan i slutet av metoden om vi skall returnera ett värde att det är mindre än eller lika med tusen:
Check.Ensure(argument <= 1000, “Argument must be less than or equal to 1000.”);
Vi skulle nu också kunna lägga in loggning och annat i våra Pre- och PostConditionExceptions vilket blir ganska smidigt.
En viktig sak att tänka på också är att vara defensiv med defensiv programmering. Det kan lätt bli så att man kollar alla värden hela tiden vilket kan leda till både svårläst kod samt prestanda problem. Som i allt annat är lagom bäst. I Code Complete föreslår Steve McConnell att man bygger upp en så kallad barrikad (barricade) med valideringsklasser som tar hand om valideringen från inkommande externa objekt. Då behöver inte heller de interna klasserna validera data lika hårt. Bilden till höger beskriver detta.
För koden ovan valde jag att gå vägen med en valideringsklass som barrikad mot de interna klasserna. Genom att göra så fick jag även ut valideringen från den kod som hanterar importeringen av filer. En mycket lyckad refaktorisering som förbättrar koden på många sätt (high cohesion) och ser till att klasserna bara har hand om en enda sak (separation of concerns).
Om man inte vill skriva kod för validering själv så kan jag rekommendera att kolla in Enterprise Library och Validation Application Block. Jag började själv med att fundera på om det skulle vara en bättre väg att gå genom att avända det. Men jag valde bort det då det skulle göra att den lilla bit av kod som har hand om importering av filer skulle bli beroende av Validation Application Block samt de två andra delar av Enterprise Library som det i sig är beroende av. Har man däremot stora delar av ett system som behöver använda sig av validering på detta sättet så kan det vara en mycket god idé att använda sig av Validation Application Block. Koden finns också tillgänglig att titta på och ändra i om man skulle vilja det.