10 Vanliga anti-patterns och hur du undviker dem

Inledning

Att skriva effektiv och underhållbar kod är en viktig del av mjukvaruutveckling, men trots bästa intentioner kan vi ibland falla i fällor som leder till dåliga lösningar. Dessa fällor kallas för anti-mönster eller anti-patterns. Anti-mönster är vanliga fel eller dåliga val som kan smyga sig in i koden, vilket leder till problem som är svåra att felsöka, underhålla eller skala. I den här artikeln ska vi gå igenom tio vanliga anti-mönster inom C#-programmering, hur de kan upptäckas, och hur de kan undvikas eller rättas till.

God Class (Gudklass)

En ”Gudklass” är en klass som hanterar för mycket logik och ansvar. Detta bryter mot Single Responsibility Principle (SRP) och gör koden svår att underhålla.

Upptäckt: Om du ser en klass med många metoder och stora mängder kod, eller om klassen ansvarar för flera olika uppgifter, är detta en tydlig indikator.

Exempel:

God class - Exempel
public class OrderManager
{
    public void CreateOrder() { /* logik för att skapa order */ }
    public void ValidateOrder() { /* logik för att validera order */ }
    public void SaveOrderToDatabase() { /* logik för att spara order till databas */ }
    public void SendOrderConfirmationEmail() { /* logik för att skicka bekräftelsemejl */ }
}

Lösning: Dela upp klassen i mindre klasser, där varje klass har ett tydligt ansvar.

God class - Lösning
public class OrderService
{
    public void CreateOrder() { /* logik för att skapa order */ }
}

public class OrderValidator
{
    public void ValidateOrder() { /* logik för att validera order */ }
}

public class OrderRepository
{
    public void SaveOrder() { /* logik för att spara order till databas */ }
}

public class EmailService
{
    public void SendOrderConfirmation() { /* logik för att skicka bekräftelsemejl */ }
}

Magic Numbers

Magic numbers är hårdkodade siffror som används direkt i koden utan någon förklaring. Detta gör koden svår att förstå och underhålla.

Upptäckt: Se efter siffror som används direkt i beräkningar eller logik utan någon förklaring.

Exempel:

Magic numbers - Exempel
if (orderAmount > 1000)
{
    // Ge rabatt
}

Lösning: Ersätt siffrorna med konstanter eller variabler med beskrivande namn.

Magic numbers - Lösning
const decimal DiscountThreshold = 1000m;

if (orderAmount > DiscountThreshold)
{
    // Ge rabatt
}

Hardcoded Strings

Precis som med magic numbers är hårdkodade strängar direkt i koden problematiska, särskilt för översättning, felsökning eller ändringar.

Upptäckt: Leta efter strängar som används direkt i metoder eller logik.

Exempel:

Hardcoded strings - Exempel
//..
Console.WriteLine("Welcome to the application!");
//..

Lösning: Använd konstanter, resurser eller konfigurationsfiler för strängar.

Hardcoded strings - Lösning
//..
Console.WriteLine(Resources.WelcomeMessage);
//..

Copy-Paste Programming

Detta anti-mönster uppstår när kod block kopieras och klistras in på flera ställen i applikationen, vilket leder till duplicerad kod.

Upptäckt: Om du ser identiska eller nästan identiska kodblock på flera ställen i projektet.

Exempel:

Copy-paste - Exempel
public void ProcessOrder()
{
    // Process order logic
}

public void ProcessInvoice()
{
    // Samma logik som ProcessOrder
}

Lösning: Abstrahera den duplicerade koden till en metod eller en separat klass.

Copy-paste - Lösning
public void ProcessDocument(Document document)
{
    // Abstraherad logik för att hantera olika dokumenttyper
}

God Object

Ett ”God Object” är ett objekt som har tillgång till för mycket information eller ansvarar för för många saker inom ett system. Till skillnad från en ”God Class”, som handlar om att en specifik klass har för mycket ansvar, är ett ”God Object” ett objekt som centraliserar för mycket logik och datahantering. Detta kan leda till hög koppling (tight coupling) mellan olika delar av systemet, vilket gör det svårt att ändra eller testa koden utan att påverka många andra delar av systemet.

Upptäckt: Om ett objekt har många beroenden eller metoder som påverkar många andra delar av systemet.

Exempel:

God object - Exempel
public class ApplicationContext
{
    public Order CurrentOrder { get; set; }
    public User CurrentUser { get; set; }
    public Settings AppSettings { get; set; }
    // ...och så vidare
}

Lösning: Dela upp ansvaret i mindre objekt eller tjänster som har specifika roller.

God object - Lösning
public class UserContext
{
    public User CurrentUser { get; set; }
}

public class OrderContext
{
    public Order CurrentOrder { get; set; }
}

public class SettingsService
{
    public Settings GetSettings() { /* Hämta inställningar */ }
}

Spaghetti Code

Spaghetti Code är en term för kod som är rörig och svår att följa på grund av dålig struktur eller avsaknad av tydlig arkitektur.

Upptäckt: Om metoder eller klasser är ologiskt organiserade eller om kodflödet är svårt att följa.

Exempel:

Spaghetti - Exempel
if (condition1)
{
    // logik
}
else if (condition2)
{
    // mer logik
}
else
{
    // ännu mer logik
}

Lösning: Refaktorisera koden genom att bryta ner komplex logik i mindre, mer hanterbara metoder eller klasser.

Spaghetti - Lösning
public void ProcessCondition()
{
    if (IsFirstConditionMet())
    {
        HandleFirstCondition();
    }
    else if (IsSecondConditionMet())
    {
        HandleSecondCondition();
    }
    else
    {
        HandleDefaultCondition();
    }
}

Golden Hammer

Golden Hammer innebär att man använder samma verktyg eller metod för alla problem, oavsett om det är lämpligt eller inte.

Upptäckt: Om du ser samma designmönster eller teknik användas överallt, även där det inte passar.

Exempel: Svårt att ge ett generellt exempel på detta. Skulle kunna vara att man t.ex. överutnyttjar ett designmönster som man behärskar, t.ex. ”Singleton” och applicerar den nästan överallt.

Lösning: Använd rätt verktyg för rätt problem, och utvärdera om det valda mönstret är det bästa valet för den aktuella situationen.

 

Lava Flow

Lava Flow uppstår när gammal, oanvänd eller dåligt dokumenterad kod lämnas kvar i systemet. Denna kod är svår att ta bort eftersom ingen förstår den längre, och den blir kvar som en del av systemet.

Upptäckt: Om det finns gammal kod som inte längre används, men ingen vågar ta bort den.

Exempel:

Lava flow - Exempel
// Gammal kod som ingen vet om den behövs
public void OldMethod()
{
    // ...
}

Lösning: Regular refaktorering och borttagning av död kod. Använd verktyg för att identifiera och ta bort oanvänd kod.

 

Shotgun Surgery

Shotgun Surgery är ett anti-mönster där små ändringar i kodbasen kräver att du gör ändringar på många olika ställen i systemet. Detta är ett tecken på hög koppling mellan komponenter.

Upptäckt: Om en ändring i en funktion kräver att du uppdaterar flera andra klasser eller metoder.

Exempel:

Shotgun Surgery - Exempel
public class SavingsAccount
{
    private const decimal MIN_BALANCE = 100m; // Exempel på minsta saldo
    private const string WITHDRAWAL_MIN_BALANCE = "Låg balans vid uttag!";
    private const string TRANSFER_MIN_BALANCE = "Låg balans vid överföring!";
    private const string MIN_BALANCE_WARNING = "Varning för låg balans!";
    private decimal balance;

    public void Withdraw(decimal amount)
    {
        if (this.balance < MIN_BALANCE)
        {
            this.NotifyAccountHolder(WITHDRAWAL_MIN_BALANCE);
            return;
        }
        // Implementera uttagslogik här
    }

    public void Transfer(decimal amount)
    {
        if (this.balance < MIN_BALANCE)
        {
            this.NotifyAccountHolder(TRANSFER_MIN_BALANCE);
            return;
        }
        // Implementera överföringslogik här
    }

    public void ProcessFees(decimal fee)
    {
        this.balance -= fee;

        if (this.balance < MIN_BALANCE)
        {
            this.NotifyAccountHolder(MIN_BALANCE_WARNING);
        }
        // Implementera ytterligare logik här om nödvändigt
    }

    private void NotifyAccountHolder(string message)
    {
        // Skicka meddelande till kontoinnehavaren
        Console.WriteLine(message);
    }
}

Lösning: Minimera beroenden mellan olika delar av systemet och eliminera logik som förekommer flera gånger. I exemplet ovan så är problemet om vi skulle behöva justera gränsen för överskridandet av kontot; ”balance < MIN_BALANCE”.

Shotgun Surgery - Lösning
public class SavingsAccount
{
    private const decimal MIN_BALANCE = 100m; // Exempel på minsta saldo
    private const string WITHDRAWAL_MIN_BALANCE = "Låg balans vid uttag!";
    private const string TRANSFER_MIN_BALANCE = "Låg balans vid överföring!";
    private const string MIN_BALANCE_WARNING = "Varning för låg balans!";
    private decimal balance;

    public void Withdraw(decimal amount)
    {
        if (AccountIsUnderMinimum())
        {
            this.NotifyAccountHolder(WITHDRAWAL_MIN_BALANCE);
            return;
        }
        // Implementera uttagslogik här
    }

    public void Transfer(decimal amount)
    {
        if (AccountIsUnderMinimum())
        {
            this.NotifyAccountHolder(TRANSFER_MIN_BALANCE);
            return;
        }
        // Implementera överföringslogik här
    }

    public void ProcessFees(decimal fee)
    {
        this.balance -= fee;

        if (AccountIsUnderMinimum())
        {
            this.NotifyAccountHolder(MIN_BALANCE_WARNING);
        }
        // Implementera ytterligare logik här om nödvändigt
    }

    private bool AccountIsUnderMinimum()
    {
        return this.balance < MIN_BALANCE;
    }

    private void NotifyAccountHolder(string message)
    {
        // Skicka meddelande till kontoinnehavaren
        Console.WriteLine(message);
    }
}

AccountIsUnderMinimum(): En privat metod som kontrollerar om saldot är under den minimala balansen (MIN_BALANCE).

På så vis kan vi enkelt kontrollera vad som annars hade behövt ändras på 3 ställen (med risk för fel).

Cargo Cult Programming

Cargo Cult Programming innebär att utvecklare kopierar kod eller designmönster utan att riktigt förstå varför de används, ofta som ett sätt att ”spela säkert” utan att förstå kontexten.

Upptäckt: Om du ser kod som verkar onödigt komplex eller överdesignad utan någon tydlig anledning.

Exempel: Ett vanligt (?) Hello World

Cargo Cult - Exempel
using System;

namespace HelloWorldApp
{
    // Interface för att definiera kontraktet för en tjänst
    public interface IMessageService
    {
        void SendMessage(string message);
    }

    // Implementering av tjänsten som skickar ett meddelande
    public class ConsoleMessageService : IMessageService
    {
        public void SendMessage(string message)
        {
            Console.WriteLine(message);
        }
    }

    // Fabrik för att skapa instanser av tjänster
    public static class ServiceFactory
    {
        public static IMessageService CreateMessageService()
        {
            return new ConsoleMessageService();
        }
    }

    // Huvudprogrammet som använder tjänster och fabriker
    class Program
    {
        static void Main(string[] args)
        {
            IMessageService messageService = ServiceFactory.CreateMessageService();
            messageService.SendMessage("Hello, World!");
        }
    }
}

Vad är problemet?

  • Onödigt Interface: Ett interface (IMessageService) används för att skicka ett meddelande, vilket är överdrivet för ett så enkelt program.
  • Onödig Fabrik: En fabrik (ServiceFactory) används för att skapa en instans av tjänsten, vilket också är överdrivet.
  • Överdriven Abstraktion: Hela strukturen med interface, fabrik och tjänst är överdrivet komplex för ett program som bara ska skriva ut ”Hello, World!”.

Lösning: Utbilda programmerare i att förstå de designval de gör och säkerställa att varje val är lämpligt för kontexten.

Scroll to top