det är ingen hemlig kod är en komplicerad sak att skriva, felsöka och underhålla vilket är nödvändigt för hög programvarukvalitet. Dessutom medför hög kodkomplexitet en högre nivå av kodfel, vilket gör koden dyrare att underhålla.
så genom att minska kodkomplexiteten kan vi minska antalet buggar och defekter, tillsammans med dess livstidskostnad. Vad exakt är komplex kod? Hur kan vi objektivt bedöma hur komplex en kod är, oavsett om det är en hel kodbas eller en liten funktion?
i den här artikeln kommer jag att gå igenom tre komplexitetsmått för att bedöma kodkomplexitet. Dessa är:
- Cyclomatic complexity
- Switch statement och logic condition complexity
- Developer skill
jag ska också gå igenom några av fördelarna med att bedöma och förstå kodkomplexitet.
Cyklomatisk komplexitet
1976 föreslog Thomas McCabe Snr ett mått för beräkning av kodkomplexitet, kallad Cyklomatisk komplexitet. Det definieras som:
ett kvantitativt mått på antalet linjärt oberoende banor genom ett program källkod…beräknas med hjälp av styrflödesdiagrammet för programmet.
om du inte är bekant med en Kontrollflödesdiagram:
det är en representation, med hjälp av grafnotation, av alla vägar som kan korsas genom ett program under dess körning.
nämnda mer rakt, ju färre vägar genom en bit kod, och mindre komplexa dessa vägar är, desto lägre Cyklomatisk komplexitet. Som ett resultat är koden mindre komplicerad. För att visa metriska, låt oss använda tre, något godtyckliga, Go – kodexempel.
exempel en
func main() { fmt.Println("1 + 1 =", 1+1)}
eftersom det bara finns en väg genom funktionen har den en Cyklomatisk Komplexitetspoäng på 1, som vi kan hitta genom att köra gocyclo på den.
exempel två
func main() { year, month, day := time.Now().Date() if month == time.November && day == 10 && year == 2018 { fmt.Println("Happy Go day!") } else { fmt.Println("The current month is", month) }}
i det här exemplet hämtar vi innevarande år, månad och dag. Med denna information kontrollerar vi sedan om det aktuella datumet är den 10 November 2018 med ett if/else-villkor.
om det är så skriver koden ” Happy Go day!”till konsolen. Om det inte är så skrivs det ut ”den aktuella månaden är” och namnet på den aktuella månaden. Kodexemplet görs mer komplicerat som om villkoret består av tre undervillkor. Med tanke på att den har en högre komplexitetspoäng på 4.
exempel tre
func main() { _, month, _ := time.Now().Date() switch month { case time.January: fmt.Println("The current month is January.") case time.February: fmt.Println("The current month is February.") case time.March: fmt.Println("The current month is March.") case time.April: fmt.Println("The current month is April.") case time.May: fmt.Println("The current month is May.") default: fmt.Println("The current month is unknown.") }}
i det här exemplet skriver vi ut den aktuella månaden, baserat på värdet month
, hämtat från samtalet till time.Now().Date()
. Det finns sju vägar genom funktionen, en för varje fall uttalanden och en för standard.
som ett resultat är dess Cyklomatiska komplexitet 7. Om vi hade redovisat alla månader på året, tillsammans med en standard, skulle dess poäng dock vara fjorton. Det händer eftersom Gocyclo använder följande beräkningsregler:
1 är baskomplexiteten för en funktion
+ 1 för varje ’if’, ’for’, ’case’, ’&&’ eller ’||’
med hjälp av dessa tre exempel kan vi se att genom att ha en standardmått för beräkning av kodkomplexitet kan vi snabbt bedöma hur komplex en bit kod är.
vi kan också se hur olika komplexa delar av koden är i jämförelse med varandra. Cyklomatisk komplexitet räcker dock inte på egen hand.
Switch Statement och Logic Condition Complexity
nästa bedömare av kodkomplexitet är switch statement och logic condition complexity. I kodexemplet nedan har jag tagit det andra Go-exemplet och delat föreningen if-tillståndet i tre kapslade förhållanden; en för var och en av de ursprungliga förhållandena.
func main() { year, month, day := time.Now().Date() output := fmt.Sprintf("The current month is %s", month) if month == time.November { if day == 13 { if year == 2018 { output = fmt.Sprintf("Happy Go day!") } } } fmt.Println(output)}
vilket är lättare att förstå (eller mindre komplicerat), den ursprungliga eller den här? Låt oss nu bygga vidare på detta genom att överväga följande tre frågor.
- vad händer om vi, som vi gör ovan, hade flera if-förhållanden och var och en var ganska komplex?
- vad händer om vi hade flera if-förhållanden och koden i var och en var ganska komplex?
- skulle koden vara lättare eller svårare att förstå?
det är rättvist att säga att ju större antal kapslade förhållanden och ju högre nivå av komplexitet inom dessa förhållanden, desto högre är kodens komplexitet.
Software Developer skicklighetsnivå
vad sägs om kompetensnivå utvecklaren? Ta en titt på C-versionen av det andra Go-exemplet nedan.
#include <stdio.h>#include <time.h>#include <string.h>int main(){ time_t t = time(NULL); struct tm tm = *localtime(&t); const char * months = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}; if (tm.tm_year == 2018 && strncmp(months, "November", strlen(months)) == 0 && tm.tm_mday == 10) { printf("Happy C Day!.\n"); } else { printf("The current month is %s.\n", months); }}
tekniskt sett gör det vad de andra exemplen gör. Det krävs dock mer kod för att uppnå samma resultat. För att vara rättvis, om jag hade en större förtrogenhet med C, kan koden inte vara längre än Go-exemplet.
låt oss dock säga att detta är det minsta som krävs för att uppnå samma resultat. Om du jämför de två, med tanke på den mer verbose karaktären av C: s syntax jämfört med Go, är det svårare att förstå.
vad mer, om du inte hade någon tidigare erfarenhet av C, trots en jämförelsevis liknande Cyklomatisk Komplexitetspoäng, vad skulle din uppfattning vara?
anser du att koden är mindre eller mer komplicerad? Så detta är en annan viktig faktor för att förstå kodkomplexitet.
fördelarna med att mäta Programvarukomplexitet
det finns fyra kärnfördelar med att mäta kodkomplexitet, plus en extra.
bättre tester
genom att veta hur många oberoende vägar det finns genom en bit kod vet vi hur många vägar det finns att testa.
jag förespråkar inte för 100% kodtäckning förresten-det är ofta en meningslös programvara. Men jag förespråkar alltid en så hög nivå av kodtäckning som är både praktisk och möjlig.
så genom att veta hur många kodvägar det finns kan vi veta hur många vägar vi måste testa. Som ett resultat har du ett mått på hur många tester som krävs, åtminstone för att säkerställa att koden täcks.
minskad Risk
som det gamla ordspråket säger:
det är svårare att läsa koden än att skriva den.
vad mer:
- koden läses mycket mer än den är skriven
- en bra mjukvaruutvecklare ska aldrig bedömas av kodraderna de har skrivit (eller ändrats), men av kvaliteten på koden de har behållit.
med tanke på att genom att minska kodkomplexiteten minskar du risken för att införa defekter; oavsett om de är små eller stora, lite pinsamma eller konkursframkallande.
lägre kostnader
när risken för potentiella defekter minskar finns det färre fel att hitta och ta bort. Som ett resultat minskar underhållskostnaden också.
vi har alla sett och är bekanta med kostnaderna för att hitta defekter i de olika stadierna i en programvaras liv, vilket exemplifieras i diagrammet nedan.
så det är vettigt att om vi förstår komplexiteten i vår kod och vilka avsnitt som är mer komplicerade än andra, är vi i en mycket bättre position för att minska nämnda komplexitet.
så genom att minska den komplexiteten minskar vi sannolikheten för att införa defekter. Det strömmar in i alla stadier av en programvaras liv.
större förutsägbarhet
genom att minska programvarukomplexiteten kan vi utvecklas med större förutsägbarhet. Vad jag menar med det är att vi bättre kan säga-med självförtroende – Hur lång tid en del av koden tar att slutföra. Genom att veta detta kan vi bättre förutsäga hur lång tid en release tar att skicka.
baserat på denna kunskap kan verksamheten eller organisationen bättre sätta sina mål och förväntningar, särskilt de som är direkt beroende av nämnda programvara. När detta händer är det lättare att ställa in realistiska budgetar, prognoser och så vidare.
hjälper utvecklare att lära sig
att hjälpa utvecklare att lära sig och växa är den slutliga fördelen med att förstå varför deras kod anses vara komplex. De verktyg jag har använt för att bedöma komplexitet fram till denna punkt gör det inte.
vad de gör är att ge en övergripande eller granulär komplexitetspoäng. Men ett omfattande kodkomplexitetsverktyg, som Codacy, gör det.
i skärmdumpen ovan kan vi se att av de sex listade filerna har en komplexitet på 30, en poäng som vanligtvis anses vara ganska hög.
Cyklomatisk komplexitet är en bra indikator för att förstå om kodkvaliteten försämras för en viss förändring. Cyklomatisk komplexitet kan vara svårare att resonera när man tittar på det eller jämför hela moduler med tanke på dess oändliga skala och inte är relaterad till modulstorleken. Något som du kanske tycker är användbart är dock att titta på Codacys fillista sorterad efter prioritet, vilket hjälper dig att förstå vilka filer som är kandidater med dålig kodkvalitet och sedan följaktligen deras moduler.
det är en Wrap
det har också varit en djupgående diskussion om vilken kodkomplexitet som är, hur den bedöms, liksom de betydande fördelarna med att minska den. Medan det finns mer att förstå kodkomplexitet än jag har täckt här, har vi gått långt för att förstå det.
om det här är första gången du hör om termen eller lär dig om något av verktygen, uppmuntrar jag dig att utforska de länkade artiklarna och verktygen så att du lär dig mer. Om du inte kodar I Go eller C, sedan google ”code complexity tool” plus ditt programspråk(er). Du är säker på att hitta många verktyg tillgängliga.
för fler tips för att förbättra kodkvaliteten kolla in några andra blogginlägg från Codacy.
slutligen, om du vill ha ett omfattande verktyg för att bedöma kodkvalitet, och en som hjälper dina utvecklare att lära sig och växa, prova sedan Codacy.
Codacy används av tusentals utvecklare för att analysera miljarder rader kod varje dag!
att komma igång är enkelt-och gratis! Använd bara ditt GitHub -, Bitbucket-eller Google-konto för att registrera dig.
KOM IGÅNG