これは一般的な状況であり、それに対処する多くの一般的な方法があります。これが正解の私の試みです。何か見逃した場合はコメントしてください。この投稿は最新の状態に保ちます。
これは矢です
あなたが話していることは、矢印アンチパターンとして知られています。ネストされた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;
}
これは、客観的かつ定量的に読みやすいからです。
- 特定の論理ブロックの{および}文字は、互いに接近しています
- 特定の行を理解するために必要なメンタルコンテキストの量が少ない
- if条件に関連するロジック全体が1ページにある可能性が高い
- コーダーがページ/アイトラックをスクロールする必要性が大幅に軽減されます
最後に一般的なコードを追加する方法
ガードパターンの問題は、いわゆる「日和見的リターン」または「日和見的出口」に依存していることです。言い換えれば、すべての関数が厳密に1つの出口点を持つべきであるというパターンを破ります。これは2つの理由で問題です。
- たとえば、Pascalでのコーディングを学んだ人は、1つの関数= 1つの出口点であることを知っています。
- 対象となる内容に関係なく、終了時に実行されるコードのセクションは提供されません。
以下に、言語機能を使用するか、問題を完全に回避することにより、この制限を回避するためのいくつかのオプションを示します。
オプション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
代わりにを使用できますwhile
。break
キーワード付きの言語構成はおそらく機能するでしょう。
オプション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と評価された場合、右側は評価されません。
このトリックは、コンパクトなコードが必要な場合や、よく知られているアルゴリズムを実装している場合など、コードがあまりメンテナンスを必要としない場合に最も役立ちます。より一般的なコーディングでは、このコードの構造は非常に脆弱です。ロジックを少し変更しただけでも、完全な書き換えが発生する可能性があります。