「if」チェーンを回避する方法は?


266

私がこの疑似コードを持っていると仮定します:

bool conditionA = executeStepA();
if (conditionA){
    bool conditionB = executeStepB();
    if (conditionB){
        bool conditionC = executeStepC();
        if (conditionC){
            ...
        }
    }
}

executeThisFunctionInAnyCase();

前の関数executeStepXが成功した場合にのみ、関数を実行する必要があります。いずれの場合も、executeThisFunctionInAnyCase関数は最後に呼び出す必要があります。私はプログラミングの初心者なので、非常に基本的な質問で申し訳ありません。ifコードの読みやすさを犠牲にして、そのような「コードのピラミッド」を生成する長いチェーンを回避する方法はありますか(C / C ++など)?

executeThisFunctionInAnyCase関数呼び出しをスキップできれば、コードは次のように簡略化できます。

bool conditionA = executeStepA();
if (!conditionA) return;
bool conditionB = executeStepB();
if (!conditionB) return;
bool conditionC = executeStepC();
if (!conditionC) return;

ただし、制約はexecuteThisFunctionInAnyCase関数呼び出しです。breakステートメントは何らかの方法で使用できますか?


254
@FrédéricHamidi間違って間違って!例外を使用してプログラムフローを実行することが良いとは決して言わないでください!例外は、非常に多くの理由により、この目的には明らかに適していません。
Piotr Zierhoffer 2014年

26
@Piotr、私はPythonに甘やかされました(これは実際にこれを促進します)。例外はC ++でのフロー制御に使用されるべきではないことを知っていますが、これは本当にフロー制御ですか?関数を返すfalseことは例外的な状況のようなものと見なすことができませんか?
フレデリックハミディ2014年

13
それはプログラムのセマンティクスに依存します。falseリターンはかなり正常であることができます。
dornhege 2014年

28
質問を最初のリビジョンにロールバックしました。特定の数の質問(> 0)を受け取った後、根本的に質問を変更しないでください。変更すると、その時点までに与えられたすべての回答が無効になり、混乱が生じます。代わりに新しい質問を開いてください。

13
すべての「初心者プログラマー」がこのようなデザインの質問をすることを望みます。
Jezen Thomas 14年

回答:


486

&&(論理AND)を使用できます。

if (executeStepA() && executeStepB() && executeStepC()){
    ...
}
executeThisFunctionInAnyCase();

これにより、両方の要件が満たされます。

  • executeStep<X>()前のテストが成功した場合にのみ評価する必要があります(これは短絡評価と呼ばれます)
  • executeThisFunctionInAnyCase() いずれにしても実行されます

29
これは、意味的に正しい(実際には、すべての条件が真であることを望んでいる)だけでなく、非常に優れたプログラミング手法(短絡評価)でもあります。さらに、これは、関数を作成するとコードがめちゃくちゃになる複雑な状況で使用できます。
Sanchises 2014年

58
@RobAu:その後、ジュニアプログラマーがショートカット評価に依存するコードを最終的に確認し、このトピックについて調査するよう促すことができます。これにより、最終的にシニアプログラマーになるのに役立ちます。明らかに、双方にとって有利なことです。まともなコードと、誰かがそれを読んで何かを学んだことです。
x4u 2014年

24
これが最高の答えになるはずです
表示名

61
@RobAu:これは、あいまいな構文のトリックを利用したハックではなく、ほぼすべてのプログラミング言語で非常に慣用的であり、議論の余地なく標準的な慣行となっています。
BlueRaja-Danny Pflughoeft 2014年

38
このソリューションは、条件が実際に単純な関数呼び出しである場合にのみ適用されます。実際のコードでは、これらの条件は2〜5行になる可能性があり(それ自体が他の多くの&&andの組み合わせになる||)、if読みやすさを損なうことなくそれらを単一のステートメントに結合する方法はありません。また、それらの条件を外部関数に移動するのは必ずしも簡単ではありません。それらが以前に計算されたローカル変数の多くに依存している可能性があるため、個々の引数として各条件を渡そうとすると、ひどい混乱を引き起こします。
hamstergene 14年

358

追加の関数を使用して、2番目のバージョンを機能させるだけです。

void foo()
{
  bool conditionA = executeStepA();
  if (!conditionA) return;

  bool conditionB = executeStepB();
  if (!conditionB) return;

  bool conditionC = executeStepC();
  if (!conditionC) return;
}

void bar()
{
  foo();
  executeThisFunctionInAnyCase();
}

深くネストされたifs(最初のバリアント)を使用するか、「関数の一部」から抜け出したいという願望は、通常、追加の関数が必要であることを意味します。


51
+1最終的に誰かが正解を投稿しました。これは、私の意見では、最も正確で安全で読みやすい方法です。
ランディン2014年

31
+1これは、「単一責任の原則」の良い例です。関数はfoo、一連の関連する条件とアクションを通じて機能します。機能barは決定から明確に分離されています。条件とアクションの詳細を見た場合、それfooがまだ多すぎることが判明するかもしれませんが、今のところ、これは良い解決策です。
GraniteRobert 2014年

13
欠点は、Cにはネストされた関数がないため、変数を使用するために必要な3つのステップがbar手動でfooパラメーターとして渡される必要があるということです。それが事実であり、foo一度しか呼び出されない場合、私はgotoバージョンを使用して、非常に再利用できなくなる2つの密結合関数を定義しないようにします。
hugomg 2014年

7
Cの短絡構文は不明ですが、C#ではfoo()は次のように記述できますif (!executeStepA() || !executeStepB() || !executeStepC()) return
Travis

6
@ user1598390 Gotoは、特に多くのセットアップコードを巻き戻す必要があるシステムプログラミングで、常に使用されます。
スコッティバウアー

166

gotoこの場合、旧式のCプログラマーが使用します。これは、gotoLinuxスタイルガイドで実際に推奨されている使用法の1つであり、集中関数出口と呼ばれます。

int foo() {
    int result = /*some error code*/;
    if(!executeStepA()) goto cleanup;
    if(!executeStepB()) goto cleanup;
    if(!executeStepC()) goto cleanup;

    result = 0;
cleanup:
    executeThisFunctionInAnyCase();
    return result;
}

一部の人々gotoは、ボディをループに包み、そこから抜け出すことによって使用を回避しますが、効果的に両方のアプローチが同じことを行います。成功したgoto場合executeStepA()にのみ他のクリーンアップが必要な場合は、このアプローチの方が適しています。

int foo() {
    int result = /*some error code*/;
    if(!executeStepA()) goto cleanupPart;
    if(!executeStepB()) goto cleanup;
    if(!executeStepC()) goto cleanup;

    result = 0;
cleanup:
    innerCleanup();
cleanupPart:
    executeThisFunctionInAnyCase();
    return result;
}

ループアプローチでは、その場合、2つのレベルのループが発生します。


108
+1。多くの人がを見てgotoすぐに「これはひどいコードだ」と思っていますが、有効な用途があります。
Graeme Perrow 2014年

46
メンテナンスの観点からは、これは実際にはかなり厄介なコードです。特に、ラベルが複数ある場合、コードも読みにくくなります。これを実現するより洗練された方法があります:関数を使用します。
ランディン2014年

29
-1わかりましたgoto。これがひどいコードであることを確認する必要はありません。私はかつてそのようなものを維持しなければならなかった、それは非常に厄介です。OPは質問の最後に妥当なC言語の代替案を提案し、私はそれを私の回答に含めました。
乾杯とhth。-アルフ

56
gotoのこの限られた自己完結型の使用には何の問題もありません。しかし、注意してください、goto ゲートウェイドラッグであり、注意しないと、誰かが足でスパゲッティを食べていないことにいつか気づくでしょう。それ以外の場合は悪夢のようなラベルが付いたバグ...」
Tim Post

66
このスタイルで、おそらく10万行の非常に明確で保守しやすいコードを記述しました。理解すべき2つの重要なことは、(1)規律です!すべての関数のレイアウトについて明確なガイドラインを確立し、非常に必要な場合にのみ違反します。(2)ここで行っていることは、例外を持たない言語で例外をシミュレートしていることを理解しますthrow多くの点で、最終ローカルコンテキストからさえ明確ではないgotoため、よりも悪いです。例外の場合と同じように、gotoスタイルの制御フローにも同じ設計上の考慮事項を使用します。throw
Eric Lippert、2014年

131

これは一般的な状況であり、それに対処する多くの一般的な方法があります。これが正解の私の試みです。何か見逃した場合はコメントしてください。この投稿は最新の状態に保ちます。

これは矢です

あなたが話していることは、矢印アンチパターンとして知られています。ネストされたifsのチェーンがコードブロックを形成し、コードペインが右側に「ポイント」する視覚的な矢印を形成するように、矢印ブロックと呼ばれます。

ガードで矢を平らにする

Arrowを回避するいくつかの一般的な方法をここで説明します。最も一般的な方法は、ガードパターンを使用することです。この場合、コードは最初に例外フローを処理し、次に基本フローを処理します。

if (ok)
{
    DoSomething();
}
else
{
    _log.Error("oops");
    return;
}

...使用します...

if (!ok)
{
    _log.Error("oops");
    return;
} 
DoSomething(); //notice how this is already farther to the left than the example above

一連のガードが長い場合、すべてのガードが左端まで表示され、ifがネストされていないため、コードがかなり平坦化されます。さらに、論理条件とそれに関連するエラーを視覚的に組み合わせているため、何が起こっているのかをはるかに簡単に判断できます。

矢印:

ok = DoSomething1();
if (ok)
{
    ok = DoSomething2();
    if (ok)
    {
        ok = DoSomething3();
        if (!ok)
        {
            _log.Error("oops");  //Tip of the Arrow
            return;
        }
    }
    else
    {
       _log.Error("oops");
       return;
    }
}
else
{
    _log.Error("oops");
    return;
}

ガード:

ok = DoSomething1();
if (!ok)
{
    _log.Error("oops");
    return;
} 
ok = DoSomething2();
if (!ok)
{
    _log.Error("oops");
    return;
} 
ok = DoSomething3();
if (!ok)
{
    _log.Error("oops");
    return;
} 
ok = DoSomething4();
if (!ok)
{
    _log.Error("oops");
    return;
} 

これは、客観的かつ定量的に読みやすいからです。

  1. 特定の論理ブロックの{および}文字は、互いに接近しています
  2. 特定の行を理解するために必要なメンタルコンテキストの量が少ない
  3. if条件に関連するロジック全体が1ページにある可能性が高い
  4. コーダーがページ/アイトラックをスクロールする必要性が大幅に軽減されます

最後に一般的なコードを追加する方法

ガードパターンの問題は、いわゆる「日和見的リターン」または「日和見的出口」に依存していることです。言い換えれば、すべての関数が厳密に1つの出口点を持つべきであるというパターンを破ります。これは2つの理由で問題です。

  1. たとえば、Pascalでのコーディングを学んだ人は、1つの関数= 1つの出口点であることを知っています。
  2. 対象となる内容関係なく、終了時に実行されるコードのセクションは提供されません

以下に、言語機能を使用するか、問題を完全に回避することにより、この制限を回避するためのいくつかのオプションを示します。

オプション1.これを行うことはできません。 finally

残念ながら、C ++開発者はこれを行うことはできません。しかし、これがまさにそれが目的であるので、これはfinallyキーワードを含む言語の一番の答えです。

try
{
    if (!ok)
    {
        _log.Error("oops");
        return;
    } 
    DoSomething(); //notice how this is already farther to the left than the example above
}
finally
{
    DoSomethingNoMatterWhat();
}

オプション2.問題を回避する:関数を再構成する

コードを2つの関数に分割することで問題を回避できます。このソリューションには、あらゆる言語で機能するという利点があり、さらに、サイクロマティックな複雑さを軽減できます。これは、欠陥率を低減する実証済みの方法であり、自動ユニットテストの特異性を向上させます。

次に例を示します。

void OuterFunction()
{
    DoSomethingIfPossible();
    DoSomethingNoMatterWhat();
}

void DoSomethingIfPossible()
{
    if (!ok)
    {
        _log.Error("Oops");
        return;
    }
    DoSomething();
}

オプション3.言語トリック:偽のループを使用する

他の答えに示されているように、私が目にするもう1つの一般的なトリックは、while(true)とbreakを使用することです。

while(true)
{
     if (!ok) break;
     DoSomething();
     break;  //important
}
DoSomethingNoMatterWhat();

これはを使用する場合よりも「正直」gotoではありませんが、ロジックスコープの境界を明確に示すため、リファクタリング時に混乱する可能性が低くなります。あなたのラベルまたはあなたのカットアンドペーストする素朴なコーダーgotoステートメントは、大きな問題を引き起こす可能性があります。(そして率直に言って、このパターンは非常に一般的であるようになりました。私はそれが意図を明確に伝えていると思うので、まったく「不誠実」ではありません)。

このオプションには他にもバリエーションがあります。たとえば、のswitch代わりにを使用できますwhilebreakキーワード付きの言語構成はおそらく機能するでしょう。

オプション4.オブジェクトのライフサイクルを活用する

他の1つのアプローチは、オブジェクトのライフサイクルを活用します。コンテキストオブジェクトを使用してパラメーター(私たちの素朴な例には疑わしいほど欠けているもの)を持ち歩き、完了したらそれを破棄します。

class MyContext
{
   ~MyContext()
   {
        DoSomethingNoMatterWhat();
   }
}

void MainMethod()
{
    MyContext myContext;
    ok = DoSomething(myContext);
    if (!ok)
    {
        _log.Error("Oops");
        return;
    }
    ok = DoSomethingElse(myContext);
    if (!ok)
    {
        _log.Error("Oops");
        return;
    }
    ok = DoSomethingMore(myContext);
    if (!ok)
    {
        _log.Error("Oops");
    }

    //DoSomethingNoMatterWhat will be called when myContext goes out of scope
}

注:選択した言語のオブジェクトのライフサイクルを必ず理解してください。これが機能するには、ある種の確定的なガベージコレクションが必要です。つまり、デストラクタがいつ呼び出されるかを知る必要があります。一部の言語では使用する必要がありますDisposeでは、デストラクタの代わり。

オプション4.1。オブジェクトのライフサイクルを活用する(ラッパーパターン)

オブジェクト指向のアプローチを使用する場合は、正しく実行することもできます。このオプションは、クラスを使用して、クリーンアップが必要なリソースとその他の操作を「ラップ」します。

class MyWrapper 
{
   bool DoSomething() {...};
   bool DoSomethingElse() {...}


   void ~MyWapper()
   {
        DoSomethingNoMatterWhat();
   }
}

void MainMethod()
{
    bool ok = myWrapper.DoSomething();
    if (!ok)
        _log.Error("Oops");
        return;
    }
    ok = myWrapper.DoSomethingElse();
    if (!ok)
       _log.Error("Oops");
        return;
    }
}
//DoSomethingNoMatterWhat will be called when myWrapper is destroyed

繰り返しますが、オブジェクトのライフサイクルを必ず理解してください。

オプション5.言語トリック:短絡評価を使用する

別の手法は、短絡評価を利用することです。

if (DoSomething1() && DoSomething2() && DoSomething3())
{
    DoSomething4();
}
DoSomethingNoMatterWhat();

このソリューションでは、&&演算子の機能を利用しています。&&の左側がfalseと評価された場合、右側は評価されません。

このトリックは、コンパクトなコードが必要な場合や、よく知られているアルゴリズムを実装している場合など、コードがあまりメンテナンスを必要としない場合に最も役立ちます。より一般的なコーディングでは、このコードの構造は非常に脆弱です。ロジックを少し変更しただけでも、完全な書き換えが発生する可能性があります。


12
最後に?C ++には、finally節はありません。デストラクタを持つオブジェクトは、finally句が必要だと思われる状況で使用されます。
Bill Door

1
上記の2つのコメントに対応するように編集されました。
John Wu

2
些細なコード(たとえば、私の例)では、ネストされたパターンがおそらくよりわかりやすくなります。実世界のコード(数ページにわたる可能性があります)では、スクロールとアイトラッキングの必要性が少なくなるため、ガードパターンは客観的に読みやすくなります。たとえば、{から}までの平均距離は短くなります。
John Wu

1
1920 x 1080の画面にコードが表示されないネストされたパターンを確認しました... 3番目のアクションが失敗した場合に実行されるエラー処理コードを見つけてください... do {...}を使用しました(真){...}「続ける」ことができますが、何度も繰り返し、すべてを開始する一方で、あなたが、一方で(最終休憩を必要としない代わりになる(0)。
gnasher729

2
オプション4は、C ++でのメモリリークです(マイナーな構文エラーは無視されます)。このような場合は「new」を使用せず、「MyContext myContext;」とだけ言います。
Sumudu Fernando 14

60

するだけ

if( executeStepA() && executeStepB() && executeStepC() )
{
    // ...
}
executeThisFunctionInAnyCase();

とても簡単です。


それぞれが根本的に質問を変更した3つの編集(バージョン1へのリビジョンを数えれば4つ)のため、私が回答しているコード例を含めます。

bool conditionA = executeStepA();
if (conditionA){
    bool conditionB = executeStepB();
    if (conditionB){
        bool conditionC = executeStepC();
        if (conditionC){
            ...
        }
    }
}

executeThisFunctionInAnyCase();

14
私は質問の最初のバージョンに応じて(より簡潔に)これに回答しました。OrbitのDarkness Racesによる解説と編集の後、Bill the Lizardによって削除される前に20票の賛成票を得ました。
乾杯とhth。-アルフ

1
@ Cheersandhth-Alf:mod-deleteされたなんて信じられません。それはあまりにも悪いです。(+1)
常磁性クロワッサン2014年

2
あなたの勇気への私の+1!(3回は重要です:D)。
2014年

初心者プログラマーが複数のブール値の「ands」を使用した注文の実行、異なる言語での違いなどについて学ぶことは完全に重要です。そして、この答えはそれへの素晴らしい指針です。しかし、これは通常の商用プログラミングにおける「スターターではない」にすぎません。単純ではなく、保守もできず、変更もできません。もちろん、自分用の簡単な「カウボーイコード」を書いているだけなら、これを行ってください。しかし、「今日行われているような日常のソフトウェアエンジニアリングとはまったく関係がない」というだけのことです。ところで、ここで我慢しなければならなかったとんでもない「編集の混乱」のために申し訳ありません、@ cheers :)
Fattie

1
@JoeBlow:私はこれに関してAlfと一緒にいます- &&リストはより明確にわかります。私は通常、条件を個別の行に分割// explanationし、各行に末尾を追加します...最終的には、調べるコードが大幅に少なくなり、&&動作を理解すれば、継続的な精神的努力はありません。私の印象は、ほとんどのプロのC ++プログラマーがこれに精通していると思いますが、さまざまな業界/プロジェクトで言うように、焦点と経験は異なります。
Tony Delroy、2014

35

実際には、C ++でアクションを延期する方法があります。オブジェクトのデストラクタを利用することです。

C ++ 11にアクセスできると仮定します。

class Defer {
public:
    Defer(std::function<void()> f): f_(std::move(f)) {}
    ~Defer() { if (f_) { f_(); } }

    void cancel() { f_ = std::function<void()>(); }

private:
    Defer(Defer const&) = delete;
    Defer& operator=(Defer const&) = delete;

    std::function<void()> f_;
}; // class Defer

そして、そのユーティリティを使用します:

int foo() {
    Defer const defer{&executeThisFunctionInAnyCase}; // or a lambda

    // ...

    if (!executeA()) { return 1; }

    // ...

    if (!executeB()) { return 2; }

    // ...

    if (!executeC()) { return 3; }

    // ...

    return 4;
} // foo

67
これは私にとって完全な難読化です。なぜ多くのC ++プログラマーが、できるだけ多くの言語機能を可能な限り投げることで問題を解決したいのか、本当に理解できません。現在、これらのエキゾチックな言語機能をすべて使用することに非常に関心があります。そこから、何日も何週間もの間、メタコードを作成し、メタコードを保守して、メタコードを処理するためのメタコードを作成して、忙しくすることができます。
ランディン2014年

24
@Lundin:まあ、私は、早期の継続/中断/復帰が導入されるか例外がスローされるとすぐに壊れる脆弱なコードに満足できる方法を理解していません。一方、このソリューションは将来のメンテナンスに備えて回復力があり、C ++の最も重要な機能の1つである巻き戻し中にデストラクタが実行されるという事実にのみ依存しています。控えめに言っても、すべての標準コンテナに力を与えるのは根本的な原則ですが、これをエキゾチックなものと認定します。
Matthieu M.2014

7
@Lundin:Matthieuのコードの利点は、例外がスローされてexecuteThisFunctionInAnyCase();も実行されることですfoo();。例外セーフなコードを作成する場合、そのようなクリーンアップ関数をすべてデストラクタに配置することをお勧めします。
ブライアン

6
@Brianその後、例外をスローしないでくださいfoo()。そして、あなたがそうするなら、それを捕まえてください。問題が解決しました。回避策を書くのではなく、バグを修正して修正してください。
ランディン2014年

18
@Lundin:Deferクラスは再利用可能な小さなコード片であり、ブロックの終わりのクリーンアップを例外安全な方法で実行できます。より一般的にはスコープガードとして知られています。はい、スコープガードの使用は、他のより手動の方法でfor表現できwhileます。これは、ループをブロックおよびループとして表現できるのと同様に、必要に応じて、アセンブリ言語で表現できるifand gotoで表現できます。または真のマスターである人のために、特別な短いうなり声とチャントのバタフライ効果によって導かれる宇宙線を介してメモリ内のビットを変更することによって。しかし、なぜそうするのか。
乾杯とhth。-アルフ

34

returnステートメント(Itjaxで規定されているメソッド)で追加のラッパー関数を必要としない素晴らしいテクニックがあります。それはdo while(0)疑似ループを利用します。while (0)それは実際にループではなく、一度だけ実行することを保証します。ただし、ループ構文では、breakステートメントを使用できます。

void foo()
{
  // ...
  do {
      if (!executeStepA())
          break;
      if (!executeStepB())
          break;
      if (!executeStepC())
          break;
  }
  while (0);
  // ...
}

11
複数のリターンを持つ関数を使用することは、私の意見では似ていますが、より読みやすくなっています。
ランディン2014年

4
はい、間違いなくより読みやすくなっています...しかし、効率の観点から、追加の関数呼び出し(パラメーターの受け渡しと戻り)のオーバーヘッドは、do {} while(0)構文を使用して回避できます。
Debasis 2014年

5
あなたはまだ関数を作るのは自由inlineです。とにかく、これはこの問題以外にも役立つため、知っておくと便利なテクニックです。
ルスラン2014年

2
@Lundinコードの局所性を考慮に入れる必要があります。コードをあまりにも多くの場所に分散すると、問題も発生します。
API-Beast

3
私の経験では、これは非常に珍しいイディオムです。一体何が起こっているのかを理解するには少し時間がかかりますが、コードをレビューしているとき、それは悪い兆候です。他の、より一般的で読みやすいアプローチに比べて利点がないように見えるので、私はそれを承認することができませんでした。
コーディグレイ

19

これを行うこともできます:

bool isOk = true;
std::vector<bool (*)(void)> funcs; //vector of function ptr

funcs.push_back(&executeStepA);
funcs.push_back(&executeStepB);
funcs.push_back(&executeStepC);
//...

//this will stop at the first false return
for (auto it = funcs.begin(); it != funcs.end() && isOk; ++it) 
    isOk = (*it)();
if (isOk)
 //doSomeStuff
executeThisFunctionInAnyCase();

このようにして、最小の線形拡張サイズ、1コールあたり+1行で、簡単にメンテナンスできます。


編集:(@Undaに感謝)IMOが見えなくなるため、大ファンではありません:

bool isOk = true;
auto funcs { //using c++11 initializer_list
    &executeStepA,
    &executeStepB,
    &executeStepC
};

for (auto it = funcs.begin(); it != funcs.end() && isOk; ++it) 
    isOk = (*it)();
if (isOk)
 //doSomeStuff
executeThisFunctionInAnyCase();

1
それはpush_back()内の偶発的な関数呼び出しについてでしたが、とにかくそれを修正しました:)
Quentin

4
これが反対投票になった理由を知りたいです。実行ステップが実際には対称的であると想定すると、それらはクリーンで保守可能です。
ClickRick、2014年

1
これは間違いなくきれいに見えるかもしれませんが。人々にとっては理解するのが難しいかもしれませんし、コンパイラーにとっても理解するのは間違いなく難しいです!
ロイT.

1
多くの場合、データを関数として扱うことは良い考えです。一度行うと、その後のリファクタリングにも気付くでしょう。さらに優れているのは、処理する各部分が単なる関数参照ではなくオブジェクト参照である場合です。これにより、コードをさらに改善することができます。
ビルK

3
些細な場合のために少し過剰に設計されていますが、この手法には他の人にはない優れた機能があります:実行時に呼び出される関数の[実行数]と[数]を変更できます。これはすばらしいことです。)
Xocoatzin

18

これはうまくいくでしょうか?これはあなたのコードと同等だと思います。

bool condition = true; // using only one boolean variable
if (condition) condition = executeStepA();
if (condition) condition = executeStepB();
if (condition) condition = executeStepC();
...
executeThisFunctionInAnyCase();

3
私は通常、okこのように同じ変数を使用するときに変数を呼び出します。
マッケ14年

1
私はなぜ反対票を投じるのかを知りたいと思っています。ここで何が問題になっていますか?
ABCplus 2014

1
循環的複雑度について、urの回答と短絡アプローチを比較してください。
AlphaGoku

14

希望のコードが私が現在見ているものであると仮定します:

bool conditionA = executeStepA();
if (conditionA){
    bool conditionB = executeStepB();
    if (conditionB){
        bool conditionC = executeStepC();
        if (conditionC){
            ...
        }
    }
}    
executeThisFunctionInAnyCase();

正しいアプローチは、読むのが最も簡単で、保守が最も簡単であるので、インデントのレベルが少なくなります。これが(現在のところ)質問の目的です。

// Pre-declare the variables for the conditions
bool conditionA = false;
bool conditionB = false;
bool conditionC = false;

// Execute each step only if the pre-conditions are met
conditionA = executeStepA();
if (conditionA)
    conditionB = executeStepB();
if (conditionB)
    conditionC = executeStepC();
if (conditionC) {
    ...
}

// Unconditionally execute the 'cleanup' part.
executeThisFunctionInAnyCase();

これは避けどんなの必要性gotoの、例外、ダミーwhileのループ、またはその他の困難な構造をして単に手で簡単な仕事を持つになります。


ループを使用する場合returnbreak追加の「フラグ」変数を導入する必要なしに、ループを使用してジャンプすることが通常は許容されます。この場合、gotoを使用しても同様に無害です。追加のgotoの複雑さを、可変の変数の複雑さと交換していることに注意してください。
hugomg 14年

2
@hugomg変数は元の質問にありました。ここでは余分な複雑さはありません。質問に関して仮定(たとえば、編集されたコードでは変数が必要であるなど)が行われたため、保持されました。それらが不要な場合はコードを簡略化できますが、質問の不完全な性質を考えると、有効にできる他の仮定はありません。
ClickRick

非常に便利なアプローチ、特に。自称の使用のためにnewbie、それは欠点のないよりクリーンなソリューションを提供します。またsteps、同じシグネチャを持つことや、ブロックではなく関数であることにも依存しないことに注意してください。これは、より洗練されたアプローチが有効な場合でも、最初のパスのリファクタリングとして使用されていることがわかります。
キース

12

breakステートメントは何らかの方法で使用できますか?

最善の解決策ではないかもしれませんが、ステートメントをdo .. while (0)ループに入れて、のbreak代わりにステートメント を使用できますreturn


2
それを否定したのは私ではありませんでしたが、これは、現在求められている効果であるものの、必然的に苦痛をもたらすようなものに対するループ構造の乱用です。たぶん、次の開発者が別のプロジェクトに移ってから2年後にそれを維持しなければならないでしょう。
ClickRick、2014年

3
do .. while (0)マクロ定義に@ClickRickを使用してもループが悪用されますが、問題はありません。
ouah 2014年

1
おそらく、しかしそれを達成するためのより明確な方法があります。
ClickRick

4
@ClickRick私の回答の唯一の目的は回答することです。break ステートメントは何らかの方法で使用でき、回答ははいです。私の回答の最初の単語は、これが使用するソリューションではない可能性があることを示唆しています。
ouah 2014年

2
この回答はコメントにする必要があります
msmucker0527 '27

12

すべてのif条件を独自の関数に好きなようにフォーマットして置くと、戻り時にexecuteThisFunctionInAnyCase()関数が実行されます。

OPの基本的な例から、条件のテストと実行を分割することができます。

void InitialSteps()
{
  bool conditionA = executeStepA();
  if (!conditionA)
    return;
  bool conditionB = executeStepB();
  if (!conditionB)
    return;
  bool conditionC = executeStepC();
  if (!conditionC)
    return;
}

そして、そのように呼ばれました。

InitialSteps();
executeThisFunctionInAnyCase();

C ++ 11ラムダが利用可能な場合(OPにC ++ 11タグはありませんでしたが、それらはオプションである可能性があります)、別の関数を省略して、これをラムダにラップできます。

// Capture by reference (variable access may be required)
auto initialSteps = [&]() {
  // any additional code
  bool conditionA = executeStepA();
  if (!conditionA)
    return;
  // any additional code
  bool conditionB = executeStepB();
  if (!conditionB)
    return;
  // any additional code
  bool conditionC = executeStepC();
  if (!conditionC)
    return;
};

initialSteps();
executeThisFunctionInAnyCase();

10

ループが嫌いgotoで嫌いでdo { } while (0);C ++を使いたい場合は、一時的なラムダを使用して同じ効果を得ることができます。

[&]() { // create a capture all lambda
  if (!executeStepA()) { return; }
  if (!executeStepB()) { return; }
  if (!executeStepC()) { return; }
}(); // and immediately call it

executeThisFunctionInAnyCase();

1
if あなたはgoto && が嫌いdo {} while(0) && はC ++が好き ...申し訳ありませんが、抵抗できませんでしたが、質問にcc ++の
ClickRick

@ClickRickみんなを満足させるのはいつも難しい。で、私の意見C / C ++、あなたがそれらのいずれかと他の利用で通常のコードが眉をひそめているようなものはありません。
Alex

9

コード内のIF / ELSEのチェーンは言語の問題ではなく、プログラムの設計です。プログラムをリファクタリングまたは書き直すことができる場合は、デザインパターン(http://sourcemaking.com/design_patterns)を調べてより良い解決策を見つけることをお勧めします。

通常、コードに多くのIFとその他のものがある場合、それは戦略デザインパターン(http://sourcemaking.com/design_patterns/strategy/c-sharp-dot-net)またはおそらく組み合わせを実装する機会です他のパターンの。

if / elseの長いリストを書く代替案があると私は確信していますが、チェーンがあなたにきれいに見えることを除いて、それらが何かを変えるとは思えません(ただし、美しさはまだコードに適用されますあまりにも:-))。あなたは次のようなことを心配する必要があります(新しい状態があり、このコードについて何も覚えていない6か月以内に、私は簡単にコードを追加できますか?または、チェーンが変更された場合、どのくらい迅速かつエラーフリーか?実装しますか)


9

あなたはこれをします

coverConditions();
executeThisFunctionInAnyCase();

function coverConditions()
 {
 bool conditionA = executeStepA();
 if (!conditionA) return;
 bool conditionB = executeStepB();
 if (!conditionB) return;
 bool conditionC = executeStepC();
 if (!conditionC) return;
 }

100の99回です。これが唯一の方法です。

コンピュータコードで「トリッキー」なことをしようとすることは決してありません。


ちなみに、あなたが考えてい実際の解決策は次のとおりです...

継続ステートメントはアルゴリズムのプログラミングに重要です。(アルゴリズムプログラミングでは、gotoステートメントが重要です。)

多くのプログラミング言語でこれを行うことができます:

-(void)_testKode
    {
    NSLog(@"code a");
    NSLog(@"code b");
    NSLog(@"code c\n");
    
    int x = 69;
    
    {
    
    if ( x == 13 )
        {
        NSLog(@"code d---\n");
        continue;
        }
    
    if ( x == 69 )
        {
        NSLog(@"code e---\n");
        continue;
        }
    
    if ( x == 13 )
        {
        NSLog(@"code f---\n");
        continue;
        }
    
    }
    
    NSLog(@"code g");
    }

(まず最初に注意:その例のようなネイキッドブロックは、特に「アルゴリズム」プログラミングを扱っている場合は、美しいコードを書く上で重要かつ重要な部分です。)

もう一度、それは まさにあなたの頭の中にあったものですよね?そしてそれはそれを書くための美しい方法なので、あなたは良い本能を持っています。

ただし、悲劇的なことに、objective-cの現在のバージョンでは(さておき、Swiftについてはわかりません)には、囲んでいるブロックがループであるかどうかをチェックするリスクのある機能があります。

ここに画像の説明を入力してください

これを回避する方法は次のとおりです...

-(void)_testKode
    {
    NSLog(@"code a");
    NSLog(@"code b");
    NSLog(@"code c\n");
    
    int x = 69;
    
    do{
    
    if ( x == 13 )
        {
        NSLog(@"code d---\n");
        continue;
        }
    
    if ( x == 69 )
        {
        NSLog(@"code e---\n");
        continue;
        }
    
    if ( x == 13 )
        {
        NSLog(@"code f---\n");
        continue;
        }
    
    }while(false);
    
    NSLog(@"code g");
    }

ですので、忘れないでください。

{} while(false);を実行します。

「このブロックを1回実行する」という意味です。

つまり、書くことdo{}while(false);と単に書くことの間に全く違いはありません{}

これは今あなたが望むように完全に機能します...これが出力です...

ここに画像の説明を入力してください

だから、それはあなたが頭の中でアルゴリズムを見る方法である可能性があります。あなたはいつも頭の中にあるものを書くように努めるべきです。(特にあなたが冷静でない場合、それはかなりが出てくるときです!:))

これが頻繁に発生する「アルゴリズム」プロジェクトでは、objective-cには常に次のようなマクロがあります...

#define RUNONCE while(false)

...だから、これを行うことができます...

-(void)_testKode
    {
    NSLog(@"code a");
    int x = 69;
    
    do{
    if ( x == 13 )
        {
        NSLog(@"code d---\n");
        continue;
        }
    if ( x == 69 )
        {
        NSLog(@"code e---\n");
        continue;
        }
    if ( x == 13 )
        {
        NSLog(@"code f---\n");
        continue;
        }
    }RUNONCE
    
    NSLog(@"code g");
    }

2つのポイントがあります。

a、objective-cがcontinueステートメントが含まれているブロックのタイプをチェックするのは愚かですが、「それと戦う」のは面倒です。だから、それは難しい決断です。

b、例では、そのブロックをインデントする必要があるという質問がありますか?私はそのような質問で眠りを失うので、アドバイスはできません。

それが役に立てば幸い。



より多くの担当者を獲得するために賞金を投入しましたか?:)
TMS

これらすべてのコメントをに配置する代わりに、ifよりわかりやすい関数名を使用して、コメントを関数に配置することもできます。
Thomas Ahle 14

ああ。私は、この混乱の中で、短絡評価(20年以上言語で使用されており、よく知られている)を簡潔に読み取る1行のソリューションをいつでも取り上げます。私たちはお互いに協力し合えなくて幸せだと私たちは同意できると思います。
M2tM 2014

8

実行関数が失敗した場合、falseを返す代わりに例外をスローします。次に、呼び出しコードは次のようになります。

try {
    executeStepA();
    executeStepB();
    executeStepC();
}
catch (...)

もちろん、元の例では、ステップ内でエラーが発生した場合にのみ実行ステップがfalseを返すと想定していますか?


3
フローを制御するために例外を使用することは、多くの場合、悪い習慣と臭いコードと見なされます
user902383 '30

8

すでに多くの良い答えがありますが、それらのほとんどは柔軟性のいくつか(確かにごくわずか)をトレードオフしているようです。このトレードオフを必要としない一般的なアプローチは、ステータス/キープゴーイング変数を追加することです。もちろん、価格は追跡するための1つの追加の値です。

bool ok = true;
bool conditionA = executeStepA();
// ... possibly edit conditionA, or just ok &= executeStepA();
ok &= conditionA;

if (ok) {
    bool conditionB = executeStepB();
    // ... possibly do more stuff
    ok &= conditionB;
}
if (ok) {
    bool conditionC = executeStepC();
    ok &= conditionC;
}
if (ok && additionalCondition) {
    // ...
}

executeThisFunctionInAnyCase();
// can now also:
return ok;

なぜok &= conditionX;そして単にではないのですok = conditionX;か?
ABCplus 2014年

@ user3253359多くの場合、そうです。これは概念的なデモです。作業コードでは、できるだけ単純化しようとしています
blgt

+1 質問で規定されている、cで機能する数少ないクリーンで保守可能な回答の1つ。
ClickRick 14

6

C ++(質問にはCとC ++の両方のタグが付けられています)で、例外を使用するように関数を変更できない場合でも、次のような小さなヘルパー関数を作成すると、例外メカニズムを使用できます。

struct function_failed {};
void attempt(bool retval)
{
  if (!retval)
    throw function_failed(); // or a more specific exception class
}

次に、コードは次のように読み取ることができます。

try
{
  attempt(executeStepA());
  attempt(executeStepB());
  attempt(executeStepC());
}
catch (function_failed)
{
  // -- this block intentionally left empty --
}

executeThisFunctionInAnyCase();

派手な構文に興味がある場合は、代わりに明示的なキャストで機能させることができます。

struct function_failed {};
struct attempt
{
  attempt(bool retval)
  {
    if (!retval)
      throw function_failed();
  }
};

次に、コードを次のように書くことができます

try
{
  (attempt) executeStepA();
  (attempt) executeStepB();
  (attempt) executeStepC();
}
catch (function_failed)
{
  // -- this block intentionally left empty --
}

executeThisFunctionInAnyCase();

値チェックを例外にリファクタリングすることは必ずしも良い方法ではありません。例外を巻き戻すのにかなりのオーバーヘッドがあります。
John Wu

4
-1このようなC ++での通常のフローに例外を使用することは、プログラミングの習慣としては不十分です。C ++では、例外は例外的な状況のために予約する必要があります。
Jack Aidley 14年

1
質問テキストから(私が強調):「関数executeStepXは、前の関数が成功した場合にのみ実行する必要があります」つまり、戻り値は失敗を示すために使用されます。つまり、これはエラー処理です(障害例外的であることを期待しています)。エラー処理はまさに例外が発明されたものです。
celtschk 2014年

1
いいえ。まず、例外は、エラー処理ではなくエラー伝播を可能にするために作成されました。次に、「関数executeStepXは、前の関数が成功した場合にのみ実行する必要があります。」前の関数によって返されたブール値のfalseが明らかに例外的/エラーのケースを示すという意味ではありません。したがって、あなたの発言は不平等ではありません。エラー処理や消毒を流れは、多くの異なる手段で実装することができ、例外が可能なツールですエラー伝播アウトオブプレースことで、エラー処理、およびExcelを。

6

コードが例のように単純で、言語が短絡評価をサポートしている場合、これを試すことができます。

StepA() && StepB() && StepC() && StepD();
DoAlways();

関数に引数を渡して他の結果を取得し、以前の方法でコードを記述できない場合は、他の多くの答えが問題に適しています。


実際、トピックをよりよく説明するために質問を編集しましたが、ほとんどの回答が無効にならないように拒否されました。:\
ABCplus 2014

私はSOの新しいユーザーであり、初心者プログラマーです。次に2つの質問:この質問のために、あなたが言ったような別の質問が重複としてマークされるリスクはありますか?もう1つのポイントは、初心者のSOユーザー/プログラマーは、そこにあるすべての中から最良の答えをどのように選択できるでしょうか(ほとんどの場合、私は推測します)。
ABCplus 2014

6

C ++ 11以降では、Dのscope(exit)メカニズムと同様のスコープ出口システムを実装するのが良い方法です。

これを実装する1つの可能な方法は、C ++ 11ラムダといくつかのヘルパーマクロを使用することです。

template<typename F> struct ScopeExit 
{
    ScopeExit(F f) : fn(f) { }
    ~ScopeExit() 
    { 
         fn();
    }

    F fn;
};

template<typename F> ScopeExit<F> MakeScopeExit(F f) { return ScopeExit<F>(f); };

#define STR_APPEND2_HELPER(x, y) x##y
#define STR_APPEND2(x, y) STR_APPEND2_HELPER(x, y)

#define SCOPE_EXIT(code)\
    auto STR_APPEND2(scope_exit_, __LINE__) = MakeScopeExit([&](){ code })

これにより、関数から早期に戻り、定義したクリーンアップコードがスコープの終了時に常に実行されるようにすることができます。

SCOPE_EXIT(
    delete pointerA;
    delete pointerB;
    close(fileC); );

if (!executeStepA())
    return;

if (!executeStepB())
    return;

if (!executeStepC())
    return;

マクロは本当に単なる装飾です。MakeScopeExit()直接使用できます。


これを機能させるためにマクロは必要ありません。そして[=]通常、スコープ付きラムダでは間違っています。
Yakk-Adam Nevraumont 2014

はい、マクロは装飾用であり、捨てることができます。しかし、値によるキャプチャが最も安全な「一般的な」アプローチだと思いませんか?
2014

1
いいえ:ラムダがラムダが作成された現在のスコープを超えて存続しない場合は、次を使用します[&]。安全で、最小限の驚きです。ラムダ(またはコピー)が宣言の時点でスコープよりも長く存続できる場合にのみ値でキャプチャします...
Yakk-Adam Nevraumont 14

はい、それは理にかなっています。変更します。ありがとう!
2014

6

なぜ誰も最も簡単な解決策を与えていないのですか?:D

すべての関数に同じシグネチャがある場合は、次のようにすることができます(C言語の場合)。

bool (*step[])() = {
    &executeStepA,
    &executeStepB,
    &executeStepC,
    ... 
};

for (int i = 0; i < numberOfSteps; i++) {
    bool condition = step[i]();

    if (!condition) {
        break;
    }
}

executeThisFunctionInAnyCase();

クリーンなC ++ソリューションの場合、executeメソッドを含み、ステップをオブジェクトにラップするインターフェースクラスを作成する必要 があります。
次に、上記のソリューションは次のようになります。

Step *steps[] = {
    stepA,
    stepB,
    stepC,
    ... 
};

for (int i = 0; i < numberOfSteps; i++) {
    Step *step = steps[i];

    if (!step->execute()) {
        break;
    }
}

executeThisFunctionInAnyCase();

5

個別の条件変数は必要ないと仮定すると、テストを反転し、「ok」パスとしてelse-falthroughを使用すると、より垂直なif / elseステートメントのセットを取得できます。

bool failed = false;

// keep going if we don't fail
if (failed = !executeStepA())      {}
else if (failed = !executeStepB()) {}
else if (failed = !executeStepC()) {}
else if (failed = !executeStepD()) {}

runThisFunctionInAnyCase();

失敗した変数を省略すると、コードがIMOを少しあいまいにします。

内部で変数を宣言するのは問題ありません。= vs ==について心配する必要はありません。

// keep going if we don't fail
if (bool failA = !executeStepA())      {}
else if (bool failB = !executeStepB()) {}
else if (bool failC = !executeStepC()) {}
else if (bool failD = !executeStepD()) {}
else {
     // success !
}

runThisFunctionInAnyCase();

これはあいまいですが、コンパクトです:

// keep going if we don't fail
if (!executeStepA())      {}
else if (!executeStepB()) {}
else if (!executeStepC()) {}
else if (!executeStepD()) {}
else { /* success */ }

runThisFunctionInAnyCase();

5

これは状態マシンのように見えます。これは、state-patternで簡単に実装できるので便利です。

Javaでは次のようになります。

interface StepState{
public StepState performStep();
}

実装は次のように機能します。

class StepA implements StepState{ 
    public StepState performStep()
     {
         performAction();
         if(condition) return new StepB()
         else return null;
     }
}

等々。次に、big if条件を次のように置き換えることができます。

Step toDo = new StepA();
while(toDo != null)
      toDo = toDo.performStep();
executeThisFunctionInAnyCase();

5

Rommikが述べたように、これにデザインパターンを適用できますが、呼び出しをチェーンしたいので、Strategyではなく、Decoratorパターンを使用します。コードが単純な場合は、入れ子にならないように、うまく構造化された回答の1つを使用します。ただし、複雑な場合や動的なチェーンが必要な場合は、Decoratorパターンが適しています。ここにyUMLクラス図があります:

yUMLクラス図

次に、LinqPad C#プログラムの例を示します。

void Main()
{
    IOperation step = new StepC();
    step = new StepB(step);
    step = new StepA(step);
    step.Next();
}

public interface IOperation 
{
    bool Next();
}

public class StepA : IOperation
{
    private IOperation _chain;
    public StepA(IOperation chain=null)
    {
        _chain = chain;
    }

    public bool Next() 
    {
        bool localResult = false;
        //do work
        //...
        // set localResult to success of this work
        // just for this example, hard coding to true
        localResult = true;
        Console.WriteLine("Step A success={0}", localResult);

        //then call next in chain and return
        return (localResult && _chain != null) 
            ? _chain.Next() 
            : true;
    }
}

public class StepB : IOperation
{
    private IOperation _chain;
    public StepB(IOperation chain=null)
    {
        _chain = chain;
    }

    public bool Next() 
    {   
        bool localResult = false;

        //do work
        //...
        // set localResult to success of this work
        // just for this example, hard coding to false, 
            // to show breaking out of the chain
        localResult = false;
        Console.WriteLine("Step B success={0}", localResult);

        //then call next in chain and return
        return (localResult && _chain != null) 
            ? _chain.Next() 
            : true;
    }
}

public class StepC : IOperation
{
    private IOperation _chain;
    public StepC(IOperation chain=null)
    {
        _chain = chain;
    }

    public bool Next() 
    {
        bool localResult = false;
        //do work
        //...
        // set localResult to success of this work
        // just for this example, hard coding to true
        localResult = true;
        Console.WriteLine("Step C success={0}", localResult);
        //then call next in chain and return
        return (localResult && _chain != null) 
            ? _chain.Next() 
            : true;
    }
}

デザインパターンについて読むのに最適な本、IMHOはHead First Design Patternsです。


ジェフリーの答えのようなものに対するこれの利点は何ですか?
Dason、2014

要件が変更されると、ドメインの知識があまりなくても、このアプローチの管理が簡単になります。特に、ネストされたifsの一部のセクションがどのくらい深くて長いかを検討する場合に注意してください。すべてが非常に壊れやすくなるため、作業するリスクが高くなります。誤解しないでください。一部の最適化シナリオでは、これを取り除いてifsに戻る可能性がありますが、99%の時間で問題ありません。しかし、重要なのは、そのレベルに達したときに、パフォーマンスを必要とする保守性を気にしない場合です。
John Nicholas 14

4

いくつかの回答は、特にネットワークプログラミングで、私が何度も目にして使用したパターンをほのめかしました。ネットワークスタックでは、多くの場合、要求のシーケンスが長く、そのいずれかが失敗してプロセスが停止する可能性があります。

一般的なパターンは使用することでした do { } while (false);

while(false)はそれを作るためにマクロを使用しましたdo { } once;一般的なパターンは:

do
{
    bool conditionA = executeStepA();
    if (! conditionA) break;
    bool conditionB = executeStepB();
    if (! conditionB) break;
    // etc.
} while (false);

このパターンは比較的読みやすく、適切に破壊するオブジェクトを使用できるようになり、複数の戻りを回避してステップとデバッグを少し簡単にしました。


4

MathieuのC ++ 11の回答を改善し、の使用によって発生する実行時のコストを回避するstd::functionには、次を使用することをお勧めします

template<typename functor>
class deferred final
{
public:
    template<typename functor2>
    explicit deferred(functor2&& f) : f(std::forward<functor2>(f)) {}
    ~deferred() { this->f(); }

private:
    functor f;
};

template<typename functor>
auto defer(functor&& f) -> deferred<typename std::decay<functor>::type>
{
    return deferred<typename std::decay<functor>::type>(std::forward<functor>(f));
}

この単純なテンプレートクラスは、パラメーターなしで呼び出すことができるファンクターを受け入れ、動的メモリ割り当てなしで受け入れるため、不必要なオーバーヘッドなしでC ++の抽象化の目標により適切に準拠します。追加の関数テンプレートは、テンプレートパラメーターの控除による使用を簡略化するためにあります(クラステンプレートパラメーターでは使用できません)

使用例:

auto guard = defer(executeThisFunctionInAnyCase);
bool conditionA = executeStepA();
if (!conditionA) return;
bool conditionB = executeStepB();
if (!conditionB) return;
bool conditionC = executeStepC();
if (!conditionC) return;

マシューの回答と同じように、このソリューションは完全に例外安全でありexecuteThisFunctionInAnyCase、すべての場合に呼び出されます。必要がありexecuteThisFunctionInAnyCase、それ自体は、デストラクタが暗黙のうちにマークされ、投げるnoexceptので、呼び出しがするstd::terminateスタックアンワインド中にスローされる例外を発生させたのではなく、発行されます。


+1この答えを探していたので、投稿する必要はありませんでした。を強制する必要はなくfunctordeferred'dコンストラクターで完全転送する必要がありmoveます。
Yakk-Adam Nevraumont 2014

@Yakkはコンストラクタを転送コンストラクタに変更しました
Joe

3

1つのブロックからすべての呼び出しを実行したいようです。他の人がそれを提案したように、whileループを使用して去るか、breakまたは去ることができる新しい関数を使用する必要がありますreturnあります(よりクリーンな場合があります)。

私は個人的に追放する goto、関数の出口であってもします。デバッグ時にそれらを見つけるのは困難です。

ワークフローで機能するエレガントな代替手段は、関数配列を作成してこれを反復することです。

const int STEP_ARRAY_COUNT = 3;
bool (*stepsArray[])() = {
   executeStepA, executeStepB, executeStepC
};

for (int i=0; i<STEP_ARRAY_COUNT; ++i) {
    if (!stepsArray[i]()) {
        break;
    }
}

executeThisFunctionInAnyCase();

幸い、デバッガーがそれらを見つけます。デバッグしていて、コードを1ステップずつ実行していない場合、それは間違っています。
コーディグレイ

私はあなたが何を意味するのか理解できません、なぜ私がシングルステップを使用できなかったのですか?
AxFab 2014年

3

[...コードのブロック...]もあるので実行と実行の間に、メモリ割り当てまたはオブジェクトの初期化があると思います。このようにして、終了時に初期化済みのすべてをクリーンアップすることに注意する必要があり、問題が発生していずれかの関数がfalseを返す場合もクリーンアップする必要があります。

この場合、私の経験(CryptoAPIで作業したとき)で最も良かったのは、小さなクラスを作成することでした。コンストラクターでデータを初期化し、デストラクターでデータを初期化解除しました。次の各関数クラスは、前の関数クラスの子である必要があります。何か問題が発生した場合-例外をスローします。

class CondA
{
public:
    CondA() { 
        if (!executeStepA()) 
            throw int(1);
        [Initialize data]
    }
    ~CondA() {        
        [Clean data]
    }
    A* _a;
};

class CondB : public CondA
{
public:
    CondB() { 
        if (!executeStepB()) 
            throw int(2);
        [Initialize data]
    }
    ~CondB() {        
        [Clean data]
    }
    B* _b;
};

class CondC : public CondB
{
public:
    CondC() { 
        if (!executeStepC()) 
            throw int(3);
        [Initialize data]
    }
    ~CondC() {        
        [Clean data]
    }
    C* _c;
};

そして、あなたのコードではあなたは呼び出すだけです:

shared_ptr<CondC> C(nullptr);
try{
    C = make_shared<CondC>();
}
catch(int& e)
{
    //do something
}
if (C != nullptr)
{
   C->a;//work with
   C->b;//work with
   C->c;//work with
}
executeThisFunctionInAnyCase();

ConditionXのすべての呼び出しが何かを初期化し、メモリを割り当てるなどの場合は、それが最善のソリューションだと思います。すべてが確実にクリーンアップされるようにするのが最善です。


3

興味深い方法は、例外を処理することです。

try
{
    executeStepA();//function throws an exception on error
    ......
}
catch(...)
{
    //some error handling
}
finally
{
    executeThisFunctionInAnyCase();
}

そのようなコードを書いた場合、どういうわけか間違った方向に進んでいます。私はそれをそのようなコードを持つことの「問題」とは考えませんが、そのような厄介な「アーキテクチャ」を持つことはできません。

ヒント:これらのケースについては、信頼できる経験豊富な開発者と話し合ってください;-)


このアイデアはすべてのifチェーンを置き換えることはできないと思います。とにかく、多くの場合、これは非常に優れたアプローチです。
WoIIe 14

3

別のアプローチ- do - whileループ、それが前に述べられていたとしても、それがどのように見えるかを示すその例はありませんでした:

do
{
    if (!executeStepA()) break;
    if (!executeStepB()) break;
    if (!executeStepC()) break;
    ...

    break; // skip the do-while condition :)
}
while (0);

executeThisFunctionInAnyCase();

(まあループにはすでに答えがありwhileますが、do - whileループは真に(開始時に)冗長にチェックしませんが、代わりにxDの終わりに(これはスキップできます))。


Hey Zaffy-この回答には、do {} while(false)アプローチの大規模な説明があります。 stackoverflow.com/a/24588605/294884 他の2つの回答でも言及されています。
Fattie 14

この状況でCONTINUEまたはBREAKを使用する方がエレガントであるかどうかは、興味深い質問です。
Fattie 14

ちょっと@JoeBlow私はすべての答えを見ました...それについて話すのではなく見せたいだけ
でし

ここでの私の最初の答えは「誰もこれについて言及していません...」と言ったところ、すぐに誰かがそれが2番目のトップの答えであると親切に指摘しました:)
Fattie

@ジョーブローええ、あなたは正しいです。私はそれを修正しようとします。私は... xDとにかく感じます。とにかくありがとうございます。次回はもう少し追加料金を支払います:)
Zaffy
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.