Cette limitation du développement piloté par les tests (et Agile en général) est-elle pratiquement pertinente?

30

Dans Test Driven Development (TDD), vous commencez avec une solution sous-optimale, puis vous en produisez de manière itérative de meilleures solutions en ajoutant des cas de test et en refactorisant. Les étapes sont censées être petites, ce qui signifie que chaque nouvelle solution sera en quelque sorte dans le voisinage de la précédente.

Cela ressemble à des méthodes mathématiques d'optimisation locale comme la descente de gradient ou la recherche locale. Une limitation bien connue de ces méthodes est qu'elles ne garantissent pas de trouver l'optimum global, ni même un optimum local acceptable. Si votre point de départ est séparé de toutes les solutions acceptables par une grande région de mauvaises solutions, il est impossible d'y arriver et la méthode échouera.

Pour être plus précis: je pense à un scénario dans lequel vous avez implémenté un certain nombre de cas de test, puis constatez que le prochain cas de test nécessiterait une approche concurrentielle différente. Vous devrez jeter votre travail précédent et recommencer.

Cette pensée peut en fait être appliquée à toutes les méthodes agiles qui procèdent par petites étapes, pas seulement au TDD. Cette analogie proposée entre TDD et l'optimisation locale présente-t-elle de sérieux défauts?

Frank Puffer
la source
Faites-vous référence à la sous-technique TDD appelée triangulation ? Par «solution acceptable», entendez-vous une solution correcte ou une solution maintenable / élégante / lisible?
guillaume31
6
Je pense que c'est un vrai problème. Puisque c'est juste mon opinion, je n'écrirai pas de réponse. Mais oui, puisque TDD est présenté comme une pratique de conception , c'est un défaut qu'il peut conduire à des maxima locaux ou à aucune solution du tout. Je dirais qu'en général, le TDD n'est pas bien adapté à la conception algorithmique. Voir la discussion connexe sur les limites de TDD: Solving Sudoku with TDD , dans laquelle Ron Jeffries se fait un cul tout en tournant en rond et "faisant TDD", tandis que Peter Norvig fournit la solution réelle en connaissant réellement le sujet,
Andres F.
5
En d'autres termes, je proposerais (je l'espère) une affirmation non controversée selon laquelle TDD est bon pour minimiser la quantité de classes que vous écrivez dans des problèmes "connus", produisant ainsi du code plus propre et plus simple, mais ne convient pas aux problèmes algorithmiques ou aux problèmes complexes où en fait, regarder la situation dans son ensemble et avoir des connaissances spécifiques au domaine est plus utile que d'écrire des tests au coup par coup et de «découvrir» le code que vous devez écrire.
Andres F.
2
Le problème existe, mais n'est pas limité à TDD ou même Agile. Les exigences changeantes qui signifient que la conception de logiciels écrits précédemment doit changer tout le temps.
RemcoGerlich
@ guillaume31: Pas nécessairement la triangulation mais toute technique utilisant des itérations au niveau du code source. Par solution acceptable, j'entends une solution qui réussit tous les tests et qui peut être maintenue raisonnablement bien ..
Frank Puffer

Réponses:

8

Une limitation bien connue de ces méthodes est qu'elles ne garantissent pas de trouver l'optimum global, ni même un optimum local acceptable.

Pour rendre votre comparaison plus adéquate: pour certains types de problèmes, les algorithmes d'optimisation itérative sont très susceptibles de produire de bons optima locaux, pour d'autres situations, ils peuvent échouer.

Je pense à un scénario dans lequel vous avez mis en œuvre un certain nombre de cas de test, puis constatez que le prochain cas de test nécessiterait une approche différente. Vous devrez jeter votre travail précédent et recommencer.

Je peux imaginer une situation où cela peut se produire dans la réalité: lorsque vous choisissez la mauvaise architecture d'une manière dont vous devez recréer tous vos tests existants à partir de zéro. Disons que vous commencez à implémenter vos 20 premiers cas de test dans le langage de programmation X sur le système d'exploitation A. Malheureusement, l'exigence 21 comprend que le programme entier doit s'exécuter sur le système d'exploitation B, où X n'est pas disponible. Ainsi, vous devez jeter la plupart de votre travail et réimplémenter dans la langue Y. (Bien sûr, vous ne jeteriez pas le code complètement, mais le porteriez dans le nouveau langage et le nouveau système.)

Cela nous apprend, même lorsque vous utilisez TDD, c'est une bonne idée de faire une analyse et une conception globales au préalable. Ceci, cependant, est également vrai pour tout autre type d'approche, donc je ne vois pas cela comme un problème TDD inhérent. Et, pour la majorité des tâches de programmation du monde réel, vous pouvez simplement choisir une architecture standard (comme le langage de programmation X, le système d'exploitation Y, le système de base de données Z sur le matériel XYZ), et vous pouvez être relativement sûr qu'une méthodologie itérative ou agile comme TDD ne vous amènera pas dans une impasse.

Citant Robert Harvey: "Vous ne pouvez pas développer une architecture à partir de tests unitaires." Ou pdr: "TDD ne m'aide pas seulement à trouver le meilleur design final, il m'aide à y arriver en moins de tentatives."

Donc en fait ce que tu as écrit

Si votre point de départ est séparé de toutes les solutions acceptables par une grande région de mauvaises solutions, il est impossible d'y arriver et la méthode échouera.

pourrait devenir vrai - lorsque vous choisissez une mauvaise architecture, vous n'atteindrez probablement pas la solution requise à partir de là.

D'un autre côté, lorsque vous faites une planification globale au préalable et choisissez la bonne architecture, l'utilisation de TDD devrait être comme démarrer un algorithme de recherche itérative dans une zone où vous pouvez vous attendre à atteindre le "maximum global" (ou au moins un maximum suffisant) ) en quelques cycles.

Doc Brown
la source
20

Je ne pense pas que TDD ait un problème de maxima locaux. Le code que vous écrivez pourrait, comme vous l'avez correctement remarqué, mais c'est pourquoi la refactorisation (réécriture de code sans changer de fonctionnalité) est en place. Fondamentalement, à mesure que vos tests augmentent, vous pouvez réécrire des parties importantes de votre modèle d'objet si vous en avez besoin tout en gardant le comportement inchangé grâce aux tests. Les tests indiquent des vérités invariantes sur votre système qui, par conséquent, doivent être valides à la fois dans les maxima locaux et absolus.

Si vous êtes intéressé par les problèmes liés au TDD, je peux en mentionner trois différents auxquels je pense souvent:

  1. Le problème d' exhaustivité : combien de tests sont nécessaires pour décrire complètement un système? Le «codage par des exemples de cas» est-il une façon complète de décrire un système?

  2. Le problème de durcissement : quel que soit l'interface de test, il doit avoir une interface immuable. Les tests représentent des vérités invariantes , rappelez-vous. Malheureusement, ces vérités ne sont pas connues du tout pour la plupart du code que nous écrivons, au mieux uniquement pour les objets extérieurs.

  3. Le problème des dommages liés aux tests : afin de rendre les assertions testables, nous pourrions avoir besoin d'écrire du code sous-optimal (moins performant, par exemple). Comment écrire des tests pour que le code soit aussi bon qu'il peut l'être?


Modifié pour répondre à un commentaire: voici un exemple d'échanger un maximum local pour une fonction "double" via le refactoring

Test 1: lorsque l'entrée est 0, retourne zéro

La mise en oeuvre:

function double(x) {
  return 0; // simplest possible code that passes tests
}

Refactoring: pas nécessaire

Test 2: lorsque l'entrée est 1, retournez 2

La mise en oeuvre:

function double(x) {
  return x==0?0:2; // local maximum
}

Refactoring: pas nécessaire

Test 3: lorsque l'entrée est 2, retournez 4

La mise en oeuvre:

function double(x) {
  return x==0?0:x==2?4:2; // needs refactoring
}

Refactoring:

function double(x) {
  return x*2; // new maximum
}
Sklivvz
la source
1
Ce que j'ai vécu, cependant, c'est que ma première conception ne fonctionnait que pour quelques cas simples et j'ai réalisé plus tard que j'avais besoin d'une solution plus générale. Le développement de la solution plus générale a nécessité plus de tests tandis que les tests originaux pour les cas spéciaux ne fonctionneront plus. J'ai trouvé acceptable de supprimer (temporairement) ces tests pendant que je développe la solution plus générale, en les ajoutant une fois que le temps est prêt.
5gon12eder
3
Je ne suis pas convaincu que le refactoring soit un moyen de généraliser du code (en dehors de l'espace artificiel de "design patterns", bien sûr) ou d'échapper aux maxima locaux. La refactorisation nettoie le code, mais cela ne vous aidera pas à découvrir une meilleure solution.
Andres F.
2
@Sklivvz Compris, mais je ne pense pas que cela fonctionne de cette façon en dehors des exemples de jouets comme ceux que vous avez publiés. En outre, cela vous a aidé à nommer votre fonction "double"; d'une manière que vous connaissiez déjà la réponse. TDD est certainement utile lorsque vous connaissez plus ou moins la réponse mais que vous voulez l'écrire "proprement". Cela n'aiderait pas à découvrir des algorithmes ou à écrire du code vraiment complexe. C'est pourquoi Ron Jeffries n'a pas réussi à résoudre le Sudoku de cette façon; vous ne pouvez pas implémenter un algorithme que vous ne connaissez pas en le TDD hors de l'obscurité.
Andres F.
1
@VaughnCato Ok, maintenant je suis en position de vous faire confiance ou d'être sceptique (ce qui serait grossier, alors ne le faisons pas). Disons simplement, d'après mon expérience, que cela ne fonctionne pas comme vous le dites. Je n'ai jamais vu un algorithme raisonnablement complexe sortir du TDD. Peut-être que mon expérience est trop limitée :)
Andres F.
2
@Sklivvz "Tant que vous pouvez écrire les tests appropriés" est précisément le point: cela ressemble à me demander la question. Ce que je dis, c'est que vous ne pouvez souvent pas . Il n'est pas plus facile de penser à un algorithme ou à un solveur en écrivant d'abord des tests . Vous devez regarder le tableau d' ensemble d' abord . Il est bien sûr nécessaire d'essayer des scénarios, mais notez que TDD ne consiste pas à écrire des scénarios: TDD consiste à tester la conception ! Vous ne pouvez pas piloter la conception d'un solveur Sudoku (ou d'un nouveau solveur pour un jeu différent) en écrivant d'abord des tests. Comme preuve anecdotique (ce qui n'est pas suffisant): Jeffries ne pouvait pas.
Andres F.
13

Ce que vous décrivez en termes mathématiques est ce que nous appelons vous peindre dans un coin. Cet événement n'est guère exclusif à TDD. Dans la cascade, vous pouvez rassembler et remplir les exigences pendant des mois en espérant voir le maximum global uniquement pour y arriver et réaliser qu'il y a une meilleure idée juste à la prochaine colline.

La différence réside dans un environnement agile que vous ne vous attendiez pas à être parfait à ce stade, vous êtes donc plus que prêt à jeter l'ancienne idée et à passer à la nouvelle idée.

Plus spécifique à TDD, il existe une technique pour éviter que cela ne vous arrive lorsque vous ajoutez des fonctionnalités sous TDD. C'est le lieu de priorité de transformation . Lorsque TDD a un moyen formel pour vous de refactoriser, c'est un moyen formel d'ajouter des fonctionnalités.

candied_orange
la source
13

Dans sa réponse , @Sklivvz a soutenu de manière convaincante que le problème n'existe pas.

Je veux faire valoir que cela n'a pas d'importance: la prémisse fondamentale (et la raison d'être) des méthodologies itératives en général et Agile et en particulier TDD en particulier, est que non seulement l'optimum global, mais les optimums locaux aussi ne sont pas '' t connu. Donc, en d'autres termes: même si c'était un problème, il n'y a aucun moyen de le faire de manière itérative de toute façon. En supposant que vous acceptez la prémisse de base.

Jörg W Mittag
la source
8

Les pratiques TDD et Agile peuvent-elles promettre de produire une solution optimale? (Ou même une "bonne" solution?)

Pas exactement. Mais ce n'est pas leur but.

Ces méthodes fournissent simplement un "passage sûr" d'un état à un autre, reconnaissant que les changements prennent du temps, sont difficiles et risqués. Et le but des deux pratiques est de garantir que l'application et le code sont à la fois viables et éprouvés pour répondre aux exigences plus rapidement et plus régulièrement.

... [TDD] s'oppose au développement de logiciels qui permettent d'ajouter des logiciels dont il n'est pas prouvé qu'ils répondent aux exigences ... Kent Beck, qui est reconnu pour avoir développé ou "redécouvert" la technique, a déclaré en 2003 que TDD encourage les simples conçoit et inspire confiance. ( Wikipedia )

TDD se concentre sur la garantie que chaque "morceau" de code satisfait aux exigences. En particulier, cela permet de garantir que le code répond aux exigences préexistantes, au lieu de laisser les exigences être motivées par un mauvais codage. Mais, il ne fait aucune promesse que la mise en œuvre est "optimale" en aucune façon.

Quant aux processus Agiles:

Le logiciel de travail est la principale mesure du progrès ... À la fin de chaque itération, les parties prenantes et le représentant du client examinent le progrès et réévaluent les priorités en vue d'optimiser le retour sur investissement ( Wikipedia )

L'agilité n'est pas à la recherche d'une solution optimale ; juste une solution de travail - dans le but d'optimiser le retour sur investissement . Il promet une solution de travail plus tôt plutôt que plus tard ; pas «optimal».

Mais, ça va, parce que la question est fausse.

Les optimaux dans le développement de logiciels sont des cibles floues et mobiles. Les exigences sont généralement changeantes et truffées de secrets qui n'apparaissent, à votre grande gêne, que dans une salle de conférence pleine de patrons de votre patron. Et la «bonté intrinsèque» de l'architecture et du codage d'une solution est évaluée par les opinions divisées et subjectives de vos pairs et celle de votre suzerain managérial - dont aucun ne peut réellement rien savoir sur un bon logiciel.

À tout le moins, les pratiques TDD et Agile reconnaissent les difficultés et tentent d'optimiser pour deux choses qui sont objectives et mesurables: Travailler contre Ne pas Travailler et Plus Tôt contre Plus Tard.

Et, même si nous avons des mesures objectives «fonctionnelles» et «plus tôt», votre capacité à les optimiser dépend principalement des compétences et de l'expérience d'une équipe.


Les choses que vous pourriez interpréter comme des efforts produisant des solutions optimales comprennent des choses comme:

etc..

Que chacune de ces choses produise réellement des solutions optimales serait une autre excellente question à se poser!

svidgen
la source
1
C'est vrai, mais je n'ai pas écrit que l'objectif du TDD ou de toute autre méthode de développement logiciel est une solution optimale dans le sens d'un optimum global. Ma seule préoccupation est que les méthodologies basées sur de petites itérations au niveau du code source pourraient ne trouver aucune solution acceptable (assez bonne) dans de nombreux cas
Frank Puffer
@Frank Ma réponse est destinée à couvrir les optimums locaux et globaux. Et la réponse est: "Non, ce n'est pas pour cela que ces stratégies sont conçues - elles sont conçues pour améliorer le retour sur investissement et atténuer les risques." ... ou quelque chose comme ça. Et cela est en partie dû à la réponse de Jörg: les «optimaux» sont des cibles mobiles. ... j'irais même plus loin; non seulement ils déplacent des cibles, mais ils ne sont pas entièrement objectifs ou mesurables.
svidgen
@FrankPuffer Peut-être que cela vaut la peine d'être ajouté. Mais, l'essentiel est que vous vous demandez si ces deux choses atteignent quelque chose qu'elles ne sont pas du tout conçues ou destinées à réaliser. Plus encore, vous demandez s'ils peuvent réaliser quelque chose qui ne peut même pas être mesuré ou vérifié.
svidgen
@FrankPuffer Bah. J'ai essayé de mettre à jour ma réponse pour mieux la dire. Je ne suis pas sûr d'avoir fait mieux ou pire! ... Mais je dois quitter SE.SE et retourner au travail.
svidgen
Cette réponse est correcte, mais le problème que j'ai avec elle (comme avec certaines des autres réponses) est que «l'atténuation des risques et l'amélioration du retour sur investissement» ne sont pas toujours les meilleurs objectifs. Ce ne sont pas vraiment des objectifs en soi. Lorsque vous avez besoin de quelque chose pour fonctionner, l'atténuation du risque ne va pas le couper. Parfois, de petites étapes relativement non dirigées comme dans TDD ne fonctionnent pas - vous minimiserez bien les risques, mais vous n'atteindrez aucun endroit utile à la fin.
Andres F.
4

Personne n'a ajouté jusqu'à présent que le "développement TDD" que vous décrivez est très abstrait et irréaliste. Cela peut être comme ça dans une application mathématique où vous optimisez un algorithme mais cela n'arrive pas souvent dans les applications métier sur lesquelles la plupart des codeurs travaillent.

Dans le monde réel, vos tests exercent et valident essentiellement des règles commerciales:

Par exemple - Si un client est un non-fumeur de 30 ans avec une femme et deux enfants, la catégorie premium est "x" etc.

Vous n'allez pas changer de manière itérative le moteur de calcul premium tant qu'il n'est pas correct pendant très longtemps - et presque certainement pas tant que l'application est en direct;).

Ce que vous avez réellement créé est un filet de sécurité afin que, lorsqu'une nouvelle méthode de calcul est ajoutée pour une catégorie particulière de clients, toutes les anciennes règles ne se brisent pas soudainement et ne donnent pas la mauvaise réponse. Le filet de sécurité est encore plus utile si la première étape du débogage consiste à créer un test (ou une série de tests) qui reproduit l'erreur avant d'écrire le code pour corriger le bogue. Ensuite, un an plus tard, si quelqu'un recrée accidentellement le bogue d'origine, le test unitaire se casse avant même que le code ne soit archivé. mais cela ne devrait pas faire partie intégrante de votre travail.

mcottle
la source
1
Premièrement, quand j'ai lu votre réponse, j'ai pensé "oui, c'est le point central". Mais après avoir repensé la question, j'ai pensé qu'elle n'était pas nécessairement si abstraite ou irréaliste. Si l'on choisit aveuglément l'architecture complètement fausse, TDD ne résoudra pas cela, pas après 1000 itérations.
Doc Brown
@Doc Brown D'accord, cela ne résoudra pas ce problème. Mais il vous donnera une suite de tests qui appliquent chaque hypothèse et règle métier afin que vous puissiez améliorer l'architecture de manière itérative. L'architecture si mauvaise qu'elle a besoin d'une réécriture de fond pour corriger est très rare (j'espère) et même dans ce cas extrême, les tests unitaires de règles métier seraient un bon point de départ.
mcottle
Quand je dis "mauvaise architecture", j'ai en tête des cas où l'on doit jeter la suite de tests existante. Avez-vous lu ma réponse?
Doc Brown
@DocBrown - Oui, je l'ai fait. Si vous vouliez dire «mauvaise architecture» pour signifier «changer toute la suite de tests», vous auriez peut-être dû dire cela. La modification de l'architecture ne signifie pas que vous devez supprimer tous les tests s'ils sont basés sur des règles métier. Vous devrez probablement les modifier tous pour prendre en charge toutes les nouvelles interfaces que vous créez et même en réécrire complètement, mais les règles métier ne seront pas remplacées par une modification technique, de sorte que les tests resteront. Certes, investir dans des tests unitaires ne devrait pas être invalidé par la possibilité improbable d'effacer complètement l'architecture
mcottle
bien sûr, même si l'on a besoin de réécrire chaque test dans un nouveau langage de programmation, on n'a pas besoin de tout jeter, au moins on peut porter la logique existante. Et je suis d'accord à 100% pour les grands projets du monde réel, les hypothèses de la question sont assez irréalistes.
Doc Brown
3

Je ne pense pas que cela gêne. La plupart des équipes n'ont personne capable de trouver une solution optimale même si vous l'avez écrite sur leur tableau blanc. TDD / Agile ne les gênera pas.

De nombreux projets ne nécessitent pas de solutions optimales et ceux qui le font, le temps, l'énergie et l'attention nécessaires seront faits dans ce domaine. Comme tout le reste, nous avons tendance à commencer par le faire fonctionner. Faites vite. Vous pouvez le faire avec une sorte de prototype si les performances sont si importantes, puis reconstruire le tout avec la sagesse acquise à travers de nombreuses itérations.

Je pense à un scénario dans lequel vous avez mis en œuvre un certain nombre de cas de test, puis constatez que le prochain cas de test nécessiterait une approche différente. Vous devrez jeter votre travail précédent et recommencer.

Cela pourrait se produire, mais ce qui est plus susceptible de se produire, c'est la peur de modifier des parties complexes de l'application. Ne pas avoir de test peut créer un plus grand sentiment de peur dans ce domaine. L'un des avantages de TDD et d'avoir une suite de tests est que vous avez construit ce système avec l'idée qu'il devra être changé. Lorsque vous proposez cette solution optimisée monolithique dès le début, il peut être très difficile de la changer.

Aussi, mettez cela dans le contexte de votre préoccupation de sous-optimisation, et vous ne pouvez pas vous empêcher de passer du temps à optimiser les choses que vous ne devriez pas avoir et à créer des solutions inflexibles parce que vous étiez tellement hyper concentré sur leurs performances.

JeffO
la source
0

Il peut être trompeur d'appliquer un concept mathématique comme "l'optimum local" à la conception de logiciels. L'utilisation de tels termes rend le développement logiciel beaucoup plus quantifiable et scientifique qu'il ne l'est réellement. Même si "optimal" existait pour le code, nous n'avons aucun moyen de le mesurer et donc aucun moyen de savoir si nous l'avons atteint.

Le mouvement agile était vraiment une réaction contre la croyance que le développement de logiciels pouvait être planifié et prédit avec des méthodes mathématiques. Pour le meilleur ou pour le pire, le développement de logiciels ressemble plus à un métier qu'à une science.

JacquesB
la source
Mais était-ce une réaction trop forte? Cela aide certainement dans de nombreux cas où une planification initiale stricte s'est révélée lourde et coûteuse. Cependant, certains problèmes logiciels doivent être traités comme un problème mathématique et avec une conception initiale. Vous ne pouvez pas les TDD. Vous pouvez TDD l'interface utilisateur et la conception globale de Photoshop, mais vous ne pouvez pas TDD ses algorithmes. Ce ne sont pas des exemples triviaux comme dériver "sum" ou "double" ou "pow" dans les exemples TDD typiques [1]). Vous ne pouvez probablement pas taquiner un nouveau filtre d'image en écrivant certains scénarios de test; vous devez absolument vous asseoir et écrire et comprendre des formules.
Andres F.
2
[1] En fait, je suis presque sûr fibonacci, que j'ai vu utilisé comme un exemple / tutoriel TDD, est plus ou moins un mensonge. Je suis prêt à parier que personne n'a jamais "découvert" fibonacci ou toute autre série similaire en TDD. Tout le monde commence par déjà connaître fibonacci, qui est de la triche. Si vous essayez de découvrir cela en TDD, vous atteindrez probablement l'impasse à laquelle le PO demandait: vous ne pourrez jamais généraliser la série en écrivant simplement plus de tests et de refactorisation - vous devez appliquer des mathématiques raisonnement!
Andres F.
Deux remarques: (1) Vous avez raison de dire que cela peut être trompeur. Mais je n'ai pas écrit que TDD est identique à l'optimisation mathématique. Je l'ai juste utilisé comme analogie ou modèle. Je crois que les mathématiques peuvent (et devraient) être appliquées à presque tout tant que vous êtes conscient des différences entre le modèle et la réalité. (2) La science (travaux scientifiques) est généralement encore moins prévisible que le développement de logiciels. Et je dirais même que le génie logiciel ressemble plus à un travail scientifique qu'à un métier. Les travaux manuels nécessitent généralement un travail beaucoup plus routinier.
Frank Puffer
@AndresF .: TDD ne signifie pas que vous n'avez pas à penser ou à concevoir. Cela signifie simplement que vous écrivez le test avant d'écrire l'implémentation. Vous pouvez le faire avec des algorithmes.
JacquesB
@FrankPuffer: OK, alors quelle est la valeur mesurable qui a un "optimum local" dans le développement de logiciels?
JacquesB