Som exempel 8 visar måste utvecklare hantera de beroenden som uppstår som ett resultat av deras dekomponering av ett problem och dess lösning i ett antal moduler. Vi säger att en modul i ett system är beroende av en annan om det är möjligt att en ändring av en modul kräver en ändring av en annan. Om ett företag till exempel ändrar sina produktionsmetoder kan detta leda till en följdändring i sättet att beräkna de betalningar som krävs för de varor som produceras.
En utvecklare måste inte bara ta itu med arten av varje beroende utan också med antalet beroenden. Inom programvaruteknik används ”koppling” för att hänvisa till graden av ömsesidigt beroende mellan de olika delarna av ett system. Det är lätt att se att vissa system kan ha kedjor av ömsesidigt beroende moduler där t.ex. modul A är beroende av modul B, som är beroende av modul C och så vidare. I vissa fall kan dessa kedjor ansluta sig till varandra och skapa ett cirkulärt beroende, vilket är en särskild form av stark (eller hög) koppling.
Utvecklare försöker konstruera löst kopplade system eftersom de är lättare att förstå och underhålla. Ett bra mjukvarusystem har alltså låg koppling, vilket innebär att ändringar i en del har mindre sannolikhet att fortplanta sig genom resten av systemet. En ytterligare fördel med låg koppling är att komponenterna är lätta att ersätta och eventuellt återanvända.
Oavsett graden av koppling i ett programvarusystem är det viktigt att veta vilka moduler som är kopplade. Om det inte fanns några register över kopplingen mellan modulerna skulle en utvecklare behöva ägna tid åt att arbeta sig igenom modulerna för att avgöra om varje modul påverkades av en ändring eller inte. Resultatet skulle bli att mycket arbete läggs ner på att kontrollera, även om inga ändringar behövs. Exempel 9 illustrerar faran med att ha mer än en modul som använder sig av gemensamma eller delade data.
Exempel 9
Datumhantering har alltid varit ett problem för programvaruutvecklare. För program av en viss ålder var det lämpligaste lagringsformatet för att representera ett år ett tal mellan 0 och 99. Det var logiskt eftersom 1966 lagrades som 66, 1989 som 89 och så vidare, och därför behövdes mindre utrymme för att lagra bara två siffror. Om datum lagras som siffror var det dessutom enkelt att sortera efter datumordning – den 22 januari 1989 lagras som 890122, är efter den 22 december 1966 lagras som 661222.
Olyckligtvis användes ett antal av dessa program fortfarande när år 2000 närmade sig, så varje modul i varje program som använde den korta formen av år måste undersökas.
En viktig aspekt av problemet i exempel 9 var att olika utvecklare hade olika sätt att läsa och manipulera de värden som lagrades i variabler som använde det sexsiffriga datumformatet. Detta ökade den ansträngning som krävdes för att lösa den så kallade millenniebuggen. Om utvecklarna hade haft ett konsekvent sätt att manipulera datum som inte var beroende av lagringsformatet skulle millenniebuggen inte ha varit ett problem.
Kohesion är ett sätt att beskriva hur nära aktiviteterna inom en enskild modul är relaterade till varandra. Sammanhållning är ett allmänt begrepp – till exempel kan en avdelning i en organisation ha en sammanhängande uppsättning ansvarsområden (t.ex. bokföring) eller inte (diverse tjänster). I programvarusystem utför en mycket sammanhållen modul en enda uppgift eller uppnår ett enda mål – ”gör en sak och gör den bra” är ett användbart motto att tillämpa. En modul bör genomföra en enda logisk uppgift eller en enda logisk enhet.
Låg koppling och hög sammanhållning är konkurrerande mål. Om varje modul endast utför en sak på en låg abstraktionsnivå kan vi behöva en komplex uppbyggnad av högt kopplade moduler för att utföra en aktivitet på högre abstraktionsnivåer. En utvecklare bör försöka uppnå den bästa balansen mellan kopplings- och sammanhållningsnivåerna för ett programvarusystem. Exempelvis genererar hotell inkomster genom att hyra ut sina rum till gäster. Det är troligt att begreppet rum finns representerat någonstans i programvarusystemet för hotellbokningar. Det kan vara lämpligt att använda en modul eller klass som representerar begreppet rum för att samla in och lagra uppgifter om de inkomster som genereras genom att hyra ut rum. En bättre lösning är dock att ha en separat faktura- eller betalningsmodul, eftersom det är mer sammanhängande, särskilt när ett hotell genererar inkomster på andra sätt, till exempel genom att servera måltider till personer som inte är bofasta gäster.
Aktivitet 5 Dela och erövra
- a.Varför kan du överväga att dela upp ett stort projekt i mindre delar?
- b.Hur påverkar komplexiteten hos ett programvarusystem underhållsuppgiften?
- c.Vad är en modul?
- d.Varför hjälper det att ha låg koppling i ett programvarusystem?
- e.Ge exempel på vilken typ av information som skulle vara värdefull när man överväger att ändra en viss modul.
- f.Vad är en moduls kontextberoenden? Hur förhåller de sig till en moduls gränssnitt?
- g.Vilka är fördelarna med att använda moduler med definierade gränssnitt?
- h.Varför hjälper det att ha hög sammanhållning i modulerna i ett programvarusystem?
- i.Vilka egenskaper bör en modul uppvisa som bidrar till att säkerställa att den är lätt och billig att utveckla och underhålla och att felen hålls till ett minimum?
- j.Varför är det viktigt att uppnå en balans mellan koppling och sammanhållning?
Svar
- a.Det finns en gräns för hur mycket en person kan förstå vid ett och samma tillfälle. Det finns alltså en gräns för hur stort ett programvarusystem som en person kan hantera. Genom att dela upp ett stort projekt i mindre delar är det möjligt att identifiera ett antal mer hanterbara uppgifter för de inblandade.
- b.Det är viktigt att kunna göra en ändring i ett programvarusystem utan att behöva veta allt om det systemet. Varje ändring blir svår när kontrollflödet och beroendena inom programmen är komplexa. Ju större antal och typ av beroenden, desto svårare är det att underhålla ett programvarusystem.
- c.En modul är en identifierbar del av ett programvarusystem som betraktas separat. Moduler kan till exempel vara underrutiner (i ett procedurspråk motsvarande metoder), klasser (i ett objektorienterat språk), biblioteksfunktioner eller andra konstruktioner som kan behandlas oberoende.
- d.Med låg koppling finns det få beroenden mellan moduler. Därför är det mindre sannolikt att ändringar som görs i en del (en eller flera moduler) av ett programvarusystem sprids i hela systemet. (En tydlig registrering av beroendena mellan modulerna hjälper dig att förutsäga effekterna av en föreslagen ändring av ett programvarusystem.)
- e.Det finns två typer av information som bidrar till analysen av en föreslagen ändring:
- Vilka moduler är klienter till modulen i fråga? Denna information visar hur långt en ändring kan sprida sig i programvarusystemet.
- Vilka antaganden har gjorts i klientmoduler till modulen i fråga? En förståelse för de förväntade tjänsterna i en modul hjälper till att bedöma de risker som är förknippade med en viss ändring.
- f.Kontextuella beroenden för en modul är de tjänster från andra moduler som modulen behöver för att fungera korrekt. Du kan uttrycka kontextberoendet för en modul i termer av andra gränssnitt. I själva verket kan du uttrycka en moduls ansvar i termer av dess gränssnitt och kontextberoenden. Om kontexten tillhandahåller de tjänster som modulen behöver och klienterna uppfyller de villkor som anges i gränssnittet, kan modulen garantera tillhandahållandet av de tjänster som beskrivs i dess gränssnitt.
- g.Fördelarna är följande:
- Utvecklare behöver bara känna till modulens gränssnitt (dess syntax och vad det kräver och åstadkommer – dess semantik), inte hur den tillhandahåller dessa tjänster. Följaktligen kan utvecklarna vara mer produktiva.
- Utvecklarna kan förstå aspekter av programvarusystemet mer ingående, så färre fel kommer att införas.
- Det bör bli lättare att hitta fel, eftersom irrelevanta moduler undviks.
- Möjligheten till återanvändning av moduler ökar när det är känt vad modulen tillhandahåller och kräver.
- h.Med hög kohesion utför en modul en vettig uppsättning operationer eller aktiviteter. I idealfallet innebär hög sammanhållning endast en större abstraktion per modul. Gränssnittet abstraherar från det som en utvecklare måste känna till för att kunna använda en modul. Detta gör det lättare för utvecklare att förstå syftet med modulen och hur den ska användas. Dessutom tenderar hög sammanhållning att göra en modul mer återanvändbar i andra tillämpningar, eftersom den tillhandahåller en uppsättning operationer som passar naturligt ihop.
- i.En modul bör ha låg koppling och hög sammanhållning, representera en bra abstraktion och ha ett väldefinierat gränssnitt som är en inkapslad abstraktion av ett välförstått koncept.
- j.När du konstruerar ett system kan du ha ett val mellan en mindre uppsättning löst kopplade, mindre sammanhållna moduler eller en större uppsättning tätt kopplade, mer sammanhållna moduler. I det förra fallet kan varje modul vara svår att förstå, medan relationerna mellan dem i det senare fallet kan vara överkomplexa. Du måste hitta en lämplig balans.