Kasta om dina beroenden (Dependency Inversion)

clock september 18, 2008 15:14 by author Ola Håkansson

Inom objekt-orienterad programmering finns det väldigt många koncept och principer. Dependency Inversion Principle (DIP) är en av dem och används ofta när man talar om Dependency Injection och Inversion of Control containers. Men man behöver inte använda en container så som Unity, Castle Windsor, Autofac eller Ninject för att få använda sig av DIP.

Principen med dependency inversion säger helt enkelt att vi skall kasta om de beroenden som vår kod har. För att inte göra en parafras så citerar jag här på engelska direkt från en artikel om DIP hos Object Mentor (PDF):

  • High level modules should not depend upon low level modules. Both should depend upon abstractions.
  • Abstractions should not depend upon details. Details should depend upon abstractions.

Så som alla goda principer inom objekt-orienterad programmering är själva språket eller plattformen man kodar på totalt oviktig. Principen med dependency inversion fungerar lika bra om du sitter och kodar i Java, .NET eller något annat. Däremot kan DIP anses vara lite vagt formulerad för oss som just nu sitter på .NET-plattformen. Vad betyder det här med moduler? Det närmsta vi skulle komma är nog assemblies. Och vad är det för abstraktioner vi vill göra egentligen?

Poängen med dependency inversion principle är att vi vill ha kod som är lätt att ändra på. Vissa kanske skulle anse att dålig kod är något som t ex konkatenering av strängar. Visst varje gång vi konkatenerar strängar så slängs objekt till garbage collector och StringBuilder är kanske att föredra. Men det är inte alltid dåligt att konkatenera strängar. Bra kod är kod som man tittar på utan att ta en djup suck och funderar över var man skall börja ändra någonstans eller hur mycket som kommer gå fel när man ändrar i den. Om du känner att det kommer bli jobbigt att ändra på en bit kod, att det blir oanade följder när en bit kod ändras eller att koden är omöjlig att återanvända då är något fel.

Säg att vi har en bit kod som ser ut såhär:

public class CurrencyConverter { public decimal Convert(decimal amount, Currency fromCurrency, Currency toCurrency) { var client = new CurrencyConvertorSoapClient(); decimal rate = client.ConversionRate(fromCurrency, toCurrency); return rate * amount; } }

Så vad är det som kan vara fel med den här lilla biten kod? Vad den gör är ju ganska självklart. Tar in ett belopp och konverterar från en valuta till en annan med hjälp av en webbservice. Väldigt enkelt.

När vi kodar har vi oftast två koncept eller mått för vad vi vill uppnå. Cohesion och Coupling. Med cohesion menas hur sammanhållen koden är och att den bara har ett väldefinerat ansvar. Koden ovan har flera olika ansvar, den hämtar både växlingskursen och räknar ut beloppet. Vad vi vill ha är hög sammanhålling (high cohesion). Koden har också en stark koppling (coupling) till en webbservice. Vad händer om vi vill hämta växelkursen från någon annan källa. En databas eller så kanske den kommer in till systemet från fil. Det går heller inte att enhetstesta koden. Växelkursen ändras hela tiden och att gå mot en webbservice tar oftast ganska lång tid. Vi har inte uppnått low coupling.

Att fixa detta är faktiskt väldigt enkelt. Genom att kasta om våra beroenden kan vi refaktorisera om koden så att den uppnår detta.

public interface IExchangeRateRetriever { decimal GetExchangeRate(Currency fromCurrency, Currency toCurrency); } public class WebServiceExchangeRateRetriever : IExchangeRateRetriever { public decimal GetExchangeRate(Currency fromCurrency, Currency toCurrency) { var client = new CurrencyConvertorSoapClient(); return client.ConversionRate(fromCurrency, toCurrency); } } public class CurrencyConverter { private IExchangeRateRetriever _retriever; public CurrencyConverter(IExchangeRateRetriever retriever) { _exchangeRateRetriever = retriever; } public decimal Convert(decimal amount, Currency fromCurrency, Currency toCurrency) { decimal rate = _retriever.GetExchangeRate(fromCurrency, toCurrency); return rate * amount; } }

Vad vi har gjort är helt enkelt att bryta ner vår klass CurrencyConverter till dess minsta beståndsdelar. Nu har den bara hand om att räkna ut beloppet och ingenting annat. Vi har skapat ett kontrakt (interface) som vi använder oss av. Genom att CurrencyConverter nu måste ta emot ett objekt som uppfyller det här kontraktet i konstruktorn så använder vi oss av constructor injection. Vi kodar mot ett gränssnitt och inte implementationen genom att vi använder oss av dependency inversion principle. Vi har också uppnåt high cohesion och low coupling. Det går nu också att enhetstesta koden genom att t ex bara skapa en fejkad klass som uppfyller kontraktet IExchangeRateRetriever.

public class FakeExchangeRateRetriever : IExchangeRateRetriever { public decimal GetExchangeRate(Currency fromCurrency, Currency toCurrency) { return 0.1507; } }

Dependency injection handlar om att bryta ut saker som inte skall förhålla sig till varandra. Koden för att konvertera beloppet skall inte ha något med växlingskursen att göra. Det är nu också oerhört enkelt att ändra på koden som så skulle behövas. Det enda problemet som kvarstår nu är hur vi får in beroenden till klassen nu. Detta är något som Inversion of Control containers löser och det sparar vi till ett annat inlägg!



Tips: Strängar över flera rader

clock juni 26, 2008 15:33 by author Ola Håkansson

Vid saker som hanterar långa strängar ser jag ofta kod som delar upp strängen på flera rader och sedan slår ihop dem igen genom att konkatenera. Säg att vi av någon anledning har en sträng med SQL så här:

string sql = "SELECT * FROM OrderLines" + "WHERE OrderID = @OrderID" + "AND Quantity > 100";

Att konkatenera strängar på det här sättet är dumt och onödigt då vi har så kallade verbatim string literals i C# sedan version 1.0 där vi kan skriva ut en sträng på flera rader. Detta görs genom att sätta ett @-tecken framför strängen:

string sql = @"SELECT * FROM OrderLines WHERE OrderID = @OrderID AND Quantity > 100";

På så sätt slipper vi konkateneringen och kan ha en sträng på flera rader. Verbatim string literals kan också vara mycket användbara till annat också då de behandlas ord för ord och inte tar hand om escape sequences. T ex för sökvägen till en katalog:

string path = @"C:\Verbatim\String\Literals";

Du hittar mer om verbatim string literals i C# Language Specification.


Validering och defensiv programmering

clock juni 24, 2008 14:09 by author Ola Håkansson

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! :-)

Mer...



CodeCamp

clock juni 15, 2008 18:36 by author Ola Håkansson

Under förra torsdagen (12 juni) befann sig större delen av oss på dotnet-gruppen på SweNug CodeCamp. Detta var det första i sitt slag i Sverige (inom dotnet) och blev oerhört lyckat. Mycket mingel och många riktigt bra sessioner. Det är riktigt kul att se att det finns så många duktiga dotnet-utvecklare som man kan dela och utbyta erfarenheter med.

Själv tog jag tillfället att göra en presentation om IronPython och dynamiska språk i .NET. CodeCamps är riktigt bra för den här sortens presentationer då man helst skall prata om kod man skrivit själv samt utbyta erfarenheter från det. Jag har själv inte använt mig av IronPython länge nu, bara lite till och från i några veckor så inga ordentliga applikationer än. Jag ville ändå visa på att dynamiska språk under CLR verkligen fungerar bra och att vi har en fullt fungerande implementation och Python att lägga till vår verktygslåda med IronPython.

Tyvärr gick tiden lite fort och jag hann inte med alla demos jag tänkt göra, knappt hälften. Samt ett litet missöde med att min uppkoppling mot nätet gick ner. Stort tack till den i publiken som räddade mig ur den situationen! Jag hade hoppats på att kunna få en diskussion de sista 10 till 15 minutrarna för att disktuera var det kan vara lämpligt att använda sig av dynamiska språk när man utvecklar. Det får bli en lärdom att ta med till nästa gång jag gör en presentation.

Jag har lagt upp presentationen på Google Docs så det går att bläddra i den här. Klicka på den för att få upp den i större format. Det finns även en zip-fil som jag lagt upp med all kod från presentationen. Då jag mestadels visade demon direkt i interpretatorn så blev det inte så mycket. Men demot för hur man använder Python i ASP.NET finns med samt en fil demo.py där mycket av det jag visade finns med. Presentationen ligger även med i zip-filen som PDF och PowerPoint, där går det även att läsa speaker notes som jag har skrivit till varje slide.

IronPython_Presentation.zip (1,62 mb)