Dans quelle mesure est-il nécessaire de suivre des pratiques de programmation défensives pour un code qui ne sera jamais rendu public?

45

J'écris une implémentation Java d'un jeu de cartes, alors j'ai créé un type spécial de Collection que j'appelle une Zone. Toutes les méthodes de modification de la collection Java ne sont pas prises en charge, mais il existe une méthode dans l'API de zone move(Zone, Card), qui déplace une carte de la zone donnée vers elle-même (à l'aide de techniques de paquet privé). De cette façon, je peux m'assurer qu'aucune carte n'est sortie d'une zone et qu'elle ne disparaisse tout simplement. ils ne peuvent être déplacés que vers une autre zone.

Ma question est la suivante: dans quelle mesure ce type de codage défensif est-il nécessaire? C'est "correct" et cela semble être la bonne pratique, mais ce n'est pas comme si l'API Zone faisait partie d'une bibliothèque publique. C'est juste pour moi, alors c'est un peu comme si je protégeais mon code alors que je pourrais probablement être plus efficace en utilisant simplement Collections standard.

Jusqu'où dois-je prendre cette idée de zone? Quelqu'un peut-il me donner des conseils sur la mesure dans laquelle je devrais penser à préserver les contrats dans les classes que j'écris, en particulier pour ceux qui ne seront pas vraiment accessibles au public?

décrypteur
la source
4
= ~ s / nécessaire / recommandé / gi
GrandmasterB
2
Les types de données doivent être corrects par construction, ou bien sur quoi construisez-vous? Ils doivent être encapsulés de telle manière que, modifiables ou non, ils ne puissent être que dans des états valides. Si vous ne parvenez pas à appliquer cette contrainte statique (ou excessivement difficile), vous devriez générer une erreur d'exécution.
Jon Purdy
1
Ne jamais dire jamais. À moins que votre code ne soit jamais utilisé, vous ne pouvez jamais savoir avec certitude où votre code aboutira. ;)
Izkata
1
Le commentaire de @codebreaker GrandmasterB est une expression de remplacement. Cela signifie: remplacer "nécessaire" par "recommandé".
Ricardo Souza
1
Le code sans codification # 116 Confiance personne n'est probablement particulièrement approprié ici.

Réponses:

72

Je ne vais pas aborder le problème de conception - juste la question de savoir s'il faut faire les choses "correctement" dans une API non publique.

c'est juste pour moi, alors c'est un peu comme si je protégeais mon propre code

C'est exactement le but. Peut-être y at-il des codeurs qui se souviennent des nuances de chaque classe et méthode qu’ils ont jamais écrites et ne les appellent jamais par erreur avec le mauvais contrat. Je ne suis pas l'un d'eux. J'oublie souvent que le code que j'ai écrit est censé fonctionner en quelques heures. Une fois que vous pensez avoir bien compris les choses une fois, votre esprit aura tendance à changer de sujet en fonction du problème sur lequel vous travaillez actuellement .

Vous avez des outils pour lutter contre cela. Ces outils incluent (sans ordre particulier) des conventions, des tests unitaires et d’autres tests automatisés, la vérification des conditions préalables et la documentation. J'ai moi-même constaté que les tests unitaires étaient d'une valeur inestimable, car ils vous obligent tous les deux à réfléchir à la manière dont votre contrat sera utilisé et fournissent une documentation ultérieure sur la conception de l'interface.

Michael K
la source
Bon à savoir. Dans le passé, je programmais aussi efficacement que possible. J'ai donc parfois du mal à m'habituer à de telles idées. Je suis content d’aller dans la bonne direction.
Codebreaker
15
"Efficacement" peut signifier beaucoup de choses différentes! D'après mon expérience, les novices (même si je ne dis pas que vous en êtes un) oublient souvent avec quelle efficacité ils pourront soutenir le programme. Le code passe généralement beaucoup plus de temps dans la phase de support du cycle de vie de son produit que dans la phase "d'écriture de nouveau code", je pense donc que c'est une efficacité qu'il convient d'examiner attentivement.
Charlie Kilian
2
Je suis vraiment d'accord Au collège, je n'ai jamais eu à y penser.
Codebreaker
25

Je suis généralement quelques règles simples:

  • Essayez de toujours programmer par contrat .
  • Si une méthode est accessible au public ou reçoit des contributions du monde extérieur , appliquez certaines mesures défensives (par exemple IllegalArgumentException).
  • Pour tout le reste accessible uniquement en interne, utilisez des assertions (par exemple assert input != null).

Si un client est vraiment intéressé, il trouvera toujours un moyen de faire en sorte que votre code se comporte mal. Ils peuvent toujours le faire par réflexion, au moins. Mais c'est la beauté de la conception par contrat . Vous n'approuvez pas une telle utilisation de votre code et vous ne pouvez donc pas garantir qu'il fonctionnera dans de tels scénarios.

En ce qui concerne votre cas particulier, si cela Zonen’est pas censé être utilisé et / ou utilisé par des personnes extérieures, finalvous pouvez soit rendre la classe privée à un paquet (et éventuellement ), soit utiliser de préférence les collections que Java vous fournit déjà. Ils sont testés et vous n'avez pas à réinventer la roue. Notez que cela ne vous empêche pas d'utiliser des assertions dans votre code pour vous assurer que tout fonctionne comme prévu.

afsantos
la source
1
+1 pour mentionner Design by Contract. Si vous ne pouvez pas complètement interdire le comportement (et que c'est difficile à faire), au moins, vous expliquez clairement que rien ne garantit un mauvais comportement. J'aime aussi lancer une exception IllegalStateException ou UnsupportedOperationException.
user949300
@ user949300 Bien sûr. J'aime croire que de telles exceptions ont été introduites dans un but significatif. Le respect des contrats semble correspondre à un tel rôle.
afsantos
16

La programmation défensive est une très bonne chose.
Jusqu'à ce que cela commence à gêner l'écriture de code. Alors ce n'est pas une si bonne chose.


Parlant un peu plus pragmatique ...

On dirait que vous êtes sur le point de pousser les choses trop loin. Le défi (et la réponse à votre question) consiste à comprendre quelles sont les règles de gestion ou les exigences du programme.

En utilisant votre API de jeu de cartes comme exemple, il existe des environnements dans lesquels tout ce qui peut être fait pour empêcher la tricherie est essentiel. De grosses sommes d'argent réel peuvent être impliquées, il est donc logique de mettre en place un grand nombre de contrôles pour s'assurer que la triche ne peut pas se produire.

D'autre part, vous devez rester attentif aux principes SOLID, en particulier à la responsabilité unique. Demander à la classe conteneur de vérifier efficacement où vont les cartes peut être un peu difficile. Il peut être préférable d’avoir une couche d’audit / contrôleur entre le conteneur de cartes et la fonction qui reçoit les demandes de déplacement.


En rapport avec ces préoccupations, vous devez comprendre quels composants de votre API sont publiquement exposés (et donc vulnérables) par rapport à ceux qui sont privés et moins exposés. Je ne suis pas un ardent défenseur d'un "revêtement extérieur dur avec un intérieur doux", mais le meilleur retour de vos efforts est de durcir l'extérieur de votre API.

Je ne pense pas que l'utilisateur final d'une bibliothèque soit aussi critique quant à la détermination de la programmation défensive que vous avez mise en place. Même avec des modules que j'écris pour mon propre usage, je mets tout de même une mesure de contrôle en place pour m'assurer que mon avenir ne commettra pas une erreur par inadvertance en appelant la bibliothèque.


la source
2
+1 pour "Jusqu'à ce que ça commence à gêner l'écriture de code." Surtout pour des projets personnels à court terme, coder de manière défensive peut prendre beaucoup plus de temps que cela n'en vaut la peine.
Corey
2
Je suis d’accord, mais j’aimerais ajouter que c’est une bonne chose de pouvoir / pouvoir / programmer de manière défensive, mais il est également crucial de pouvoir programmer de manière prototypée. La capacité de faire les deux vous permettra de choisir l'action la plus appropriée, ce qui est bien meilleur que beaucoup de programmeurs que je connais qui sont capables de programmer (en quelque sorte) de manière défensive.
David Mulder
13

Le codage défensif n'est pas simplement une bonne idée pour le code public. C'est une excellente idée pour tout code qui n'est pas immédiatement jeté. Bien sûr, vous savez comment il est censé s'appeler maintenant , mais vous ne savez pas à quel point vous vous en souviendrez bien dans six mois à compter de votre retour au projet.

La syntaxe de base de Java vous offre une défense bien intégrée, comparée à un langage de niveau inférieur ou interprété comme C ou Javascript, respectivement. En supposant que vous nommez vos méthodes de manière claire et que vous ne disposez pas d'un "séquencement de méthodes" externe, vous pouvez probablement vous contenter de spécifier des arguments comme type de données correct et d'inclure un comportement judicieux si des données correctement typées peuvent toujours être invalides.

(D'un autre côté, si les cartes doivent toujours être dans la zone, je pense que vous obtenez un meilleur rapport qualité-prix en faisant en sorte que toutes les cartes en jeu soient référencées par une collection globale à votre objet Game, et que Zone soit la propriété de chaque carte. Mais comme je ne sais pas ce que font vos zones à part garder des cartes, il est difficile de savoir si cela convient.)

DougM
la source
1
Je considérais que la zone était une propriété de la carte, mais comme mes cartes fonctionnaient mieux comme des objets immuables, j'ai décidé que cette méthode était la meilleure. Merci pour le conseil.
Codebreaker
3
@codebreaker une chose qui peut aider dans ce cas encapsule la carte dans un autre objet. C'est un As de pique. L'emplacement ne définit pas son identité et une carte devrait probablement être immuable. Peut-être qu'une zone contient des cartes: peut-être une CardDescriptorcarte qui contient une carte, son emplacement, son statut face recto / verso, ou même la rotation pour les jeux qui tiennent à cela. Ce sont toutes des propriétés modifiables qui ne modifient pas l'identité d'une carte.
1

Commencez par créer une classe qui conserve une liste de zones afin de ne pas perdre une zone ou ses cartes. Vous pouvez ensuite vérifier qu'un transfert se trouve dans votre ZoneList. Cette classe sera probablement une sorte de singleton, car vous n'aurez besoin que d'une instance, mais vous voudrez peut-être des ensembles de zones plus tard, alors laissez vos options ouvertes.

Deuxièmement, ne faites pas que Zone ou ZoneList implémentent Collection ou quoi que ce soit d'autre, sauf si vous pensez en avoir besoin. Autrement dit, si une zone ou ZoneList seront transmis à quelque chose qui attend une collection, puis la mettre en œuvre. Vous pouvez désactiver un ensemble de méthodes en leur faisant renvoyer une exception (UnimplementedException ou quelque chose du genre) ou simplement en leur demandant de ne rien faire. (Réfléchissez bien avant d'utiliser la deuxième option. Si vous le faites parce que c'est facile, vous constaterez qu'il vous manque des bugs que vous auriez peut-être déjà détectés.)

Il y a de vraies questions sur ce qui est "correct". Mais une fois que vous aurez compris ce que vous voudrez faire, vous voudrez faire les choses de cette façon. En deux ans, vous aurez oublié tout cela, et si vous essayez d'utiliser le code, vous serez vraiment ennuyé par le type qui l'a écrit de manière aussi peu intuitive et qui n'a rien expliqué.

RalphChapin
la source
2
Votre réponse est un peu trop centrée sur le problème à résoudre plutôt que sur les questions plus larges posées par le PO concernant la programmation défensive en général.
En fait, je passe les zones aux méthodes qui prennent Collections, donc la mise en oeuvre est nécessaire. Une sorte de registre de zones dans le jeu est une idée intéressante, cependant.
Codebreaker
@ GlenH7: Je trouve que travailler avec des exemples spécifiques est souvent plus utile que la théorie abstraite. Le PO fournissait un programme assez intéressant, alors j’y suis allé.
RalphChapin
1

Le codage défensif dans la conception des API consiste généralement à valider les entrées et à sélectionner avec soin un mécanisme de traitement des erreurs approprié. Les choses que les autres réponses mentionnent méritent également d'être notées.

En réalité, ce n’est pas ce que dit votre exemple. Vous êtes là pour limiter la surface de votre API, pour une raison très spécifique. Comme GlenH7 le mentionne, lorsque le jeu de cartes doit être utilisé dans un jeu réel, avec un jeu de cartes ('utilisé' et 'non utilisé'), une table et des mains, par exemple, vous voulez certainement mettre des chèques en place pour vous assurer que La carte du jeu est présente une fois et une seule fois.

Que vous ayez conçu ceci avec des "zones", c’est un choix arbitraire. En fonction de l'implémentation (une zone ne peut être qu'une main, un pont ou une table dans l'exemple ci-dessus), il peut très bien s'agir d'une conception complète.

Cependant, cette implémentation ressemble à un type dérivé d’un Collection<Card>ensemble de cartes plus ressemblant, avec une API moins restrictive. Par exemple, lorsque vous souhaitez créer une calculatrice de valeur de main, ou une intelligence artificielle, vous voulez sûrement être libre de choisir laquelle et combien de cartes pour chacune de vos itérations.

Il est donc bon d'exposer une API aussi restrictive si son seul objectif est de s'assurer que chaque carte est toujours dans une zone.

CodeCaster
la source