For-ifアンチパターン


9

for-ifアンチパターンについてこのブログ投稿を読んでいましたが、なぜそれがアンチパターンであるのか理解できません。

foreach (string filename in Directory.GetFiles("."))
{
    if (filename.Equals("desktop.ini", StringComparison.OrdinalIgnoreCase))
    {
        return new StreamReader(filename);
    }
}

質問1:

それが理由であるreturn new StreamReader(filename);内部for loop?または、forこの場合ループが必要ないという事実ですか?

ブログの作者が指摘したように、これのクレイジーでないバージョンは次のとおりです:

if (File.Exists("desktop.ini"))
{
    return new StreamReader("desktop.ini");
} 

の作成前にファイルが削除されるとStreamReader、を取得するため、どちらも競合状態になりますFile­Not­Found­Exception

質問2:

2番目の例を修正するには、ifステートメントなしで書き直し、代わりにをStreamReadertry-catchブロックで囲み、それがスローされるFile­Not­Found­Exception場合は、それにcatch応じてブロックで処理しますか?



1
@amon-ありがとうございます。ただし、追加のポイントはありません。記事の内容と修正方法を理解しようとしています。

3
私はそんなに考えません。ブログのポスターは、誰も書かないばかげたコードを作り、それに名前を付け、アンチパターンと呼んでいます。ここにもう1つあります。「私はこれを無意味な割り当てパターンと呼びます。x= x。これは常に行われているのがわかります。とても愚かです。それについてブログを書こうと思います。」
マーティンマート

4
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 File­Not­Found­Exception you handle it in the catch block accordingly?-はい、それはまさに私がすることです。競合状態を解決することは、「制御フローとしての例外」という概念よりも重要であり、エレガントかつクリーンに解決します。
ロバートハーベイ、

1
どちらのファイルも指定された基準を満たしていない場合はどうなりますか?戻りnullますか?通常、LINQを使用して、例のコードのようなコードをクリーンアップできます。2つのLINQ呼び出しの間の演算子にreturn Directory.GetFiles(".").FirstOrDefault(fileName => fileName.Equals("desktop.ini", StringComparison.OrdinalIgnoreCase))?.Select(fileName => new StreamReader(filename)); 注意してください?.。また、人々は、このようなオブジェクトの作成はLINQの最も適切な使用法ではないと主張するかもしれませんが、ここでは問題ないと思います。これはあなたの質問への答えではありませんが、その一部について余談です。
Panzercrisis

回答:


7

これは次の形式をとるため、アンチパターンです。

loop over a set of values
   if current value meets a condition
       do something with value
   end
end

と置き換えることができます

do something with value

この典型的な例は次のようなコードです:

for (var i=0; i < 5; i++)
{
    switch (i)
        case 1:
            doSomethingWith(1);
            break;
        case 2:
            doSomethingWith(2);
            break;
        case 3:
            doSomethingWith(4);
            break;
        case 4:
            doSomethingWith(4);
            break;
    }
}

以下がうまくいくとき:

doSomethingWith(1);
doSomethingWith(2);
doSomethingWith(3);
doSomethingWith(4);

自分がループとiforを実行していることに気付いた場合switchは、停止して、何をしているのかを考えてください。あなたは物事を複雑にしすぎていませんか?ループ全体とテストを単純な "ただやる"行で置き換えることができますか?ただし、そのループを実行する必要がある場合もあります(たとえば、複数のアイテムが1つの条件に一致する場合があります)。その場合、パターンは適切です。

それがアンチパターンである理由です:それは「ループとテスト」パターンを受け取り、それを悪用します。

2番目の質問について:はい。コードがテスト中のアイテムの状態を変更する可能性のあるデバイス全体の唯一のスレッドではない状況では、「try do」パターンは「test then do」パターンよりも堅牢です。

このコードの問題:

if (File.Exists("desktop.ini"))
{
    return new StreamReader("desktop.ini");
}

間の時間であるということFile.ExistsStreamReader、そのファイルを開こうとすると、別のスレッドまたはプロセスがファイルを削除することができます。したがって、例外が発生します。したがって、その例外は次のような方法で保護する必要があります。

try
{
    return new StreamReader("desktop.ini");
}
catch (File­Not­Found­Exception)
{
    return null; // or whatever
}

@Flaterは良い点を提起しますか?これ自体はアンチパターンですか?制御フローとして例外を使用していますか?

コードが次のようなものを読んだ場合:

try
{
    if (!File.Exists("desktop.ini")
    {
        throw new IniFileMissingException();
        return new StreamReader("desktop.ini");
    }
}
catch (IniFileMissingException)
{
    return null;
}

それから私は確かに例外を栄光のgotoとして使用していて、それは確かにアンチパターンです。ただし、この場合は、新しいストリームを作成するという望ましくない動作に対処しているだけなので、これはそのアンチパターンの例ではありません。しかし、それはそのアンチパターンを回避する例です。

もちろん、私たちが本当に望んでいるのは、ストリームを作成するよりエレガントな方法です。何かのようなもの:

return TryCreateStream("desktop.ini", out var stream) ? stream : null;

またtry catch、このコードを頻繁に使用している場合は、このようなユーティリティメソッドでコードをラップすることをお勧めします。


1
@Flater、私はそれが理想的ではないことに同意する傾向があります(私はそれがその回答が語るアンチパターンの例であることに異議を唱えますが)。コードは、メカニズムではなく、その意図を示す必要があります。私は、Scalaのようなもの好むと思いますのでTry、例えばreturn Try(() => new StreamReader("desktop.ini")).OrElse(null);、私たちは不格好バージョンで動作するように持っているので、C#は、(サードパーティのライブラリを使用せずに)その構文をサポートしていません。
デビッドアルノ

1
@Flater、私は例外が例外であるべきであることに完全に同意します。しかし、それらはめったにありません。たとえば、存在しないファイルはほとんど例外ではありませんFile.Openが、ファイルが見つからない場合は例外をスローします。例外的な例外がスローされているため、制御フローの一部としてトラップする必要があります(アプリをクラッシュさせたい場合を除きます)。
デビッドアルノ

1
@Flater:2番目のコードサンプルは、ファイルを開くための正しい方法です。それ以外のものは競合状態を引き起こします。ファイルの存在を確認したとしても、その確認から実際にファイルを開くまでの間に、何かがそのファイルを使用できなくする可能性があり、Open()呼び出しが爆発します。とにかく、例外の準備ができている必要があります。だから、あなたは同様にチェックを気にすることなく、それを試して開くだけでもよいでしょう。例外は私たちが物事を成し遂げるのを助けるツールであり、それらの使用は宗教的な教義と見なされるべきではありません。
whatsisname

1
@Flater:ファイル操作の試行/キャッチを回避する方法では、競合状態が発生します。File.Createを呼び出す前に、書き込み先のファイルシステムが使用できなくなり、チェックが無効になります。
whatsisname

1
@Flater「すべてのメソッド呼び出しには戻り値の型が必要であると効果的に主張しています...効果的に別の方法で例外を再発明しようとしています」正しい。その再発明は私のものではありませんが。関数型言語で何年も使用されています。彼らは一般に、ユニオンタイプを使用することで、「制御フローの問題としての例外」(混乱の元、あるパターンのアンチパターンであり、次に賛成するという主張)を回避します。たとえば、この場合、Maybe<Stream>タイプを使用します。これはnothing、ファイルが存在しない場合は戻り、存在する場合はストリームを返します。
デビッドアルノ

1

質問1:(このforループはアンチパターンですか?)

はい。クエリ可能なサブシステムに格納されているアイテムを独自に検索する必要がないためです。

要約すると、データベースと同様に、ファイルシステムはクエリに応答できます。クエリ可能なサブシステムとやり取りするとき、サブシステムの外部でマッチングを実行するためにそのようなサブシステムにコンテンツを列挙させるか、サブシステムのネイティブクエリ機能を使用するかについて、基本的な選択があります。

ファイルシステム内のディレクトリ内のファイルではなく、データベース内のレコードを検索しているとしましょう。むしろ見たいですか

SELECT * FROM SomeTable;

次に、返されたカーソルをループして(たとえばC#で)ID = 100を探しますか、それともクエリ可能なサブシステムに、探しているものを正確に見つけるためにできることを実行させますか?

SELECT * FROM SomeTable WHERE ID = 100;

私たちのほとんどは、サブシステムに目的の正確なクエリを実行させることを正しく選択するだろうと考えるべきです。代替案には、サブシステムとの潜在的な多数のラウンドトリップ、非効率的な等価性テスト、およびデータベースとファイルシステムの両方が提供するインデックスやその他の検索アクセラレータの使用を忘れることが含まれます。


質問2:2番目の例を修正するには、ifステートメントなしで書き直して、代わりにStreamReaderをtry-catchブロックで囲み、FileNotFoundExceptionをスローする場合は、それに応じてcatchブロックで処理しますか?

はい、これはその特定のAPIが機能する方法です。これはライブラリ関数であるため、実際には私たちの選択ではありません。呼び出し前のifチェックでは追加の値は提供されません。(1)FileNotFound以外の他のエラーが発生する可能性があるため、(2)競合状態であるため、とにかくtry / catchを使用する必要があります。


0

質問1:

これは、新しいStreamReader(filename)を返すためですか。forループ内?または、この場合はforループが必要ないという事実ですか?

streamreaderはそれとは何の関係もありません。との間の明確な意図の対立により、アンチパターンが出現foreachifます。

の目的は何foreachですか?

あなたの答えは次のようなものになると思います:「特定のコードを繰り返し実行したい」

処理する予定のファイルはいくつありますか?

特定のフォルダーには特定のファイル名(拡張子を含む)を1つだけ含めることができるため、これは、コードが1つの適用可能なファイルを見つけることを意図していることを証明します。

これは、すぐに値を返すという事実によっても確認されます。2番目の一致が存在していても、実際には気にしません。


これがアンチパターンではない状況があります。

  • サブディレクトリ(Directory.GetFiles(".", SearchOption.AllDirectories))も調べると、同じファイル名(拡張子を含む)を持つ複数のファイルを見つけることができます。
  • 部分的なファイル名の一致を探す場合(たとえば、名前がで始まる"Test_"すべての"*.zip"ファイル、またはすべてのファイル)。

これらのケースはどちらも実際に複数の一致を処理する必要があるため、すぐに値を返さないことに注意してください。


質問2:

2番目の例を修正するには、ifステートメントなしで書き直し、代わりにStreamReaderをtry-catchブロックで囲み、FileNotFoundExceptionをスローする場合は、それに応じてcatchブロックで処理しますか?

例外は高価です。適切な制御ロジックの代わりに使用することはできません。例外は、その名前が例外的な状況を示唆しいるためです。

そのため、を削除しないでくださいif

SoftwareEngineering.SEのこの回答に従って

一般に、制御フローの例外の使用はアンチパターンであり、顕著な状況固有および言語固有の咳例外の咳があります。

その理由の簡単な要約として、一般的に、それはアンチパターンです:

  • 例外は、本質的に、洗練されたGOTOステートメントです
  • したがって、例外を伴うプログラミングは、コードの読み取りと理解をさらに困難にします。
  • ほとんどの言語には、例外を使用せずに問題を解決するように設計された既存の制御構造があります
  • 効率性の引数は、例外が制御フローに使用されないという前提で最適化される傾向にある最近のコンパイラーにとっては重要ではありません。

より詳細な情報については、ウォードのwikiでの議論を読んでください。

これをtry / catchでラップする必要があるかどうかは、状況に大きく依存します。

  • 競合状態に遭遇する可能性はどのくらいありますか?
  • 実際にこの状況を処理できますか、または処理方法がわからないため、この問題をユーザーに吹き上げますか?

「常にそれを使用する」という問題はありません。私の要点を証明するには:

別の調査では、安全ヘルメット、安全ゴーグル、防弾チョッキを着用すると怪我をする可能性が低いことが証明されています。

では、なぜ私たち全員が常にこの安全装備を着用していないのでしょうか。

簡単な答えは、それらを身に着けることには欠点があるためです:

  • お金がかかる
  • それはあなたの動きをより面倒にします
  • 履くとかなり暖かくなります。

今、私たちはどこかに着いています:長所と短所があります。言い換えれば、プロが短所を上回る場合にのみ、この機器を着用することは理にかなっています。

  • 建設作業員は、仕事中に怪我をする可能性がはるかに高くなります。彼らは安全ヘルメットの恩恵を受けています。
  • 一方、オフィスワーカーは、けがをする可能性がはるかに低くなります。安全ヘルメットはそれだけの価値はありません。
  • SWATチームメンバーは、オフィスワーカーに比べて、撃たれる可能性がはるかに高くなります。

通話をtry / catchでラップする必要がありますか?それは、それを行うことの利点がそれを実装するコストを上回るかどうかに大きく依存します。

それをラップするのに数回のキーストロークしか必要としないと主張する人もいるので、それは明らかに行われるべきであることに注意してください。しかしそれだけではありません。

  • 例外をキャッチしたら、何をするかを決める必要があります。
  • コードベース全体にわたってさまざまなファイルへのさまざまな呼び出しが多数ある場合、1つをtry / catchでラップすることを決定すると、通常、これらすべてのケースをラップする必要があります。これは、実装に必要な労力に劇的な影響を与える可能性があります。
  • 意図的に例外を処理したくない場合は、完全に可能です。
    • アプリケーションはある時点で例外を処理する必要がありますが、例外が発生した直後であるとは限りません。

したがって、選択はあなた次第です。そうすることの利点はありますか?それは、アプリケーションを実装するための労力を費やすよりも、アプリケーションを改善すると思いますか?

更新 -私が書いたコメントから他の回答まで。これもあなたにとって重要な考慮事項だと思います。

それは周囲の状況に大きく依存します。

  • streamreaderを開く前にが付いてif(!File.Exists) File.Create()いる場合、streamreaderを開くときにファイルが存在しないことは確かに例外です。
  • ファイル名が既存のファイルのリストから選択された場合、その突然の不在は再び例外的です。
  • 実際にディレクトリに対してまだテストしていない文字列を使用している場合。ファイルが存在しないことは完全に論理的な結果であり、例外ではありませ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.