J'arrive souvent à des positions dans mon code où je me retrouve à vérifier une condition spécifique encore et encore.
Je veux vous donner un petit exemple: supposons qu'il existe un fichier texte qui contient des lignes commençant par "a", des lignes commençant par "b" et d'autres lignes et je ne souhaite en fait travailler qu'avec les deux premières sortes de lignes. Mon code ressemblerait à quelque chose comme ça (en utilisant python, mais le lire comme pseudocode):
# ...
clear_lines() # removes every other line than those starting with "a" or "b"
for line in lines:
if (line.startsWith("a")):
# do stuff
elif (line.startsWith("b")):
# magic
else:
# this else is redundant, I already made sure there is no else-case
# by using clear_lines()
# ...
Vous pouvez imaginer que je ne vérifierai pas seulement cette condition ici, mais peut-être aussi dans d'autres fonctions et ainsi de suite.
Le considérez-vous comme du bruit ou ajoute-t-il de la valeur à mon code?
coding-style
clean-code
marktani
la source
la source
assert()
essai pour aider, mais au-delà, c'est probablement excessif. Cela dit, cela variera selon la situation.elif (line.startsWith("b"))
? par ailleurs, vous pouvez supprimer en toute sécurité ces parenthèses environnantes sur les conditions, elles ne sont pas idiomatiques en Python.Réponses:
Il s'agit d'une pratique extrêmement courante et la façon de la gérer est d'utiliser des filtres d' ordre supérieur .
Essentiellement, vous transmettez une fonction à la méthode de filtrage, ainsi que la liste / séquence que vous souhaitez filtrer et la liste / séquence résultante contient uniquement les éléments que vous souhaitez.
Je ne connais pas la syntaxe python (cependant, elle contient une telle fonction comme vu dans le lien ci-dessus), mais en c # / f # cela ressemble à ceci:
c #:
f # (suppose ienumerable, sinon List.filter serait utilisé):
Donc, pour être clair: si vous utilisez du code / des modèles éprouvés, c'est un mauvais style. Cela, et la mutation de la liste en mémoire de la façon dont vous semblez via clear_lines () vous fait perdre la sécurité des threads et tout espoir de parallélisme que vous auriez pu avoir.
la source
(line for line in lines if line.startswith("a") or line.startswith("b"))
.clear_lines
est vraiment une mauvaise idée. En Python, vous utiliseriez probablement des générateurs pour éviter de charger le fichier complet en mémoire.lines
s'agit d'une collection générée.skip
,take
,reduce
(aggregate
.NET),map
(select
.NET), et il y a plus , mais c'est un début vraiment solide.J'ai récemment dû implémenter un programmeur de micrologiciel utilisant le format Motorola S-record , très similaire à ce que vous décrivez. Étant donné que nous avions un peu de temps, mon premier projet a ignoré les redondances et a apporté des simplifications en fonction du sous-ensemble que j'avais réellement besoin d'utiliser dans ma demande. Il a réussi mes tests facilement, mais a échoué durement dès que quelqu'un d'autre l'a essayé. Il n'y avait aucune idée du problème. Il a réussi à traverser mais a échoué à la fin.
Je n'ai donc pas eu d'autre choix que de mettre en œuvre tous les contrôles redondants, afin de préciser où était le problème. Après cela, il m'a fallu environ deux secondes pour trouver le problème.
Il m'a fallu peut-être deux heures de plus pour le faire correctement, mais j'ai également perdu une journée de temps pour le dépannage. Il est très rare que quelques cycles de processeur valent une journée de dépannage inutile.
Cela étant dit, en ce qui concerne la lecture des fichiers, il est souvent avantageux de concevoir votre logiciel pour travailler avec sa lecture et son traitement une ligne à la fois, plutôt que de lire l'intégralité du fichier en mémoire et de le traiter en mémoire. De cette façon, il fonctionnera toujours sur de très gros fichiers.
la source
Vous pouvez lever une exception au
else
cas où. De cette façon, ce n'est pas redondant. Les exceptions ne sont pas des choses qui ne devraient pas se produire mais qui sont quand même vérifiées.la source
"c"
, cela pourrait être moins clair.En conception par contrat , on devine que chaque fonction doit faire son travail comme décrit dans sa documentation. Ainsi, chaque fonction a une liste de conditions préalables, c'est-à-dire les conditions sur les entrées de la fonction ainsi que les conditions postérieures, c'est-à-dire les conditions de sortie de la fonction.
La fonction doit garantir à ses clients que si les entrées respectent les conditions préalables, la sortie sera telle que décrite par les conditions préalables. Si au moins une des conditions préalables n'est pas respectée, la fonction peut faire ce qu'elle veut (planter, retourner n'importe quel résultat, ...). Les pré et post-conditions sont donc une description sémantique de la fonction.
Grâce au contrat, une fonction est sûre que ses clients l'utilisent correctement et un client est sûr que la fonction fait son travail correctement.
Certaines langues gèrent les contrats de manière native ou via un cadre dédié. Pour les autres, le mieux est de vérifier les pré et post-conditions grâce aux assertions, comme l'a dit @Lattyware. Mais je n'appellerais pas cela une programmation défensive, car dans mon esprit, ce concept est davantage axé sur la protection contre les intrants de l'utilisateur (humain).
Si vous exploitez des contrats, vous pouvez éviter la condition vérifiée de façon redondante car soit la fonction appelée fonctionne parfaitement et vous n'avez pas besoin de la double vérification, soit la fonction appelée est dysfonctionnelle et la fonction appelante peut se comporter comme elle le souhaite.
Le plus difficile est alors de définir quelle fonction est responsable de quoi et de documenter strictement ces rôles.
la source
En fait, vous n'avez pas besoin de clear_lines () au début. Si la ligne n'est ni "a" ni "b", les conditions ne se déclencheront tout simplement pas. Si vous voulez vous débarrasser de ces lignes, transformez le else en clear_line (). En l'état, vous effectuez deux passages dans votre document. Si vous sautez clear_lines () au début et le faites dans le cadre de la boucle foreach, vous réduisez de moitié votre temps de traitement.
Ce n'est pas seulement un mauvais style, c'est un mauvais calcul.
la source
"a"
/"b"
. Ne pas dire que c'est probable (le nom clair implique qu'ils sont jetés), juste qu'il y a une possibilité que cela soit nécessaire. Si cet ensemble de lignes est itéré à plusieurs reprises à l'avenir, il pourrait également être utile de les supprimer au préalable pour éviter beaucoup d'itérations inutiles.Si vous voulez vraiment faire quelque chose si vous trouvez une chaîne non valide (texte de débogage de sortie par exemple), je dirais que c'est tout à fait correct. Quelques lignes supplémentaires et quelques mois plus tard quand il cesse de fonctionner pour une raison inconnue, vous pouvez regarder la sortie pour savoir pourquoi.
Si, cependant, il est prudent de simplement l'ignorer, ou si vous savez avec certitude que vous n'obtiendrez jamais une chaîne non valide, alors il n'est pas nécessaire d'utiliser la branche supplémentaire.
Personnellement, je suis toujours pour mettre au moins une sortie de trace pour toute condition inattendue - cela rend la vie beaucoup plus facile lorsque vous avez un bug avec une sortie attachée vous indiquant exactement ce qui ne va pas.
la source
Je déteste les
if...then...else
constructions. J'éviterais tout le problème:la source