Back to "Comment je construis le logiciel: faire en sorte que ça marche, le rendre joli, le verrouiller vers le bas"

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

Comment je construis le logiciel: faire en sorte que ça marche, le rendre joli, le verrouiller vers le bas

Wednesday, 19 November 2025

Pourquoi j'écris des tests en dernier, et pourquoi ce n'est pas ce que tu penses que ça veut dire

**Prise controversée:**Test-Driven Development est une pratique brillante qui résout le mauvais problème pour le travail le plus créatif.

Voilà ce qui fonctionne mieux.

Les trois phases du logiciel de construction (qui fonctionne réellement)

C'est comme ça que je construis des logiciels.

C'est simple, c'est efficace, et ça rend les puristes TDD poliment sillonner leurs sourcils:

Fais en sorte que ça marche.*Fais-le joliment.*Verrouillez-le avec des tests.

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

Dans cet ordre.*Toujours.*Pas parce que je suis paresseux.

Pas parce que je n'apprécie pas les tests.

Mais parce que

C'est comme ça que se passe le travail créatif.

lorsque vous explorez un espace de problèmes que vous ne comprenez pas encore complètement.

  • Pas parce que je suis paresseux.
  • Pas parce que je n'apprécie pas les tests.
  • Mais parce que
  • C'est comme ça que se passe le travail créatif.

lorsque vous explorez un espace de problèmes que vous ne comprenez pas encore complètement.

Laisse-moi t'expliquer.

Phase 1: Faites en sorte que cela fonctionne (le croquis privé)

C'est la partie désordonnée.La partie où je n'ai aucune idée de ce que je fais.

J'essaie de comprendre :

Cette API fonctionne-t-elle vraiment comme l'affirment les docs ?

Puis-je même résoudre ce problème avec les outils que j'ai ?

Qu'est-ce que «correcte» signifie même dans ce contexte?*C'est un problème de deux heures ou de deux semaines ?*C'est de l'invention.

C'est de l'exploration.

C'est du 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

Et tu sais ce qui tue le jazz ?**Quelqu'un se tenant au-dessus de votre épaule disant "écrivez le test d'abord."**Pourquoi pas encore de tests?

Parce que*la forme est encore fluide.*Imaginez que vous êtes sculpteur.

Vous avez un bloc de marbre et une vague idée : "Je veux tailler un oiseau."

TDD dit: "Avant de toucher ce marbre, écrivez exactement à quoi ressemble un oiseau.

Définissez son envergure.Précisez l'angle de chaque plume.

Configurez maintenant ces spécifications."*Ce n'est pas comme ça que les oiseaux sont sculptés. (Ou le logiciel est écrit, comme il se trouve.)*Véritables sculpteurs

D'abord, la forme est rudimentaire.Ils dessinent.

Ils fabriquent des maquettes.*Ils éloignent de gros morceaux de pierre pour trouver la forme générale.*Ce n'est que plus tard qu'ils peaufinent les détails.

Le code est le même.

Dans la phase 1, j'écris :

Ce code est

privé.

C'est une conversation avec moi-même.

C'est comme ça que je comprends ce que "travailler" veut dire.

L'écriture de tests pour cela est pire qu'inutile—c'est

activement nuisible.

Parce que chaque fois que je réalise "oh attends, toute cette approche est fausse", je devrais réécrire les tests aussi.

Ce n'est pas la rigueur.

C'est de la bureaucratie.

La liberté d'échouer rapidement

# 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')
    ]

La phase 1 est d'environ

// 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);
}

vitesse d'apprentissage.

Je dois essayer cinq approches différentes en une heure.

J'ai besoin de découvrir que la bibliothèque tierce que j'ai pensé utiliser... n'est pas tout à fait comme annoncé.*J'ai besoin de comprendre que le problème que je résolve n'est pas le problème que j'ai.devraitRésolvez-vous.*Les tests ralentissent ça.

Pas parce que les tests sont lents, mais parce que

  • La spécification prématurée, c'est la mort.
  • Si j'écris des tests avant de comprendre le problème, j'écris des tests pour le
  • Mauvaise chose.

Alors je suis émotionnellement investi dans la mauvaise chose.

Alors je défends la mauvaise chose dans la révision du code.

Mieux vaut échouer rapidement, en privé, sans aucun test à maintenir et sans ego à protéger.Ce que signifie "travailler" dans la phase 1Ça veut dire : "Je l'ai fait.

Je lui ai donné quelques entrées.

# 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
)

Il a produit des produits qui semblent raisonnables.

// 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();

Je suis sûr de 60 % que je comprends le problème maintenant. »

C'est ça.**Pas de cas de bord.**Pas d'erreur à gérer.

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

Pas d'élégance.

*Juste une pointe.*Une preuve de concept.

Un croquis.Phase 2 : Rendre ça joli (Raffinement)

Ok, donc maintenant je comprends le problème.J'ai quelque chose qui fonctionne, du moins pour le chemin heureux.

Maintenant je peux commencer à m'occuper de la qualité.

  • La phase 2 est la suivante :
    1. Le Conseil de l'Europe a adopté une résolution du Conseil de l'Europe sur la situation des droits de l'homme dans le monde.
  • Nettoyez le mess

Exemple de python :*C# Exemple :*Mieux vaut s'appeler.

Tapez des annotations.*Documentation XML.*Mise en œuvre de LINQ plus propre.

2. Le Président. — L'ordre du jour appelle le rapport (doc.

Alignez-vous avec la spécification

Maintenant que je sais ce que je construis en fait, je retourne aux exigences d'origine et je m'assure que je résolve le problème.

droite

problème, pas seulement

aproblème.

Cela révèle souvent des lacunes :

"Oh, ils voulaient que cela gère les nombres négatifs différemment."

  1. "Attendez, il y a un cas de bord où la valeur pourrait être Aucune contre 0.""La spécification dit 'double' mais je pense qu'ils voulaient dire 'échelle par un facteur configurable.'"
  2. **Je répare ça.**Parfois, je repousse sur la spécification: "En fait, nous devrions faire X à la place parce que Y."
  3. **3. Les droits de l'homme sont garantis par le Pacte international relatif aux droits économiques, sociaux et culturels.**Poliser la surface de l'API
  4. C'est là que je pense à laC'est chez l'appelant.

l'expérience.**Python :**C#:

Mieux vaut nommer.

  • Défauts sensibles.
  • Configuration où elle compte.
  • API fluide le cas échéant.
  • C'est ça.
  • embarcation.

C'est d'où vient le bon logiciel.

Pourquoi toujours aucun test?

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, je le fais constamment.

J'ai un REPL ouvert.

  • J'essaie différentes entrées.
  • J'exerce tous les chemins.
  • Mais je ne le suis pas.
  • écrivant ces expériences comme des tests formels.

Pourquoi ?

Parce que

# 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]

Je découvre encore ce qui vaut la peine d'être testé.

Dans la phase 1, je ne savais même pas si cette fonction existait.Dans la phase 2, je découvre :"Oh, le facteur d'échelle ne peut pas être négatif.

C'est une validation."

"Si la liste est vide, nous devrions retourner vide, pas None.""Nous devons gérer avec grâce les valeurs non numériques."

Ces points de vue

En effet, il s'agit là d'un phénomène qui découle d'une refacturation.

Ils ne sont pas évidents à l'avance.

  1. **Si j'avais écrit des tests dans la phase 1, je les réécrirais maintenant.**Si je les écris
  2. aprèsJ'ai affiné l'implémentation, ils seront corrects la première fois.
  3. **Phase 3 : Verrouillez-le avec des tests (la phase de partage)**Ok, maintenant c'est réel.

Le code est propre.*L'API est logique.*J'ai couvert les affaires de bord.

Je suis confiant dans ce que j'ai construit.

Maintenant, j'écris des tests.

Beaucoup de tests.

Les tests sont proches de la couverture complète.Pourquoi maintenant ?

Parce que

Les tests sont un mécanisme de partage.Ils ne sont plus pour moi.

Je comprends déjà ce code intimement.

  • J'y vis depuis des heures ou des jours.
  • Les essais portent sur:
  • À l'avenir
  • (qui oubliera tout dans trois mois)Mes coéquipiers(qui ont besoin de faire confiance à ce code fonctionne)

Le pipeline de l'IC

(qui doit prévenir les régressions)

Mainteneurs futurs

(qui ont besoin de corriger cela en toute sécurité)

Les essais sont les suivants:couche de présentation

pour ma mise en œuvre.Ils documentent:

Ce que fait ce codeQuelles sont les entrées validesQuels sont les résultats attendus?

Quels cas de bord j'ai pris en considération

Quelles suppositions je fais

Couverture complète, pas de compromis

  • Dans la phase 3, je fais des tests difficiles :
  • C'est le filet de sécurité.
  • Maintenant, mes coéquipiers peuvent :

Modifier cette fonction avec confiance

  • Comprendre les cas de bord
  • Régression immédiate des captures
  • Réfacteur sans crainte

Essais comme documentation

  • Les bons tests sont une meilleure documentation que les commentaires:*Les tests ne mentent pas.*Ils ne peuvent pas dériver.
  • Si le comportement change, l'essai s'interrompt.
  • C'est ça.

la documentation exécutable.

  • C'est ce qui rend les tests précieux.
  • La frontière du partage*Voici le point crucial :*La phase 3 se produit avant que quelqu'un d'autre touche le code.
  • Je ne partage pas de code sans tests.

Jamais.

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

**Mais je n'écris pas non plus de tests avant que le code ne soit prêt à être partagé.**Les phases sont les suivantes:

Exploration privée(seulement moi, pas de test)

Perfectionnement privé

(seulement moi, toujours pas de tests) |-----|-------------| Partage public (tests, CI, révision de code, tous les neuf verges) Cela protège à la fois ma créativitéet | La santé mentale de mes coéquipiers. Pourquoi cela protège la créativité

Les défenseurs de la DTS disent que les tests vous donnent confiance en refactor.

C'est vrai !Mais seulement si vous savez déjà ce que vous construisez.

Quand je suis dans la phase 1, je n'ai pas besoin de confiance pour refactorer.

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

J'ai besoin d'une permission pour tout jeter.

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

Les tests créent une dette psychologique.

Une fois que je les ai écrites, je suis réticent à les supprimer.

Même s'ils testent la mauvaise chose.En reportant les essais jusqu'à la phase 3, je maintiens les phases 1 et 2

psychologiquement bon marché.

Je peux :

  • Essayez des idées sauvages
  • Jetez des approches entières
  • Changer radicalement la conception

Découvrez le problème que je suisEn faitrésolution

Tout cela sans la culpabilité de "mais j'ai écrit tous ces tests..."

C'est ainsi que fonctionne la créativité.

// 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 }));
}

Vous esquissez librement d'abord.

Tu ne commences pas avec les détails.

// 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);
    }
}

Comment cela s'adapte avec Agile et XP (et où il diverge)

Soyons clairs sur quelque chose :

Ce n'est pas "test de développement ultérieur".

C'est ça.

  • "test tout en se développant, mais au bon moment."Le calendrier
  • lorsque

ajouter des tests unitaires est essentiel.

  • Trop tôt et vous spécifiez l'inconnu.
  • Trop tard et vous embarquez sans filets de sécurité.

Les pratiques XP que je garde

  • Extreme Programming a des pratiques brillantes qui valent la peine de suivre:
  • Intégration continue

Chaque fonctionnalité passe par CI avant la fusion

  • Les tests automatisés s'effectuent sur chaque commit à mainPas de constructions brisées toléréesPaire programmation / révision de code
  • Le code de la phase 3 fait l'objet d'un examen avant le partage

Les tests font partie de l'artefact susceptible d'examen.

Le raffinement collaboratif améliore la conceptionConception simple

La phase 2 est

  • Tout ce qu'il y a, c'est
  • simplification
  • Enlevez ce dont vous n'avez pas besoin
  • Le rendre aussi simple que possible, pas plus simple

Refactoration

La phase 2 est consacrée au temps de refacturationNettoyer le codeavant

Verrouillage vers le bas

**Les essais protègent le refactoring (en phase 3+)**Où je diverge d'un TDD strict

La DNT dit :"Les tests d'abord.

Toujours.Red-Green-Refactor est le seul moyen."

Je dis :"Tests quand vous savez ce que vous testez.

Explore-Refine-Lock est plus naturel."

La différence clé:

TDD (Troisième phase)Le test définit l'interface. L'exploration définit l'interface.

  • Cycle Red-Green-Refactor
  • Tests écrits avant la mise en œuvre
  • partage

Tester d'abord pour tout. Tester au bon moment pour chaque chose.

  • Microcycles (minutes)
  • Le moment est critique
  • Voici le point de vue crucial :
  • Ce n'est pas "tests dernier". C'est "tests avant partage".
  • Mauvais moment (trop tôt):

À l'heure indiquée (phase 3) :

Même résultat de qualité.*La moitié de la courbure.*C'est Agile.

Agile dit :

"Répondre au changement en suivant un plan."La DTS peut devenir sa propre forme de suivi des plans.

Une fois que vous avez écrit des tests, vous êtes psychologiquement engagé dans ce design.

  • Mon approche embrasse le changement :
  • Phase 1: Changez rapidement, découvrez la bonne solution
  • Phase 2 : Changer délibérément, affiner vers l'élégance
  • Phase 3 : Changer soigneusement, protéger ce qui fonctionne

C'est ça.plus

agile que strict TDD, parce qu'il reporte l'engagement jusqu'à ce que vous ayez des informations.

Approches contrastées : un exemple C#

Approche TDD stricte :

Tu as remarqué le churn ?

Trois tests sont réécrits au fur et à mesure de l'évolution de la conception.

  1. **Approche en trois phases :**Une série de tests.
  2. **Écrit une fois.**Corrige la première fois.
  3. **Parce que je savais ce que je construisais.**La connexion du Manifeste Agile

Souvenez-vous des valeurs :

"Logiciel de travail sur documentation complète"

La phase 1 arrive au logiciel de travail

rapide

  1. La phase 3 fait des tests la documentation complète"Répondre au changement sur la suite d'un plan"
  2. Les phases 1 et 2 prennent en compte le changementVerrouillages de la phase 3 dans la conception finale
  3. **« Individuels et interactions sur les processus et les outils »**Ne laissez pas le dogme TDD surpasser le bon jugement

Collaborer quand il aide (phase 2-3), explorer seul quand il ne le fait pas (phase 1)

"La collaboration des clients sur la négociation de contrats"

La phase 2 s'aligne sur les spécifications

après

  1. comprendre le problèmeMieux vaut livrer ce dont ils ont besoin que ce qu'ils ont spécifié
  2. Pourquoi cela réduit le chunn inutileLe sale secret de TDD :
  3. **la plupart des tests écrits avant la mise en œuvre sont réécrits ou supprimés.**Pourquoi ?

Parce que :

Vous avez mal compris les exigences.

Vous ne connaissiez pas encore les affaires de bord.Vous avez découvert un meilleur design d'APIVous avez réalisé que la fonctionnalité n'était pas nécessaire du tout

Chaque test que vous écrivez dans la phase 1 est probablement un effort gaspillé.

Mieux vaut écrire

une

ensemble de tests dans la phase 3, quand vous savez réellement ce que vous construisez, que d'écrire-réécrire-réécrire pendant l'exploration.La promesse de la DTS par rapport à la réalitéTDD promet :

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

"Les tests conduisent votre conception!

  • Ils vous aident à découvrir de bonnes API!"
  • Réalité de la DTS :
  • "J'ai écrit des tests pour une API que je pensais logique.
  • Puis je l'ai implémenté et je me suis rendu compte que l'API était gênante.

Maintenant, je réécris à la fois les tests et le code."**Ce n'est pas du design.**C'est ça.

C'est fou.Mon approche :

Concevoir par l'implémentation, puis le verrouiller avec des tests.

  1. Le résultat final est le même code bien testé et bien conçu.
  2. Mais j'y suis arrivé avec la moitié du churn.
  3. Pourquoi cela livre encore des logiciels disciplinés
  4. Voici ce que cette approche est
  5. ne pas:
  6. "Codage de Cowboy"

"Pas d'essais"*"Passer et prier"*Au moment où j'en ai fini avec la phase 3, mon code a:

Couverture complète des essaisCas de bord traités

Mise en œuvre propre et lisible

Effacer la conception de l'APIDocumentation (par essais)

Exactement la même chose que TDD.

  1. La différence est de

    • lorsque
    • Ces tests ont été écrits.
    • La discipline vient de la frontière
  2. La règle est simple :

    - flake8 (style checking)
    - pylint (code quality)
    - mypy (type checking)
    - black (formatting)
    - radon (complexity analysis)
    
  3. **Aucun code ne quitte la phase 2 sans la phase 3.**Je ne sais pas.

    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
    

Commettre le code non testéOuvrir les PR sans testFusionner vers le secteur sans passer par CI

  • Déploiement sans rapport de couverture
  • La discipline n'est pas en train d'écrire des tests tôt.
  • C'est dedans.
  • ne pas partager le code sans tests.
  • Les métaphores de l'artisanat

Ce n'est pas une nouvelle idée.C'est comme ça que le travail créatif a toujours fonctionné :

Croquis → Affiner → Vernis

Les peintres ne commencent pas avec les coups de pinceau finals.*Ils :*Croquisla composition (phase 1)

Peinture*les couches (phase 2)*Vernis

# 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)
"""
    )

à protéger et à présent (phase 3)

  • Le vernis ne vient pas en premier.
  • Mais ce n'est pas facultatif.
  • Ébauche → Modifier → Publier
  • Les écrivains n'éditent pas comme ils dessinent.

Ils :Projetdésordre (phase 1)

  1. Modifier**impitoyablement (phase 2)**Publier
  2. avec confiance (phase 3)**"Écrire ivre, éditer sobre" est un terrible conseil de vie, mais un excellent conseil d'écriture.**Clay → Forme → Glace
  3. Les potiers ne brillent pas avant de façonner.**Ils :**Lancer
  4. l'argile (phase 1)Trimer et raffinerle formulaire (phase 2)Éblouissement et incendie

**pour la durabilité (phase 3)**La glaçure est cruciale.

Mais ça vient en dernier.

Comment cela s'inscrit dans le pipeline DiSE CodegenC'est là que ça devient intéressant : en fait, j'aimise en œuvre

cette philosophie dans le système DiSE (Directed Synthetic Evolution).

  • Le pipeline de codegen incarne explicitement ces trois phases:
  • Phase 1 de DiSE : l'étape d'exploration
  • Lorsque vous demandez à DiSE de résoudre un problème, il ne saute pas directement à l'écriture de code parfait, testé.
  • Au lieu de cela,

Groupe électrogène

  • (codellama ou similaire) obtient un mandat clair:
  • Le système génère un code exploratoire:
  • Concentrez-vous sur le chemin heureux
  • Gestion minimale des erreurs

Pas d'optimisation prématurée

  • Juste assez pour tester l'approcheEnsuite, leExécuteur
  • C'est parti.*Pas avec des tests unitaires formels — juste avec les entrées de la spécification.*Si ça échoue ?
  • Pas de problème.*C'est ça.*l'apprentissage.
  • Le système comporte une escalade adaptative en 6 étapes :

Essayez avec un modèle rapide (à basse température)

Essayez encore avec plus de créativité (température plus élevée)

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

Escalatez-vous vers un modèle plus puissant

  • Ajouter l'enregistrement de débogage pour comprendre les échecs
  • Donner tout le contexte au modèle puissant
  • Utiliser le modèle de niveau "dieu" en dernier recours
  • C'est ça.

*Exactement.*Phase 1.

Le système explore l'espace de solution, apprend ce qui fonctionne, échoue rapidement et s'adapte.

Aucun test n'est écrit pendant cette phase.Le code est privé du processus de génération.

Phase 2 de DiSE : l'étape d'optimisationUne fois que le code passe l'exécution de base, DiSE passe àl'optimisation.

  1. **C'est la phase de raffinement.**Le système:
  2. Nettoie la mise en œuvreSupprime l'enregistrement de débogage qui a été ajouté lors d'échecs
  3. Simplifie le code trop complexeAméliore le nom et la structure
  4. Exécute l'analyse statiqueOptimise itérativement

(3 itérations par défaut)

C'est ça.Exactement.

Phase 2.

Le code est toujours privé (pas encore dans le registre), mais nous le polissons :Meilleurs noms

Type d'indicesStructure plus propre

Faible complexité

Meilleure performanceLa forme n'est plus fluide.

Nous savons ce que nous construisons.

  1. **Maintenant, nous y arriverons.**Tant mieux.
  2. Phase 3 de DiSE : l'étape d'essai et de stockageSeulement
  3. aprèsl'optimisation génère et exécute DiSE

des tests d'unité formels.

  • Voici la partie cruciale: les tests sont générés à partir de la
  • spécification et mise en œuvre affinées
  • , pas l'exploration initiale.

Les essais portent sur:*Exemples de spécifications (correctibilité)*Cas de bord découverts au cours de la phase 2

Gestion des erreurs qui ont été ajoutées lors du raffinement

Caractéristiques de performance mesurées*Alors,*seulement si les essais sont réussis

, le code est:

Entreposés dans le

Mémoire RAG

# 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]

(avec emboîtements)

Ajouté à laRegistre des nœuds:

  1. (comme un artefact exécutable)
  2. Mises à disposition pour
  3. réutilisation

dans les futurs flux de travail

Suivi avec

scores de qualité

logo

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