Strukturera dina klasser och dina rutiner på ett effektivt sätt.
Förord
Förkunskaper: Grunder i objektorientering eller principiell förståelse för detta. Kunna läsa/förstå C# kod. Syftet: När jag skriver artiklar är mitt syfte inte enbart att lära ut nya och intressanta saker utan att även väcka en diskussion runt det jag skriver. Så kom gärna med synpunkter, idéer och tankar, längst ner finner ni länkar till diskussionsforum ang. denna artikel och kommentera gärna om ni skall sätta betyg. Tänk även på att exemplerna är inte real-live exempel utan förenklade exempel för att försöka förklara det tankesätt eller mönster som finns, i ett verkligt projekt skulle koden troligen vara mer komplex, så se mer vad texten säger än den tekniska detaljen i koden. I många år har vi utvecklat datasystem, vissa mer robusta än andra. En sak vi alla har gjort minst flera tusen gånger om inte ännu fler, är fel i koden, buggar, förstörelser, felberäkningar m.m. Jag byggde en gång en kundregistreringsida i ett affärssystem . Där satte jag kravet på lösenorden att de inte fick innehålla specialtecken såsom & ? ’ ” etc. En dag så fungerade inte login för ett fåtal kunder. Självklart kliade man sig i huvudet och undrade vad det var för fel. Efter lite testande så visade det sig att kunderna lyckats att ändra sitt lösenord och angivit tecken jag inte tillät, i det administrativa kundregistret. Jag gick då över till en kollega och frågade om användarna kunde ändra lösen i hans del av systemet, själva affärssystemet i sig. Jag fick svaret ja och ställde följdfrågan om han tillät alla tecken. Han svarade att det gjorde han. Då bad jag honom sätta samma begränsningar som jag gjort. Tyvärr ansåg han detta onödigt och ville ha bevis på varför han skulle göra detta. Han tyckte att jag lika gärna kunde öppna upp alla tecken. Jag skrev då in en script-tag som nytt lösen och sparade. Scriptet exekverade. De hade nämligen valt att visa lösenordet i klartext. Jag frågade om vi var i en testmiljö och fick ja till svar. Så jag skrev med glädje in ’—som lösen. (PLEASE Don’t try this At home ;-) ) och bad honom köra detta. Inget speciellt hände så jag hånades lite. Dock ringde support efter fem minuter och undrade varför inga kunder kunde logga in. Det visade sig att det inte alls var en testmiljö utan för honom var en testanvändare samma sak som något man kunde testa vad som mot utan fara. De tecken vi la in i passwordfältet dödade hela WHERE satsen i en då dynamiskt uppbygg sql-sats. Det hela ledde till att alla användare fick testanvändarens användarnamn och ett tomt lösenord. Vad jag vill säga med detta är att det är viktigt att man har en bra struktur och ett bra sätt att effektivisera sin kod på vid byggandet av system. Hade organisationen varit bättre på detta företag och vi hade kommunicerat mellan projekten. Om den systemansvarige hade krävt bra struktur och arkitektur hade vi alla nyttjat samma klass och rutiner för att logga in samt ändra lösen. Då hade inte detta problem behövt uppstå. Ett problem som också kostade en hel del pengar. Jag har tidigare publicerat en artikel hur man kan mappa om sin specifikation till en objektorienterad kod. Ni finner den här. Mappa din specifikation (User Story) till kod. (Innehåll
»»
»
»
»
»
»
»
»
»
»
Relaterade artiklar
» Mappa din specifikation (User Story) till kod.
Inkapsling
När du designar en klass är inkapslingen ett av de viktigaste kraven. Vad som menas med inkapsling är att kapsla in så mycket logik som möjligt i klassen och göra den gömd för andra. Där öppnar man bara upp de interface (metoder) en användare behöver känna till. Jag har sett mycket kod under min tid där man alltid satte sina metoder som publika vare sig de var unika för en annan metod eller ej. Om vi tar ett exempel. (som ni vet älskar jag att visa exempel. ;-) ) Vi har en XMLBuilder klass, men denna vill vi skapa XML-data, genom att bara ge text som input. För att inte göra den för avancerad så säger vi att denna bara kan ge en struktur på följande sätt. Idén här är att förklara tankarna runt bygget inte att ge en fungerande korrekt lösning. Exempelerna i denna artikel skall inte tas för allvarligt då de varken fungerar eller bör byggas på detta sätt. Det är syftet med texten som är det viktiga här och inte kodexemplerna i sig.
hej
Vi behöver här en metod som skapar start- och end-tag samt en funktion som lägger till texten i mitten, För att få fram denna sträng kan vi skriva våra anrop på följande sätt:
xmlBuild.AddTag(”data”);
xmlBuild.AddText(”hej”);
string result = xmlBuild.ToXML();
Inne i vår XMLBuild klass har vi tre metoder som anropas av ovanstående metoder, en som sätter start-tag och en som sätter end-tag.
public void SetStartTag(string value)
{
xmlStringBuilder.Append(”<”);
xmlStringBuilder.Append(value);
xmlStringBuilder.Append(”>”);
}
public void SetEndTag(string value)
{
xmlStringBuilder.Append(””);
xmlStringBuilder.Append(value);
xmlStringBuilder.Append(”>”);
}
zmlStringBuilder är en instans av en StringBuilder i class scopet. Valde att göra så för att inte göra exemplet för tungt. I vanliga fall hade vi säkerligen valt att skicka in denna i interna metoder för att skapa
upp vårt innehåll.
Sedan har vi vår metod för att sätta ett vanlig värde mellan dessa. I mitt exempel texten ”hej”.
public void SetText(string text)
{
xmlStringBuilder.Append(text);
}
vår ToXML metod nyttjar sedan dessa för att få fram dess xml sträng.
public string ToXML()
{
this.SetStartTag(this._addedTagString);
this.SetText(this._addedText);
this.SetEndTag(this.addedTagString);
return ( xmlBuilder.ToString() );
}
[Note: _ (underscore) är en notation jag använder för globala fält, detta är en gammal notation som jag tror härstammar från C språken. Jag gör detta för att skilja på lokala vs globala fält i en klass.]
Som ni ser, satte jag mina tre metoder som nyttjas av ToXML som publika. När du som utvecklare skall använda denna klass så kommer du få se ett interface i stil med:
AddTag(string)
AddText(string)
SetStartTag(string)
SetEndTag(string)
SetText(string)
ToXML()
Hur vet du vilka av dessa metoder du skall använda dig av? Hur kan du garantera att det du gör görs på rätt sätt? SetText(…) och AddText(…) vilken av dessa skall du använda dig av? Som regel skall man inte behöva kolla i koden för att få dessa svar. Det är nu vikten av inkapsling kommer. Om vi sätter Set-metoderna till private så kommer vi bara få se
AddTag(string)
AddText(string)
ToXML()
Detta gör det genast lättare för en utvecklare att förstå hur denna XMLBuild klass skall användas. Det framgår tydligt att man Adderar en tag och sedan text. Observera nu att detta bara är ett exempel och ingen silver-bullet kod.
Metoder i en klass som inte behöver användas av en användare bör skyddas och inkapslas väl för att öka förståelsen för klasserna och hur de skall användas. Ta exempelvis SqlCommand som har en hel hög privata metoder såsom denna:
private _SqlRPC BuildPrepExec(CommandBehavior behavior)
Om denna var satt som public skulle du då enkelt och smidigt förstå vad den var till för? Kan säga så här: du hade inte kunnat använda den. Inte ens om du skickade in en behavior som parameter. Den nyttjar en rad inkapslade variabler som inte är exponerade.
Nu råkar jag veta detta, men det är inte meningen att jag skall känna till en klass rutiner, när det enda jag är intresserad av är att använda dem jag behöver, för det jag vill göra. Hur den fungerar internt hjälper inte mig att använda den bättre. En metod som skall visas publikt skall vara en metod som garanterar ren funktionalitet genom anrop. Man skall helst inte bli tvingad att sätta en massa värden för att den skall fungera. Undantag finns ju förstås.
När vet man om ens klass och rutiner är självförklarade?
Jag brukar säga så här, om jag ger en användare en klass som jag byggt med en rad publika metoder, om denna inte förstår hur koden skall användas med det interface koden ger så kan jag ligga illa till. Då är det möjligt att min kod är dåligt byggd. Om användaren av min kod kommer till mig och säger att han/hon inte förstår koden är det viktigt att jag ändrar det som verkar klurigt så det är mer självförklarande. exempelvis genom att byta namn på metoden och kanske tom lägga till en del metoder och ta bort andra. Jag skall inte behöva berätta hur koden skall användas då är det lätt kan tappa momentet av självförklaring. Om användaren fortfarande inte förstår hur koden skall användas, fast jag gjort den så självförklarande jag bara kan, då är det dags att förklara hur den är tänkt att fungera. Hade jag förklarat med en gång hur man skall använda min kod hade användaren bara för stunden kommit ihåg hur den skulle användas men efter några månader eller kanske redan efter en vecka stått där igen och inte fattat vad klassen gör. Det är viktigt att lägga till ett moment av inlärning.
Kontrakt
I vår trevliga (hum.ja.) värld vi lever i så existerar saker såsom bevis, kvitton, fakturor och kontrakt. Vad är ett kontrakt när vi pratar om en klass? Ett kontrakt är som alla vet något som måste uppfyllas och som först även utlovas. I dag har vi inte direkt stöd för effektiv kontrakthantering i klasser inom de stora .net språken så som C# och VB .Net. Eifel är ett språk som länge haft förenklat syntaxstöd för hantering uppbyggnad av din kod efter kontrakt. Ett kontrakt för en klass är de regler och den pålitlighet vi utlovar en användare att vår kod skall ha. Där ingår klassens interface och signatur, felhantering och garantin att man inte kan göra fel. (Nu är det ju så att vi inte kan bygga helt felfri kod men vi kan göra så gott vi kan och där hjälper kontraktet oss.) Att bygga efter ett kontrakt kallas i finare form Design by Contract (DbC). Kontraktet speglar för det mesta det vi har skrivet i vår specifikation att vår klass skall göra och inte skall få göra. Ex, har vi en time-klass skall vi inte kunna sätta timmar till något över 24, om vi tillåter 25 så har vi gjort fel och brutit mot kontraktet. Timmar kan inte vara mer än 24 och då skall inte ett värde större än 24 godkännas. Inte heller värden under 1 förutsatt att vi inte vill ha 00 istället för 24. Vi har två olika hanteringar när vi designar en klass by contract. För rutiner har vi olika conditions och för klassen i sig något vi kallar invarant. Låt mig förklara dessa vi börjar med conditions.
Conditions
När vi nu har skyddat vår XMLBuilder och sett till så bara rätt metoder finns publika måste vi gå vidare till nästa steg. Vi har nu gjort en struktur som i princip inte kan användas fel. Om användaren gör på rätt sätt kommer allt gå bra. Dock skall vi inte lita på att utvecklaren vet vad som är rätt eller fel. Om användaren gör fel måste ju detta tydligt förklaras. Vad vi då kan ta hänsyn till är nått vi kallas för precondition. Hittade ingen bra översättning på detta, förutsättning kanske? När vi ropar på våra metoder så som AddTag(string value) behöver vi ta hänsyn till några saker. Vi behöver kolla så inputvärdet verkligen är satt, om inte kommer vi få problem och det vill vi ju inte ha. Det första vi gör i vår AddTag metod är en kontroll om string är null eller empty. Detta blir vår pre condition. Strukturen ser ut på följande sätt:
Require 'Pre condition
' ... Some check ...
do
'... Some insertion algorithm ...
end
Implementeringen efter denna struktur kan se ut på följande sätt:
public void AddTag(string value)
{
if(String.IsNullOrEmpty(value)
throw new ArgumentNullException(“Input string value cannot be null or empty!”);
…
}
[Note: InNullOrEmpty(string value) är en ny metod som kommer med i .Net 2.0]
Om en användare nu skulle använda denna på fel sätt kommer den få ett felmeddelande som ger god förklaring vad den gjorde för fel. För att öka kvalitén och garantin att resterande rutiner går rätt till kan vi lägga till ett postcondition. (efterkrav) Vår struktur kommer då att se ut på följande sätt:
require
' ... Some check ...
do
'... Some insertion algorithm ...
Ensure //post contidion
'... Some check ...
end
I min SetStartTag metod använde jag en StringBuilder för att skapa min XML sträng. Jag hämtade då mitt tagvärde genom en globalt privat variabel: _addedTagString. I min AddTag metod efter precondition ”require” kommer jag att denna variabel, detta blir min ”do”
this._addedTagString = value;
Innan jag lämnar metoden gör jag min postcondition ”Ensure” för att verkligen vara säker på att allt gått rätt till Jag kontrollerar då att min input-value är samma som sattes till min _addedTagString.
public void AddTag(string value)
{
if(String.IsNullOrEmpty(value) 'Precondition “Require”
throw new ArgumentNullException(“Input string value cannot be null or empty!”);
this._addedTagString = value; 'Do
if(!this._addedTagString.Equals(value) 'Postcondition “Ensure”
throw new ArgumentException(“The value was not set, something went wrong!”);
}
Nu kan typ inget gå fel. Användaren får bra hänvisningar om något skulle gå fel på vägen. Som ni ser så kan vi egentligen vara säkra på att värdet sätts och behöver då ingen post-condition. Detta för att vi nu vet att en sträng alltid kommer att sättas annars skulle det bli fel i språket. Nu är mitt exempel som sagt väldigt enkelt men i mer komplexa klasser och metoder kan en hel del saker hända mellan pre- och post-condition och därför är det bra att ha post condition i tankarna vid bygget av sin rutin.
Är vi klara nu? Nej, det fattas en del viktiga saker. Även om AddTag metoden skulle fungera felfritt och ge bra hänvisningar om att man gör fel, så har vi den andra metoden AddText(string text). Här ser jag genast att vår pre- och post-condition kommer bli mer krävande än vad AddTag var. Varför? Jo, innan vi lägger till en text måste vi försäkra oss att en tag redan är satt, eftersom texten skall sättas mellan taggarna. Vår pre-condition måste först kolla så att input värdet inte är null eller empty string, men även informera användaren att först ange en tag om detta inte redan är gjort. Den slutliga koden skulle kunna se ut på följande sätt:
public void AddText(string text)
{
if(String.IsNullOrEmpty(text) 'Precondition
throw new ArgumentNullException(“Input string text cannot be null or empty!”);
if(String.IsNullOrEmpty(this._addedTagString)
throw new ArgumentNullException(“You must add a tag first, use AddTag for this!”);
this._addedTextString = text; 'Do
if(!this._addedTextString.Equals(text) 'Postcondition
throw new ArgumentException(“The text was not set, something went wrong!”);
}
I vår ToXML metod kan vi kolla så både _addedTagString samt _addedTextString är satta innan vi försöker bygga vår xml-data. Detta behöver endast göras om vi inte tillåter ett tomt resultat. Jag väljer att göra på följande sätt: Om jag inte satt någon tag så kommer jag skriva ut null, annars ett färdigt resultat. Mitt post condition blir att se till så min stringbuilder inte är null. Så koden skulle kunna se ut på följande sätt:
public string ToXML()
{
if(string.IsNullOrEmpty(this._addedTagString)
return ( null ) ; 'no tag was set, and no xml will be created
this.SetStartTag(this_addedTagString);
this.SetText(this._addedText);
this.SetEndTag(this.addedTagString);
if(this.xmlStringBuilder != null)
return ( this.xmlStringBuilder.ToString() );
else
throw new Excpetion(“Couldn’t build string!”);
}
Som ni ser kan man inte använda denna XMLBuilder på fel sätt, användaren av den får information som är logisk för att förstå vad denne gjorde för fel och kan enkelt rätta till detta. Många struntar i att göra kontroller och kan få de mest underliga fel och inte ha en aning om varför, en klassiker är Reference Null exception, den kan man verkligen bli tokig på.
Invariant
Som jag tidigare sagt så är Pre- och postcondition bundna till rutinerna men invariant är bunden till klassen i sin helhet. Invariants kan placeras lite olika i vår struktur. Antingen kan man kalla på den före eller efter en precondition. Detsamma gäller för Post-condition. Vanligast är att ha kontrollen efter en preondition och efter en postcondition. Invariant kontrollen kan eller skall man även anropa i sina rutiner för properties set och get, samt i klassens konstruktor. En invariant skall se till så klassens hela tillstånd verkligen fungerar, som jag tidigare sagt så kan inte timmar vara mer än 00-23 och detta är något som gäller hela klassen då det är en time-klass. Strukturen kan se ut på följande sätt i en metod.
require
'... Some check ...
Invariant
'... some check ...
do
' ... Some insertion algorithm ...
Ensure //post contidion
'... Some check ...
Invariant
'... some check ...
End
För att ta ett mer förståeligt exempel från vår verkliga värld lämnar jag nu XMLBuilder och går in på ett flygplan istället. Abstraktionen av ett flygplan lägger jag på en rätt hög nivå. Jag tar med planet och dess sittplatser och inget mer, jag strutar alltså i vinge, motor, längd vikt etc… När vi tar fram ett kontrakt för ett flygplan vill vi ju ha en garanti att det uppfyller våra krav, vi kan exempelvis ta våra sittplatser. Om vi nyttjar ett Boing eller ett litet propellerplan vill vi ju gärna göra rätt och dessutom veta att vi verkligen gör rätt. Vi vill ju slippa kontrollera att vi gjort rätt så fort vi gör något med vårt flygplan. Hämtar vi ett flygplan som lovar oss 250 sittplatser så skall vi ha en garanti att vi kan fylla planet med exakt 250 sittplatser och om vi överskrider 250 sittplatser vill vi gärna veta detta. Vi vill slippa att hela tiden fråga planet hur många som sitter i planet eller hur många sittplatser som är kvar? Vi vill gärna veta när planet är fullsatt och försäkra oss om att det aldrig kan bli fler passagerare än antal sittplatser på planet. Nu kanske ni tycker och tänker, aaa men hallå Johan detta sköter ju bokningssystemet. Bokningssystemet vet ju om hur många som kan sitta på planet och hur många som satt sig i planet. Nja, det är inte helt sant faktiskt. Ett bokningssystem vet visst hur många som bokat sig och hur många som bordat planet och vet även planets maxplatser. Men känner bokningssystemet till alla undantag? Tänk om en person går ombord via lastutrymmet och tar en sittplats den vägen? Detta kommer aldrig bokningssystemet få veta. Detta kan vår invariant-kontroll då hålla reda på. Exempel på en invariant är just antal platser och en kontroll att dessa platser inte redan är fullsatta. Ok en precondition kan väl fixa detta tänker ni säkert nu. Jo, på ett sätt är det sant om det var bara för just den rutinen. Men som jag tidigare sa så är invariant något som gäller hela klassen medans pre- och postcondition är något som bara gäller för enskild rutin. Ett flygplans platser kan sättas på många olika sätt, via konstruktorn, via metoder och via property. I alla dessa lägen bör en invariant kontroll ske. Vår invariant för flygplanet kan vara en IsSeatFull metod, denna ger oss helt enkelt en boolean som svar om platser finns kvar eller ej.
public class Airplane
{
private const int _numberOfSeats;
private ArrayList _seats = new ArrayList();
public Airplane()
{
This._numberOfSeats = 250;
}
private bool IsSeatFull()
{
if(this._seats.Count < 250)
return ( true );
else
return ( false );
}
public void AddSeat(Seat seat)
{
Assert.IsNotNull(seat,”Seat cannot be null!”); 'Pre condition
if(IsSeatFull()) 'Invariant kontroll (kollar klassens tillstånd)
throw new SeatIsFullException(“The Seats are full!”);
this._seats.Add(seat); 'Do
Assert.IsTrue(this._seats.Contains(seat),”Seat wasn’t added!”); 'Post condition
}
}
Låt säga att vi tog emot pilot via konstruktorn. Då skall piloten få en sittplats och även här bör vi kolla så platser finns kvar för att vara på den säkra sidan.
public Airplane(Public pilot)
{
if(IsSeatFull())
throw new SeatIsFullException(“The Seats are full!”);
this._seats.Add(pilot.Seat);
}
En invariant skall som regel kunna placeras i varje rutin utan att sabba rutinen. Den skall inte vara bunden till något annat än klassen själv, till dess globala fält. En invariant skall inte ta mot inputparametrar för då bryter den mot klassens egna tillstånd och gör den rutinbunden. Om man exempelvis skulle bygga en metod IsSeatFull(seat) så har man plötsligt ett annat tillstånd än klassen själv. För om vi skulle ha en metod vid namn OpenDoor() så skall man kunna anropa IsSeatFull() utan några problem i denna.
Slutsats Struktur.
Jag har nu gått genom tre viktiga saker som kommer att göra koden mer buggfri, mer självförklarande och mer effektiv. Om man strukturerar sin klass och dess rutiner på detta sätt har vi stor garanti att feltoleransen minskar i användandet av dem. Detta är ingen silver-bullet design för att skapa buggfria klasser och metoder, det kommer vi aldrig kunna göra, men det är ett bra verktyg för att skapa färre buggar. Så när du strukturerar din klass ta då fram ett kontrakt efter specifikationen du har. Sätt upp kontraktet på papper eller i något modelleringsverktyg bara det är väl specificerat . När detta är klart kan vi sätta upp vår invariant kontroll. Vi kan öppna upp de metoder (sätta dem som publika) som användaren behöver känna till och där i hantera pre- och postcondition för att säkerställa att inget går fel till samt kolla vår invariant, för att garantera vår klass tillstånd, så allt stämmer även när vi lämnar en metod eller property.
Checklista.
1… Ta fram kontrakt. Max- och minvärden, krav på levande instanser, interface, signatur etc…2… Skapa vår invariant kontroll.
3… Skapa pre- och postconditions för de olika rutiner som läggs till.
Följer du detta mönster kan inget gå fel. Skulle något fel uppstå så är det bara att utöka de olika conditions som satt upp.
Mer kod
Det är många som aktivt arbetar med en rutin att skriva så lite kod som möjligt och på så få rader de bara kan. Detta är lite av ondo. Jag själv gillade tanken att optimera min kod så jag fick mindre antal rader men det gav i långa loppet större problem. Det blev inte lika förändringsbart och den interna strukturen blev ibland mer hårdkopplad med andra delar, vilket man skall undvika. Felhantering ger mer kod, men jag vill säga detta: Se inte felhantering som något ont utan något som hjälper er och andra att snabbt se om något inte gick som det skulle. ” It's not the success that give you the value - it's the exceptions that give you all the value!
Because while a success won't actually tell you that a feature is working a exception will tell you something very interesting indeed - namely that something in your app doesn't work, or at least does not behave according to your expectations.”
- Mats Helander
Det är en del av utvecklingen, många struntar dock i felhanteringen för att de själva vet hur de skall använda sina saker. Du bör utgå från att du gör koden för andra och inte bara dig själv. Inte heller skall du skriva kod som tvingar andra att eventuellt göra mer hårdkopplad än vad den redan är. Ellerbygga workarounds för att de inte förstår hur det hela fungerar.
Hårdkoppling
Jag har tidigare pratat om inkapsling och hur viktigt det är En annan viktig del är även hårdkopplingen. Med hårdkoppling menar man hur fast en sak är med en annan. Ta ett glas med mjölk, här har vi två objekt; mjölk och glas. Glas måste ju inte innehålla mjölk, men många skulle nog programmera detta så att Glas alltid hade en instans av mjölk i sig,
public class Glas
{
Mjölk mjölk = new Mjölk();
…
Detta medför att mjölk och glas är hårdkopplade. Du skulle alltså inte kunna byta ut mjölken. Många hårdkopplar saker utan att tänka på det, men det är även viktigt att förstå att man inte skall bygga för löskopplat heller då detta kan leda till överdriven kod och öka tiden för projektet. Det viktigaste är vad kraven berättar för dig. Exempelvis om man har ett flygplan där vingen inte skall kunna plockas av. Då skulle man mycket väl kunna göra vingen hårdkopplad med planet. Men om vingen skall kunna plockas av som en egen bit skall den hanteras löskopplad. Även metoder i en klass kan vara lös- och hårdkopplade. Snart följer ett litet exempel som härstammar fråm ett projekt jag nyss arbetade i.
När jag skriver kod vill jag göra koden så lättläst som möjligt och se till så den som läser koden inte måste arbeta som en tok med sitt huvud.
En annan sak som jag också anser vara viktigt är att hålla så enkel nivå som möjligt på koden om jag inte känner till kvalitén på dem som skall ta över den, det är ibland att föredra att skriva kod som ett barn även kan förstå än endast nått en vuxen (mer erfaren programmerare) kan förstå. För en vuxen förstår både barnets kod och den vuxna personens kod men ett barn kan ha svårt att förstå en vuxen.
Det gör dock att man ibland skriver kod som en del kanske inte tycker om och vill effektivisera, för de anser att man kan få ner antalet rader. Det viktiga är faktiskt att koden är läsbar, kan man effektivisera en kod förstår man oftast även hur den fungerar, vilket är målet när man skriver kod. Men var dock försiktig med att effektivisera den för mycket, då det kan leda till svårförståelig kod där man måste tänka extra mycket för att förstå vad man egentligen gör. Nu till mitt lilla exempel.
Tänk dig följande kod (obs! bara ett föenklat exempel, i verkliga livet kunde det ta vara mer komplext!) Om en array är noll så skull knapparna inte synas annars skall de visas.
if(listMenuItemContainer.Count == 0)
{
SetArrowButtonsEnableMode(false);
}
else
{
SetArrowButtonsEnableMode(true);
}
För att minimera antal rader och effektivisera koden mera skulle man kunna skriva ovan rad på följande sätt:
SetArrowEnableMode(listMenuItemContainer.Count == 0);
Argumentet här är att man då får mindre kod och att första koden är onödig.
Självklart är det så; först koden ger mer rader kod. Men den första koden visar enklare vad det är som händer och man ser genast vart man kan lägga till mer funktioner under samma kondition om det behövs. Säg att jag även vill sätta en editerarknapp som disable samtidigt som ett kondition då pilarna gör det. Jag behöver inte bry mig om if satsen och förstå vad den gör för att förstå vart jag skall lägga till denna hantering, då jag tydligt ser i min kod vart false hanteras. OBS! Det är även viktigt här att ha en god guideline inom företaget där man exempelvis bestämmer sig för att alltid utgå från Enable och inte Disable eller båda två. Det är lätt av misstag att läsa metoder fel i stressade tider och tolka Disable som Enable för att de senaste 30 metoderna hette hela tiden Enable. (Men detta är en annan diskussion, ta gärna upp den i denna artikels tråd.)
if(...............)
{
SetArrowButtonsEnableMode(false);
SetEditButtonsEnableMode(false);
}
Det motargument jag brukar få angående sådan kod som ovan är att man kunde lägga kondition i metoden som input istället för det blir samma effekt.
Risken här är att en mindre erfaren utvecklare kanske inte vill bryta den kod man skrivit och lägger helt andra undantag i SetArrowButtonsEnableMode metoden istället. (Det är lätt hänt.)
Visst, detta går men då får inte metoden den mening den har, alltså att sätta pilknappar som disabled eller enabled.
Om jag skulle lägga till en kontroll i min SetArrowButtonsEnableMode(bool value) och även där i sätta min knapp som enable eller disable så skulle metoden bli mer hårdkopplad med andra rutiner, som inte hör hemma där. När man bygger metoder och namnger dem skall namnet reflektera vad dessa metoder gör. Detta för att den som skriver koden och andra enklare skall förstå den. Det är bättre än att hårdkoppla metoder med andra metoder, vilket till slut ger rutiner där buggar riskerar att öka och oftast är ett tecknen på spagettikod snart är ett faktum.
Slutsats
Försök göra saker så löskopplade som möjligt. Se till att klasser har god inkapsling av sina rutiner. Exponera bara sånt som användaren måste ha tillgång till och inget mera. Detta får kosta mer rader kod men i det långa loppet (och även i det korta) medför det mindre fel i programvaran och bättre förståelse för vad koden gör, vilket ökar chanserna till att snabbt hitta fel och rätta dem. Open close Principle (OcP) finns egentligen inte som inbyggt stöd i C# eller VB .Net, vilket gör att vi får ta och bygga principen för hand med pre- postconditions samt med invariant kontroll. Ett samlingsnamn för detta manuella sätt är defense programming. Det återstår att se om något inbyggt stöd kommer att dyka upp i framtida versioner av C# eller VB .Net. Syftet med OcP är att minimera risker för fel, att skydda användarna (utvecklarna av APIet) från att använda det hela fel samt försäkra att saker går rätt till. Det finns mer att prata om angående dessa ämnen men jag avslutar här för att eventuellt i framtiden kunna ta upp fler intressanta tankar kring det hela.
Tack till:
Ann Kapborg, Mats Helander, Andreas Håkansson och Manus för att ni hjälpe mig med att granska denna artikel. Fel i artikeln kan säkert finnas, om de är störande skicka gärna ett mail så jag har möjlighet att rätta till dem
Diskutera artikeln här:
Mikael Åhlén
Tack Johan för en bra artikel, det här gör mig till en bättre programmerare. Satt ett högt betyg, för den var intressant och jag förstod det mesta. /m
Ulf Elfving
Lägger upp denna som "favvis" för att kunna gå tillbaka till. Mera sådana artiklar! Högsta betyg utan att tveka.