Pourquoi le support Design by Contract est-il si limité dans la plupart des langages de programmation modernes?

40

J'ai récemment découvert Design by Contract (DbC) et je trouve que c'est un moyen extrêmement intéressant d'écrire du code. Entre autres choses, il semblerait offrir:

  • Meilleure documentation. Étant donné que le contrat est la documentation, il est impossible qu’une personne soit périmée. De plus, comme le contrat spécifie exactement ce que fait une routine, il facilite la réutilisation.
  • Débogage plus simple. Étant donné que l'exécution du programme s'arrête en cas d'échec d'un contrat, les erreurs ne peuvent pas se propager et l'assertion spécifique violée sera probablement mise en évidence. Ceci offre un support lors du développement et de la maintenance.
  • Meilleure analyse statique. DbC est fondamentalement juste une implémentation de la logique de Hoare, et les mêmes principes devraient s'appliquer.

Les coûts, en comparaison, semblent plutôt minimes:

  • Dactylographie supplémentaire. Depuis les contrats doivent être précisés.
  • Prend un peu de formation pour se familiariser avec la rédaction de contrats.

Maintenant, connaissant principalement Python, je me rends compte qu’il est en fait possible d’écrire des conditions préalables (en lançant juste des exceptions pour des entrées inappropriées) et qu’il est même possible d’utiliser des assertions pour tester à nouveau certaines post-conditions. Mais il n'est pas possible de simuler certaines fonctionnalités telles que "ancien" ou "résultat" sans une magie supplémentaire qui serait finalement considérée comme non pythonique. (En outre, quelques bibliothèques offrent un support, mais au final, j'ai l'impression qu'il serait erroné de les utiliser, contrairement à la plupart des développeurs.) Je suppose que c'est un problème similaire pour tous les autres langages (sauf bien sûr , Eiffel).

Mon intuition me dit que le manque de soutien doit être le résultat d’une sorte de rejet de la pratique, mais la recherche en ligne n’a pas été fructueuse. Je me demande si quelqu'un peut expliquer pourquoi la plupart des langues modernes semblent offrir si peu d'assistance? DbC est-il défectueux ou trop cher? Ou est-ce simplement obsolète en raison de la programmation extrême et d'autres méthodologies?

César Bautista
la source
Cela ressemble à une façon trop compliquée de faire de la programmation pilotée par des tests, sans l'avantage de tester également votre programme.
Dan
3
@Dan, pas vraiment, je le considère plutôt comme une extension du système de types. Par exemple, une fonction ne prend pas simplement un argument entier, elle prend un entier qui est contractuellement tenu d'être supérieur à zéro
Carson63000
4
Les contrats de code @Dan réduisent considérablement le nombre de tests que vous devez faire.
Rei Miyasaka
24
@ Dan, je dirais plutôt que TDD est le contrat du pauvre, pas l'inverse.
SK-logic
En langage dynamique, vous pouvez "décorer" vos objets avec des contrats basés sur un drapeau optionnel. J'ai un exemple d' implémentation qui utilise des indicateurs d'environnement pour éventuellement corriger les objets existants avec les contrats. Oui, le support n’est pas natif, mais c’est facile à ajouter. La même chose s'applique pour tester les harnais, ils ne sont pas natifs mais ils sont faciles à ajouter / écrire.
Raynos

Réponses:

9

On peut soutenir qu'ils sont pris en charge dans pratiquement tous les langages de programmation.

Ce dont vous avez besoin, ce sont des "assertions".

Celles-ci sont facilement codées comme "if":

if (!assertion) then AssertionFailure();

Avec cela, vous pouvez rédiger des contrats en plaçant ces assertions en haut de votre code pour les contraintes d'entrée; ceux aux points de retour sont des contraintes de sortie. Vous pouvez même ajouter des invariants dans votre code (bien que cela ne fasse pas vraiment partie de la "conception par contrat").

Donc, je soutiens qu'ils ne sont pas répandus parce que les programmeurs sont trop paresseux pour les coder, pas parce que vous ne pouvez pas le faire.

Vous pouvez les rendre un peu plus efficaces dans la plupart des langues en définissant une "vérification" de constante booléenne au moment de la compilation et en révisant un peu les instructions:

if (checking & !Assertion) then AssertionFailure();

Si vous n'aimez pas la syntaxe, vous pouvez recourir à diverses techniques d'abstraction de langage telles que les macros.

Certaines langues modernes vous offrent une syntaxe intéressante, et c'est ce que vous entendez par "support des langues modernes". C'est un soutien, mais c'est assez mince.

Ce que la plupart des langues modernes ne vous donnent pas, ce sont des assertions "temporelles" (sur des états arbitraires antérieurs ou suivants [opérateur temporel "éventuellement"], dont vous avez besoin si vous voulez rédiger des contrats très intéressants. Les instructions IF ne vous aideront pas vous ici.

Ira Baxter
la source
Le problème que je rencontre avec le seul accès aux assertions est qu’il n’existe aucun moyen efficace de vérifier les post-conditions sur les commandes car il est souvent nécessaire de comparer l’état de postcondition à l’état de précondition (Eiffel appelle cet 'ancien' et le passe automatiquement à la routine de postcondition. .) En Python, cette fonctionnalité pourrait être recréée de manière triviale à l’aide de décorateurs, mais elle est insuffisante au moment de désactiver les assertions.
Ceasar Bautista
Quelle part de l'état précédent Eiffel a-t-il réellement économisé? Puisqu'il ne peut raisonnablement pas savoir quelle partie vous pouvez accéder / modifier sans résoudre le problème d'arrêt (en analysant votre fonction), il doit soit sauvegarder l'état complet de la machine, soit, en tant que hueristique, seulement une partie très superficielle de celle-ci. Je soupçonne ce dernier; et ceux-ci peuvent être "simulés" par de simples assignations scalaires avant la condition préalable. Je serais heureux d'apprendre que Eiffel fait autrement.
Ira Baxter
7
... viens de vérifier le fonctionnement de Eiffel. "old <exp>" est la valeur de <exp> à l'entrée de la fonction. Il effectue donc des copies superficielles à l'entrée de la fonction, comme je m'y attendais. Vous pouvez les faire aussi. Je conviens que faire en sorte que le compilateur implémente la syntaxe pre / post / old est plus pratique que de faire tout cela à la main, mais le fait est que l’on peut le faire à la main et ce n’est vraiment pas difficile. Nous sommes de retour aux programmeurs paresseux.
Ira Baxter
@ IraBaxter No. Code devient plus simple si vous pouvez séparer le contrat de la logique réelle. En outre, si le compilateur peut dire contrat et en dehors du code, il peut réduire les doubles emplois par beaucoup . Par exemple, en D, vous pouvez déclarer un contrat sur une interface ou une super-classe et les assertions seront appliquées à toutes les classes implémentées / étendues, quel que soit le code de leurs fonctions. Par exemple, avec Python ou Java, vous devez appeler la superméthode complète et éventuellement jeter les résultats si vous souhaitez uniquement vérifier les contrats sans les dupliquer. Cela aide vraiment à implémenter du code propre et conforme au LSP.
marstato
@marstato: J'ai déjà convenu que le support dans la langue est une bonne chose.
Ira Baxter
15

Comme vous le dites, Design by Contract est une fonctionnalité d’Eiffel, qui est depuis longtemps l’un de ces langages de programmation très respectés dans la communauté mais qui n’a jamais fait son chemin.

DbC n'est pas dans les langages les plus populaires car ce n'est que récemment que la communauté de la programmation traditionnelle a fini par accepter que l'ajout de contraintes / attentes à leur code était une chose "raisonnable" à attendre des programmeurs. Il est maintenant courant pour les programmeurs de comprendre à quel point les tests unitaires sont précieux, et cela a beaucoup plu aux programmeurs qui acceptaient mieux de mettre du code pour valider leurs arguments et de voir les avantages. Mais il y a dix ans, la plupart des programmeurs diraient probablement que "ce n'est que du travail supplémentaire pour des choses que vous savez, ça va toujours aller".

Je pense que si vous vous adressiez au développeur moyen aujourd'hui et que vous parlez de post-conditions, il inclinerait la tête avec enthousiasme et dirait "OK, c'est comme des tests unitaires". Et si vous parlez de conditions préalables, ils diraient "OK, c'est comme la validation de paramètre, ce que nous ne faisons pas toujours, mais, vous savez, je suppose que ça va ..." Et ensuite, si vous parlez d'invariants , ils commençaient à dire: "Gee, combien de temps cela représente? Combien de bugs allons-nous attraper?" etc.

Je pense donc qu’il reste encore beaucoup à faire avant que DbC soit très largement adopté.

Larry OBrien
la source
OTOH, les programmeurs grand public étaient habitués à écrire les affirmations assez longtemps. L'absence de préprocesseur utilisable dans les langages traditionnels les plus modernes a rendu cette belle pratique inefficace, mais elle reste courante en C et C ++. Il fait maintenant un retour en force avec les contrats de code de Microsoft (basés sur AFAIK, une réécriture de bytecode pour les versions de la version).
SK-logic
8

Mon intuition me dit que le manque de soutien doit être le résultat d'une sorte de rejet de la pratique, ...

Faux.

C'est une pratique de conception . Il peut être incorporé explicitement dans le code (style Eiffel) ou implicitement dans le code (la plupart des langues) ou dans des tests unitaires. La pratique du design existe et fonctionne bien. Le support linguistique est partout sur la carte. Il est toutefois présent dans de nombreuses langues dans la structure de test unitaire.

Je me demande si quelqu'un peut expliquer pourquoi la plupart des langues modernes semblent offrir si peu d'assistance? DbC est-il défectueux ou trop cher?

C'est cher. Et. Plus important encore, certaines choses ne peuvent pas être prouvées dans une langue donnée. La terminaison de boucle, par exemple, ne peut pas être prouvée dans un langage de programmation, elle nécessite une capacité de preuve "d'ordre supérieur". Certains types de contrats sont donc techniquement inexprimables.

Ou est-ce simplement obsolète en raison de la programmation extrême et d'autres méthodologies?

Non.

Nous utilisons principalement des tests unitaires pour démontrer que DbC est remplie.

Comme vous l'avez noté, pour Python, le DBC est utilisé à plusieurs endroits.

  1. Les résultats du test docstring et docstring.

  2. Assertions pour valider les entrées et les sorties.

  3. Tests unitaires.

Plus loin.

Vous pouvez adopter des outils de style de programmation alphabétisés afin de rédiger un document contenant vos informations DbC et générant des scripts de test d'unité propres Python. L'approche de programmation alphabète vous permet d'écrire une belle littérature qui inclut les contrats et la source complète.

S.Lott
la source
Vous pouvez prouver des cas triviaux de terminaison de boucle, tels qu'une itération sur une séquence finie fixe. Il s’agit d’une boucle généralisée dont la terminaison est triviale (dans la mesure où elle pourrait être à la recherche de solutions à des conjectures mathématiques «intéressantes»); c'est l'essence même du problème de l'arrêt.
Donal Fellows
+1 Je pense que vous êtes le seul à avoir couvert le point le plus critique - there are some things which cannot be proven. La vérification formelle peut être formidable, mais tout n’est pas vérifiable! Donc, cette fonctionnalité limite réellement ce que le langage de programmation peut réellement faire!
Dipan Mehta
@DonalFellows: Comme le cas général ne peut pas être prouvé, il est difficile d'incorporer un ensemble de fonctionnalités qui sont (a) coûteuses et (b) connues pour être incomplètes. Mon argument dans cette réponse est qu’il est plus facile d’éviter toutes ces fonctionnalités et d’éviter de créer de fausses attentes en ce qui concerne les preuves de correction formelle en général, quand il ya des limitations. Comme exercice de conception (en dehors de la langue), de nombreuses techniques de preuve peuvent (et devraient) être utilisées.
S.Lott
Ce n'est pas du tout coûteux dans un langage tel que C ++ où la vérification de contrat est compilée dans la version finale. Et l’utilisation de DBC tend à rendre le code de construction plus léger, car vous introduisez moins de contrôles à l’exécution pour que le programme soit dans un état légal. J'ai perdu le compte du nombre de bases de code pieux que j'ai vu où de nombreuses fonctions vérifient si un état est illégal et retournent false, alors qu'il ne devrait JAMAIS être dans cet état dans une version correctement testée.
Kaitain
6

Juste deviner. Une des raisons pour lesquelles il n'est pas si populaire est parce que "Design by Contract" est une marque déposée par Eiffel.

DPD
la source
3

Une hypothèse est que, pour un programme complexe suffisamment volumineux, en particulier pour les cibles mobiles, la masse des contrats eux-mêmes peut devenir aussi complexe et difficile à déboguer que le code du programme seul. Comme avec tout modèle, il peut y avoir une utilisation passée des rendements décroissants, ainsi que des avantages évidents lorsqu'elle est utilisée de manière plus ciblée.

Une autre conclusion possible est que la popularité des "langues gérées" est la preuve actuelle de la prise en charge, conception par contrat, des fonctionnalités gérées sélectionnées (bornes de tableau par contrat, etc.).

hotpaw2
la source
> la masse des contrats eux-mêmes peut devenir aussi difficile et difficile à déboguer, voire plus que le code du programme seul, je n'ai jamais vu cela.
Kaitain
2

La raison pour laquelle la plupart des langages traditionnels n'ont pas de fonctionnalités DbC dans le langage est le rapport coût / bénéfice de sa mise en œuvre est trop élevé pour le développeur de langage.

une partie de cela a déjà été examinée dans les autres réponses, des tests unitaires et d'autres mécanismes d'exécution (ou même des mécanismes de compilation avec méta-programmation) peuvent vous donner déjà une bonne part de la bonté de DbC. Par conséquent, bien qu’il y ait un avantage, il est probablement perçu comme assez modeste.

L’autre côté, c’est le coût, l’adaptation rétroactive de DbC à un langage existant est probablement un changement trop important et très complexe à démarrer. Introduire une nouvelle syntaxe dans un langage sans casser l'ancien code est difficile. Mettre à jour votre bibliothèque standard existante pour utiliser un changement d'une telle ampleur coûterait cher. Nous pouvons donc en conclure que l’implémentation de fonctionnalités DbC dans un langage existant a un coût élevé.

Je voudrais également noter que les concepts qui sont à peu près des contrats pour des modèles, et donc quelque peu liés à DbC, ont été abandonnés de la dernière norme C ++ car même après des années de travail sur ces derniers, il a été estimé qu'ils en avaient encore besoin. Il est trop difficile de mettre en œuvre ces types de changements de grande envergure et généraux.

jk.
la source
2

DbC serait utilisé plus largement si les contrats pouvaient être vérifiés au moment de la compilation, de sorte qu'il ne serait pas possible d'exécuter un programme qui violerait un contrat.

Sans le support du compilateur, "DbC" n'est qu'un autre nom pour "vérifier les invariants / hypothèses et lever une exception en cas de violation".

Ingo
la source
Cela ne vous pose-t-il pas un problème?
Ceasar Bautista
@Ceasar Cela dépend. Certaines hypothèses peuvent être vérifiées, d'autres non. Par exemple, il existe des systèmes de type permettant d'éviter de passer une liste vide en argument ou d'en renvoyer une.
Ingo
Bon point (+1) bien que Bertrand Meyer dans sa partie de "Masterminds of programming" mentionne également son système de création de classes aléatoires et de vérification des appels pour violation des contrats. C'est donc une approche mixte compilation / exécution, mais je doute que cette technique fonctionne dans toutes les situations
Maksee
C'est vrai dans une certaine mesure, même si cela devrait être une défaillance catastrophique plutôt qu'une exception (voir ci-dessous). Le principal avantage de DBC est la méthodologie, qui conduit en fait à des programmes mieux conçus, et l’assurance que toute méthode donnée DOIT être dans un état juridique à l’introduction, ce qui simplifie en grande partie la logique interne. En règle générale, vous ne devriez pas avoir à lancer des exceptions en cas de violation d'un contrat. Des exceptions doivent être utilisées lorsque le programme peut LEGALEMENT être dans l'état ~ X et que le code client doit le gérer. Un contrat dira que ~ X est tout simplement illégal.
Kaitain
1

J'ai une explication simple, la plupart des gens (y compris les programmeurs) ne veulent pas de travail supplémentaire à moins de le juger nécessaire. Programmation avionique où la sécurité est considérée comme très importante Je n'ai pas vu la plupart des projets sans elle.

Mais si vous envisagez de programmer un site Web, un ordinateur de bureau ou un appareil mobile, les plantages et les comportements inattendus ne sont parfois pas considérés comme mauvais et les programmeurs éviteront tout travail supplémentaire lorsque le signalement des bogues et leur résolution ultérieure sont jugés suffisants.

C’est probablement la raison pour laquelle, à mon avis, Ada n’a jamais repris contact avec l’industrie de la programmation aéronautique, car il nécessite plus de travail de codage, même si Ada est un langage génial et si vous souhaitez construire un système fiable, c’est le meilleur langage pour le travail langage basé sur Ada).

Les bibliothèques de contrats de conception pour C # étaient expérimentales et fournies par Microsoft. Elles sont très utiles pour créer des logiciels fiables, mais elles n’ont jamais pris de l’élan sur le marché, sans quoi elles auraient déjà été intégrées au langage C #.

Les assertions ne correspondent pas à un support entièrement fonctionnel pour les conditions pré / post et invariant. Bien qu'il puisse essayer de les émuler, un langage / compilateur avec le support approprié effectue l'analyse de «l'arbre de syntaxe abstrait» et vérifie les erreurs de logique que de simples assertions ne peuvent pas.

Edit: j'ai fait une recherche et suivre une discussion connexe qui pourrait être utile: https://stackoverflow.com/questions/4065001/are-there-any-provable-real-weal-languages-scala

FOINS
la source
-2

Les raisons sont principalement les suivantes:

  1. Il n'est disponible que dans les langues qui ne sont pas populaires
  2. C'est inutile car les mêmes choses peuvent être faites de manière différente dans les langages de programmation existants par quiconque veut le faire.
  3. Il est difficile à comprendre et à utiliser - il faut des connaissances spécialisées pour le faire correctement. Il y a donc peu de gens qui le font.
  4. cela nécessite de grandes quantités de code - les programmeurs préfèrent minimiser le nombre de caractères qu'ils écrivent - si cela prend un long morceau de code, il doit y avoir un problème
  5. les avantages ne sont pas là - il ne trouve tout simplement pas assez de bogues pour que cela en vaille la peine
tp1
la source
1
Votre réponse n'est pas bien argumentée. Vous déclarez simplement qu’il n’ya aucun avantage, qu’il nécessite de grandes quantités de code et qu’il est inutile, car il est possible de le faire avec les langues existantes (le PO a spécifiquement abordé ce problème!).
Andres F.
Je ne pense pas aux affirmations, etc., pour les remplacer. Ce n'est pas bonne façon de le faire dans les langues existantes (il n'a pas été abordée encore).
TP1
@ tp1, si les programmeurs voulaient vraiment minimiser la frappe, ils ne tomberaient jamais dans quelque chose d'aussi bavard et éloquent que Java. Et oui, la programmation elle-même nécessite des "connaissances spécialisées" "pour le faire correctement". Ceux qui ne possèdent pas une telle connaissance ne devraient tout simplement pas être autorisés à coder.
SK-logic
@ Sk-logic: Eh bien, il semble que la moitié du monde ne se trompe pas parce qu'ils ne veulent pas écrire de fonctions de transfert depuis les fonctions membres vers les fonctions membres des membres de données. C'est un gros problème dans mon expérience. Ceci est directement causé par la réduction du nombre de caractères à écrire.
tp1
@ tp1, si les gens voulaient vraiment minimiser la dactylographie, ils ne toucheraient même jamais le OO, jamais. OOP est naturellement éloquent, même dans ses meilleures implémentations, comme Smalltalk. Je ne dirais pas que c'est une mauvaise propriété, l'éloquence aide parfois.
SK-logic