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?
la source
Réponses:
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 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.
la source
Je suis généralement quelques règles simples:
IllegalArgumentException
).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
Zone
n’est pas censé être utilisé et / ou utilisé par des personnes extérieures,final
vous 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.la source
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
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.)
la source
CardDescriptor
carte 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.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é.
la source
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.
la source