Kodanalys
Det finns verktyg som vi kan använda för att förbättra vår kodkvalitet och ett av dem är “Microsoft.CodeAnalysis.NetAnalyzers”. Från och med .NET5 så finns den redan med men du behöver “sätta på” den (“enabla”) i din projektfil.
Lägg till kodanalysen
Section titled “Lägg till kodanalysen”Vi lägger till .NETs inbyggda kodanalys genom att stå i vårt projekt och uppdatera projektfilen för det projekt vi vill analysera, i det här fallet MyBank:
// i projektfilen till exempel MyBank/MyBank.csproj<PropertyGroup> ... <EnableNETAnalyzers>true</EnableNETAnalyzers> <AnalysisLevel>latest</AnalysisLevel></PropertyGroup>När vi sedan bygger och kör vårt projekt med dotnet run (eller dotnet build) så analyseras vår kod automatiskt enligt standardreglerna. Du kan lägga till ytterligare regler om du vill, men vi kör på standard. Om du vill läsa mer om standardregler, så läs Code quality rules. Eventuella kommentarer kommer i terminalen när vi kör dotnet build eller dotnet run.
Det är viktigt att notera att .NET Code Analysis Tool (CAT) är ett relativt nytt verktyg och dess funktionalitet och användning kan komma att förändras över tid. Du kan också hitta andra tredjepartsverktyg som erbjuder funktioner för att analyser koden i .NET-projekt men CAT är ett officiellt verktyg från .NET-teamet.
Kodexempel
Section titled “Kodexempel”Vi känner till DRY (Don’t Repeat Yourself) vilket innebär att vi inte ska skriva samma kod på flera ställen. Istället gör vi en metod som vi återanvänder. En klass ska ha ett ansvarsområde liksom att en metod ska göra en sak.
| Dålig kod | Bra kod | Förklaring dålig kod |
|---|---|---|
| class a | public class Adult | Klassnamn med liten bokstav, namnet ej beskrivande samt åtkomstbehörighet som public saknas |
| int a | private int numberOfAdults | Variabelnamnet ej beskrivande samt åtkomstbehörighet saknas |
| int number | private int numberOfAdults | Variabelnamnet ej tillräckligt beskrivande, antal av vad? samt åtkomstbehörighet saknas |
| Metod med 25 rader | Dela upp metoden i flera metoder | Svårt att läsa och underhålla koden. Riktlinje är max 10 rader. |
Bra kodkvalitet, några bra punkter
Section titled “Bra kodkvalitet, några bra punkter”Här är några viktiga aspekter och bästa praxis för att uppnå hög kodkvalitet:
-
Läsbarhet:
-
Använd tydliga och beskrivande namn på variabler, konstanter, metoder och klasser.
-
Följ kodstandarder och konventioner, som att använda PascalCase för metoder och camelCase för variabler.
-
Kommentera koden där det behövs för att förklara komplex logik eller komplexa beslut.
-
-
Underhållbarhet:
-
Modularisera koden genom att dela upp den i mindre, återanvändbara komponenter. En klass ska ha ett eget ansvarsområde (Single responsibility)
-
Använd designmönster där det är lämpligt för att lösa vanliga problem på ett strukturerat sätt.
-
Skriv enhetstester för att säkerställa att koden fungerar som förväntat och för att underlätta framtida ändringar.
-
-
Effektivitet:
-
Optimera prestanda genom att undvika onödig bearbetning och genom att använda effektiva datastrukturer.
-
Hantera resurser som minne och nätverksanslutningar på ett effektivt sätt.
-
-
Säkerhet:
-
Validera indata för att förhindra säkerhetsproblem som SQL-injektion och XSS-attacker.
-
Använd säkerhetsbibliotek och ramverk för att hantera vanliga säkerhetsproblem.
-
-
Verktyg och processer:
-
Använd statisk kodanalys för att automatiskt identifiera potentiella problem i koden.
-
Genomför kodgranskningar regelbundet för att få feedback från andra utvecklare och förbättra kodens kvalitet.
-
Genom att följa dessa riktlinjer kan vi säkerställa att vår C#-kod är av hög kvalitet och lätt att underhålla och vidareutveckla.
Introduktion C# och designmönster
Section titled “Introduktion C# och designmönster”Designmönster är återanvändbara lösningar på vanliga problem inom objektorienterad programmering. De är inte färdig kod – utan tänkbara strukturer, för hur man kan organisera sin programdesign. Istället för att uppfinna hjulet på nytt används beprövade lösningar.
Inom C# används designmönster för att skapa kod som är:
-
Enkel att underhålla
-
Enkel att testa
-
Lätt att bygga ut
-
Tydligt separerad i ansvar
Om din kod känns svår att testa, är fylld av beroenden och full av switch-satser/if-satser då är designmönster ofta lösningen.
Ett exempel på designmönster för vår bank kan vara en abstract klass BankAccount som innehåller gemensam logik och abstrakta metoder som subklasserna (de olika kontotyperna) måste implementera.
Jämför två kodexempel, BadBank och GoodBank
Section titled “Jämför två kodexempel, BadBank och GoodBank”Nu jämför vi två kodexempel, två implementationer av bankexemplet. Den ena med lite fel/problem i heter BadBank och den som är bättre design heter GoodBank. Koden finns att ladda ner med task download-code -- kmom06/BadBank och task download-code -- kmom06/GoodBank. Titta först på det sämre exemplet och sen på det bättre exemplet.
BadBank
Section titled “BadBank”Det finns flera problem i BadBank:
-
Brist på inkapsling: Bankklassen manipulerar direkt BankAccount-array, vilket bryter mot inkapslingsprinciperna. (Lack of encapsulation)
-
Ineffektiv hantering av array: Bankklassen använder en array med fast storlek för att lagra bankkonton, vilket kan leda till problem när banken överskrider gränsen.
-
Brist på felhantering: Koden saknar korrekt felhantering, såsom hantering av otillräckligt saldo på kontot eller array bounds-fel (anrop av element i arrayen utanför gränserna, går att anropa element på plats 150).
-
Dåligt arv: Klasserna InterestAccount och PayrollAccount ärver från BankAccount men deras beteenden är inte helt inkapslade eller sammanhängande.
-
Brist på abstraktion: Koden saknar korrekt abstraktion, vilket gör det svårt att utöka eller modifiera funktionaliteten i framtiden.
-
Namngivning av variabler följer inte namnstandarden.
GoodBank
Section titled “GoodBank”I den här förbättrade versionen, GoodBank har vi:
-
Förbättrad inkapsling: Bankklassen använder en
List<BankAccount>för att lagra konton, vilket ger bättre inkapsling och flexibilitet. Dessutom kan vi ta bort attributet “numAccounts” eftersom antalet konton är längden på listan med bankkonton. -
Förbättrad felhantering: Korrekt felhantering implementeras för uttagsoperationer, inklusive otillräckligt saldo på kontot och övertrasseringsgränser.
-
Bättre användning av arv: Klasserna InterestAccount och PayrollAccount ärver från BankAccount och ger sammanhängande beteenden för specifika kontotyper.
-
Förbättrad abstraktion: Koden är strukturerad med bättre abstraktion, vilket gör den lättare att förstå och utöka i framtiden.
-
Utnyttjande av “property”: I BankAccount har vi nu använd “property” för kontoattribut, för att förbättra läsbarheten och göra det lättare att underhålla. Även i Bank gör vi BankName till en “property”. Properties är överkurs men det finns kvar i exemplet för att visa hur det kan användas.
-
Förbättrad namngivning: Privata attribut har fått namn enligt C# namngivning med understreck (Accounts blev _accounts i Bank.cs) samt konstanter har PascalCase.
-
Avstår från förenkling: Verktyget föreslår att vi ska använda defaultkonstruktor. Men vi tycker att vi har kvar en egen konstruktor, då underlättar vid vidareutveckling. Däremot förenklar vi gärna listan i konstruktorn (från
new List<BankAccount>()till[].)
Kod utan this
Section titled “Kod utan this”Hittills har vi nu har använt this för att accessa attribut och metoder inne i en klass men analysverktygen menar att det är onödigt. Frågan är om det är viktigare att koden är tydlig eller om det ska vara så lite kod som möjligt.
Om vi applicerar det på vårt bankexempel så kan vi ändra det och ta bort this.
// För tydlighets skull så skriver vi this här men det är inte nödvändigt public void AddAccount(BankAccount account) { this._accounts.Add(account); }
// Vi kan lika gärna skriva så här utan this. Kompilatorn hittar rätt attribut ändå. public void AddAccount(BankAccount account) { _accounts.Add(account); }Efter detta kursmoment är det inget krav på att använda this för att accessa attribut och metoder inuti klasser.
Förenklat new (från och med C# 9.0)
Section titled “Förenklat new (från och med C# 9.0)”Vi kan använda ett förenklat new-uttryck för att skapa ett objekt om typen är tydlig av sammanhanget. Om vi deklarerar en variabel av en klasstyp så kan vi använda ett förenklat new-uttryck. Om vi däremot använder var istället för klasstypen, måste vi ange typen (konstruktorns namn) efter new.
// För denna rad säger kodanalysen: Simplify new expression (IDE0090)Weather weather = new Weather("Soligt", 18, 8);// Här förstår kompilatorn att det är ett objekt av typen Weather som ska skapas.Weather weather = new("Soligt", 18, 8);// Om vi deklarerar variabeln var så måste vi ange typen av objektet.var weather = new Weather("Soligt", 18, 8);Formatera koden
Section titled “Formatera koden”dotnet format formaterar koden och korrigerar ibland kodstilen enligt det regler som finns fördefinierade i projektet. Vi använder standardreglerna som är inbyggda i .NET SDK, det vill säga den .NET mjukvara vi installerade i början.
För att få en detaljerad utskrift vad som görs vid dotnet format kommandot så använder vi flaggan -v d.
// i terminalen i projektetdotnet format -v ddotnet format --verify-no-changes // identifierar problem men ändrar ingetFör att själv kunna styra vilka regler som används finns en konfigureringsfil .editorconfig som ska ligga i rotkatalogen till ditt projekt.
Sammanfattning
Section titled “Sammanfattning”Då har vi tittat på hur vi kan förbättra kodkvalitet med kodanalys. Mer information om .NETs kodanalys i artikeln Overview of .NET source code analysis.
Vi har också tittat på exempel på bra och dålig kod. Här är en artikel för dig som vill titta på dålig kod som stegvis förbättras i denna artikel av Radosław Sadowski C# Bad Practices: Learn How to Make Good Code by Using Examples of Bad Code.