Ma valise de programmeur
Accueil du site > Essais > La programmation defensive n’est pas agile

La programmation defensive n’est pas agile

dimanche 29 juin 2008, par Etienne Charignon

A votre avis, qui est le plus agile ? Le chevalier avec ses 90 kg d’armure ou le paladin en tunique avec sa hache ?

Supposons qu’au cours de l’écriture de mon programme je rencontre soudainement le besoin d’une fonction d’affichage.

def affiche (a, b)

Supposons, dans un premier temps, que je sois sûr que cette fonction n’est jamais appelée avec des paramètres nuls.

Deux tactiques sont possibles ici :

  • Programmation défensive :
    • J’écris un test unitaire pour vérifier que ma méthode affiche bien a et b dans le cas où ils sont tous les deux non nuls
    • J’ajoute immédiatement dans le corps de ma fonction, au début, quelques lignes de code pour retourner une exception si l’un des deux paramètres est nul [1].
  • Programmation délibérée :
    • J’écris un test unitaire pour vérifier que ma méthode affiche bien a et b dans le cas où ils sont tous les deux non nuls.
    • rien d’autre. Je laisse le cas où les paramètres sont nuls comme indéterminé (il n’y a pas de test).

J’entends déjà les critiques : "il est pas propre ton code !", "il faut se blinder contre toute utilisation imprévue de ta fonction".

Bon, je pourrais toujours argumenter que ce n’est pas parce qu’on est dans une culture du blâme qu’il faut se construire une armure, mais là n’est pas mon propos.

Maintenant, imaginons qu’un peu de temps passe et que finalement, je rencontre un cas où j’ai besoin d’appeler ma fonction "affiche" avec b nul et que dans ce cas, je veux qu’elle affiche la chaine "nul" à la place de b.

Dans le premier cas, j’ai fait du travail inutile, pire, je ne peux pas réutiliser ma fonction ! En effet, peut-être que quelqu’un utilise ma fonction et s’attend à recevoir une exception en cas de paramètre nul. Je ne peux pas retirer mon exception sans risquer de casser son code. Je suis obligé de faire une autre fonction !

Dans le deuxième cas, pas de problème. J’étends le comportement de la fonction maintenant que j’ai un besoin précis pour le cas des paramètres nuls.

En tant que développeur, j’ai décidé de ne plus jamais faire de programmation défensive et je sais pourquoi.

Notes

[1] voir aussi mon poste précédent sur la programmation par coïncidence

9 Messages de forum

  • La programmation defensive n’est pas agile 29 juin 2008 19:26, par Bruno Orsier

    personnellement je pratique et encourage une 3ème stratégie, pour laquelle il faudrait trouver un nom... peut-être programmation raisonnée ?
    - test unitaires bien sûr pour a et b non nuls
    - assertions à l’entrée de la méthode/function

    Comme ca je documente le contrat de la fonction. Mes camarades sont informés des limitations de cette fonction, soit en lisant mon code, soit a l’exécution. Ensuite on peut discuter de ces limitations : si elles ne sont pas justifiées on peut alors faire évoluer la fonction.

    Certains vont jusqu’à tester les assertions en question en test unitaire. Cela ne paraît pas une bonne idée - les assertions ne sont pas des exceptions à proprement parler (elles ne devraient pas avoir d’influence sur le flot de controle), et de toute manière elles sont souvent désactivées en code de production.

    Voir en ligne : http://bruno-orsier.developpez.com/

    Répondre à ce message

  • La programmation defensive n’est pas agile 30 juin 2008 09:46, par Francois Wauquier

    Bonjour,

    Ce que tu appelles Programmation défensive, d’autres l’appellent Programmation par Contrat, et la valeur ajoutée de ce type de programmation est de consolider l’interface. En d’autres termes, palier aux limitations de la signature d’une méthode.

    Cela dit, je comprends ta remarque. Ce type de vérfification peut paraitre inutile. De plus, Il peut arriver que ces vérifications soit faites plusieurs fois !

    Francois Wauquier

    Répondre à ce message

    • La programmation defensive n’est pas agile 3 juillet 2008 14:53, par Etienne Charignon

      Bonjour François,

      Je ne ferais pas l’amalgame entre programmation défensive et programmation par contrat.

      Pour moi la programmation par contrat consiste à définir clairement le contrat de chaque fonction. C’est ce que je fais avec les tests unitaires.

      La programmation défensive correspondrait à ajouter dans ma fonction du code pour me protéger des utilisations hors contrat.

      Répondre à ce message

  • La programmation defensive n’est pas agile 30 juin 2008 10:55, par Eric

    Je comprends ton argument, mais je ne suis pas sûr qu’il résiste à la mise en pratique. Je pense que dans la plupart des cas, un utilisateur de la méthode fera des essais pour voir quel est le comportement réel de la méthode. Il se basera sur ce comportement pour son propre code, qu’il soit documenté ou pas. L’utilisation de la méthode est donc sans rapport direct avec ce que tu as voulu coder...

    + embêtant : si tu étends ta fonction et que tu ajoutes un comportement explicite, tu vas annoncer une évolution de la méthode. Mais pas une régression pour ceux qui se basaient sur le comportement par défaut !

    Tu vas me dire : si qqun utilise un comportement non documenté, il a intérêt à le documenter lui-même avec ses propres tests unitaires, ce qui lui permettra d’être informé des régressions. Mais ça me parait un peu trop espérer.

    La limite de mon raisonnement, bien sûr, c’est que ça ne marche que pour les cas évidents, comme dans ton exemple. S’il y a des cas non anticipés, tu seras de toute façon forcé à appliquer la technique que tu proposes. Peut-être qu’on peut dire que, comme il y a forcément des cas limites non prévus, alors on a intérêt à n’en prévoir aucun (ou le moins possible), car cela enverra un message plus clair et cohérent aux utilisateurs de la méthode.

    Répondre à ce message

  • La programmation defensive n’est pas agile 1er juillet 2008 14:47, par Pascal Sem

    Haha, je ne pensais pas que tu me voyais comme une sentinelle armée d’une hallebarde plus armure de plates complète :)

    Bref, je (grâce à toi notammment) suis convaincu que la programmation défensive n’est effectivement pas agile :

    - environnement agile : j’aurais mis les assertions dont parle Bruno parmi les tests unitaires de la fonction, justifiant éventuellement les développements supplémentaires dans celle-ci ou l’appelant

    - environnement peu hostile : j’aurais comme Bruno renseigné le contrat de la fonction, et charge au développeur de se débrouiller et/ou corriger

    - environnement hostile : j’aurais soumis le code à un bloc try/catch(Throwable t) qui aurait pour but unique de journaliser le cas anormal, sans oublier de renvoyer l’erreur telle quelle.

    Par environnement hostile, je désigne tout contexte de développement dans laquelle le développeur n’a pas de garant de ses interets personnels (culture du blame, bonne poire, etc.).

    Répondre à ce message

  • La programmation defensive n’est pas agile 9 août 2008 12:10, par Patrice Stoessel

    Ton article m’a interpellé, et j’ai donc commencé par rechercher une définition de ce qu’est la programmation défensive Je n’ai pas trouvé de définition standard ... mais partons de la première que j’ai trouvée :

    "La programmation défensive est un état d’esprit qui consiste à écrire son code de façon à s’attendre au pire. Le fait est que le programmeur peut insérer des fautes non détectées ou des inconsistances. Pour s’en prémunir, il faut prévoir un traitement pour les fautes :
    - soit en ajoutant du code vérifiant l’état du système,
    - soit par un traitement d’erreur classique. Dans l’idéal, il faudrait penser à toutes les sources d’erreurs possibles et prévoir un traitement pour chacune d’elles."

    Je suis bien d’accord avec toi pour critiquer la dernière partie "...Dans l’idéal, il faudrait penser à toutes les sources d’erreurs possibles et prévoir un traitement pour chacune d’elles."

    Par contre, la première partie me semble très importante. Cela rejoint l’article de "programmation délibérée" des "pragmatic programmers", qui suggèrent de documenter ses hypothèses ... ce que je comprends comme "utiliser des assertions" pour vérifier ses hypothèses.

    A partir de là, dans la vraie vie, chacun doit composer avec son environnement de développement. Et comme ils sont loin d’être tous aussi performants, on doit plus ou moins enrichir cet environnement pour arriver à écrire un code clair sans s’encombrer de mécanismes lourds de vérification à tous les niveaux. Voici quelques exemples de critères qui aident le développeur :
    - une machine virtuelle qui va détecter les problèmes et permettre une correction rapide
    - un gestionnaire de mémoire
    - un mécanisme d’exception
    - un langage de haut-niveau
    - des contrôles poussés effectués à la compilation
    - des contrôles runtime générés par le compilateur
    - la réutilisation de librairies éprouvées et solides

    Si l’un ou plusieurs de ces points manquent, le développeur doit compenser d’une manière ou d’une autre. Cela peut être fait en développant / réutilisant des librairies, en appliquant des règles de codage, etc

    Dans mon travail, j’utilise le C++ (pas par choix ;-) En plus de pratiques XP, j’applique systématiquement les techniques suivantes :
    - code le plus simple possible, en évitant les subtilités du langage
    - génération de code aux interfaces (entrées / sorties de messages), ce qui permet d’avoir une couche de contrôle sans avoir à la coder à la main.
    - pas de multi-thread dans le cœur de l’application
    - traitement uniforme des alertes (info, warning, error, fatal)
    - mécanisme de vérification d’assertions
    - possibilité de mettre une instruction "TODO" dans le code, quand le développeur voit qu’il y a un trou et que (le "client XP") ne sait pas quoi y mettre
    - possibilité de mettre une instruction "BUG" dans le code, quand on arrive à un endroit inatteignable en temps normal (switch / case / default)

    C’est une autre manière "d’enrichir le langage".

    En définitive, je pense que l’agilité (orientée XP) est un équilibre entre :
    - une intention claire des objectifs à atteindre (par les tests)
    - un code explicite qui documente ses hypothèses (par les assertions)
    - des conventions de codage qui complètent les parties précédentes, quand l’environnement technique est trop rustique

    Ci-joint un article qui m’a servi : http://martinfowler.com/ieeeSoftware/failFast.pdf

    A+

    Répondre à ce message

    • La programmation defensive n’est pas agile 24 août 2008 22:56, par Etienne Charignon

      Bonjour Patrice,

      Je suis complètement d’accord avec tout ce que tu as écrit. J’aurais bien voulu l’écrire moi-même.

      Tes remarques sont très intéressantes. J’apprécie particulièrement les règles que tu donnes à la fin. Bien que j’applique plus ou moins ces règles, je ne suis pas encore arrivé à ce niveau de normalisation.

      C’est rigolo comme mon article a fait réagir beaucoup de monde. En fin de compte, ce qui me gêne le plus dans la programmation défensive, ce sont les mauvaises ondes que cela transporte. Si la définition est "un état d’esprit qui consiste à écrire son code de façon à s’attendre au pire.", elle est souvent pensée comme "un état d’esprit qui consiste à écrire son code de façon à s’attendre au pire de la part des autres programmeurs".

      Répondre à ce message

    • La programmation defensive n’est pas agile 22 juin 2009 16:11, par tatoute

      La programmation défensive n’est pas toujours un traitement des fautes. La meilleure approche à mon sens face à un cas impossible/interdit est de rechercher le sens qu’il peut avoir, et selon le cas :
      - s’il ne peut avoir de sens : refactoring de la source de cas afin d’éviter que le client (au sens du contrat) tombe dans ce cas (par exemple utiliser un passage de parametre rendant impossible le parametre nul).
      - si le sens est ambigü : refactoring pour réduire à un seul sens
      - sens clair : adaptation minime du code en cours d’écriture pour en faire un cas normal, ou si developpement lourd, exception de non implementation.

      Répondre à ce message

  • La programmation defensive n’est pas agile 3 octobre 2008 23:23, par Antoine L.

    Un truc me gène : tu présupposes qu’un client peut choisir de traiter une exception envoyée suite à une violation de contrat.

    Je préfère le principe suivant : un client ne doit JAMAIS tolérer une violation de contrat. En d’autres termes, la passage par une exception ne doit jamais être un chemin normal d’exécution d’un programme.

    Du coup, le développeur de l’objet serveur est libre de faire évoluer son code tant qu’il y a compatibilité ascendante dans le respect du contrat.

    Tu as alors intérêt à expliciter le contrat dans tous les cas, particulièrement pour interdire les cas que tu ne gères pas. Le jour où tu voudras étendre le comportement de ta classe, tu enlèveras les interdictions inutiles (sans effet de bord pour tes clients), et les vérifications du contrat restantes te permettront plus vite de détecter tes nouvelles erreurs.

    Ne pas expliciter le contrat dans les cas où le comportement de l’objet est non-défini ne facilite pas l’extension, cela ne fait que te masquer les problèmes. C’est une sorte de "politique de l’autruche".

    Il est donc Agile d’expliciter ses contrats, tout le temps ! Car c’est une manière de gagner du temps lors du refactoring...

    PS : A mon avis, la bonne chose à faire est d’implémenter la fonction "assert()" via un crash et via comme un lancement d’exception.

    Répondre à ce message

Répondre à cet article

SPIP | squelette | | Plan du site | Suivre la vie du site RSS 2.0