Pourquoi devrais-je utiliser des contrats de code

26

J'ai récemment découvert le cadre de Microsoft pour les contrats de code.

J'ai lu un peu de documentation et je me suis constamment demandé: "Pourquoi voudrais-je jamais faire cela, car il ne fait pas et souvent ne peut pas effectuer une analyse statique."

Maintenant, j'ai déjà une sorte de style de programmation défensive, avec des exceptions de garde comme celle-ci:

if(var == null) { throw new NullArgumentException(); }

J'utilise également beaucoup de modèles NullObject et j'ai rarement des problèmes. Ajoutez-y des tests unitaires et vous êtes tous prêts.

Je n'ai jamais utilisé d'assertions et je ne les ai jamais manquées. Bien au contraire. Je déteste vraiment le code qui contient beaucoup d'assertions dénuées de sens, ce qui est juste du bruit pour moi et me distrait de ce que je veux vraiment voir. Les contrats de code, au moins à la manière de Microsoft, sont sensiblement les mêmes - et pire encore. Ils ajoutent beaucoup de bruit et de complexité au code. Dans 99%, une exception sera levée de toute façon - donc je me fiche que ce soit de l'assertion / du contrat ou du problème réel. Il ne reste que très, très peu de cas où les états du programme sont réellement corrompus.

Alors, franchement, quel est l'avantage d'utiliser des contrats de code? Y en a-t-il du tout? Si vous utilisez déjà des tests unitaires et du code de manière défensive, je pense que l'introduction de contrats ne vaut tout simplement pas le coût et fait du bruit dans votre code qu'un responsable maudira lorsqu'il mettra à jour cette méthode, un peu comme je le fais quand je ne vois pas ce que fait le code en raison d'affirmations inutiles. Je n'ai pas encore vu une bonne raison de payer ce prix.

Faucon
la source
1
Pourquoi dites-vous "il ne fait pas et souvent ne peut pas effectuer une analyse statique"? Je pensais que c'était l'un des points de ce projet (par opposition à simplement utiliser des assertions ou des levées d'exceptions défensives)?
Carson63000
@ Carson63000 Lisez attentivement la documentation et vous constaterez que le vérificateur statique ne produira que beaucoup, beaucoup d'avertissements, les plus inutiles (les développeurs l'admettent) et que la plupart des contrats définis lèveront simplement une exception et ne feront rien d'autre .
Falcon
@Falcon: étrangement, mon expérience est complètement différente. L'analyse statique ne fonctionne pas parfaitement, mais plutôt bien, et je vois rarement des avertissements inutiles, même lorsque je travaille au niveau d'avertissement 4 (le plus élevé).
Arseni Mourzenko
Je suppose que je vais devoir l'essayer pour savoir comment il se comporte.
Falcon

Réponses:

42

Vous auriez tout aussi bien pu demander quand la frappe statique est meilleure que la frappe dynamique. Un débat qui fait rage depuis des années sans fin en vue. Alors jetez un oeil à des questions comme les études de langues typiquement vs statiquement

Mais l'argument de base serait la possibilité de découvrir et de résoudre des problèmes au moment de la compilation qui, autrement, auraient pu glisser en production.

Contrats contre gardes

Même avec des gardes et des exceptions, vous êtes toujours confronté au problème selon lequel le système ne pourra pas exécuter sa tâche prévue dans certains cas. Peut-être une instance qui sera assez critique et coûteuse à échouer.

Contrats vs tests unitaires

L'argument habituel sur celui-ci est que les tests prouvent la présence de bugs, tandis que les types (contrats) prouvent l'absence. F.ex. en utilisant un type, vous savez qu'aucun chemin dans le programme ne pourrait éventuellement fournir une entrée non valide, tandis qu'un test pourrait seulement vous dire que le chemin couvert a fourni la bonne entrée.

Contrats vs modèle d'objet nul

Maintenant, c'est au moins dans le même parc de balle. Des langages comme Scala et Haskell ont eu beaucoup de succès avec cette approche pour éliminer entièrement les références nulles des programmes. (Même si Scala autorise formellement les null, la convention est de ne jamais les utiliser)

Si vous utilisez déjà ce modèle pour éliminer les NRE, vous avez essentiellement supprimé la plus grande source d'échecs d'exécution, essentiellement de la manière dont les contrats vous permettent de le faire.

La différence pourrait être que les contrats ont une option pour exiger automatiquement tout votre code pour éviter la null, et donc vous forcer à utiliser ce modèle dans plus d'endroits pour passer la compilation.

En plus de cela, les contrats vous donnent également la flexibilité de cibler des choses au-delà de zéro. Donc, si vous ne voyez plus de NRE dans vos bogues, vous voudrez peut-être utiliser des contrats pour étrangler le prochain problème le plus courant que vous pourriez avoir. Off par un? Index hors limites?

Mais...

Tout cela étant dit. Je suis d'accord que les contrats de bruit syntaxique (et même de bruit structurel) ajoutés au code sont assez importants et l'impact de l'analyse sur votre temps de construction ne doit pas être sous-estimé. Donc, si vous décidez d'ajouter des contrats à votre système, il serait probablement judicieux de le faire très soigneusement en vous concentrant sur la classe de bogues que vous essayez de résoudre.

John Nilsson
la source
6
C'est une excellente première réponse sur les programmeurs. Heureux d'avoir votre participation dans la communauté.
13

Je ne sais pas d'où vient l'affirmation selon laquelle "il ne fait pas et ne peut souvent pas effectuer une analyse statique". La première partie de l'affirmation est manifestement erronée. Le second dépend de ce que vous entendez par «souvent». Je dirais plutôt que souvent , il effectue une analyse statique et rarement, il échoue. Dans une application commerciale ordinaire, devient rarement beaucoup plus proche que jamais .

Le voici donc, le premier avantage:

Avantage 1: analyse statique

Les assertions ordinaires et la vérification des arguments ont un inconvénient: elles sont reportées jusqu'à l'exécution du code. En revanche, les contrats de code se manifestent à un niveau beaucoup plus précoce, soit à l'étape de codage, soit lors de la compilation de l'application. Plus vous détectez une erreur tôt, moins il est coûteux de la corriger.

Avantage 2: sorte de documentation toujours à jour

Les contrats de code fournissent également une sorte de documentation toujours à jour. Si le commentaire XML de la méthode SetProductPrice(int newPrice)indique que celle-ci newPricedoit être supérieure ou égale à zéro, vous pouvez espérer que la documentation est à jour, mais vous pouvez également découvrir que quelqu'un a changé la méthode de sorte que newPrice = 0jette un ArgumentOutOfRangeException, mais n'a jamais changé la documentation correspondante. Étant donné la corrélation entre les contrats de code et le code lui-même, vous n'avez pas le problème de documentation désynchronisée.

Le type de documentation fourni par les contrats de code est également précieux d'une manière qui, souvent, les commentaires XML n'expliquent pas bien les valeurs acceptables. Combien de fois je me demandais si nullou string.Emptyou \r\nétait une valeur autorisée pour une méthode, et les commentaires XML étaient silencieux à ce sujet!

En conclusion, sans contrats de code, beaucoup de morceaux de code sont comme ça:

J'accepterai certaines valeurs mais pas d'autres, mais vous devrez deviner ou lire la documentation, le cas échéant. En fait, ne lisez pas la documentation: elle est dépassée. Parcourez simplement toutes les valeurs et vous verrez celles qui me font lever des exceptions. Vous devez également deviner la plage de valeurs qui peut être renvoyée, car même si je vous en disais un peu plus, ce n'est peut-être pas vrai, compte tenu des centaines de modifications qui m'ont été apportées ces dernières années.

Avec les contrats de code, cela devient:

L'argument title peut être une chaîne non nulle d'une longueur de 0 à 500. L'entier qui suit est une valeur positive, qui ne peut être nulle que lorsque la chaîne est vide. Enfin, je vais retourner un IDefinitionobjet, jamais nul.

Avantage 3: contrats d'interfaces

Un troisième avantage est que les contrats de code renforcent les interfaces. Disons que vous avez quelque chose comme:

public interface ICommittable
{
    public ICollection<AtomicChange> PendingChanges { get; }

    public void CommitChanges();

    ...
}

Comment garantissez-vous, en utilisant uniquement des assertions et des exceptions, que vous CommitChangesne pouvez être appelé que lorsqu'il PendingChangesn'est pas vide? Comment garantiriez-vous que ce PendingChangesn'est jamais null?

Avantage 4: appliquer les résultats d'une méthode

Enfin, le quatrième avantage est de pouvoir bénéficier Contract.Ensuredes résultats. Et si, lors de l'écriture d'une méthode qui renvoie un entier, je veux être sûr que la valeur n'est jamais inférieure ou égale à zéro? Y compris cinq ans plus tard, après avoir subi de nombreux changements de la part de nombreux développeurs? Dès qu'une méthode a plusieurs points de retour, cela Assertdevient un cauchemar de maintenance pour cela.


Considérez les contrats de code non seulement comme un moyen d'exactitude de votre code, mais comme un moyen plus strict d'écrire du code. De la même manière, une personne qui utilise exclusivement des langages dynamiques peut se demander pourquoi appliquer des types au niveau du langage, alors que vous pouvez faire la même chose dans les assertions si nécessaire. Vous pouvez, mais la saisie statique est plus facile à utiliser, moins sujette aux erreurs par rapport à un tas d'assertions et à l'auto-documentation.

La différence entre le typage dynamique et le typage statique est extrêmement proche de la différence entre la programmation ordinaire et la programmation par contrats.

MainMa
la source
3

Je sais que c'est un peu tard pour la question, mais il y a un autre avantage aux contrats de code qui mérite d'être mentionné

Les contrats sont vérifiés en dehors de la méthode

Lorsque nous avons des conditions de garde à l'intérieur de notre méthode, c'est l'endroit où la vérification a lieu et où les erreurs sont signalées. Il se peut alors que nous devions enquêter sur la trace de la pile pour savoir où se trouve la source réelle de l'erreur.

Les contrats de code sont situés dans la méthode, mais les conditions préalables sont vérifiées en dehors de la méthode lorsque quelque chose tente d'appeler cette méthode. Les contrats font partie de la méthode et déterminent si elle peut être appelée ou non. Cela signifie que nous obtenons une erreur signalée beaucoup plus près de la source réelle du problème.

Si vous avez une méthode protégée par contrat qui est appelée pour de nombreux endroits, cela peut être un réel avantage.

Alex White
la source
2

Deux choses vraiment:

  1. Prise en charge de l'outillage, VS peut créer des données de contrat en dehors de l'intelligence afin qu'elles fassent partie de l'aide de la saisie semi-automatique.
  2. Lorsqu'une sous-classe remplace une méthode, vous perdez tous vos chèques (qui devraient toujours être valides si vous suivez le LSP) avec des contrats, ils suivent automatiquement leurs classes enfants.
stonemetal
la source