# Koodilla leikkiminen: Tehokas agiliili lähestymistapa

<datetime class="hidden">2025-11-23T10:00</datetime>

<!-- category -- Software Development, Agile, Best Practices, Craftsmanship -->
**Miksi oksan hoitaminen hiekkalaatikkona – ja pelaaminen laillisena työnä – tuottaa parempia ohjelmistoja vähemmällä stressillä.**

> **Kuuma otto:** Useimmat kehittäjät yrittävät olla täydellisiä kaikissa kehitysvaiheissa, ja se tappaa sekä heidän luovuutensa että tuottavuutensa.

## Johdanto

Olen rakentanut ohjelmistoja ammattimaisesti... No, sanotaan vain, että muistan, kun "Agile" oli uusi idea, eikä projektipäälliköiden aseellistama yritysten muotisana, jolla perusteltiin lisää standupeja.

Vuosien varrella olen huomannut jotain: paras koskaan kirjoittamani koodi tuli projekteista, joissa tunsin *lupa pelata*Pahin koodi tuli projekteista, joissa jokainen näppäily tuntui siltä, että sitä tutkittiin, ja yritin kirjoittaa "tuotantovalmiin" koodin yhdestä minuutista alkaen.

Tämä ei ole sattumaa. **Luovuus ja paine eivät sovi yhteen.**

Olen siis kehittänyt lähestymistavan, joka erottaa sotkuisen, luovan tutkimustyön kiillotetusta, tuotantovalmiista toimituksesta.[Laita se toimimaan, tee siitä kaunis, lukitse se](/blog/makeitworkthenmakeitpretty)" - mutta oikeastaan kyse on siitä, että annat itsellesi luvan kokeilla ilman täydellisyyden taakkaa.

[TOC]

## Kolme vaihetta

Haluan tehdä selväksi, että kyse ei ole huolimattomuudesta tai kurinpidon välttämisestä. *oikeanlaisen kurin soveltaminen oikeaan aikaan*.

```mermaid
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
```

### Vaihe 1: Saa se toimimaan

**Mantra:** Pelaa ensin, vahvista olettamuksesi.

Tämä on vaihe, jossa haarasi on hiekkalaatikko. Sotkuinen koodi on hyvä. Kuolleet ovat kunnossa. *hienonna*Et rakenna tänne katedraalia, vaan piirrät lautasliinaa.

Se, mitä yrität selvittää:

- Käyttääkö tämä API todella niin kuin lääkärit väittävät? (Spoiler: se harvoin tekee)
- Voinko edes ratkaista tämän ongelman työkaluilla, joita minulla on?
- Mitkä ovat varsinaiset vaatimukset, eivät lippuun merkityt?
- Onko tämä kahden tunnin vai kahden viikon ongelma?

**Kriittinen näkemys:** Siihen asti, kunnes nostat PR:n, haarasi on *omasi*. Se on kokeellinen laboratoriosi. Kenenkään ei tarvitse nähdä vääriä aloituksiasi, kommentoidun vianetsintäkoodiasi, "TODO: tämä on kamalaa, korjaa myöhemmin" -kommenttejasi. Mutta tarkista OFTEN-sivustolta aina, kun sinulla on jotain toimivaa, sitoutuvaa ja työntävää. Koukkaa CI-putkeesi, hyvä putki antaa sinulle enemmän tietoa (integraatiotestit, muualla tapahtuneet muutokset jne.). On hyvä tarkistaa rumat, hädin tuskin ommellut franken-koodit. Se on kohta. Jopa kolmikymppisenä veteraanina teen tämän. Se vapauttaa mieleni.

**Sivuhyödyt:** Taajuussitoumukset ovat sydämen syke. Kauko-/asynkkijoukkueissa "ruman mutta etenevän" vakaa polku vakuuttaa kaikki, että työ liikkuu – ilman, että Slackissa joudutaan suorittamaan tuottavuutta.

Tämä psykologinen vapaus on tärkeää. Kun tiedät, että voit heittää kaiken pois ja aloittaa alusta, teet rohkeampia päätöksiä. Kokeilet outoa lähestymistapaa, joka ei todennäköisesti toimi. Joskus se toimii loistavasti. Joskus sinun täytyy lukea artikkeli, katsoa YouTube-video, ajatella jonkin aikaa. Muista: työsi on tuottaa hyviä ohjelmia ja hyviä päätöksiä, ei kirjoittaa nopeasti. Hyvät johtajat ymmärtävät, että olet **EI TYYPPI**.

```csharp
// 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 };
}
```

Onko tämä tuotantokoodi? Onko siitä hyötyä? Ehdottomasti. Se kertoo, onko lähestymistapasi edes kannattavaa, ennen kuin investoit tunteja hiomalla jotain, mikä ei toimi.

**Palautetta koskeva tiedote:** Palautetta saat osoitteesta *mitä tahansa* kohta ensimmäisessä vaiheessa – tärkeintä on valita *kuka* Tavoitteena on rakentaa paras ominaisuus, ei noudattaa tiukkaa prosessia.

API:n rakentaminen, jonka toinen tiimi kuluttaa? Ota heidät mukaan ajoissa. Nopea "onko tässä hahmossa järkeä?" -keskustelu voi säästää päiväkausia uudelleen työskentelyltä. Jos rakennat jotain käyttäjää varten, voit ehkä odottaa, että näytät liike-elämän sidosryhmälle, kunnes se on vähemmän rajua – jotkut ihmiset kiinnittävät huomiota siihen, miten jotain on tekeillä. *näyttää* sen sijaan, onko se *teoksia*Hyvä on, he antavat palautetta toisessa vaiheessa.

Käytä harkintakykyäsi. Joskus tarvitset palautetta **selkiytä**"Spekti sanoo "käsivirheitä sulavasti" - tarkoittaako se, että yritä uudelleen kolme kertaa vai epäonnistu nopeasti ja ilmoita?" Se on ensimmäisen vaiheen keskustelu.

Jos sidosryhmien panos on kriittinen, mutta he kamppailisivat karkean koodin kanssa, he rakentaisivat tyhjänpäiväisen demon. Konseptin näyttävä kovakoodattu iloinen polku voi avata arvokasta palautetta ilman, että puuttuvat reunajutut häiritsevät.

Tavoitteena on paras ominaisuus, ei prosessin puhtaus.

### Vaihe 2: Tee siitä kaunis

**Mantra:** Nyt kun tiedät, mitä rakennat, tee se *hyvä*.

Kokeiluvaihe on tehnyt työnsä. Olet vahvistanut, että lähestymistapa toimii, ymmärrät ongelman muodon, ja olet todennäköisesti löytänyt tusinan teräviä tapauksia, joita alkuperäisessä lipussa ei mainita.

Nyt siivoat sen:

```csharp
// 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
    };
}
```

Tässä kohtaa sinä:

- Sovella SOLID-periaatteita *pragmaattisesti* (Tunne heidät, mutta älä palvo heitä)
- Anna asioille oikeat nimet
- Lisää tyyppivihjeet ja XML-dokumentaatio
- Ajattele API-pintaa soittajan näkökulmasta
- Hoitakaa ensimmäisessä vaiheessa löytämänne reunajutut.

**Kriittisesti tämä on paras aika ei-tekniselle sidosryhmäpalautteelle.** Ominaisuus on nyt näkyvä ja käyttökelpoinen, mutta et ole vielä sijoittanut tunteja sen kirjoittamiseen. Jos he sanovat, että "olemme itse asiassa halunneet sen tekevän X:n", voit kääntää sen ilman, että sydän särkyy, kun kattava testisarja poistetaan.

### Vaihe 3: Lukitse se alas

**Mantra:** Kovettaa, testata, tehdä kestäväksi.

Nyt – ja vasta nyt – kirjoitat testisi, lisäät turvatarkastukset, panet täytäntöön oikean virheenkäsittelyn tuotannon reuna-asioissa, lisäät valvonta- ja suojakaiteet.

Miksi odottaa tähän asti? **Vihdoin tiedät, mitä testaat**.

Vaiheessa 1 kirjoitetut testit olisivat olleet virheellisiä – et vielä ymmärtänyt ongelmaa. Vaiheessa 2 kirjoitetut testit olisivat olleet virheellisiä – olit vielä hiomassa API-rajapintaa. Vaiheessa 3 kirjoitetut testit ovat *oikein*, koska toteutus on vakaa.

```csharp
[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);
}
```

Tähän vaiheeseen kuuluu myös:

- Turvallisuuskatsaus (varmennus, lupa, syötteiden validointi)
- Valuuttaseikat (mitä tapahtuu kuormitettuna?)
- Epäonnistumistilan analyysi (mikäli tietokanta on hidas? Entä jos API loppuu?)
- Seuranta (miten tiedämme, hajoaako tuotanto?)

## Palaute Koreografia

Eri ihmisten pitäisi olla mukana eri vaiheissa. Jos teet tämän väärin, hukut kitkaan.

```mermaid
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
```

**Vaihe 1:** Vain tekniset äänet – hyvät devsit, arkkitehdit. He näkevät sotkun ohi ratkaisun muotoon. Muut kuin tekniset sidosryhmät joutuvat paniikkiin karkealla koodilla ja kysyvät nappiväreistä, kun olet vielä selvittämässä datamallia.

**Vaihe 2:** Ei-tekniset äänet ovat tervetulleita. Ominaisuus on käyttökelpoinen, painikkeet tekevät asioita. Ota tuotteen omistajat ja suunnittelijat mukaan nyt – muutokset ovat edelleen halpoja, et ole vielä tehnyt kirjallisia testejä.

**Vaihe 3:** "Mitä jos tämä saa 10-kertaisen liikenteen?" "Mitä jos joku läpäisee ilkivaltaisen syötön?" Näillä on merkitystä vain, kun ominaisuus todella toimii.

## Haarukoita hiekkalaatikoina

Tässä on asia, joka muutti kehityselämäni: **kunnes nostat PR:n, haarasi on täysin yksityinen**.

Se voi kuulostaa ilmeiseltä, mutta näen kehittäjien kohtelevan jokaista sitoumusta kuin se olisi heidän pysyvällä levyllään. He pelkäävät kokeiluja, pelkäävät kirjoittaa rumaa koodia, pelkäävät rikkoa asioita.

Haluan, että pidät ominaisuuttasi hiekkalaatikkona, leikkikenttänä, laboratoriona, jossa odotetaan räjähdyksiä, eikä kukaan loukkaannu.

```mermaid
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
```

Käytännössä tämä tarkoittaa:

- Toimita usein, vaikka koodia ei koostettaisikaan
- Kirjoita `FIXME` sekä `TODO` kommentteja kaikkialla
- Jätä vianetsintäkoodi paikoilleen tutkiessasi
- Moninkertainen "yritä tätä lähestymistapaa" sitoo
- Älä huoli sitoutumisviesteistä (muistutat myöhemmin)

Sitten, ennen kuin nostat PR:

1. Puhdista koodi (vaihe 2)
2. Lisätestit (vaihe 3)
3. Vuorovaikutteinen uudelleenpohja murskaa kaiken tuon sotkuisen historian
4. Kirjoita oikea toimitusviesti

PR-arvostelijat näkevät puhtaan ammattikoodin, jossa on selkeä kuvaus. He eivät näe kuutta väärää aloitusta, aamukolmen "miksi ei tämä hemmetin työ" tai "undo undo unch rever" -historiaa.

> **Hiekkalaatikko pysyy yksityisenä ja kiillotustyöt julkistetaan.**

## Miksi tämä vähentää stressiä

Perinteinen "ole aina ammattimainen" kehitys on murskaavaa. Jokainen linja tuntuu pysyvältä. Jokainen päätös tuntuu seuraukselta. Tämä lähestymistapa poistaa paineen hillitsemällä kaaosta: **pelaaminen on laillista työtä** Vaiheessa 1 palautetta tulee, kun kääntely on halpaa vaiheessa 2, ja testit kirjoitetaan kerran oikein vaiheessa 3, koska vihdoin tiedät, mitä testaat.

> **"Pelaaminen on laillista työtä."**

TDD toimii loistavasti, kun verkkotunnus on hyvin ymmärretty tai viat ovat tiedossa. Mutta kun ongelma on vielä epäselvä, testauksen kirjoittaminen tarkoittaa usein väärän asian testaamista kolme kertaa peräkkäin. Tämä lähestymistapa sivuuttaa sen, että: tutki ensin, testaa kun tilanne on vakaa.

## Pragmaattinen SOLID (ja DRY:n likainen salaisuus)

Joten mitä tämä kolmivaiheinen ajattelutapa tekee suunnitteluperiaatteillesi? Lyhyesti sanottuna se tappaa dogmaattisuuden. Näin se muuttaa ajatteluani SOLIDista, DRY:stä ja C#:n rajapinnoista.

Mainitsin, että SOLID-periaatteita "käytännöllisesti" sovelletaan 2. vaiheessa. Haluan kertoa tarkemmin, mitä tarkoitan.

SOLID on hyvä periaatekokonaisuus. Opetan niitä. Käytän niitä. Mutta olen nähnyt joukkueiden katoavan abstraktiokaninreikiin yhden vastuun tai avoimen/suljetun nimissä luoden niin joustavia järjestelmiä, että ne ovat käsittämättömiä.

Tässä on heuristiikkani:

**Levitä SOLID, kun:**

- Sinulla on todisteita, joita tarvitset joustavuutta.
- Abstraktio tekee koodista selkeämmän
- Rakennat jotain, mitä muut käyttävät

**Älä käytä SOLID-valmistetta, kun:**

- Te spekuloitte tulevaisuuden vaatimuksilla
- Abstraktio lisää monimutkaisuutta ilman selvyyttä
- Rakennat sisäistä koodia, jota vain sinä pidät yllä

Kolme samanlaista koodilinjaa on parempi kuin ennenaikainen abstraktio. Kaavan ilmetessä kuvion voi aina poimia myöhemmin. Arkkitehtuuriin leivottua abstraktiota ei voi helposti perua.

```csharp
// 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
    }
}
```

Jos tarvitset joustavuutta myöhemmin, refactoring on halpaa. Liiallinen suunnittelu etukäteen on kallista, koska ylläpidät abstraktiokerroksia, jotka eivät ansaitse ylläpitoaan.

### DRY:n ansa

Ja samalla kun tapamme pyhiä lehmiä, puhutaan **DRY** (Älä toista itseäsi) Se on ehkä dogmaattisimmin sovellettu periaate alallamme, ja kun sitä sovelletaan ajattelematta, siitä on enemmän haittaa kuin hyötyä.

Ongelmana on seuraava: **kaikki päällekkäisyydet eivät ole samanlaista päällekkäisyyttä**.

Kun kaksi koodinpalaa näyttävät samanlaisilta mutta palvelevat eri tarkoituksia, niiden kokoaminen yhteiseksi abstraktioksi parisuhdetta, jonka pitäisi kehittyä itsenäisesti. Nyt kun yhden käyttötapauksen on muututtava, on joko:

1. Parametrien ja ehdollisten parametrien lisääminen molempien tapausten käsittelyyn (abstraktion pahentaminen)
2. Toisen käyttölaatikon katkaiseminen
3. Jaetun koodin kopiointi ja muokkaaminen (DRY:n hyväksyminen oli väärin tässä)

```csharp
// "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 */ }
```

Kyllä, toisessa lähestymistavassa on "kaksoiskappaleita", mutta jokainen menetelmä on:

- Helppo ymmärtää eristyksissä
- Helppo muuttaa ilman sivuvaikutuksia
- Helppo poistaa, kun kyseinen yhteisötyyppi katoaa
- Helppo testata selkeillä tuloksilla ja tuotoksilla

DRY-versio on painajaismainen, koska se yrittää olla kaikkea kaikille soittajille. Jokainen muutos vaatii kaikkien käyttötapausten ymmärtämistä. Jokainen viankorjaus voi rikkoa jotain muuta.

**Minun sääntöni:** Toleroi päällekkäisyyksiä, kunnes olet nähnyt *sama* Asia kolme kertaa ja olet varma, että se edustaa *sama* Käsite, joka kehittyy yhdessä. Siihen asti, hieman kopiointi-paste on hyvä. Se ei ole moraalinen epäonnistuminen, se on sopiva varovaisuus.

Kopiointi on paljon halvempaa kuin väärä abstraktio. Voit aina deduplicata myöhemmin, kun kuvio on selkeä. Huonoa abstraktiota, joka on kudottu koodipohjan läpi, ei voi helposti perua.

### Liitäntäongelma C#:ssä

Tässä on toinen pyhä lehmä, joka pitää teurastaa: **"jokainen luokka tarvitsee rajapintakuvion"**.

Jo .NET-riippuvuusruiskeen alkuaikoina joku päätti, että koodin testaaminen edellyttää rajapintojen ruiskuttamista kaikkialle. Logiikka oli: ei voi pilkata konkreettista luokkaa, joten jokainen palvelu tarvitsee `IService` sekä `ServiceImpl`. Yhtäkkiä jokainen C#-koodipohja oli täynnä vain testaamista varten luotuja yhden toiminnon rajapintoja.

```csharp
// 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
```

Tämä oli rahtikulttiohjelmointia, josta maksoimme monimutkaisuusveroa. *teoreettinen* testattavuus ja *teoreettinen* tuleva joustavuus, joka harvoin toteutui.

**Juttu on näin: et tarvitse tätä enää.**

Nykyaikaiset pilkkakehykset, kuten [NSubstitutioi](https://nsubstitute.github.io/) sekä [Moq](https://github.com/moq/moq4) ASP.NET Coren DI-kontti toimii erinomaisesti betonityyppien kanssa:

```csharp
// 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
}
```

Ei rajapintoja. `IOrderService`Vain tarvitsemaasi palvelua, suoraan ruiskutettuna.

**"Mutta entä testaaminen?"** Kuulen sinun itkevän.

Yksikön testeissä on vaihtoehtoja:

1. Tee avainmetodeista `virtual` ja käyttää pilkkaavaa kehystä
2. Käytä todellista palvelua testitietokannan kanssa (integraatiotestit ovat usein muutenkin arvokkaampia)
3. Lisää rajapinta *kun sitä oikeasti tarvitaan testaamiseen*

Kolmas seikka on ratkaisevan tärkeä: **Refactoring-työkalut ovat poikkeuksellisen hyviä rajapintojen poistossa**.

Riderissä tai Visual Studiossa se on kirjaimellisesti `Ctrl+.` → "Extract Interface" ja olet valmis. Jokainen menetelmä poistetaan, luokka päivitetään sen toteuttamiseen, ja voit tarvittaessa löytää/korvata kaikki käyttötavat. Se kestää sekunteja.

```csharp
// 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 { ... }
```

Työnkulkuni nyt: **Liitännät tulevat vaiheessa 3, ei vaiheessa 1**.

"Tee se toimivaksi" ja "nätiksi", kirjoitan vain tunteja. Ei ennenaikaista abstraktiota. Ei rajapintaveroa kaikille tyypeille. Koodi on yksinkertaisempi, helpompi navigoida (ei hyppyä välillä). `IFoo` sekä `Foo`) ja nopeammin kirjoittamaan.

Kun painan "sammuta se" ja minun täytyy kirjoittaa testejä, *sitten* Minä päätän, mitä palveluita on oikeasti pilkattava. Yleensä se on vähemmän kuin luulisi - usein vain ulkoisia integraatioita, kuten HTTP-asiakkaita, tietokantoja ja viestijonoja.

"Sisäisen liiketoiminnan logiikan palveluihin?" Puolet ajasta testaan niitä suoraan todellisilla riippuvuuksilla. Testit ovat joka tapauksessa arvokkaampia, koska ne testaavat todellista käyttäytymistä, eivät pilkkaa sitä, mitä minä teen. *ajattele* käytöksen pitäisi olla.

```csharp
// 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
```

**Asian ydin on:** Lopeta rajapintojen kirjoittaminen etukäteen "ihan varmuuden vuoksi". Kirjoita konkreettisia kursseja. Jos tarvitset rajapinnan testausta tai aitoa polymorfismia varten myöhemmin, poimiminen vie kirjaimellisesti sekunteja modernilla työkalulla.

Koodipohjasi on yksinkertaisempi, navigoitavampi, eikä sinulla ole ylläpitotaakkaa siitä, että pidät rajapinnat yhteneväisinä toteutusten kanssa hyödyttämättä.

## Kiukkuinen yhteys

Ironista kyllä, tämä lähestymistapa on linjassa *alkuperäinen* Ketterä manifesti kuin useimmat "Agile"-prosessit, joita olen kohdannut. Työohjelmistot nopeat (Pase 1) Muutokseen reagoiminen (Pase 1-2). Asiakasyhteistyö, kun ominaisuus on todistettavissa (Pase 2). Ei kiripisteitä, ei nopeusseurantaa, ei verisiä burndown-taulukoita.

Modernin "Agilin" ovat vallanneet prosessiharrastajat, jotka ovat lisänneet niin paljon seremonioita, ettei tutkimiselle ole enää aikaa. Tämä lähestymistapa tuo leikin takaisin – ei mieltymyksenä, vaan luovan työn laillisena vaiheena.

## Kun tätä lähestymistapaa ei tule käyttää

Minun pitäisi olla rehellinen: tämä ei aina ole oikea lähestymistapa.

**Virhekorjaukset:** Kun korjaat tunnettua vikaa, TDD-tyyli on järkevämpi. Kirjoita testi, joka toistaa vian, korjaa se, tehty.

**Hyvin ymmärrettyjä ongelmia:** Jos olet ottamassa käyttöön standardialgoritmia tai mallia, jota olet käyttänyt sata kertaa, et tarvitse tutkimusvaihetta.

**Tiukat määräajat tunnetuille vaatimuksille:** Jos todella tiedät tarkalleen, mitä rakennat ja kun se tarvitsee lähetystä, saatat jättää ensimmäisen vaiheen tutkimusretken väliin.

**Hyvin säännellyt ympäristöt:** Hiekkanyrkkeily on hankalampaa, jos jokainen sitoumus vaatii hyväksyntää, mutta voit silti tehdä tutkimuksen paikallisesti ennen työntämistä.

Mutta *eniten* Ominaisuuskehitys – jossa vaatimukset ovat hieman epämääräisiä, tekninen lähestymistapa ei ole ilmeinen, ja tarvitaan tilaa ajatella – tämä lähestymistapa toimii loistavasti.

## Muistiinpano juniorikehittäjille

Jos olet urasi alussa, saatat tuntea painetta näyttää "täydellistä" koodia koko ajan. Sinun ei tarvitse. Käytä tätä kolmivaiheista lähestymistapaa – selvitä vain, missä vaiheessa olet, ja viimeistele työ aina 3. vaiheeseen asti. Kukaan ei syytä sinua sotkuisesta tutkimuskoodista, jos se johtaa kiillotettuun, testattuun, tuotantovalmiiseen koodiin lopussa.

## "Perusviiva"

**Laita se toimimaan, tee siitä kaunis, lukitse se.**

Aloita yksinkertaisella suunnittelulla, ennakoinnilla, mutta ei spekulaatiolla. Iteroi nopeasti, lisää monimutkaisuutta vain silloin, kun se tuottaa tulosta.

> **"Korjaus on paljon halvempaa kuin väärä abstraktio."**

Tutkimusvaihe ei ole syyllinen salaisuus, se on määritelty osa prosessia. Haarakonttorisi on hiekkalaatikkosi, kunnes nostat PR:n. Tekninen palaute tulee aikaisin, ei-tekninen palaute tulee kesken, operaatiot tulevat ja turvallisuuspalaute tulee myöhässä.

Tämä pitää luovuuden elossa, vähentää stressiä ja tuottaa järjestelmiä, jotka skaalautuvat tyylikkäästi – koska ymmärsit ongelman ennen kuin sitouduit ratkaisuun.

> **Älä yritä olla täydellinen ensimmäisestä hetkestä lähtien, vaan anna itsellesi lupa pelata.**

---


## Aiheeseen liittyvää lukemista

Jos tämä resonoi, saatat myös nauttia näistä asiaan liittyvistä kirjoituksista:

- **[Miten rakennan ohjelmistoja](/blog/makeitworkthenmakeitpretty)** - syventyy tämän lähestymistavan taustalla olevaan testausfilosofiaan, jossa on yksityiskohtaisia koodiesimerkkejä kolmesta vaiheesta.
- **[Päivittäiset Standupit ovat paskapuhetta (elleivät he ansaitse palkkaansa)](/blog/agile-standups-ceremony-tax)** - Oma käsitykseni siitä, miksi seremonia on veroa, ja miten arvioida, ansaitsevatko ketterät rituaalisi pitää.
- **[Agile Spectures: Itse asiassa toimivien ominaisuusnäytösten kirjoittaminen](/blog/writingfeaturespecs)** - Koska spektaakkelit ovat työkaluja, eivät Raamattuja, ja niiden käsitteleminen elävinä asiakirjoina on ainoa tapa rakentaa oikea asia.
- **[Lahjonnan koodien työstäminen](/blog/workingonlegacysystems)** - Käytännöllisiä opetuksia vuosikymmeniä kestäneestä urakoitsijaelämästä, mukaan lukien se, miten näitä periaatteita sovelletaan, kun opettelee järjestelmää, jota ei ole rakentanut.