Back to "Hoe ik bouw Software: Maak het werken, maak het mooi, vergrendel het naar beneden"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

Best Practices Craftsmanship Software Development TDD Testing

Hoe ik bouw Software: Maak het werken, maak het mooi, vergrendel het naar beneden

Wednesday, 19 November 2025

Waarom ik de laatste test schrijf, en waarom dat niet is wat jij denkt dat het betekent.

**Controversiële opname:**Test-Driven Development is een briljante praktijk die het verkeerde probleem voor de meeste creatieve werk oplost.

Dit is wat beter werkt.

De drie fasen van de bouwsoftware (dat werkt eigenlijk)

Zo bouw ik software.

Het is eenvoudig, het is effectief, en het maakt TDD puristen beleefd groeven hun wenkbrauwen:

Zorg dat het werkt.*Maak het mooi.*Sluit het af met testen.

graph TB
    Start[New Feature Request] --> P1[Phase 1: Make It Work]

    P1 --> P1_Private[Private Exploration]
    P1_Private --> P1_Sketch["Sketch the solution<br/>Messy code OK<br/>No tests yet<br/>Focus on learning"]
    P1_Sketch --> P1_Run["Run it manually<br/>Try different inputs<br/>See what breaks<br/>Understand the problem"]
    P1_Run --> P1_Decision{Does it work<br/>basically?}
    P1_Decision -->|No| P1_Iterate[Try different approach]
    P1_Iterate --> P1_Sketch
    P1_Decision -->|Yes| P2

    P2[Phase 2: Make It Pretty] --> P2_Refine[Private Refinement]
    P2_Refine --> P2_Clean["Clean up the code<br/>Better names<br/>Type hints/annotations<br/>Clear structure"]
    P2_Clean --> P2_Align["Align with spec<br/>Cover all requirements<br/>Handle edge cases<br/>Polish API surface"]
    P2_Align --> P2_Optimise["Optimise<br/>Performance<br/>Readability<br/>Maintainability"]
    P2_Optimise --> P3

    P3[Phase 3: Lock It Down] --> P3_Share[Public Sharing]
    P3_Share --> P3_Tests["Write comprehensive tests<br/>Full coverage<br/>Edge cases<br/>Error conditions"]
    P3_Tests --> P3_CI["CI/CD Integration<br/>Automated testing<br/>Code review<br/>Documentation"]
    P3_CI --> P3_Deploy["Ready to Share<br/>Commit to main<br/>Deploy to prod<br/>Other devs can use it"]

    P3_Deploy --> Done[Well-Tested,<br/>Well-Designed Code]

    style P1 stroke:#ff6b6b,stroke-width:3px
    style P2 stroke:#4ecdc4,stroke-width:3px
    style P3 stroke:#95e1d3,stroke-width:3px
    style Done stroke:#38ada9,stroke-width:4px

In die volgorde.*Altijd.*Niet omdat ik lui ben.

Niet omdat ik geen testen waardeer.

Maar omdat

Dit is hoe creatief werk daadwerkelijk gebeurt

Als je een probleemruimte verkent die je nog niet volledig begrijpt.

  • Niet omdat ik lui ben.
  • Niet omdat ik geen testen waardeer.
  • Maar omdat
  • Dit is hoe creatief werk daadwerkelijk gebeurt

Als je een probleemruimte verkent die je nog niet volledig begrijpt.

Laat het me uitleggen.

Fase 1: Laat het werken (de privé-sketch)

Dit is het rommelige gedeelte.Het deel waar ik geen idee heb wat ik doe.

Ik probeer het te begrijpen:

Werkt deze API echt zoals de dokters beweren?

Kan ik dit probleem oplossen met het gereedschap dat ik heb?

Wat betekent "correct" in deze context?*Is dit een probleem van twee uur of twee weken?*Dit is uitvinding.

Dit is verkenning.

Dit is jazz.

# Ugly, exploratory code
def process_thing(data):
    # TODO: This is terrible, fix later
    result = []
    for item in data:
        # Not sure if this is the right approach...
        x = item.get('value')
        if x:  # Is this even the right condition?
            result.append(x * 2)  # Why multiply by 2? Testing something...
    return result

En weet je wat jazz doodt?**Iemand die over je schouder staat en zegt: "Schrijf eerst de test."**Waarom nog geen tests?

Omdat*De vorm is nog steeds vloeibaar.*Stel je voor dat je beeldhouwer bent.

Je hebt een blok marmer en een vaag idee: "Ik wil een vogel snijden."

TDD zegt: "Voordat je dat marmer aanraakt, schrijf je precies op hoe een vogel eruit ziet.

Definieer zijn spanwijdte.Specificeer de hoek van elke veer.

Snijd nu naar die specificaties."*Dat is... niet hoe vogels gekerfd worden. (Of software wordt geschreven, zo blijkt.)*Echte beeldhouwers

Eerst de vorm uitvegen.Ze schetsen.

Ze maken maquettes.*Ze verscheuren grote stukken steen om de algemene vorm te vinden.*Pas later verfijnen ze de details.

Code is hetzelfde.

In fase 1 schrijf ik:

Deze code is

Privé.

Het is een gesprek met mezelf.

Het is hoe ik erachter kom wat "werken" zelfs betekent.

Het schrijven van tests voor dit is erger dan nutteloze het's

actief schadelijk.

Want elke keer als ik me realiseer "oh wacht, deze hele aanpak is verkeerd," zou ik de tests ook moeten herschrijven.

Dat is geen rigor.

Dat is bureaucratie.

De vrijheid om snel te falen

# Before (Phase 1) - Exploratory code
def process_thing(data):
    result = []
    for item in data:
        x = item.get('value')
        if x:
            result.append(x * 2)
    return result

# After (Phase 2) - Refined code
def extract_doubled_values(items: list[dict]) -> list[int]:
    """Extract 'value' field from items and double each one.

    Only includes items where 'value' is present and truthy.
    """
    return [
        item['value'] * 2
        for item in items
        if item.get('value')
    ]

Fase 1 gaat over

// Before (Phase 1) - Exploratory code
public List<int> ProcessThing(List<Dictionary<string, object>> data)
{
    var result = new List<int>();
    foreach (var item in data)
    {
        if (item.ContainsKey("value"))
        {
            var x = item["value"];
            if (x != null)
            {
                result.Add(Convert.ToInt32(x) * 2);
            }
        }
    }
    return result;
}

// After (Phase 2) - Refined code
/// <summary>
/// Extracts 'value' field from items and doubles each one.
/// Only includes items where 'value' is present and non-null.
/// </summary>

public IEnumerable<int> ExtractDoubledValues(IEnumerable<IDictionary<string, object>> items)
{
    return items
        .Where(item => item.ContainsKey("value") && item["value"] != null)
        .Select(item => Convert.ToInt32(item["value"]) * 2);
}

Leersnelheid.

Ik moet vijf verschillende benaderingen proberen in een uur.

Ik moet ontdekken dat de bibliotheek die ik dacht te gebruiken... niet zo geadverteerd is.*Ik moet me realiseren dat het probleem dat ik oplos niet het probleem is.moetom het op te lossen.*Tests vertragen dit.

Niet omdat testen traag is, maar omdat

  • Voortijdige specificatie is dood.
  • Als ik tests schrijf voordat ik het probleem begrijp, schrijf ik tests voor de
  • Verkeerde zaak.

Dan ben ik emotioneel betrokken bij de verkeerde dingen.

Dan verdedig ik het verkeerde in code review.

Beter om snel te falen, privé, zonder tests om te behouden en zonder ego om te beschermen.Wat "werken" betekent in fase 1Het betekent: "Ik heb het gerund.

Ik gaf het wat input.

# Phase 1: Works, but awkward to use
process_thing(data)

# Phase 2: Clear, flexible, composable
extract_doubled_values(
    items,
    scale_factor=2.0,
    include_zeros=False
)

Het produceerde producties die redelijk lijken.

// Phase 1: Works, but awkward to use
ProcessThing(data);

// Phase 2: Clear, flexible, composable
ExtractDoubledValues(
    items,
    scaleFactor: 2.0,
    includeZeros: false
);

// Or even better with a fluent API
items.ExtractValues()
     .Scale(by: 2.0)
     .ExcludingZeros()
     .ToList();

Ik ben 60% ervan overtuigd dat ik het probleem nu begrijp."

Dat is het.**Geen randgevallen.**Geen fout bij het afhandelen.

graph LR
    subgraph "Phase 2 Refinement Process"
        A[Rough Code] --> B[Clean Structure]
        B --> C[Add Types]
        C --> D[Polish API]
        D --> E[Static Analysis]
        E --> F{Quality Gates Pass?}
        F -->|No| G[Fix Issues]
        G --> B
        F -->|Yes| H[Ready for Phase 3]
    end

    style A stroke:#ff6b6b,stroke-width:3px
    style H stroke:#95e1d3,stroke-width:3px

Geen elegantie.

*Gewoon een spike.*Een bewijs van het concept.

Een schets.Fase 2: Maak het mooi (de verfijning)

Oké, dus nu begrijp ik het probleem.Ik heb iets dat werkt, tenminste voor het gelukkige pad.

Nu kan ik beginnen met zorgen te maken over kwaliteit.

  • Fase 2 is waar ik:
  • Opruimen van de puinhoop

Python Voorbeeld:*C# Voorbeeld:*Betere naam.

Typ annotaties.*XML documentatie.*Cleaner LINQ implementatie.

2.

Uitlijnen met de spec

Nu ik weet wat ik eigenlijk aan het bouwen ben, ga ik terug naar de oorspronkelijke vereisten en zorg ervoor dat ik het oplossen van de

rechts

probleem, niet alleen

aprobleem.

Dikwijls vertoont dit hiaten:

"Oh, ze wilden dat dit negatieve getallen anders zou behandelen."

  1. "Wacht, er is een rand geval waar waarde kan zijn Geen vs. 0.""De spec zegt 'dubbel' maar ik denk dat ze bedoelden 'schaal met een configureerbare factor.'"
  2. **Ik repareer deze.**Soms duw ik terug op de spec: "Eigenlijk moeten we X doen in plaats van omdat Y."
  3. **3.**Pools het API-oppervlak
  4. Dit is waar ik denk aan debeller's

ervaring.**Python:**C#:

Betere naamgeving.

  • Sensible defaults.
  • Configuratie waar het belangrijk is.
  • Vloeiende API's, indien van toepassing.
  • Dit is
  • ambacht.

Dit is waar goede software vandaan komt.

Waarom nog steeds geen tests?

def test_extract_doubled_values_basic():
    items = [{'value': 5}, {'value': 10}]
    assert extract_doubled_values(items) == [10, 20]

def test_extract_doubled_values_skips_missing_values():
    items = [{'value': 5}, {'name': 'foo'}, {'value': 3}]
    assert extract_doubled_values(items) == [10, 6]

def test_extract_doubled_values_handles_zeros():
    items = [{'value': 0}, {'value': 5}]
    # By default, zeros are excluded (falsy)
    assert extract_doubled_values(items) == [10]

def test_extract_doubled_values_includes_zeros_when_configured():
    items = [{'value': 0}, {'value': 5}]
    assert extract_doubled_values(items, include_zeros=True) == [0, 10]

def test_extract_doubled_values_custom_scale_factor():
    items = [{'value': 5}]
    assert extract_doubled_values(items, scale_factor=3.0) == [15.0]

def test_extract_doubled_values_rejects_negative_scale_factor():
    with pytest.raises(ValueError):
        extract_doubled_values([], scale_factor=-1.0)

def test_extract_doubled_values_handles_empty_list():
    assert extract_doubled_values([]) == []

def test_extract_doubled_values_handles_non_numeric_values():
    items = [{'value': 'not a number'}]
    with pytest.raises(TypeError):
        extract_doubled_values(items)

Oh, ik doe het constant.

Ik heb een repl open.

  • Ik probeer verschillende ingangen.
  • Ik oefen alle paden uit.
  • Maar dat ben ik niet.
  • Ik schrijf die experimenten op als formele tests.

Waarom?

Omdat

# This comment will drift from reality:
# Doubles all numeric values, skipping None

# This test will fail if reality changes:
def test_extract_doubled_values_skips_none_values():
    items = [{'value': None}, {'value': 5}]
    assert extract_doubled_values(items) == [10]

Ik ben nog steeds aan het ontdekken wat het testen waard is.

In fase 1 wist ik niet of deze functie zou bestaan.In fase 2 ontdek ik:"Oh, de schaalfactor kan niet negatief zijn.

Dat is een bevestiging."

"Als de lijst leeg is, moeten we leeg retourneren, niet geen.""We moeten gracieus omgaan met niet-numerieke waarden."

Deze inzichten

tevoorschijn komen uit refactoring.

Ze zijn niet voor de hand liggend.

  1. **Als ik tests had geschreven in fase 1, zou ik ze nu herschrijven.**Als ik ze schrijf
  2. naIk heb de implementatie verfijnd, ze zullen de eerste keer correct zijn.
  3. **Fase 3: Vergrendelen met tests (de deelfase)**Oké, nu is het echt.

De code is schoon.*De API is logisch.*Ik heb de randgevallen onderzocht.

Ik heb vertrouwen in wat ik heb opgebouwd.

Nu schrijf ik tests.

Veel testen.

Zo dicht mogelijk bij volledige dekking-zo-mogelijke tests.Waarom nu?

Omdat

tests zijn een gedeeld mechanisme.Ze zijn niet meer voor mij.

Ik begrijp deze code al intiem.

  • Ik leef er al uren of dagen in.
  • De tests hebben betrekking op:
  • Toekomst
  • (die alles zal vergeten in drie maanden)Mijn teamgenoten(die deze code moeten vertrouwen werkt)

De CI Pipeline

(die regressies moet voorkomen)

Toekomstige onderhouders

(die dit veilig moeten refactoreren)

De proeven zijn depresentatielaag

voor mijn implementatie.Zij documenteren:

Wat deze code doetWelke inputs geldig zijnWelke outputs te verwachten zijn

Welke randgevallen heb ik overwogen?

Welke veronderstellingen maak ik?

Volledige dekking, geen compromissen

  • In fase 3 ga ik hard op testen:
  • Dit is het vangnet.
  • Nu kunnen mijn teamgenoten:

Wijzig deze functie vol vertrouwen

  • Begrijp de edge cases
  • Onmiddellijke regressies van de vangst
  • Refactor zonder angst

Tests als documentatie

  • Goede tests zijn betere documentatie dan opmerkingen:*Tests liegen niet.*Ze kunnen niet drijven.
  • Als het gedrag verandert, breekt de test.
  • Dat is

Uitvoerbare documentatie.

  • Dat maakt testen waardevol.
  • The Sharing Boundary*Hier is het cruciale punt:*Fase 3 gebeurt voordat iemand anders de code aanraakt.
  • Ik deel geen code zonder tests.

Nooit.

graph TB
    subgraph "Traditional TDD (Red-Green-Refactor)"
        TDD1[Write Failing Test] --> TDD2[Write Minimal Code to Pass]
        TDD2 --> TDD3[Refactor]
        TDD3 --> TDD1
    end

    subgraph "Three-Phase Approach (Explore-Refine-Lock)"
        P1[Explore Solution Space] --> P2[Refine Implementation]
        P2 --> P3[Write Comprehensive Tests]
        P3 --> P4[Share With Team]
    end

    style TDD1 stroke:#ffaa00,stroke-width:2px
    style TDD2 stroke:#ffaa00,stroke-width:2px
    style TDD3 stroke:#ffaa00,stroke-width:2px
    style P1 stroke:#ff6b6b,stroke-width:3px
    style P2 stroke:#4ecdc4,stroke-width:3px
    style P3 stroke:#95e1d3,stroke-width:3px

**Maar ik schrijf ook geen testen voordat de code klaar is om te delen.**De fasen zijn:

Particuliere exploratie(alleen ik, geen testen)

Particuliere verfijning

(alleen ik, nog steeds geen testen) |-----|-------------| Delen van het publiek (tests, CI, code herziening, de hele negen yards) Dit beschermt mijn creativiteit.en | De gezondheid van mijn teamgenoten. Waarom dit Creativiteit beschermt

TDD advocaten zeggen "tests geven je vertrouwen om te refactoreren."

Klopt!Maar alleen als je al weet wat je aan het bouwen bent.

Als ik in fase 1 zit, heb ik geen vertrouwen nodig om te refactoreren.

Hour 1: Write test for API I haven't designed yet
Hour 2: Realize the API is wrong, rewrite test
Hour 3: Discover a better approach, rewrite both
Hour 4: Finally get it working
Hour 5: Rewrite tests to match what actually works

Ik heb toestemming nodig om alles weg te gooien.

Hour 1: Explore 3 different APIs, settle on the best one
Hour 2: Refine the implementation, handle edge cases
Hour 3: Write comprehensive tests for what I built
Hour 4: All tests pass, commit with confidence

Tests leiden tot psychologische schulden.

Zodra ik ze geschreven heb, wil ik ze niet meer verwijderen.

Zelfs als ze het verkeerde testen.Door tests uit te stellen tot fase 3, houd ik fase 1 en 2

psychologisch goedkoop.

Ik kan:

  • Probeer wilde ideeën
  • Gooi volledige naderingen weg
  • Het ontwerp radicaal veranderen

Ontdek het probleem dat ik benEigenlijkoplossen

Allemaal zonder de schuld van "maar ik schreef al die tests..."

Zo werkt creativiteit.

// Test 1: Write test first (but I don't know the right API yet)
[Test]
public void TestProcessData()
{
    var processor = new DataProcessor();
    var result = processor.Process(new[] { 1, 2, 3 });
    Assert.That(result, Is.EqualTo(new[] { 2, 4, 6 }));
}

// Implementation 1: Make it pass
public class DataProcessor
{
    public int[] Process(int[] data) => data.Select(x => x * 2).ToArray();
}

// Later: Realize I need configurability, rewrite test
[Test]
public void TestProcessDataWithFactor()
{
    var processor = new DataProcessor();
    var result = processor.Process(new[] { 1, 2, 3 }, factor: 3);
    Assert.That(result, Is.EqualTo(new[] { 3, 6, 9 }));
}

// Later: Realize I should use IEnumerable, rewrite test again
[Test]
public void TestProcessDataEnumerable()
{
    var processor = new DataProcessor();
    var data = GetLargeDataset(); // This should be lazy
    var result = processor.Process(data, factor: 3);
    Assert.That(result.Take(3), Is.EqualTo(new[] { 3, 6, 9 }));
}

Je schetst eerst losjes.

Je begint niet met de details.

// Phase 1: Explore (no tests yet)
public int[] ProcessThing(int[] data)
{
    return data.Select(x => x * 2).ToArray();
}
// Try it: var result = ProcessThing(new[] { 1, 2, 3 });
// Hmm, what about configurability? What about lazy evaluation?

// Phase 2: Refine (still no tests)
public static class DataProcessorExtensions
{
    public static IEnumerable<int> Scale(
        this IEnumerable<int> source,
        int factor = 2)
    {
        return source.Select(x => x * factor);
    }
}
// Try it: var result = data.Scale(factor: 3).ToList();
// Much better API!

// Phase 3: Lock it down (NOW write tests, correctly)
[TestFixture]
public class DataProcessorTests
{
    [Test]
    public void Scale_DefaultFactor_DoublesValues()
    {
        var result = new[] { 1, 2, 3 }.Scale();
        Assert.That(result, Is.EqualTo(new[] { 2, 4, 6 }));
    }

    [Test]
    public void Scale_CustomFactor_ScalesCorrectly()
    {
        var result = new[] { 1, 2, 3 }.Scale(factor: 3);
        Assert.That(result, Is.EqualTo(new[] { 3, 6, 9 }));
    }

    [Test]
    public void Scale_LazyEvaluation_DoesNotEnumerateImmediately()
    {
        var enumerated = false;
        var data = GetTestData(() => enumerated = true);
        var scaled = data.Scale(factor: 2);

        Assert.That(enumerated, Is.False, "Should not enumerate yet");

        scaled.ToList();
        Assert.That(enumerated, Is.True, "Should enumerate on materialization");
    }

    [Test]
    public void Scale_EmptySequence_ReturnsEmpty()
    {
        var result = Enumerable.Empty<int>().Scale();
        Assert.That(result, Is.Empty);
    }
}

Hoe dit past met agile en XP (en waar het diverges)

Laten we duidelijk zijn over iets:

Dit is geen "test later ontwikkeling."

Dit is

  • "test tijdens het ontwikkelen, maar op het juiste moment."De timing van
  • wanneer

het toevoegen van unit tests is cruciaal.

  • Te vroeg en je geeft het onbekende aan.
  • Te laat en je vaart zonder veiligheidsnetten.

De XP praktijken die ik houd

  • Extreme Programming heeft briljante praktijken die het volgende waard zijn:
  • Continue integratie

Elke functie gaat via CI alvorens te mergen

  • Geautomatiseerde test draait op elke commit naar mainGeen gebroken constructies getolereerdPaar programmering / Code beoordeling
  • Fase 3 code wordt beoordeeld voordat delen

Tests maken deel uit van het te beoordelen artefact

Collaboratieve verfijning verbetert ontwerpEenvoudig ontwerp

Fase 2 is

  • alles over
  • vereenvoudiging
  • Verwijder wat je niet nodig hebt
  • Maak het zo eenvoudig mogelijk, niet eenvoudiger

Refactoring

Fase 2 is gewijde refactoring tijdDe code opruimenvoor

Sluit het af.

**Tests beschermen de refactoring (in fase 3+)**Waar ik duik van Strikte TDD

TDD zegt:'Test eerst.

Altijd.Red-Green-Refactor is de enige manier."

Ik zeg:"Tests als je weet wat je test.

Explore-Refine-Lock is natuurlijker."

Het belangrijkste verschil:

Drie fases.De test definieert de interface . . . . . . . . . de interface bepaalt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

  • Red-Green-Refactor cyclus Verkennen-Refine-Lock progressie
  • Beproevingen die vóór de uitvoering zijn geschreven . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
  • delen

Test eerst voor alles Test op het juiste moment voor elk ding

  • Microcycli (minuten) Macrofasen (uren/dagen)
  • De timing is kritiek
  • Hier is het cruciale inzicht:
  • Het is geen laatste test. Het is een test voor het delen.
  • Verkeerde timing (te vroeg):

Juiste timing (fase 3):

Zelfde kwaliteit resultaat.*De helft van de karn.*Dit IS Agile

Agile zegt:

"Reageer op verandering over het volgen van een plan."TDD kan zijn eigen vorm van plan-volgend worden.

Zodra je testen hebt geschreven, ben je psychologisch toegewijd aan dat ontwerp.

  • Mijn aanpak omarmt verandering:
  • Fase 1: Verander snel, ontdek de juiste oplossing
  • Fase 2: Met opzet veranderen, verfijnen naar elegantie
  • Fase 3: Voorzichtig veranderen, beschermen wat werkt

Dit ismeer

behendig dan strikte TDD, omdat het uitstel van commitment totdat je informatie hebt.

Contrasting Approaches: A C# Example

Strikte TDD-benadering:

Zie je de karn?

Drie tests herschrijven naarmate het ontwerp evolueerde.

  1. **Driefasebenadering:**Eén serie testen.
  2. **Ooit geschreven.**Corrigeer de eerste keer.
  3. **Omdat ik wist wat ik aan het bouwen was.**De Agile Manifest Connection

Onthoud de waarden:

"Werksoftware over uitgebreide documentatie"

Fase 1 gaat naar werkende software

snel

  1. Fase 3 maakt testen van de uitgebreide documentatie"Reageren op verandering over het volgen van een plan"
  2. Fasen 1-2 omarmen veranderingFase 3 sloten in het uiteindelijke ontwerp
  3. **"Individuen en interacties over processen en instrumenten"**Laat TDD dogma het goede oordeel niet overschrijven

Samenwerken wanneer het helpt (fase 2-3), alleen verkennen als het niet (fase 1)

"Customer collaboration over contractonderhandeling"

Fase 2 komt overeen met de specificaties

na

  1. **inzicht in het probleem;**Beter om te leveren wat ze nodig hebben dan wat ze gespecificeerd
  2. Waarom dit vermindert nutteloze curnTDD's vuil geheim:
  3. **de meeste vóór de uitvoering geschreven tests worden herschreven of verwijderd.**Waarom?

Omdat:

Je hebt de vereisten verkeerd begrepen.

Je wist nog niet van de randgevallen.U ontdekte een beter API ontwerpJe besefte dat het niet nodig was.

Elke test die je schrijft in fase 1 is waarschijnlijk verspilde moeite.

Beter om te schrijven

één

Testen in fase 3, als je echt weet wat je aan het bouwen bent, dan schrijven-herschrijven-herschrijven tijdens de exploratie.De TDD Belofte vs. RealiteitTDD belooft:

"Write code that solves the specification.
Don't worry about perfection yet.
Just get it working."

"Tests rijden uw ontwerp!

  • Ze helpen je om goede API's te ontdekken!"
  • TDD realiteit:
  • "Ik schreef tests voor een API waarvan ik dacht dat het zinvol was.
  • Toen implementeerde ik het en realiseerde me dat de API ongemakkelijk was.

Nu herschrijf ik zowel de tests als de code."**Dat is geen ontwerp.**Dat is

thrashen.Mijn aanpak:

Ontwerpen door implementatie, dan afsluiten met testen.

  1. Het eindresultaat is dezelfde goed geteste, goed ontworpen code.
  2. Maar ik kwam daar met de helft van de karn.
  3. Waarom dit nog steeds Gedisciplineerde Software levert
  4. Dit is wat deze aanpak is.
  5. niet:
  6. "Cowboy-codering"

"Geen tests"*"Verbergen en bidden"*Tegen de tijd dat ik klaar ben met fase 3, heeft mijn code:

Uitgebreide testdekkingBehandelde Rand-gevallen

Schone, leesbare implementatie

Helder API-ontwerpDocumentatie (via tests)

Precies hetzelfde als TDD.

  1. Het verschil is

    • wanneer
    • Die tests zijn geschreven.
    • De Discipline komt uit de grens
  2. De regel is simpel:

    - flake8 (style checking)
    - pylint (code quality)
    - mypy (type checking)
    - black (formatting)
    - radon (complexity analysis)
    
  3. **Geen code verlaat fase 2 zonder fase 3.**Ik weet het niet.

    for iteration in range(3):
        # Measure current performance
        metrics = measure_performance(code)
    
        # Generate improved version
        better_code = optimise(code, metrics)
    
        # Keep if better, discard if worse
        if better_code.score > code.score:
            code = better_code
    

Niet-geteste code committenOpen PR's zonder testsSamenvoegen naar hoofdvak zonder CI-passage

  • Inzetten zonder dekkingsrapporten
  • De discipline is niet in het schrijven van tests vroeg.
  • Het zit erin.
  • geen code delen zonder tests.
  • De ambachtelijke metaforen

Dit is geen nieuw idee.Het is hoe creatief werk altijd heeft gewerkt:

Sketch → Verfijnen → Varnish

Schilders beginnen niet met de laatste penseelstreken.*Zij:*Sketchde samenstelling (fase 1)

Schilderen*de lagen (fase 2)*Varnish

# Test generation happens AFTER optimisation
def generate_unit_tests(specification, optimized_code):
    """Generate comprehensive tests for refined code.

    This happens in Phase 3, after we know:
    - What the code actually does
    - What edge cases exist
    - What the API surface looks like
    """
    return llm.generate(
        f"""Generate comprehensive unit tests for this code.

Specification: {specification}
Implementation: {optimized_code}

Include tests for:
- Happy path (from spec examples)
- Edge cases (discovered during optimisation)
- Error conditions (based on actual error handling)
- Performance bounds (based on measured metrics)
"""
    )

om te beschermen en aanwezig te zijn (fase 3)

  • De vernis komt niet op de eerste plaats.
  • Maar het is niet optioneel.
  • Ontwerp → Bewerken → Publiceren
  • Schrijvers bewerken niet zoals ze ontwerpen.

Zij:Ontwerprommelig (fase 1)

  1. Bewerken**meedogenloos (fase 2)**Publiceren
  2. met vertrouwen (fase 3)**"Schrijf dronken, edit nuchter" is verschrikkelijk levensadvies, maar geweldig schrijfadvies.**Klei → Vorm → Glazuur
  3. Potters glazuren niet voordat ze vormgeven.**Zij:**Gooien
  4. de klei (fase 1)Trim en verfijnhet formulier (fase 2)Glazuur en vuur

**voor duurzaamheid (fase 3)**Het glazuur is cruciaal.

Maar het komt als laatste.

Hoe deze kaarten naar de DiSE Codegen PipelineHier is waar dit interessant wordt: ik heb eigenlijkgeïmplementeerd

deze filosofie in het DiSE (Directed Synthetic Evolution) systeem.

  • De codegen pijpleiding belichaamt expliciet deze drie fasen:
  • Fase 1 in DiSE: Exploratiefase
  • Wanneer je DiSE vraagt om een probleem op te lossen, springt het niet meteen naar perfecte, geteste code.
  • In plaats daarvan, de

Generator

  • (codellama of soortgelijke) krijgt een duidelijk mandaat:
  • Het systeem genereert verkennende code:
  • Gefocust op het gelukkige pad
  • Minimale foutafhandeling

Geen premature optimalisatie

  • Net genoeg om de nadering te testen.Dan deUitvoerder
  • Runt het.*Niet met formele eenheidstests, alleen met de input van de specificatie.*Als het mislukt?
  • Geen probleem.Dat isleren.
  • Het systeem heeft een 6 stappen adaptieve escalatie:

Probeer het met een snel model (lage temperatuur)

Probeer het opnieuw met hogere creativiteit (hogere temperatuur)

User: "Calculate fibonacci numbers"

[Phase 1: Exploration - 3 attempts]
  Attempt 1: Works for small inputs, explodes on large ones (no safety limit)
  Attempt 2: Adds safety limit, but inefficient recursive approach
  Attempt 3: Switches to iterative DP (PASS)

[Phase 2: Optimization - 3 iterations]
  Iteration 1: Add type hints, improve naming (Score: 1.05)
  Iteration 2: Optimize memory usage with generator (Score: 1.10)
  Iteration 3: Add input validation (Score: 1.15)

[Phase 3: Testing and Storage]
  Generated 8 unit tests covering:
    - Basic cases (n=0, n=1, n=5, n=10)
    - Edge cases (n=negative, n=100, n=None)
    - Type validation

  All tests pass ✓

  Stored in RAG with quality score: 1.15
  Available for reuse: YES

Escaleer naar een krachtiger model

  • Debug-logging toevoegen om fouten te begrijpen
  • Geef volledige context aan het krachtige model
  • Gebruik het "god-level" model als laatste redmiddel
  • Dit is

preciesFase 1.

Het systeem verkent de oplossingsruimte, leert wat werkt, faalt snel en past zich aan.

Tijdens deze fase worden geen tests uitgevoerd.De code is privé aan het generatieproces.

Fase 2 in DiSE: OptimalisatiefaseZodra de code de basisuitvoering passeert, verplaatst DiSE naaroptimalisatie.

  1. **Dit is de verfijningsfase.**Het systeem:
  2. Reinigt de implementatieVerwijdert debug logging die is toegevoegd tijdens fouten
  3. Vereenvoudigt te complexe codeVerbetert naamgeving en structuur
  4. Werkt statische analyseIteratief optimaliseert

(3 iteraties standaard)

Dit isprecies

Fase 2.

De code is nog privé (nog niet in het register), maar we polijsten het:Betere namen

Type hintsSchonere structuur

Lagere complexiteit

Betere prestatiesDe vorm is niet langer vloeibaar.

We weten wat we bouwen.

  1. **Nu halen we het.**Goed.
  2. Fase 3 in DiSE: de test- en opslagfaseAlleen
  3. naoptimalisatie maakt DiSE aanmaken en uitvoeren

formele eenheidstests.

  • Hier is het cruciale deel: de tests worden gegenereerd uit de
  • verfijnde specificatie en uitvoering
  • , niet de eerste verkenning.

De tests hebben betrekking op:*Specificatievoorbeelden (correctheid)*Randgevallen ontdekt tijdens fase 2

Fout bij het hanteren van dat werd toegevoegd tijdens de verfijning

Gemeten prestatiekenmerken*Dan,*alleen als de tests slagen

, de code is:

Opgeslagen in de

RAG-geheugen

# I know what sort() should do. Tests first? Sure!
def test_sort_empty_list():
    assert sort([]) == []

def test_sort_single_element():
    assert sort([5]) == [5]

def test_sort_multiple_elements():
    assert sort([3, 1, 2]) == [1, 2, 3]

(met inbeddingen)

Toegevoegd aan denode register:

  1. (als uitvoerbaar artefact)
  2. Beschikbaar gesteld voor
  3. hergebruik

in toekomstige workflows

Getraceerd met

kwaliteitsscores

logo

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