Je développe des logiciels depuis trois ans, mais je me suis récemment réveillé à quel point je suis ignorant des bonnes pratiques. Cela m'a amené à commencer à lire le livre Clean Code , qui bouleverse ma vie pour le mieux, mais j'ai du mal à obtenir un aperçu de certaines des meilleures approches pour écrire mes programmes.
J'ai un programme Python dans lequel je ...
- utilisez argparse
required=True
pour appliquer deux arguments, qui sont tous deux des noms de fichiers. le premier est le nom du fichier d'entrée, le second est le nom du fichier de sortie - avoir une fonction
readFromInputFile
qui vérifie d'abord qu'un nom de fichier d'entrée a été entré - avoir une fonction
writeToOutputFile
qui vérifie d'abord qu'un nom de fichier de sortie a été entré
Mon programme est suffisamment petit pour que je puisse croire que les vérifications # 2 et # 3 sont redondantes et doivent être supprimées, libérant ainsi les deux fonctions d'une if
condition inutile . Cependant, j'ai également été amené à croire que "la double vérification est ok" et peut être la bonne solution dans un programme où les fonctions pourraient être appelées à partir d'un emplacement différent où l'analyse des arguments ne se produit pas.
(De plus, si la lecture ou l'écriture échoue, j'ai un try except
dans chaque fonction pour déclencher un message d'erreur approprié.)
Ma question est: est-il préférable d'éviter toute vérification de condition redondante? La logique d'un programme doit-elle être si solide que les vérifications ne doivent être effectuées qu'une seule fois? Y a-t-il de bons exemples qui illustrent cela ou l'inverse?
EDIT: Merci à tous pour les réponses! J'ai appris quelque chose de chacun. Voir autant de perspectives me permet de mieux comprendre comment aborder ce problème et déterminer une solution en fonction de mes besoins. Je vous remercie!
la source
Réponses:
Ce que vous demandez est appelé "robustesse", et il n'y a pas de bonne ou de mauvaise réponse. Cela dépend de la taille et de la complexité du programme, du nombre de personnes qui y travaillent et de l'importance de détecter les défaillances.
Dans les petits programmes que vous écrivez seul et uniquement pour vous-même, la robustesse est généralement une préoccupation bien moindre que lorsque vous allez écrire un programme complexe composé de plusieurs composants, peut-être écrit par une équipe. Dans de tels systèmes, il existe des frontières entre les composants sous forme d'API publiques, et à chaque frontière, c'est souvent une bonne idée de valider les paramètres d'entrée, même si "la logique du programme doit être si solide que ces contrôles sont redondants. ". Cela facilite la détection des bogues et contribue à réduire les temps de débogage.
Dans votre cas, vous devez décider vous-même du type de cycle de vie que vous attendez de votre programme. S'agit-il d'un programme que vous prévoyez d'utiliser et de maintenir au fil des ans? Ensuite, l'ajout d'une vérification redondante est probablement préférable, car il n'est pas improbable que votre code soit refactorisé à l'avenir et que vos fonctions
read
etwrite
puissent être utilisées dans un contexte différent.Ou s'agit-il d'un petit programme uniquement à des fins d'apprentissage ou de plaisir? Ensuite, ces doubles vérifications ne seront pas nécessaires.
Dans le cadre du "Clean Code", on pourrait se demander si une double vérification viole le principe DRY. En fait, parfois, c'est le cas, au moins dans une certaine mesure: la validation des entrées peut être interprétée comme faisant partie de la logique métier d'un programme, et le faire à deux endroits peut entraîner les problèmes de maintenance habituels causés par la violation de DRY. Robustesse vs DRY est souvent un compromis - la robustesse nécessite une redondance dans le code, tandis que DRY essaie de minimiser la redondance. Et avec la complexité croissante du programme, la robustesse devient de plus en plus importante que d'être DRY dans la validation.
Enfin, permettez-moi de donner un exemple de ce que cela signifie dans votre cas. Supposons que vos besoins changent en quelque chose comme
Cela signifie-t-il que vous devrez probablement modifier votre double validation à deux endroits? Probablement pas, une telle exigence entraîne un changement lors de l'appel
argparse
, mais aucun changement danswriteToOutputFile
: cette fonction nécessitera toujours un nom de fichier. Donc, dans votre cas, je voterais pour faire la validation d'entrée deux fois, le risque d'avoir des problèmes de maintenance en raison du fait d'avoir deux endroits à changer est à mon humble avis beaucoup plus faible que le risque d'avoir des problèmes de maintenance en raison d'erreurs masquées causées par trop peu de vérifications.la source
La redondance n'est pas le péché. La redondance inutile est.
Si
readFromInputFile()
etwriteToOutputFile()
sont des fonctions publiques (et selon les conventions de dénomination Python, car leurs noms ne commencent pas par deux traits de soulignement), les fonctions pourraient un jour être utilisées par quelqu'un qui éviterait complètement l'argparse. Cela signifie que lorsqu'ils abandonnent les arguments, ils ne voient pas votre message d'erreur d'argparse personnalisé.Si
readFromInputFile()
etwriteToOutputFile()
vérifiez les paramètres eux-mêmes, vous obtenez à nouveau un message d'erreur personnalisé qui explique la nécessité de noms de fichiers.Si
readFromInputFile()
etwriteToOutputFile()
ne vérifiez pas les paramètres eux-mêmes, aucun message d'erreur personnalisé ne s'affiche. L'utilisateur devra déterminer lui-même l'exception qui en résulte.Tout se résume à 3. Écrivez du code qui utilise réellement ces fonctions en évitant argparse et produisez le message d'erreur. Imaginez que vous n'avez pas du tout regardé à l'intérieur de ces fonctions et que vous vous fiez simplement à leurs noms pour fournir suffisamment de compréhension à utiliser. Quand c'est tout ce que vous savez, y a-t-il un moyen d'être dérouté par l'exception? Faut-il un message d'erreur personnalisé?
Il est difficile de désactiver la partie de votre cerveau qui se souvient de l'intérieur de ces fonctions. À tel point que certains recommandent d'écrire le code d'utilisation avant le code utilisé. De cette façon, vous arrivez au problème en sachant déjà à quoi les choses ressemblent de l'extérieur. Vous n'avez pas à faire TDD pour cela, mais si vous faites TDD, vous arriverez déjà de l'extérieur en premier.
la source
La mesure dans laquelle vous rendez vos méthodes autonomes et réutilisables est une bonne chose. Cela signifie que les méthodes devraient être indulgentes dans ce qu'elles acceptent et qu'elles devraient avoir des résultats bien définis (précis dans ce qu'elles renvoient). Cela signifie également qu'ils devraient être en mesure de gérer avec élégance tout ce qui leur est transmis et de ne faire aucune hypothèse sur la nature de l'entrée, la qualité, le calendrier, etc.
Si un programmeur a l'habitude d'écrire des méthodes qui font des hypothèses sur ce qui se passe, basées sur des idées comme "si cela est cassé, nous avons de plus grandes choses à craindre" ou "le paramètre X ne peut pas avoir la valeur Y parce que le reste de le code l'empêche ", alors tout d'un coup vous n'avez plus vraiment de composants indépendants et découplés. Vos composants dépendent essentiellement d'un système plus large. C'est une sorte de couplage serré subtil et conduit à une augmentation exponentielle du coût total de possession à mesure que la complexité du système augmente.
Notez que cela peut signifier que vous validez les mêmes informations plusieurs fois. Mais c'est OK. Chaque composant est responsable de sa propre validation à sa manière . Ce n'est pas une violation de DRY, car les validations sont effectuées par des composants indépendants découplés, et une modification de la validation dans l'un ne doit pas nécessairement être répliquée exactement dans l'autre. Il n'y a pas de redondance ici. X a la responsabilité de vérifier ses entrées pour ses propres besoins et d'en transmettre à Y. Y a sa propre responsabilité de vérifier ses propres entrées pour ses besoins .
la source
Supposons que vous ayez une fonction (en C)
Et vous ne pouvez trouver aucune documentation sur le chemin. Et puis vous regardez la mise en œuvre et il est dit
Non seulement cela teste l'entrée de la fonction, mais cela indique également à l'utilisateur de la fonction que le chemin n'est pas autorisé à être NULL ou une chaîne vide.
la source
En général, la double vérification n'est pas toujours bonne ou mauvaise. Il y a toujours de nombreux aspects de la question dans votre cas particulier dont dépend la question. Dans ton cas:
argparse
module. C'est souvent une mauvaise idée d'utiliser une bibliothèque et de faire son travail vous-même. Pourquoi utiliser la bibliothèque alors?la source
Vos contre-vérifications semblent avoir lieu à des endroits où elles sont rarement utilisées. Ces vérifications renforcent donc simplement votre programme:
Un chèque de trop ne fera pas de mal, un de moins le pourrait.
Cependant, si vous vérifiez à l'intérieur d'une boucle qui se répète souvent, vous devriez penser à supprimer la redondance, même si la vérification elle-même n'est généralement pas coûteuse par rapport à ce qui suit après la vérification.
la source
Vous pourriez peut-être changer votre point de vue:
En cas de problème, quel est le résultat? Cela nuira-t-il à votre application / à l'utilisateur?
Bien sûr, vous pourriez toujours vous demander si plus ou moins de contrôles sont meilleurs ou pires, mais c'est une question plutôt scolaire. Et puisque vous traitez avec des logiciels du monde réel , il y a des conséquences réelles.
Du contexte que vous donnez:
Je suppose que vous faites la transformation de A à B . Si A et B sont petits et la transformation est petite, quelles sont les conséquences?
1) Vous avez oublié de préciser où lire: alors le résultat n'est rien . Et le temps d'exécution sera plus court que prévu. Vous regardez le résultat - ou mieux: recherchez un résultat manquant, voyez que vous avez invoqué la commande de manière incorrecte, recommencez et tout va bien à nouveau
2) Vous avez oublié de spécifier le fichier de sortie. Il en résulte différents scénarios:
a) L'entrée est lue immédiatement. Ensuite, la transformation démarre et le résultat doit être écrit, mais à la place, vous recevez une erreur. Selon le temps, votre utilisateur doit attendre (en fonction de la masse de données à traiter), cela peut être gênant.
b) L'entrée est lue étape par étape. Ensuite, le processus d'écriture se termine immédiatement comme en (1) et l'utilisateur recommence.
Une vérification bâclée peut être considérée comme OK dans certaines circonstances. Cela dépend entièrement de votre cas d'utilisation et de votre intention.
De plus: vous devez éviter la paranoïa et ne pas faire trop de doublons.
la source
Je dirais que les tests ne sont pas redondants.
Bien que les noms de fichiers soient vérifiés deux fois, ils sont vérifiés à des fins différentes. Dans un petit programme où vous pouvez faire confiance aux paramètres des fonctions qui ont été vérifiés, les contrôles dans les fonctions peuvent être considérés comme redondants.
Une solution plus robuste aurait un ou deux validateurs de nom de fichier.
J'utilise deux règles pour savoir quand effectuer des actions:
la source
Le chèque est redondant. Pour résoudre ce problème, vous devez supprimer readFromInputFile et writeToOutputFile et les remplacer par readFromStream et writeToStream.
Au moment où le code reçoit le flux de fichiers, vous savez que vous avez un flux valide connecté à un fichier valide ou tout autre élément auquel un flux peut être connecté. Cela évite les contrôles redondants.
Vous pourriez alors demander, eh bien, vous devez toujours ouvrir le flux quelque part. Oui, mais cela se produit en interne dans la méthode d'analyse des arguments. Vous avez là deux vérifications, une pour vérifier qu'un nom de fichier est requis, l'autre est une vérification que le fichier pointé par le nom de fichier est un fichier valide dans le contexte donné (par exemple, le fichier d'entrée existe, le répertoire de sortie est accessible en écriture). Ce sont différents types de contrôles, donc ils ne sont pas redondants et ils se produisent dans la méthode d'analyse des arguments (périmètre d'application) plutôt que dans l'application principale.
la source