Je lisais sur ce billet de blog sur l'anti-modèle for-if, et je ne suis pas sûr de comprendre pourquoi c'est un anti-modèle.
foreach (string filename in Directory.GetFiles("."))
{
if (filename.Equals("desktop.ini", StringComparison.OrdinalIgnoreCase))
{
return new StreamReader(filename);
}
}
Question 1:
Est-ce à cause de l' return new StreamReader(filename);
intérieur du for loop
? ou le fait que vous n'avez pas besoin d'une for
boucle dans ce cas?
Comme l'a souligné l'auteur du blog, la version la moins folle de ceci est:
if (File.Exists("desktop.ini"))
{
return new StreamReader("desktop.ini");
}
les deux souffrent d'une condition de concurrence car si le fichier est supprimé avant la création du StreamReader
, vous obtiendrez un FileNotFoundException
.
Question 2:
Pour corriger le deuxième exemple, le réécrivez-vous sans l'instruction if, et entourez-le StreamReader
à la place d'un bloc try-catch, et s'il le lance, FileNotFoundException
vous le gérez dans le catch
bloc en conséquence?
To fix the second example, would you re-write it without the if statement, and instead surround the StreamReader with a try-catch block, and if it throws a FileNotFoundException you handle it in the catch block accordingly?
- Oui, c'est exactement ce que je ferais. La résolution de la condition de concurrence est plus importante que certaines notions d '«exceptions comme flux de contrôle», et elle le résout avec élégance et propreté.null
? Vous pouvez généralement utiliser LINQ pour nettoyer un code qui ressemble au code de votre exemple:return Directory.GetFiles(".").FirstOrDefault(fileName => fileName.Equals("desktop.ini", StringComparison.OrdinalIgnoreCase))?.Select(fileName => new StreamReader(filename));
Remarquez l'?.
opérateur entre les deux appels LINQ. De plus, les gens peuvent arguer que la création d'objets comme celle-ci n'est pas l'utilisation la plus appropriée de LINQ, mais je pense que ça va ici. Ce n'est pas une réponse à votre question, mais je m'éloigne d'une partie de celle-ci.Réponses:
Ceci est un contre-modèle car il prend la forme de:
et peut être remplacé par
Un exemple classique de ceci est un code comme:
Lorsque ce qui suit fonctionne très bien:
Si vous vous retrouvez à exécuter une boucle et un
if
ouswitch
, alors arrêtez-vous et pensez à ce que vous faites. Êtes-vous en train de trop compliquer les choses et la boucle et le test peuvent-ils être remplacés simplement par une simple ligne "faites-le"? Parfois, cependant, vous constaterez que vous devez effectuer cette boucle (plusieurs éléments peuvent correspondre à la seule condition, par exemple), auquel cas le modèle est correct.C'est pourquoi c'est un anti-pattern: il prend le pattern "loop and test" et en abuse.
Concernant votre deuxième question: oui. Un modèle «essayer» est plus robuste qu'un modèle «tester puis faire» dans toutes les situations où votre code n'est pas le seul thread sur l'ensemble de l'appareil qui peut changer l'état de l'élément en cours de test.
Le problème avec ce code:
est que dans l'intervalle entre le
File.Exists
et laStreamReader
tentative d'ouverture de ce fichier, un autre thread ou processus pourrait supprimer le fichier. Vous obtiendrez donc une exception. Par conséquent, cette exception doit être protégée contre quelque chose comme:@Flater soulève un bon point? Est-ce lui-même un contre-modèle? Utilisons-nous des exceptions comme flux de contrôle?
Si le code lit quelque chose comme:
alors j'utiliserais en effet des exceptions comme un goto glorifié et ce serait en effet un anti-modèle. Mais dans ce cas, nous travaillons simplement sur le comportement indésirable de la création d'un nouveau flux, donc ce n'est pas un exemple de cet anti-modèle. Mais c'est un exemple de contourner cet anti-modèle.
Bien sûr, ce que nous voulons vraiment, c'est une façon plus élégante de créer le flux. Quelque chose comme:
Et je recommanderais d'envelopper ce
try catch
code dans une méthode utilitaire comme celle-ci si vous vous retrouvez à utiliser beaucoup ce code.la source
Try
, par exemplereturn Try(() => new StreamReader("desktop.ini")).OrElse(null);
, mais C # ne prend pas en charge cette construction (sans utiliser une bibliothèque tierce), nous devons donc travailler avec la version maladroite.File.Open
lancera un s'il ne trouve pas le fichier. Étant donné que nous avons déjà une exception non exceptionnelle levée, le piéger dans le cadre du flux de contrôle est une nécessité (à moins que l'on veuille simplement laisser l'application se bloquer).Maybe<Stream>
type qui retournenothing
si le fichier n'existe pas et un flux s'il existe.Oui, car vous n'avez pas besoin de faire votre propre recherche pour les éléments stockés dans un sous-système interrogeable.
Dans l'abstrait, le système de fichiers, comme une base de données, est capable de répondre aux requêtes. Lorsque nous interagissons avec un sous-système interrogeable, nous avons un choix fondamental quant à savoir si nous voulons qu'un tel sous-système nous énumère son contenu afin que nous effectuions la correspondance en dehors du sous-système, ou pour utiliser les capacités de requête native du sous-système.
Imaginons un instant que vous recherchez un enregistrement dans une base de données au lieu d'un fichier dans un répertoire d'un système de fichiers. Préférez-vous voir
puis en boucle (par exemple en C #) sur le curseur renvoyé à la recherche d'ID = 100, ou en laissant le sous-système interrogeable faire ce qu'il peut pour trouver exactement ce que vous recherchez à la place?
Je pense que la plupart d'entre nous choisiraient à juste titre de laisser le sous-système effectuer la requête exacte qui l'intéresse. L'alternative implique potentiellement de nombreux allers-retours avec le sous-système, des tests d'égalité inefficaces et la renonciation à l'utilisation d'index ou d'autres accélérateurs de recherche, que nous fournissent les bases de données et les systèmes de fichiers.
Oui, car c'est ainsi que fonctionne cette API particulière - ce n'est pas vraiment notre choix car il s'agit d'une fonction de bibliothèque. La vérification if, avant l'appel, n'offre aucune valeur supplémentaire: nous devons quand même utiliser le try / catch, car (1) d'autres erreurs au-delà de FileNotFound peuvent se produire, et (2) la condition de concurrence critique.
la source
Le streamreader n'a rien à voir avec cela. L'anti-modèle émerge en raison d'un conflit d'intention clair entre le
foreach
et leif
:Quel est le but du
foreach
?Je suppose que votre réponse sera quelque chose comme: "Je veux exécuter à plusieurs reprises un morceau de code particulier"
Combien de fichiers prévoyez-vous de traiter?
Étant donné que vous ne pouvez avoir qu'un seul nom de fichier spécifique (y compris l'extension) dans un dossier particulier, cela prouve que votre code est destiné à trouver un fichier applicable.
Cela est également confirmé par le fait que vous renvoyez une valeur immédiatement. Vous ne vous souciez pas réellement d'un deuxième match, même s'il devait exister.
Il y a des situations où ce n'est pas un anti-modèle.
Directory.GetFiles(".", SearchOption.AllDirectories)
), il est possible de trouver plus d'un fichier avec le même nom de fichier (y compris l'extension)"Test_"
, ou chaque"*.zip"
fichier.Notez que ces deux cas nécessitent que vous traitez réellement plusieurs correspondances et que vous ne renvoyiez donc pas de valeur immédiatement.
Les exceptions coûtent cher. Ils ne doivent jamais être utilisés à la place d'une logique de flux appropriée. Les exceptions sont, comme leur nom l'indique, des circonstances exceptionnelles .
Pour cette raison, vous ne devez pas supprimer le fichier
if
.Selon cette réponse sur SoftwareEngineering.SE :
En résumé, pourquoi, généralement, c'est un anti-modèle:
Que vous ayez besoin d'envelopper dans un essai / capture, cela dépend fortement de votre situation:
Il ne s'agit jamais de "toujours l'utiliser". Pour prouver mon point:
Alors pourquoi ne portons-nous pas tous cet équipement de sécurité en tout temps?
La réponse est simple car il y a des inconvénients à les porter:
Nous arrivons maintenant quelque part: il y a des avantages et des inconvénients . En d'autres termes, il est logique de porter cet équipement dans les cas où les avantages l' emportent sur les inconvénients.
Devriez-vous terminer l'appel dans un essai / capture? Cela dépend beaucoup de la question de savoir si les avantages de le faire l'emportent sur le coût de sa mise en œuvre.
Notez que d'autres peuvent faire valoir qu'il ne faut que quelques touches pour l'envelopper, donc cela devrait évidemment être fait. Mais ce n'est pas tout l'argument:
Donc, c'est à toi de choisir. Y a-t-il un avantage à le faire? Pensez-vous que cela améliore l'application, plus que le coût des efforts de mise en œuvre?
la source