Ich habe in diesem Blog-Beitrag über das For-If-Anti-Pattern gelesen und bin mir nicht ganz sicher, warum es ein Anti-Pattern ist.
foreach (string filename in Directory.GetFiles("."))
{
if (filename.Equals("desktop.ini", StringComparison.OrdinalIgnoreCase))
{
return new StreamReader(filename);
}
}
Frage 1:
Liegt es an der return new StreamReader(filename);
Innenseite for loop
? oder die Tatsache, dass Sie for
in diesem Fall keine Schleife benötigen ?
Wie der Autor des Blogs betonte, ist die weniger verrückte Version davon:
if (File.Exists("desktop.ini"))
{
return new StreamReader("desktop.ini");
}
Beide leiden unter einer Race-Bedingung, denn wenn die Datei vor der Erstellung der gelöscht wird StreamReader
, erhalten Sie eine FileNotFoundException
.
Frage 2:
Um das zweite Beispiel zu korrigieren, würden Sie es ohne die if-Anweisung neu schreiben und stattdessen das StreamReader
mit einem try-catch-Block umgeben, und wenn es einen FileNotFoundException
auslöst, behandeln Sie es entsprechend im catch
Block?
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?
- Ja, genau das würde ich tun. Das Lösen der Rennbedingung ist wichtiger als die Vorstellung von "Ausnahmen als Kontrollfluss" und löst sie elegant und sauber.null
? Normalerweise können Sie LINQ verwenden, um Code zu bereinigen, der dem Code in Ihrem Beispielreturn Directory.GetFiles(".").FirstOrDefault(fileName => fileName.Equals("desktop.ini", StringComparison.OrdinalIgnoreCase))?.Select(fileName => new StreamReader(filename));
ähnelt : Beachten Sie den?.
Operator zwischen den beiden LINQ-Aufrufen. Auch die Leute mögen argumentieren, dass eine solche Objekterstellung nicht die am besten geeignete Verwendung von LINQ ist, aber ich denke, dass es hier in Ordnung ist. Dies ist keine Antwort auf Ihre Frage, sondern schweift über einen Teil davon ab.Antworten:
Dies ist ein Antimuster, da es die Form hat:
und kann durch ersetzt werden
Ein klassisches Beispiel hierfür ist Code wie:
Wenn Folgendes gut funktioniert:
Wenn Sie feststellen, dass Sie eine Schleife und ein
if
oder ausführenswitch
, halten Sie an und überlegen Sie, was Sie tun. Überkomplizieren Sie die Dinge und können die gesamte Schleife und der Test einfach durch eine einfache "Just Do It" -Zeile ersetzt werden? Manchmal werden Sie jedoch feststellen, dass Sie diese Schleife ausführen müssen (mehr als ein Element kann beispielsweise der einen Bedingung entsprechen). In diesem Fall ist das Muster in Ordnung.Deshalb ist es ein Anti-Pattern: Es nimmt das "Loop and Test" -Muster und missbraucht es.
Zu Ihrer zweiten Frage: Ja. Ein "try do" -Muster ist robuster als ein "test do do" -Muster in jeder Situation, in der Ihr Code nicht der einzige Thread auf dem gesamten Gerät ist, der den Status des zu testenden Elements ändern kann.
Das Problem mit diesem Code:
ist, dass in der Zeit zwischen dem
File.Exists
und demStreamReader
Versuch, diese Datei zu öffnen, ein anderer Thread oder Prozess die Datei löschen könnte. Sie erhalten also eine Ausnahme. Daher muss diese Ausnahme durch Folgendes geschützt werden:@ Flater wirft einen guten Punkt auf? Ist das selbst ein Antimuster? Verwenden wir Ausnahmen als Kontrollfluss?
Wenn der Code so etwas wie folgt lautet:
dann würde ich tatsächlich Ausnahmen als verherrlichte Goto verwenden und es wäre in der Tat ein Anti-Muster. In diesem Fall arbeiten wir jedoch nur an dem unerwünschten Verhalten beim Erstellen eines neuen Streams. Dies ist also kein Beispiel für dieses Anti-Pattern. Aber es ist ein Beispiel dafür, wie man dieses Anti-Muster umgeht.
Was wir wirklich wollen, ist natürlich eine elegantere Art, den Stream zu erstellen. So etwas wie:
Und ich würde empfehlen, diesen
try catch
Code in eine Dienstprogrammmethode wie diese einzuschließen, wenn Sie diesen Code häufig verwenden.quelle
Try
, zum Beispielreturn Try(() => new StreamReader("desktop.ini")).OrElse(null);
, aber C # unterstützt nicht , dass Konstrukt (ohne eine 3rd - Party - Bibliothek), so dass wir mit der klobigen Version arbeiten müssen.File.Open
wirft jedoch eine, wenn die Datei nicht gefunden werden kann. Da bereits eine außergewöhnliche Ausnahme ausgelöst wird, ist es eine Notwendigkeit, diese als Teil des Kontrollflusses einzufangen (es sei denn, man möchte die App einfach abstürzen lassen).Maybe<Stream>
Typ verwenden Das gibt zurück,nothing
wenn die Datei nicht existiert, und einen Stream, wenn es existiert.Ja, da Sie nicht selbst nach Elementen suchen müssen, die in einem abfragbaren Subsystem gespeichert sind.
In der Zusammenfassung kann das Dateisystem wie eine Datenbank auf Anfragen antworten. Bei der Interaktion mit einem abfragbaren Subsystem haben wir eine grundlegende Wahl, ob ein solches Subsystem seinen Inhalt für uns auflisten soll, damit wir den Abgleich außerhalb des Subsystems durchführen, oder ob die nativen Abfragefunktionen des Subsystems verwendet werden sollen.
Stellen wir uns für einen Moment vor, Sie suchen einen Datensatz in einer Datenbank anstelle einer Datei in einem Verzeichnis in einem Dateisystem. Möchten Sie lieber sehen
und dann in einer Schleife (z. B. in C #) über den zurückgegebenen Cursor nach ID = 100 suchen oder das abfragbare Subsystem tun lassen, was es kann, um stattdessen genau das zu finden, wonach Sie suchen?
Ich sollte denken, dass die meisten von uns sich zu Recht dafür entscheiden würden, das Subsystem die genaue Abfrage von Interesse durchführen zu lassen. Die Alternative umfasst potenziell zahlreiche Roundtrips mit dem Subsystem, ineffiziente Gleichheitstests und den Verzicht auf die Verwendung von Indizes oder anderen Suchbeschleunigern, die uns sowohl von Datenbanken als auch von Dateisystemen zur Verfügung gestellt werden.
Ja, denn genau so funktioniert diese bestimmte API - es ist nicht wirklich unsere Wahl, da dies eine Bibliotheksfunktion ist. Die if-Prüfung vor dem Aufruf bietet keinen zusätzlichen Wert: Wir müssen ohnehin try / catch verwenden, da (1) andere Fehler außerhalb von FileNotFound auftreten können und (2) die Race-Bedingung.
quelle
Der Streamreader hat nichts damit zu tun. Das Anti-Muster entsteht aufgrund eines klaren Absichtskonflikts zwischen dem
foreach
und demif
:Was ist der Zweck der
foreach
?Ich gehe davon aus, dass Ihre Antwort ungefähr so lautet: "Ich möchte einen bestimmten Code wiederholt ausführen."
Wie viele Dateien werden voraussichtlich verarbeitet?
Da Sie nur einen bestimmten Dateinamen (einschließlich der Erweiterung) in einem bestimmten Ordner haben können, beweist dies, dass Ihr Code eine zutreffende Datei finden soll.
Dies wird auch dadurch bestätigt, dass Sie sofort einen Wert zurückgeben. Sie interessieren sich eigentlich nicht für ein zweites Match, selbst wenn es existieren würde.
Es gibt Situationen, in denen dies kein Anti-Muster ist.
Directory.GetFiles(".", SearchOption.AllDirectories)
) suchen , ist es möglich, mehr als eine Datei mit demselben Dateinamen (einschließlich Erweiterung) zu finden."Test_"
, oder jede"*.zip"
Datei.Beachten Sie, dass Sie in beiden Fällen mehrere Übereinstimmungen verarbeiten müssen und daher nicht sofort einen Wert zurückgeben müssen.
Ausnahmen sind teuer. Sie sollten niemals anstelle einer ordnungsgemäßen Flusslogik verwendet werden. Ausnahmen sind, wie der Name schon sagt, außergewöhnliche Umstände.
Aus diesem Grund sollten Sie das nicht entfernen
if
.Gemäß dieser Antwort auf SoftwareEngineering.SE :
Als kurze Zusammenfassung, warum es im Allgemeinen ein Anti-Muster ist:
Ob Sie dies in einen Versuch / Fang einwickeln müssen, hängt stark von Ihrer Situation ab:
Nichts ist jemals eine Frage von "immer benutzen". Um meinen Standpunkt zu beweisen:
Warum tragen wir nicht alle diese Sicherheitsausrüstung immer?
Die einfache Antwort ist, weil das Tragen Nachteile hat:
Jetzt kommen wir voran: Es gibt Vor- und Nachteile . Mit anderen Worten, es ist nur dann sinnvoll, diese Ausrüstung zu tragen, wenn die Vorteile die Nachteile überwiegen .
Sollten Sie den Anruf in einen Versuch / Fang einwickeln? Das hängt sehr davon ab, ob der Nutzen die Kosten für die Implementierung überwiegt.
Beachten Sie, dass andere möglicherweise argumentieren, dass es nur ein paar Tastenanschläge dauert, um es zu verpacken, daher sollte es offensichtlich getan werden. Aber das ist nicht das ganze Argument:
Sie haben also die Wahl. Hat dies einen Vorteil? Denken Sie, dass es die Anwendung verbessert, mehr als den Aufwand für die Implementierung?
quelle