if(if else)elseを処理するエレガントな方法


161

これはささいなことではありませんが、このようなコードをコーディングする必要があるたびに、繰り返しが気になりますが、どのソリューションも悪くないかどうかはわかりません。

if(FileExists(file))
{
    contents = OpenFile(file); // <-- prevents inclusion in if
    if(SomeTest(contents))
    {
        DoSomething(contents);
    }
    else
    {
        DefaultAction();
    }
}
else
{
    DefaultAction();
}
  • この種のロジックには名前がありますか?
  • 私も少しOCDですか?

好奇心のためだけに、私は邪悪なコードの提案を受け入れています...


8
@Emmadカリーム:2つDefaultActionの呼び出しはDRY原則に違反する
Abyx

お返事ありがとうございますが、try / catchを使用しないことを除いて、結果を返さずに異常終了を引き起こす可能性のあるエラーがあるかもしれません(プログラミング言語によって異なります)。
NoChance

20
ここでの主な問題は、一貫性のない抽象化レベルで作業していることです。より高い抽象化レベルは次のとおりmake sure I have valid data for DoSomething(), and then DoSomething() with it. Otherwise, take DefaultAction()です。DoSomething()のデータを確実に取得するための重要な詳細は、抽象化レベルが低いため、別の関数にする必要があります。この関数は、より高い抽象化レベルで名前を持ち、その実装は低レベルになります。以下の良い回答は、この問題に対処しています。
ギラッドナー

6
言語を指定してください。考えられる解決策、標準イディオム、そして長年の文化的規範は、異なる言語のために異なっていて、あなたQ.に異なった答えにつながる
カレブ

1
この本「リファクタリング:既存のコードの設計の改善」を参照できます。if-else構造に関するいくつかのセクションがあります。
バッカー

回答:


96

それを抽出して関数(メソッド)を分離し、returnステートメントを使用します。

if(FileExists(file))
{
    contents = OpenFile(file); // <-- prevents inclusion in if
    if(SomeTest(contents))
    {
        DoSomething(contents);
        return;
    }
}

DefaultAction();

または、取得コンテンツとその処理を分離することをお勧めします。

contents_t get_contents(name_t file)
{
    if(!FileExists(file))
        return null;

    contents = OpenFile(file);
    if(!SomeTest(contents)) // like IsContentsValid
        return null;

    return contents;
}

...

contents = get_contents(file)
contents ? DoSomething(contents) : DefaultAction();

更新:

なぜ例外でOpenFileはなく、なぜIO例外をスローしないのか:
ファイルIOに関する質問ではなく、本当に一般的な質問だと思います。以下のような名前はFileExistsOpenFile混乱することができますが、それらを交換する場合FooBarなど、 -その明確になりますDefaultActionできるだけ頻繁に呼び出すことができDoSomething、それは非例外的なケースかもしれので、。PéterTörökは彼の答えの最後にこれについて書いた。

2番目のバリアントに3項条件演算子がある理由:
[C ++]タグがある場合、条件部分にのif宣言を含むステートメントを記述しましたcontents

if(contents_t contents = get_contents(file))
    DoSomething(contents);
else
    DefaultAction();

ただし、他の(Cライクな)言語の場合、if(contents) ...; else ...;三項条件演算子を使用した式ステートメントとまったく同じですが、より長くなります。コードの主な部分はget_contents関数であったため、私は短いバージョンを使用しました(また、contentsタイプを省略しました)。とにかく、それはこの疑問を超えています。


93
複数のリターンのための1 -方法がされたときに十分に小さく、このアプローチは私にとって最高の働いていた
ブヨ

私は時々それを使用しますが、複数のリターンの大ファンではありません。単純なものでは非常に合理的ですが、うまくスケールしません。メソッドは縮小するよりもサイズが大きくなる傾向があるため、私たちの標準は、クレイジーでシンプルなメソッド以外のすべてで回避することです。
ブライアンノブラウフ

3
複数のリターンパスは、C ++プログラムでパフォーマンスに悪影響を及ぼす可能性があり、RVO(各パスが同じオブジェクトを返さない限りNRVOも)を使用するオプティマイザーの努力を無効にします。
Functastic

2番目のソリューションのロジックを逆にすることをお勧めします。{if(file exists){set contents; if(sometest){コンテンツを返す; }} nullを返します。}フローを簡素化し、行数を減らします。
ウェッジ

1
こんにちはAbyx、ここにコメントからのフィードバックの一部を組み込んでいることに気付きました:それをしてくれてありがとう。私はあなたの答えと他の答えで対処されたすべてをクリーンアップしました。

56

使用しているプログラミング言語が(0)短絡バイナリ比較(つまり、falseを返すSomeTestifを呼び出さない場合FileExists)および(1)割り当てが値を返す場合(の結果OpenFileが割り当てられcontents、その値が引数として渡される場合) to SomeTest)、次のようなものを使用できますが、それでも、シングル=が意図的なものであることに注意してコードにコメントすることをお勧めします。

if( FileExists(file) && SomeTest(contents = OpenFile(file)) )
{
    DoSomething(contents);
}
else
{
    DefaultAction();
}

ifの複雑度に応じて、フラグ変数(DefaultActionこの場合はエラーを処理するコードで成功/失敗条件のテストを分離する)を使用した方がよい場合があります。


これは私がそれをする方法です。
アンソニー

13
if私の意見では、大量のコードを声明に入れるのはかなりつまらない。
moteutsch

15
それどころか、私はこの種の「何かが存在し、この条件を満たしている場合」の声明が好きです。+1
Gorpik

私も!個人的には、いくつかの前提が満たされていない複数のリターンを使用する方法が嫌いです。なぜそれらのifを逆にして、それら満たされたらコードを実行しませんか?
klaar

「何かが存在し、この条件を満たす場合」は問題ありません。「何かが存在し、ここで接線方向に関連する何かを行い、この条件を満たす場合」、OTOHは混乱しています。言い換えれば、私は条件の副作用を嫌います。
Piskvor

26

コードが非直交に記述されているため、DefaultActionの呼び出しの繰り返しよりも深刻なのはスタイルそのものです(直交に記述する適切な理由については、この回答を参照してください)。

非直交コードが悪い理由を示すために、ネットワークディスクに保存されているファイルを開かないという新しい要件が導入された場合の元の例を検討してください。それでは、コードを次のように更新するだけです。

if(FileExists(file))
{
    if(! OnNetworkDisk(file))
    {
        contents = OpenFile(file); // <-- prevents inclusion in if
        if(SomeTest(contents))
        {
            DoSomething(contents);
        }
        else
        {
            DefaultAction();
        }
    }
    else
    {
        DefaultAction();
    }
}
else
{
    DefaultAction();
}

ただし、2Gbを超える大きなファイルも開かないようにする必要があります。さて、再び更新するだけです。

if(FileExists(file))
{
    if(LessThan2Gb(file))
    {
        if(! OnNetworkDisk(file))
        {
            contents = OpenFile(file); // <-- prevents inclusion in if
            if(SomeTest(contents))
            {
                DoSomething(contents);
            }
            else
            {
                DefaultAction();
            }
        }
        else
        {
            DefaultAction();
        }
    else
    {
        DefaultAction();
    }
}
else
{
    DefaultAction();
}

このようなコードスタイルがメンテナンスの大きな負担になることは明らかです。

ここで適切に直交して書かれている答えの中には、Abyxの2番目の例Jan Hudecの答えがありますので、私はそれを繰り返しませんが、それらの答えに2つの要件を追加するだけで

if(! LessThan2Gb(file))
    return null;

if(OnNetworkDisk(file))
    return null;

(またはのgoto notexists;代わりにreturn null;)、追加され行以外のコードには影響しません。例えば、直交。

テスト時の一般的なルールは、通常のケースではなく、例外テストすることです。


8
私のために+1。アーリーヘッドアンチパターンを回避するには、早期返還が役立ちます。参照codinghorror.com/blog/2006/01/flattening-arrow-code.htmllostechies.com/chrismissal/2009/05/27/...このパターンについて読ん前に、私は常に機能ごとに1つの入口/出口に加入します私が15年ほど前に教えられたことによる理論。これにより、コードが非常に読みやすくなり、あなたが言及しているように、より保守しやすくなると思います。
ムース氏

3
@MrMoose:矢印のアンチパターンについてのあなたの言及は、Benjolの明示的な質問に答えます:「この種のロジックには名前がありますか?」回答として投稿してください。あなたは私の投票を獲得しました。
outis

これは素晴らしい答えです、ありがとう。そして、@ MrMoose:「arrowhead anti pattern」は私の最初の弾丸におそらく答えるので、はい、それを投稿してください。私はそれを受け入れると約束することはできませんが、票に値します!
ベンジョー

@outis。ありがとう。答えを追加しました。矢じりのアンチパターンは、確かにフロブダルの投稿に関連しており、彼のガード条項はそれらを回避するのにうまく機能します。これに2番目の箇条書きに答えることができる方法がわかりません。私はそれを診断する資格がありません:)
ムース氏

4
「通常の場合ではなく、例外をテストする」ための+1。
ロイティンカー

25

明らかに:

Whatever(Arguments)
{
    if(!FileExists(file))
        goto notexists;
    contents = OpenFile(file); // <-- prevents inclusion in if
    if(!SomeTest(contents))
        goto notexists;
    DoSomething(contents);
    return;
notexists:
    DefaultAction();
}

あなたは邪悪な解決策に対してもオープンであると言ったので、邪悪な後藤カウントを使用しますか?

実際、状況に応じて、このソリューションは、悪を2回実行する悪や、余分な変数を悪にするよりも悪さが少ないかもしれません。私はそれを関数でラップしました、なぜならそれは長い関数の真ん中では間違いなく大丈夫ではないからです(特に真ん中に戻るため)。しかし、長い機能は大丈夫ではありません、期間。

例外がある場合、特にOpenFileとDoSomethingが条件を満たさない場合に例外をスローすることができるので、例外は読みやすくなるため、明示的なチェックはまったく必要ありません。一方、C ++では、JavaとC#が例外をスローする操作は遅いため、パフォーマンスの点からはgotoの方が望ましいです。


「悪」に関する注意:C ++ FAQ 6.15では、「悪」を次のように定義しています。

これは、そのようなことを意味し、ほとんどの場合避けるべきですが、常に避けるべきではありません。たとえば、これらの「邪悪なもの」が「邪悪な選択肢のなかで最も邪悪なもの」ではない場合は、いつでも使用することになります。

そして、それはgotoこの文脈で当てはまります。ほとんどの場合、構造化されたフロー制御構造は優れていますが、条件の割り当て、約3レベル以上のネスト、コードの重複または長い条件など、自身の悪の蓄積が多すぎる状況に陥ると、goto単に終了する場合があります邪悪さを減らします。


11
私のカーソルは、すべての純粋主義者をいじめるためだけに、受け入れボタンの上にあります。Oooohh誘惑:D
ベンジョー

2
はいはい!これは、絶対に「正しい」コード記述方法です。コードの構造は、「エラーの場合、エラーを処理します。通常のアクションです。エラーの場合、エラーを処理します。通常のアクション」とまったく同じです。すべての「通常の」コードは単一レベルのインデントで記述されていますが、エラーに関連するすべてのコードには2レベルのインデントがあります。したがって、通常の最も重要なコードは最も目立つ視覚的な場所を取得し、非常に迅速かつ簡単にフローを下から順番に読み取ることができます。必ずこの答えを受け入れてください。
hlovdal

2
もう1つの側面は、この方法で記述されたコードが直交していることです。たとえば、2行「if(!FileExists(file))\ n \ tgoto notexists;」現在、この単一のエラーアスペクト(KISS)の処理にのみ関連しており、最も重要なことは、他の行には影響しません。この回答stackoverflow.com/a/3272062/23118には、コードの直交性を保ついくつかの正当な理由が記載されています。
hlovdal

5
邪悪なソリューションといえば:私は後藤なしであなたのソリューションを持つことができますfor(;;) { if(!FileExists(file)) break; contents = OpenFile(file); if(!SomeTest(contents)) break; DoSomething(contents); return; } /* broken out */ DefaultAction();

4
@herby:誰よりもgoto悪用さbreakれないように悪用しているため、あなたのソリューションは悪です。そのため、コードを読んでいる人は、明示的に言っているgotoを使用するよりも、ブレークがどこにつながるかを見るのに多くの問題があります。また、1回だけ実行される無限ループを使用しているため、かなり混乱します。残念ながらdo { ... } while(0)、正確に読み取ることはできません。なぜなら、最後に到達したときは単なる面白いブロックに過ぎず、Cは(perlとは異なり)他のブロックからの分割をサポートしていないからです。
ジャン・ヒューデック

12
function FileContentsExists(file) {
    return FileExists(file) ? OpenFile(file) : null;
}

...

contents = FileContentExists(file);
if(contents && SomeTest(contents))
{
    DoSomething(contents);
}
else
{
    DefaultAction();
}

または...余分な男性を行くと、追加FileExistsAndConditionMet(ファイル)メソッドを作成
UncleZeiv

@herby SomeTestは、SomeTestファイルの種類をチェックする場合、たとえば.gifが実際にGIFファイルであることをチェックする場合、ファイルの存在と同じセマンティクスを持つことができます。
アビックス

1
はい。依存します。@Benjolはよく知っています。
ハービー

3
...もちろん「余分なマイルに行く」ことを意味しました... :)
UncleZeiv

2
それも、私はに行っていない(と私は四肢にラビオリを取っています。この中で極端な)...私は今、それが考慮うまく読めると思いcontents && f(contents)。もう1つを保存する2つの関数?!
ハービー

12

1つの可能性:

boolean handled = false;

if(FileExists(file))
{
    contents = OpenFile(file); // <-- prevents inclusion in if
    if(SomeTest(contents))
    {
        DoSomething(contents);
        handled = true;
    }
}
if (!handled)
{
    DefaultAction();
}

もちろん、これにより、コードは別の方法でわずかに複雑になります。だから、それは主にスタイルの質問です。

別のアプローチでは、例外を使用します。例:

try
{
    contents = OpenFile(file); // throws IO exception if file not found
    DoSomething(contents); // calls SomeTest() and throws exception on failure
}
catch(Exception e)
{
    DefaultAction();
    // and the exception should be at least logged...
}

これは簡単に見えますが、次の場合にのみ適用できます

  • どのような例外が予想されるかを正確に把握し、DefaultAction()それぞれに適合します
  • ファイルの処理が成功することを期待しています。ファイルの欠落や障害SomeTest()は明らかに誤った状態であるため、例外をスローするのが適切です。

19
いや〜!フラグ変数ではありませんが、複雑で理解しにくく(where-that-flag-becomes-true)、リファクタリングが難しいため、間違いなく間違った方法です。
アビックス

可能な限りローカルに制限する場合ではありません。(function () { ... })()JavaScriptで、{ flag = false; ... }Cのようななどに

例外ロジックの+1。シナリオによっては、これが非常に適切なソリューションになる可能性があります。
スティーブンジュリス

4
+1この相互「Nooooo!」は面白い。特定のケースでは、ステータス変数と早期復帰の両方が合理的だと思います。より複雑なルーチンでは、複雑さを追加するのではなく、実際にロジックを明示的にするので、ステータス変数を使用します。それについて何も問題はありません。
grossvogel

1
これは私が仕事をする上で私たちが好むフォーマットです。2つの主な使用可能なオプションは、「複数のリターン」と「フラグ変数」です。どちらも平均的にはどのような種類の真の利点も持っていないようですが、どちらも特定の状況に他の状況より良く適合しています。あなたの典型的なケースで行かなければなりません。ちょうど別の「Emacs」対「Vi」宗教戦争。:-)
ブライアンノブラウフ

11

これは、より高いレベルの抽象化です。

if (WeCanDoSomething(file))
{
   DoSomething(contents);
}
else
{
   DefaultAction();
} 

そして、これは詳細を埋めます。

boolean WeCanDoSomething(file)
{
    if FileExists(file)
    {
        contents = OpenFile(file);
        return (SomeTest(contents));
    }
    else
    {
        return FALSE;
    }
}

11

関数は1つのことを行う必要があります。彼らはそれをうまくやるべきです。彼らはそれだけを行うべきです。
クリーンコードのロバートマーティン

一部の人々は、そのアプローチを少し極端に感じますが、それも非常にきれいです。Pythonで説明させてください:

def processFile(self):
    if self.fileMeetsTest():
        self.doSomething()
    else:
        self.defaultAction()

def fileMeetsTest(self):
    return os.path.exists(self.path) and self.contentsTest()

def contentsTest(self):
    with open(self.path) as file:
        line = file.readline()
        return self.firstLineTest(line)

彼は機能が一つのことをするべきだと言うとき、彼は一つのことを意味します。 processFile()テストの結果に基づいてアクションを選択し、それだけです。 fileMeetsTest()テストのすべての条件を組み合わせ、それがすべてです。 contentsTest()最初の行をに転送し、firstLineTest()それだけです。

多くの機能のように見えますが、実際にはまっすぐな英語のように見えます:

ファイルを処理するには、テストに適合するかどうかを確認します。もしそうなら、それから何かをしてください。それ以外の場合は、デフォルトのアクションを実行します。ファイルが存在する場合、ファイルはテストを満たし、コンテンツテストに合格します。内容をテストするには、ファイルを開いて最初の行をテストします。最初の行のテスト...

確かに、これは少し冗長ですが、メンテナーが詳細を気にしない場合は、の4行のコードだけで読み取りを停止できることに注意してくださいprocessFile()


5
+1それは良いアドバイスですが、「一つのこと」を構成するものは現在の抽象化層に依存します。processFile()は「1つのこと」ですが、2つのこと:fileMeetsTest()とdoSomething()またはdefaultAction()のいずれかです。「一つのこと」の側面が、先験的に概念を理解していない初心者を混乱させる可能性があることを恐れています。
カレブ

1
それは良い目標です...それについて私が言わなければならないのはそれだけです... ;
ブライアン・ノブラウフ

1
そのようなインスタンス変数として暗黙的に引数を渡すのは好きではありません。「役に立たない」インスタンス変数でいっぱいになり、状態を破壊して不変式を破る多くの方法があります。
hugomg

@ Caleb、ProcessFile()は確かに1つのことをしています。Karlは彼の投稿で述べているように、テストを使用して実行するアクションを決定し、アクションの可能性の実際の実装を他のメソッドに延期しています。さらに多くの代替アクションを追加する場合、即時メソッドでロジックのネストが発生しない限り、メソッドの単一目的の基準は引き続き満たされます。
S.ロビンス

6

これが何と呼ばれるかに関しては、コードがより多くの要件を処理するように成長するにつれて、簡単に矢じりアンチパターンに発展することができます(https://softwareengineering.stackexchange.com/a/122625/33922で提供される回答によって示されるように)次に、矢印に似たネストされた条件ステートメントを含むコードの巨大なセクションを持つというtrapに陥ります。

などのリンクを参照してください。

http://codinghorror.com/blog/2006/01/flattening-arrow-code.html

http://lostechies.com/chrismissal/2009/05/27/anti-patterns-and-worst-practices-the-arrowhead-anti-pattern/

Googleには、このパターンやその他のアンチパターンに関する情報がたくさんあります。

これに関してジェフが彼のブログで提供しているいくつかの素晴らしいヒントは次のとおりです。

1)条件をガード句に置き換えます。

2)条件ブロックを個別の関数に分解します。

3)ネガティブチェックをポジティブチェックに変換する

4)関数からできるだけ早く日和見的に常に戻る。

スティーブマッコネルの早期返還に関する提案についても、ジェフのブログのコメントをご覧ください。

「読みやすさを向上させるときにリターンを使用します。特定のルーチンでは、答えがわかったらすぐに呼び出し元のルーチンに戻したいと思います。エラーを検出し、すぐに返らないということは、さらにコードを書く必要があることを意味します。」

...

「各ルーチンの戻り値の数を最小限に抑える:下部を読んで、それがどこかで返される可能性に気付いていないときは、ルーチンを理解するのが難しくなります。そのため、読みやすさ。」

私は15年ほど前に教えられたことが原因で、関数ごとに1つのエントリ理論/出口理論に常にサブスクライブしました。これにより、コードが非常に読みやすくなり、あなたが言及しているように保守性が向上したと感じます


6

これはDRY、no-goto、no-multiple-returnsルールに準拠しており、私の意見ではスケーラブルで読みやすいです:

success = FileExists(file);
if (success)
{
    contents = OpenFile(file);
    success = SomeTest(contents);
}
if (success)
{
    DoSomething(contents);
}
else
{
    DefaultAction();
}

1
ただし、標準に準拠することは、必ずしも優れたコードに等しいとは限りません。現在、このコードの断片については未定です。
ブライアンノブラウフ

これは2つのdefaultAction()を置き換えるだけです。2つの同一のif条件を使用して、さらに悪いimoのフラグ変数を追加します。
リャサル

3
このような構造を使用する利点は、テストの回数が増えても、コードがififのs の中にさらにs をネストし始めないことです。また、失敗したケース(DefaultAction())を処理するコードは1箇所のみであり、デバッグの目的でコードはヘルパー関数を飛び回らず、success変数が変更された行にブレークポイントを追加することで、どのテストが成功したか(トリガーされたブレークポイント)およびテストされていないもの(下)。
frozenkoi

1
Yeeaah、私はそれを好きのようなものだが、私は名前を変更すると思うsuccessためにok_so_far:)
Benjol

これは、(1)すべてがうまくいくとプロセスが非常に線形になり、(2)そうでなければアンチパターンが矢印になる場合に私が行うことと非常に似ています。ただし、余分な変数を追加しないようにします。通常、次のステップの前提条件(前のステップが失敗したかどうかを尋ねる場合とは微妙に異なります)を考えると簡単です。ファイルが存在する場合は、ファイルを開きます。ファイルが開いている場合は、内容を読んでください。コンテンツがある場合は処理し、そうでない場合はデフォルトのアクションを実行します。
エイドリアンマッカーシー

3

私はそれを別のメソッドに抽出してから:

if(!FileExists(file))
{
    DefaultAction();
    return;
}

contents = OpenFile(file);
if(!SomeTest(contents))
{
    DefaultAction();
    return;
}

DoSomething(contents);

これも可能にします

if(!FileExists(file))
{
    DefaultAction();
    return Result.FileNotFound;
}

contents = OpenFile(file);
if(!SomeTest(contents))
{
    DefaultAction();
    return Result.TestFailed;
}

DoSomething(contents);
return Result.Success;            

次に、DefaultAction呼び出しを削除して、呼び出しDefaultAction元の実行をそのままにしておくことができます:

Result OurMethod(file)
{
    if(!FileExists(file))
    {
        return Result.FileNotFound;
    }

    contents = OpenFile(file);
    if(!SomeTest(contents))
    {
        return Result.TestFailed;
    }

    DoSomething(contents);
    return Result.Success;            
}

void Caller()
{
    // something, something...

    var result = OurMethod(file);
    // if (result == Result.FileNotFound || result == Result.TestFailed), or just
    if (result != Result.Success)        
    {
        DefaultAction();
    }
}

ジャンヌ・ピンダーのアプローチも好きです。


3

この特定のケースでは、答えは簡単です...

FileExistsとの間に競合状態がありOpenFileます:ファイルが削除されるとどうなりますか?

この特定のケースに対処する唯一の正しい方法は、スキップすることFileExistsです:

contents = OpenFile(file);
if (!contents) // open failed
    DefaultAction();
else (SomeTest(contents))
    DoSomething(contents);

これはきちんとこの問題を解決し、コードクリーナーになります。

一般的に: 問題を再考し、問題を完全に回避する別の解決策を考案してください。


2

あなたはあまりにも多くの他の人のを見て好きではない場合は別の可能性は、使用ドロップすることで、他を完全にし、余分なreturn文を投げます。 それ以外の場合は、2つ以上のアクションの可能性があるかどうかを判断するために、より複雑なロジックが必要でない限り、一種の余分なものです。

したがって、例は次のようになります。

void DoABunchOfStuff()
{
    if(FileExists(file))
    {
        DoSomethingWithFileContent(file);
        return;
    }

    DefaultAction();
}

void DoSomethingWithFileContent(file)
{        
    var contents = GetFileContents(file)

    if(SomeTest(contents))
    {
        DoSomething(contents);
        return;
    }

    DefaultAction();
}

AReturnType GetFileContents(file)
{
    return OpenFile(file);
}

個人的には、else節を使用して構いません。else節では、ロジックの動作方法が明示的に示されているため、コードの可読性が向上します。ただし、一部のコード美化ツールは、ネストロジックを防ぐために、単一のifステートメントに簡素化することを好みます。


2

サンプルコードに示されているケースは、通常、単一のifステートメントに縮小できます。多くのシステムでは、ファイルがまだ存在しない場合、ファイルを開く関数は無効な値を返します。これがデフォルトの動作である場合があります。それ以外の場合は、引数を介して指定する必要があります。これは、FileExistsテストをドロップできることを意味します。これは、存在テストとファイルを開く間のファイルの削除に起因する競合状態にも役立ちます。

file = OpenFile(path);
if(isValidFileHandle(file) && SomeTest(file)) {
    DoSomething(file);
} else {
    DefaultAction();
}

これは、複数のチェーン不可能なテストの問題を完全に回避するため、抽象レベルの混合の問題に直接対処しませんが、ファイル存在テストを廃止することは、抽象レベルの分離と互換性がありません。無効なファイルハンドルは「false」に相当し、ファイルハンドルが範囲外になったときに閉じると仮定します。

OpenFileIfSomething(path:String) : FileHandle {
    file = OpenFile(path);
    if (file && SomeTest(file)) {
        return file;
    }
    return null;
}

...

if ((file = OpenFileIfSomething(path))) {
    DoSomething(file);
} else {
    DefaultAction();
}

2

私はfrozenkoiに同意していますが、とにかくC#については、TryParseメソッドの構文に従うと役立つと思いました。

if(FileExists(file) && TryOpenFile(file, out contents))
    DoSomething(contents);
else
    DefaultAction();
bool TryOpenFile(object file, out object contents)
{
    try{
        contents = OpenFile(file);
    }
    catch{
        //something bad happened, computer probably exploded
        return false;
    }
    return true;
}

1

1つの関数であまりにも多くのことをしているため、コードがYourいです。ファイルを処理するか、デフォルトのアクションを実行するので、次のことを言って開始します。

if (!ProcessFile(file)) { 
  DefaultAction(); 
}

PerlおよびRubyプログラマーが書く processFile(file) || defaultAction()

ProcessFileに書き込みます。

if (FileExists(file)) { 
  contents = OpenFile(file);
  if (SomeTest(contents)) {
    processContents(contents);
    return true;
  }
}
return false;

1

もちろん、このようなシナリオでしかこれまでに行けませんが、ここに行く方法があります:

interface File<T> {
    function isOK():Bool;
    function getData():T;
}

var appleFile:File<Apple> = appleStorage.get(fileURI);
if (appleFile.isOK())
    eat(file.getData());
else
    cry();

追加のフィルターが必要な場合があります。次にこれを行います:

var appleFile = appleStorage.get(fileURI, isEdible);
//isEdible is of type Apple->Bool and will be used internally to answer to the isOK call
if (appleFile.isOK())
    eat(file.getData());
else
    cry();

これは同様に理にかなっているかもしれませんが:

function eat(apple:Apple) {
     if (isEdible(apple)) 
         digest(apple);
     else
         die();
}
var appleFile = appleStorage.get(fileURI);
if (appleFile.isOK())
    eat(appleFile.getData());
else
    cry();

どちらが一番ですか?それはあなたが直面している現実世界の問題に依存します。
しかし、取り去るべきことは、構成とポリモーフィズムで多くのことができるということです。


1

明らかな問題

if(!FileExists(file)) {
    DefaultAction();
    return;
}
contents = OpenFile(file);
if(!SomeTest(contents))
{
    DefaultAction();
    return;
}        
DoSomething(contents);

それは私にとってかなり標準的なようですか?多くのささいなことを起こさなければならないこの種の大きな手順では、いずれかが失敗すると後者が妨げられます。それがオプションである場合、例外はそれを少しきれいにします。


0

これは古い質問であることを認識していますが、言及されていないパターンに気付きました。主に、後で呼び出すメソッドを決定するために変数を設定します(if ... else ...以外)。

これは、コードを扱いやすくするために見るべきもう1つの角度です。また、呼び出される別のメソッドを追加したり、特定の状況で呼び出される必要がある適切なメソッドを変更したりすることもできます。

メソッドのすべての言及を置き換える必要はなく(場合によっては一部のシナリオが欠落する)、それらはすべてif ... else ...ブロックの最後にリストされ、読みやすく変更しやすいです。たとえば、いくつかのメソッドが呼び出される場合にこれを使用する傾向がありますが、ネストされたif ... else ...内では、いくつかの一致でメソッドが呼び出される場合があります。

状態を定義する変数を設定すると、多くの深くネストされたオプションを使用して、何かを実行する(または実行しない)ときに状態を更新できます。

これは、「DoSomething」が発生したかどうかを確認する質問で尋ねられた例のように使用でき、そうでない場合はデフォルトのアクションを実行します。または、呼び出したい各メソッドの状態を取得し、該当する場合に設定し、if ... else ...以外の適切なメソッドを呼び出します。

ネストされたif ... else ...ステートメントの最後で、状態をチェックし、それに応じて行動します。これは、適用する必要があるすべての場所ではなく、メソッドについて1回だけ言及する必要があることを意味します。

bool ActionDone = false;

if (Method_1(object_A)) // Test 1
{
    result_A = Method_2(object_A); // Result 1

    if (Method_3(result_A)) // Test 2
    {
        Method_4(result_A); // Action 1
        ActionDone = true;
    }
}

if (!ActionDone)
{
    Method_5(); // Default Action
}

0

ネストされたIFを減らすには:

1 /早期返却;

2 /複合式(短絡認識)

したがって、あなたの例は次のようにリファクタリングされます:

if( FileExists(file) && SomeTest(contents = OpenFile(file)) )
{
    DoSomething(contents);
    return;
}
DefaultAction();

0

私も使用する「リターン」の例を見ましたが、新しい関数の作成を避け、代わりにループを使用したい場合があります。

while (1) {
    if (FileExists(file)) {
        contents = OpenFile(file);
        if (SomeTest(contents)) {
           DoSomething(contents);
           break;
        } 
    }
    DefaultAction();
    break;
}

より少ない行を書きたい場合や、無限ループを嫌いな場合は、ループタイプを「do ... while(0)」に変更し、最後の「break」を回避できます。


0

このソリューションはどうですか:

content = NULL; //I presume OpenFile returns a pointer 
if(FileExists(file))
    contents = OpenFile(file);
if(content != NULL && SomeTest(contents))
    DoSomething(contents);
else
    DefaultAction();

OpenFileはポインターを返すと仮定しましたが、これは戻り値ではないデフォルト値(エラーコードなど)を指定することで、値型の戻り値でも機能します。

もちろん、NULLポインターに対するSomeTestメソッドを介した何らかのアクションは期待していません(しかし、あなたは決して知りません)。したがって、これはSomeTest(contents)呼び出しのNULLポインターの追加チェックとして見ることもできます。


0

明らかに、最もエレガントで簡潔なソリューションは、プリプロセッサマクロを使用することです。

#define DOUBLE_ELSE(CODE) else { CODE } } else { CODE }

これにより、次のような美しいコードを作成できます。

if(FileExists(file))
{
    contents = OpenFile(file);
    if(SomeTest(contents))
    {
        DoSomething(contents);
    }
    DOUBLE_ELSE(DefaultAction();)

この手法を頻繁に使用する場合、自動書式設定に依存するのは難しいかもしれません。また、一部のIDEは、誤った形式であると誤って想定していることについて少し怒鳴る場合があります。ことわざにもあるように、すべてはトレードオフですが、繰り返されるコードの悪を避けるために支払うのは悪い代償ではないと思います。


一部の人々および一部の言語では、プリプロセッサマクロ邪悪なコードです:)
Benjol

@ベンジョルあなたはあなたが悪の提案に寛容だと言った、いや?;)
ピーターオルソン

はい、絶対に、それはあなたの「悪を避ける」だけ
でした

4
これは私がちょうどそれをupvoteしなければならなかったので、恐ろしいです:D
back2dos

シャーリー、本気じゃない!!!!!!
ジムインテキサス州

-1

あなたは好奇心から尋ね、あなたの質問には特定の言語でタグ付けされていないので(命令型言語を念頭に置いていることは明らかですが)、遅延評価をサポートする言語は完全に異なるアプローチを可能にすることを追加する価値があるかもしれません。これらの言語では、式は必要な場合にのみ評価されるため、「変数」を定義して、意味がある場合にのみ使用できます。たとえば、遅延let/ in構造を持つ架空の言語では、フロー制御を忘れて次のように記述します。

let
  contents = ReadFile(file)
in
  if FileExists(file) && SomeTest(contents) 
    DoSomething(contents)
  else 
    DefaultAction()
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.