Att leka med Code: En effektiv agyl-metod (Svenska (Swedish))

Att leka med Code: En effektiv agyl-metod

Sunday, 23 November 2025

//

17 minute read

Varför behandlar man en gren som en sandbox?

Ta det varmt: De flesta utvecklare försöker vara perfekta på varje nivå av utveckling.

Inleiding

Jag har byggt mjukvara professionellt för ..., ,, låt mig säga att jag minns när ", Agile och " var en ny idé och inte ett företagsbizzword som projektledare använde för att rättfärdiga fler standups.

De senaste åren har jag sett att den bästa koden jag någonsin skrivit kom från projekt där jag kände tillåtelse att spela. Den värsta koden kom från projekt där varje nyckelstroke kändes undersökt

Det är inte slumpmässigt Kreativitet och tryck don' inte blandas bra

Så jag har utvecklat ett tillvägagångssätt som skiljer det stökiga, kreativa utforskandet från det polerade, produktionen, färdig bearbetning och färdig bearbeställning.Få det att fungera , göra det vackert, låsa ner den" - men egentligen ♫ , ♫ det handlar om att ge dig själv tillåtelse att experimentera utan att vikten av perfektion hänger över dig ♫

De tre faserna

Låt mig vara uppfrontlig. Detta handlar inte om att vara blyg eller undvika disciplin. Det handlar om att tillämpa rätt disciplin vid rätt tidpunkt.

graph LR
    subgraph "Phase 1: Make It Work"
        A[New Requirement] --> B[Explore & Experiment]
        B --> C{Does It Work?}
        C -->|No| D[Try Different Approach]
        D --> B
        C -->|Yes| E[Basic Solution]
    end

    subgraph "Phase 2: Make It Pretty"
        E --> F[Refactor & Clean]
        F --> G[Apply SOLID Pragmatically]
        G --> H[Get Stakeholder Feedback]
        H --> I[Polished Solution]
    end

    subgraph "Phase 3: Lock It Down"
        I --> J[Add Tests]
        J --> K[Security Review]
        K --> L[Add Monitoring]
        L --> M[Production Ready]
    end

    style A stroke:#f59e0b,stroke-width:3px
    style E stroke:#0ea5e9,stroke-width:3px
    style I stroke:#8b5cf6,stroke-width:3px
    style M stroke:#10b981,stroke-width:4px

Fas 1: gör att det fungerar

Mantran: spela först. bekräfta dina antaganden . få igång något

Detta är den fasen där ditt grann är en sandbox. Messy-kod är bra. Döda sluten är bra bra. Du, ', bygger inte en katedral här, ;, du, ', skissar på en näppaduk.

Vad du försöker förstå

  • Är det här API som faktiskt beter sig så som dokumenten säger?
  • Kan jag till och med lösa problemet med de verktyg jag har?
  • Vad är de faktiska kraven?
  • Är det ett två timmars problem eller ett två veckors problem?

Den kritiska insikten Tills du lyfter upp en PR, är ditt शाखा din., det är ditt experimentella laboratorium, ., ingen behöver se dina falska utgångspunkter, ,, dina kommenterade, M SK4, bortom debuggingkoden,,, dina uppgifter, ", vad som är fruktansvärt, MSC7, det här är hemskt. ,, stämda senare. ", kommentarer. M SK10, men kolla i OFTEN, MRK11, varje gång du har något som fungerar, MST12, gå igång och trycka på, Mst13, knuffar in i ditt CI-rör, M st14, ett bra rör kommer att ge dig mer information. MST15, integreringstester, M ST16, bryter förändringar på andra ställen, M S ST17, etc. M S T 18, det där är OK att kolla in fult, M T 20, knappt fastnat ihop, franken, MS T 21, koden, MS S T 22, att det är det där, M KS 23 är poängen, ms T 24, även när jag är tre år gammal, MST 25, en tioårig veteran, MS K 26, så gör jag fortfarande detta, MS J 27, det frigör mitt sinne, MSJ 28

Nebeneffekten frekventa antaganden är en hjärtslag

Denna psykologiska frihet är viktig. När du vet att du kan slänga allt bort och börja nyss , så tar du djärvare beslut Inte en typist.

// Phase 1 code looks like this - and THAT'S OKAY
public async Task<Result> ProcessThing(Request req)
{
    // TODO: what if this is null?
    var data = await _api.GetSomething(req.Id);

    // This probably isn't right but let's see what happens
    var transformed = data.Items
        .Where(x => x.Status != "deleted") // Is this the right filter?
        .Select(x => new Thing { Name = x.Name }); // Missing loads of fields

    // HACK: hardcoded for now
    return new Result { Items = transformed.ToList(), Total = 42 };
}

Är den här produktionskoden? ?? Gud nej! .? Är den användbar? ?? Absolut. .? Det säger till dig om ditt tillvägagångssätt är till och med hållbart innan du spenderar timmar på att poltera något som kommer att fungera.

Ett svar på feedback: Ni kan få feedback på: alla punkt i Fasen 1— nyckeln väljer som ger det. Målet är att bygga den bästa egenskapen

Att bygga en API som ett annat lag kommer konsumera.?, få dem inblandade tidigt.., en snabb lösning.M SK2, gör den här formen vettigt. ?", konversationen kan spara dagar på att göra om saker. ser ut snarare än att verk. Att ' är bra, ;, de ger feedback i fasen

Använd din bedömning. Ibland behöver du feedback klargöra, att inte godkänna —de specifikationen redan gjorde det ♫ . ♫ ♫ МSK3 ♫ specifikationerna säger ♫' ♫ hantera fel graciöst ♫

Om en inblandad persons input är kritiskt, men de kämpar med grov kod. , bygger en obetydlig demo. . En hårdkoderad lyckad, -, väg som visar konceptet, kan frigöra värdefull feedback utan att distrahera från sakna marginalfall.

Var pragmatisk. Målet är den bästa egenskapen

Fas 2: Gör det vackert

Mantran: Nu när du vet vad du bygger, gör du det bra.

Utforskningsfasen har gjort sitt jobb.

Nu städar du upp den.

// Phase 2: Same logic, but actually maintainable
public async Task<ProcessingResult> ProcessItemsAsync(
    ProcessingRequest request,
    CancellationToken cancellationToken = default)
{
    ArgumentNullException.ThrowIfNull(request);

    var items = await _itemRepository.GetActiveItemsAsync(
        request.AccountId,
        cancellationToken);

    var processedItems = items
        .Where(item => item.Status != ItemStatus.Deleted)
        .Select(item => _mapper.ToProcessedItem(item))
        .ToList();

    return new ProcessingResult
    {
        Items = processedItems,
        TotalCount = processedItems.Count,
        ProcessedAt = _timeProvider.UtcNow
    };
}

Det här är där du är

  • tillämpa SOLID-principer pragmatiskt Jag känner till dem, men jag tror inte på dem.
  • Ge saker riktiga namn
  • Lägga till typs hint och XML-dokumentation
  • Tänk på API-ytan från callers perspektiv
  • Ta hand om kanterna du upptäckte i Fasen 1

Kritiskt, det här är det bästa tillfället för icke- Funktionen är nu synlig och användbar, men du har inte investerat timmar i att skriva tester för den.

Fas 3: Slucka ner den

Mantran: Svåra den. ,. Testa den, ,. Gör den motståndskraftig.

Nu~—och bara nuM SK1~ skriver du dina tester~ .~ lägger till säkerhetskontroller~ .~ implementerar korrekta felbehandlingar för produktionsburkar~ M SK4~ lägger på övervakning och skyddsrailr~ MSC5~

Varför vänta tills nu? Eftersom du slutligen vet vad du testar.

Test som skrevs i Fasen 1 skulle ha varit felaktiga — du hade inte gjort det, ' du förstår inte problemet än, . Test som skrivits ifasen ♫ 2 ♫ skulle ha fått stötar, M SK5 ♫ du höll fortfarande på att raffinera API: n ♫ korrekt, eftersom implementeringen är stabil

[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(100)]
public async Task ProcessItemsAsync_WithValidRequest_ReturnsCorrectCount(
    int itemCount)
{
    // Arrange
    var items = _fixture.CreateMany<Item>(itemCount).ToList();
    _mockRepository
        .Setup(r => r.GetActiveItemsAsync(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
        .ReturnsAsync(items);

    // Act
    var result = await _sut.ProcessItemsAsync(
        new ProcessingRequest { AccountId = Guid.NewGuid() });

    // Assert
    result.TotalCount.Should().Be(itemCount);
    result.Items.Should().HaveCount(itemCount);
}

[Fact]
public async Task ProcessItemsAsync_WithNullRequest_ThrowsArgumentNullException()
{
    // Act & Assert
    await _sut.Invoking(s => s.ProcessItemsAsync(null!))
        .Should().ThrowAsync<ArgumentNullException>();
}

[Fact]
public async Task ProcessItemsAsync_ExcludesDeletedItems()
{
    // Arrange
    var items = new[]
    {
        new Item { Status = ItemStatus.Active },
        new Item { Status = ItemStatus.Deleted },
        new Item { Status = ItemStatus.Active }
    };
    _mockRepository
        .Setup(r => r.GetActiveItemsAsync(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
        .ReturnsAsync(items);

    // Act
    var result = await _sut.ProcessItemsAsync(
        new ProcessingRequest { AccountId = Guid.NewGuid() });

    // Assert
    result.Items.Should().HaveCount(2);
}

Den här fasen inkluderar också

  • säkerhetskontroll (avtegning, tillåtelse , inmatningsvalidation
  • Konkurentitetsfaktorer ( vad som händer under lastM SK1
  • Förlustsmodianalys ( vad händer om databasen är långsam ? Vad händer om API: n slutar
  • Monitoring ( hur kommer vi att veta om det går sönder i produktionen?

Feedback Choreografi

Alla människor borde vara inblandade i olika stadier.

graph TB
    subgraph "Phase 1: Technical Voices"
        P1[Make It Work] --> T1[Senior Dev Review]
        T1 --> T2["Does the approach<br/>make sense?"]
        T2 --> T3["Any obvious<br/>architectural issues?"]
    end

    subgraph "Phase 2: Non-Technical Voices"
        P2[Make It Pretty] --> N1[Product Owner Demo]
        N1 --> N2["Does it meet<br/>the requirement?"]
        N2 --> N3["Is the UX<br/>intuitive?"]
    end

    subgraph "Phase 3: Ops/Security Voices"
        P3[Lock It Down] --> O1[Security Review]
        O1 --> O2[Operations Review]
        O2 --> O3["Will it hold up<br/>in production?"]
    end

    T3 --> P2
    N3 --> P3

    style T1 stroke:#3b82f6,stroke-width:2px
    style N1 stroke:#8b5cf6,stroke-width:2px
    style O1 stroke:#ef4444,stroke-width:2px

Fas 1: Bara tekniska röster— högt uppsatta tjänstemän, arkitekter . De kan se bortom röran till formen av lösningen

Fas 2: Oväntade- tekniska röster välkomnad. Funktionen är användbarM SK2 knappar gör saker . få produkternas ägare och designer inblandade nu — ändringarna är fortfarande billiga

Fas 3: Operationer och säkerhetsljud . " Tänk om det här får ♫ 10x-strömförflyttning ♫?" ♫ | " ♫ Tänk om någon passerar ett ondskefullt intryck ♫

Filialer som Sandboxar

Här är det som förändrade mitt utvecklingsliv. tills du skaffar en PR, så är ditt bolag helt privat.

Det låter kanske uppenbart, men jag ser att utvecklare behandlar varje affär som det är, att ' spelar på sina permanenta albumer. . De är rädda för att experimentera, ., räddar sig för att skriva ful kod,., tvekar sig att bryta saker.

Jag vill att du tänker på din egen gren som en sandlåda.

graph TD
    A[Feature Branch Created] --> B[Experiment Freely]
    B --> C{Did it work?}
    C -->|No| D[Try Something Else]
    D --> B
    C -->|Yes| E[Clean Up the Mess]
    E --> F[Interactive Rebase]
    F --> G[Squash to Clean Commits]
    G --> H[Raise PR]
    H --> I[Public Code Review]

    style B stroke:#f59e0b,stroke-width:3px
    style D stroke:#f59e0b,stroke-width:2px
    style H stroke:#10b981,stroke-width:3px

    subgraph "Private - Go Mental"
        B
        C
        D
        E
    end

    subgraph "Public - Be Professional"
        H
        I
    end

I praktiken betyder detta

  • Kompilera ofta, även om koden inte kompilerar
  • skriva FIXME och TODO kommentarer överallt
  • Släpp debuggingkoden på plats medan du utforskar
  • Försök detta tillvägagångssätt med flera "
  • Oroa dig inte för att skicka meddelanden.

Sedan, innan du sätter upp en PR

  1. Skoda upp koden (Phasen 2)
  2. Lägg till tester (Phasen 3)
  3. Interaktiv återställning för att squasha all den där stökiga historien
  4. Skryf en korrekt anslutande meddelande

PR-bedömarna ser rena, professionella kod med en klar berättelse. De ser inte de sex felaktiga början, , de M SK4 morning " varför gör den här blodiga jobbet, ' gör det här, eller ,, eller de | " | undo undo | Undo " | historia |.

Sandlådan är privat.

Varför detta minskar stress

Den traditionella " var professionell i alla tider " utvecklingen är krossande . Alla linjer känner sig permanenta M SK3 Alla beslut känner sig konsekvenserna| . Den här metoden tar bort det trycket genom att begränsa kaosen lek är legitimt arbete i Fasen 1, kommer återkopplingen när pivot är billigt i Faset | 2, | och tester är skrivna en gång korrekt i Faser |3 | eftersom du slutligen vet vad du sätter igång med

"Play är rättvis arbete."

TDD fungerar lysande när domamet är väl förstådd eller du identifiserar en känd bug. Men när problemet fortfarande är fuzzy, betyder att skriva tester först ofta att testa fel saker tre gånger i rad.

Pragmatic SOLID (And DRY's Dirty Secret

Så vad gör den här tre- -phasen tankegången till dina designprinciper? Kort sagt, det dödar dogmat. Här är hur det förändrar mitt tankesätt om SOLID .

Jag nämnde att tillämpa SOLID-principer "pragmatiskt" i fas 2. Låt mig vara specifik med vad jag menar

SOLID är en bra uppsättning principer.

Här är min syntavla.

Apply SOLID when:

  • Du har bevis för att du kommer behöva flexibilitet.
  • Abstraktionen gör koden tydligare
  • Du bygger något som andra kommer att använda

Applicera inte SOLID när

  • Du speculaerar om framtida krav
  • Abstraktionen lägger till komplexitet utan klarhet
  • Du bygger inre kod som bara du underhåller

Tre liknande rader av kod är bättre än en för tidig abstraktion.

// Over-engineered SOLID worship
public interface IThingProcessor { }
public interface IThingProcessorFactory { }
public class ThingProcessorFactory : IThingProcessorFactory { }
public class ThingProcessor : IThingProcessor { }
public class ThingProcessorDecorator : IThingProcessor { }
public class ThingProcessorValidationDecorator : IThingProcessor { }

// What you probably actually need
public class ThingProcessor
{
    public async Task<Result> ProcessAsync(Thing thing)
    {
        // Just do the thing
    }
}

Om du behöver flexibilitet senare, så är refactoring billigt.

The DRY Trap

Och medan vi dödar heliga kor låt oss prata om DRY Det är kanske det mest dogmatiskt tillämpade principet i vår industri, och när det tillämpas utan någon tanke, orsakar det mer skada än nytta.

Problemet är det här. inte alla är samma typ av duplicering.

När två delar av kod ser likadana ut men tjänar olika syften, extraherar man dem till en gemensam abstraktion och kombinerar saker som borde utvecklas oberoende av varandra.

  1. Att lägga till parametrar och villkor för att hantera båda fallen (försämrar abstraktionen
  2. Att bryta det andra användande fallet
  3. Att kopiera ut den delade koden och redigera den.
// "DRY" solution - looks clever, causes pain
public async Task<Result> ProcessEntity<T>(
    T entity,
    bool validateFirst = true,
    bool sendNotification = false,
    Func<T, bool>? customFilter = null,
    Action<T>? preProcess = null,
    Action<T>? postProcess = null) where T : IEntity
{
    // 50 lines of conditional spaghetti trying to handle
    // "processing" for Users, Orders, and Products
    // because they all had 3 similar lines once
}

// What you probably actually need
public async Task<Result> ProcessUser(User user) { /* 15 clear lines */ }
public async Task<Result> ProcessOrder(Order order) { /* 15 clear lines */ }
public async Task<Result> ProcessProduct(Product product) { /* 15 clear lines */ }

Ja, ,, det finns ' och det finns dupliceringar i den andra metoden. Men varje metod är

  • lätt att förstå i isolering
  • Enkelt att modifiera utan biverkningar
  • Det är lätt att lösa när den enhetens typ försvinner
  • enkelt att testa med tydliga in- och utgångar

DRY-versionen är en mardröm att upprätthålla eftersom den

Min regel: Tolera duplicering tills du samma grej tre gånger, och du är säker på att det representerar samma koncept som kommer att utvecklas tillsammans.

Duplicering är mycket billigare än fel abstraktioner.

Interfaceproblemet i C#

Här är en annan helig ko som behöver slaktas. " varenda klass behöver en gränssnitt.

På den tidiga tiden med .NET beroende-injictioner , bestämde någon att för att göra koden testbar, skulle man behöva injicera gränssnitt överallt. IService och ServiceImpl. Plötsligt var varje C-kodexbas full av en enda implementeringsgränssnitt som fanns enbart för att testa

// The interface tax - early 2010s style
public interface IUserService { }
public interface IOrderService { }
public interface IEmailService { }
public interface INotificationService { }
public interface IPaymentProcessor { }
public interface IInventoryManager { }

// Each with exactly ONE implementation
public class UserService : IUserService { }
public class OrderService : IOrderService { }
// ... you get the idea

Det var cargo-kultprogram. "." Vi betalade komplexitetsskatt teoretiskt testerbarhet och teoretiskt framtida flexibilitet som sällan materialiseras

Här är det: :, du behöver inte det här längre.

Moderna mocking ramverk som NErsättning och Moq kan göra modeller av betonik klassar (med virtuella metoder). ännu viktigare , ASPMska3NET CoreM Ska4s DI-behållare fungerar utmärkt med betongsorterMske5

// Modern approach - just register the concrete type
services.AddScoped<UserService>();
services.AddScoped<OrderService>();
services.AddScoped<EmailService>();

// In your controller or service
public class OrderController(OrderService orderService, UserService userService)
{
    // Primary constructor injection - clean and simple
}

Inga gränssnitt. Nej IOrderService. Bara den service du behöver , injiceras direkt

" Men vad sägs om att testa ?" Jag hör dig gråta.

För enhetens tester, har du valmöjligheter:

  1. Gör nyckelmetoder virtual och använda ett mocking ramverk
  2. Använd den riktiga tjänsten med en testdatabas (integreringstest är ofta mer värdefulla hursomhelst
  3. Lägg till en gränssnitt när man faktiskt behöver den för att testa

Den tredje punkten är avgörande refactoring-verktyg är extraordinärt bra på att extrahera gränssnitt.

I Rider eller Visual Studio Ctrl+. → ♫ "Extraktera gränssnittet ♫ ♫" ♫ och du ♫

// Phase 1 & 2: Just write the class
public class OrderService
{
    public async Task<Order> CreateOrderAsync(CreateOrderRequest request) { ... }
    public async Task<Order?> GetOrderAsync(int orderId) { ... }
    public async Task CancelOrderAsync(int orderId) { ... }
}

// Phase 3: Need to mock it for testing? Extract interface in 2 seconds
public interface IOrderService
{
    Task<Order> CreateOrderAsync(CreateOrderRequest request);
    Task<Order?> GetOrderAsync(int orderId);
    Task CancelOrderAsync(int orderId);
}

public class OrderService : IOrderService { ... }

Mitt arbetsflöde nu: gränssnitt kommer i Fas 3, inte Fas ≥1.

Under ", gör det bra, " och " gör det vackert, M SK3, jag skriver bara lektioner, ., ingen för tidig abstraktion. ., Ingen gränssnittskatt på varje typ.., Koden är enklare, MSC7, lättare att navigera. IFoo och Foo), och snabbare att skriva

När jag trycker på "lockar den ner" och behöver skriva tester sedan Jag bestämmer vilka tjänster jag egentligen behöver mocka.

För interna företagslogiska tjänster? Hälften av tiden testar jag dem bara direkt med riktiga beroenden . Testerna är ju mer värdefulla eftersom de testar det faktiska beteendet , inte en modell på vad jag gör tänk beteendet bör vara.

// Services I typically DO extract interfaces for (external boundaries)
public interface IPaymentGateway { }      // Third-party API
public interface IEmailSender { }         // External service
public interface IBlobStorage { }         // Cloud storage

// Services I typically DON'T need interfaces for (internal logic)
public class OrderValidator { }           // Pure logic, test directly
public class PriceCalculator { }          // Pure logic, test directly
public class OrderService { }             // Test with real repo + in-memory DB

Poängen är: sluta skriva gränssnitt uppfront " endast i fallet". skriva konkreta klasser . Om du behöver ett gränssnitt för att testa eller genuin polymorfism senare M SK3 extrahera en tar bokstavligen några sekunder med moderna verktyg

Din kodbas kommer att bli enklare , mer navigerbar, och du kommer att ha samma underhållslast som att hålla gränssnitten i synkronis med implementeringar utan någon nytta

Agile Connection

Ironiskt nog är denna synvinkel mer i harmlighet med original Agil manifest än de flesta "Agile" processer jag har stött på | ' | МSK3 | Softwaren jobbar snabbt ♫ ( | Fasen |1). | Att reagera på förändringen ♪ ( |fasen ♫1-2). | Kundenkooperation när egenskaperna kan demonstreras ♫

Moderna """, "" Agile "", " "", har blivit fångad av processentusiaster som "'" har lagt till så många ceremonier där, ' "" det finns ingen tid kvar att utforska "". . "" Den här metoden återskapar lek "—", inte som en ödmjukhet, utan som en laglig fas i kreativt arbete.

När inte använda den här metoden

Jag borde vara ärlig.

Bugfixs: När du fixerar en känd bug, gör TDD-stilen mer begriplig.

Bra-understood problems: Om du implementerar en standardalgoritm eller ett mönster som du har använt hundra gånger

Straga deadlines med kända krav: Om du verkligen vet exakt vad du bygger och när det behöver skickas, kan du hoppa över Fasen 1 utforskande

Högreglerade miljöer: Om varje sammandrag behöver märke, så är sandboxing svårare. Även om du fortfarande kan utforska lokalt innan du trycker.

Men för mest featuring development—where requirements are a bit fuzzy

En anteckning för unga utvecklare

Om du är tidigt i din karriär, så känner du kanske pressen att visa koden hela tiden.

Bottom Line

Få det att fungera , få det att vara vackert, låsa ner den

Börja enkelt . Designa med förutsägelse men inte spekulationer. upprepa snabbt. Bara lägga till komplexitet när det lönar sig

"Duplicering är mycket billigare än fel abstraktioner

Uppforskningsfasen är inte "'", det är ett skyldigt hemligt . —, det är en definierad del av processen. ., ditt fält är ditt sandlåda tills du upplever ett PR.

Det håller kreativiteten levande.,, minskar stress.,, och skapar system som skalar elegant. —, eftersom du förstod problemet innan du beslutade dig för lösningen.

Sluta att försöka vara perfekt från minut ett. Ge dig själv tillåtelse att spela


Omvänt läsning

Om det här resonerar, kan du också glädjas av de här besläktade Beiträgena:

Finding related posts...
logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.