Att leka med Code: En effektiv agyl-metod
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
FIXMEochTODOkommentarer ö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
- Skoda upp koden (Phasen 2)
- Lägg till tester (Phasen 3)
- Interaktiv återställning för att squasha all den där stökiga historien
- 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.
- Att lägga till parametrar och villkor för att hantera båda fallen (försämrar abstraktionen
- Att bryta det andra användande fallet
- 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:
- Gör nyckelmetoder
virtualoch använda ett mocking ramverk - Använd den riktiga tjänsten med en testdatabas (integreringstest är ofta mer värdefulla hursomhelst
- 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:
- Hur jag bygger mjukvara - Går djupare in i testfilosofin bakom den här ansatsen , med detaljerade kodexempler av de tre faserna
- Daily Standups är skit "(", om de inte får sin lön - Min inställning till varför ceremonin är skattedriven , och hur man kan bedöma om dina agilla ritualer tjänar sitt syfte
- Agile Speccing: Att skriva egenskaper som faktiskt fungerar - Eftersom specifikationer är verktyg , inte bibeln — och att behandla dem som levande dokument är det enda sättet att bygga rätt
- Att arbeta med Legacy Codebases - Praktiska lärdomar från årtionden av byggarens liv