この擬似コードがあると仮定します:
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
ステートメントは何らかの方法で使用できますか?
&&
を使用できます(論理AND):
if (executeStepA() && executeStepB() && executeStepC()){
...
}
executeThisFunctionInAnyCase();
これにより、両方の要件が満たされます。
executeStep<X>()
は、前のものが成功した場合にのみ評価する必要があります(これは 短絡評価 と呼ばれます)executeThisFunctionInAnyCase()
はどんな場合でも実行されます追加の関数を使用して、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();
}
深くネストされたif(最初のバリアント)を使用するか、「関数の一部」から抜け出したいという要望は、通常、追加の関数が必要であることを意味します。
この場合、旧式のCプログラマはgoto
を使用します。 Linuxスタイルガイドで実際に推奨されているのは、goto
の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
を使用して、本体をループにラップし、ループから抜けることで回避しますが、事実上、両方のアプローチは同じことを行います。 executeStepA()
が成功した場合にのみ、他のクリーンアップが必要な場合は、goto
アプローチの方が適しています。
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つのレベルのループになります。
これは一般的な状況であり、それに対処する多くの一般的な方法があります。これが、正解に対する私の試みです。何か見逃した場合はコメントしてください。この投稿を最新の状態に保ちます。
あなたが議論していることは、 矢印アンチパターン として知られています。ネストされたifのチェーンがコードブロックを形成するため、矢印と呼ばれます。コードブロックは、コードエディタペインの右側を「指す」視覚的な矢印を形成して、さらに右に広がり、左に戻ります。
矢印を回避する一般的な方法をいくつか説明します here 。最も一般的な方法は guard パターンを使用することです。このパターンでは、コードが最初に例外フローを処理し、次に基本フローを処理します。の代わりに
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つの理由で問題です。
以下に、言語機能を使用するか、問題を完全に回避することにより、この制限を回避するためのオプションを提供しました。
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つの関数に分割することで問題を回避できます。このソリューションには、すべての言語で動作するという利点があり、さらに、 サイクロマティックな複雑さ を減らすことができます。
以下に例を示します。
void OuterFunction()
{
DoSomethingIfPossible();
DoSomethingNoMatterWhat();
}
void DoSomethingIfPossible()
{
if (!ok)
{
_log.Error("Oops");
return;
}
DoSomething();
}
私が見る別の一般的なトリックは、他の回答に示されているように、while(true)とbreakを使用することです。
while(true)
{
if (!ok) break;
DoSomething();
break; //important
}
DoSomethingNoMatterWhat();
これは、goto
を使用するよりも「正直」ではありませんが、ロジックスコープの境界を明確に示すため、リファクタリング時に混乱する傾向が少なくなります。ラベルまたはgoto
ステートメントをカットアンドペーストする素朴なコーダーは、大きな問題を引き起こす可能性があります! (そして率直に言って、パターンは非常に一般的であるため、意図を明確に伝えているため、まったく「不正」ではありません)。
このオプションには他のバリエーションがあります。たとえば、switch
の代わりにwhile
を使用できます。 break
キーワードを使用した言語構成体はおそらく機能します。
もう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
を使用する必要があります。
オブジェクト指向のアプローチを使用する場合は、適切に実行することもできます。このオプションは、クラスを使用して、クリーンアップを必要とするリソースやその他の操作を「ラップ」します。
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
繰り返しますが、オブジェクトのライフサイクルを必ず理解してください。
もう1つの方法は、 短絡評価 を利用することです。
if (DoSomething1() && DoSomething2() && DoSomething3())
{
DoSomething4();
}
DoSomethingNoMatterWhat();
このソリューションは、&&演算子が機能する方法を利用します。 &&の左側がfalseと評価されると、右側は評価されません。
このトリックは、コンパクトなコードが必要な場合や、コードがあまりメンテナンスされていない可能性が高い場合(たとえば、有名なアルゴリズムを実装している場合)に最も役立ちます。より一般的なコーディングでは、このコードの構造は非常に脆弱です。ロジックにわずかな変更を加えても、完全な書き換えが行われる可能性があります。
ただやる
if( executeStepA() && executeStepB() && executeStepC() )
{
// ...
}
executeThisFunctionInAnyCase();
とても簡単です。
それぞれがfundamentallyの3つの編集により、質問が変更されたため(バージョン1にリビジョンをカウントする場合は4つ)、回答しているコード例を含めます。
bool conditionA = executeStepA();
if (conditionA){
bool conditionB = executeStepB();
if (conditionB){
bool conditionC = executeStepC();
if (conditionC){
...
}
}
}
executeThisFunctionInAnyCase();
実際には、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
Returnステートメント(Itjaxで規定されているメソッド)で追加のラッパー関数を必要としないNiceテクニックがあります。 do while(0)
疑似ループを使用します。 while (0)
は、実際にはループではなく、1回だけ実行されることを保証します。ただし、ループ構文ではbreakステートメントを使用できます。
void foo()
{
// ...
do {
if (!executeStepA())
break;
if (!executeStepB())
break;
if (!executeStepC())
break;
}
while (0);
// ...
}
これを行うこともできます:
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行、そして簡単に保守できます。
EDIT:(Thanks @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();
これは機能しますか?これはあなたのコードと同等だと思います。
bool condition = true; // using only one boolean variable
if (condition) condition = executeStepA();
if (condition) condition = executeStepB();
if (condition) condition = executeStepC();
...
executeThisFunctionInAnyCase();
目的のコードが現在見ているとおりであると仮定します。
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();
これにより、anygoto
s、例外、ダミーのwhile
ループ、またはその他の困難な構造の必要性を回避し、単純なジョブを簡単に実行できます。
必要に応じてフォーマットされたすべての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();
Breakステートメントは何らかの方法で使用できますか?
最善の解決策ではないかもしれませんが、ステートメントをdo .. while (0)
ループに入れて、break
の代わりにreturn
ステートメントを使用できます。
goto
を嫌い、do { } while (0);
ループを嫌い、C++を使用したい場合は、一時的なラムダを使用して同じ効果を得ることができます。
[&]() { // create a capture all lambda
if (!executeStepA()) { return; }
if (!executeStepB()) { return; }
if (!executeStepC()) { return; }
}(); // and immediately call it
executeThisFunctionInAnyCase();
あなたはこれをするだけです。
coverConditions();
executeThisFunctionInAnyCase();
function coverConditions()
{
bool conditionA = executeStepA();
if (!conditionA) return;
bool conditionB = executeStepB();
if (!conditionB) return;
bool conditionC = executeStepC();
if (!conditionC) return;
}
100回の99回、これが唯一の方法です。
コンピューターコードで何か「つまらない」ことをやろうとしないでください。
ちなみに、私は次のことが確かだと思いますあなたが念頭に置いていた実際の解決策...
continueステートメントは、アルゴリズムプログラミングで重要です。 (多くの場合、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");
}
それを忘れないでください..
「このブロックを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、この例では、そのブロックをインデントする必要がありますか?私はそのような質問で眠れないので、アドバイスはできません。
それが役に立てば幸い。
コード内のIF/ELSEのチェーンは、言語の問題ではなく、プログラムの設計です。プログラムをリファクタリングまたはリライトできる場合は、デザインパターン( http://sourcemaking.com/design_patterns )を見て、より良い解決策を見つけることをお勧めします。 。
通常、コードに多数のIFとその他が表示される場合は、戦略設計パターンを実装する機会です( http://sourcemaking.com/design_patterns/strategy/c-sharp-dot-net )または他のパターンの組み合わせ。
If/elseの長いリストを書く代替手段があると確信していますが、チェーンがきれいに見える以外は何も変更しないと思います(ただし、その美しさは見る人の目にはまだコードに適用されますも:-))。次のようなことを心配する必要があります(新しい状態があり、このコードについて何も覚えていない場合、6か月以内に簡単に追加できますか?または、チェーンが変更された場合、どのくらい速く、エラーが発生しませんか?私はそれを実装します)
すでに多くの良い答えがありますが、それらのほとんどは柔軟性の一部(確かにごくわずか)でトレードオフのようです。このトレードオフを必要としない一般的なアプローチは、status/keep-going変数を追加することです。もちろん、価格は以下を追跡するための追加の値です。
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;
Falseを返す代わりに、失敗した場合、実行関数に例外をスローさせます。次に、呼び出しコードは次のようになります。
try {
executeStepA();
executeStepB();
executeStepC();
}
catch (...)
もちろん、元の例では、実行ステップは、ステップ内でエラーが発生した場合にのみfalseを返すと想定していますか?
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();
C++ 11以降では、 Dのscope(exit) に似た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()
は直接使用できます。
コードが例と同じくらい単純で、言語が短絡評価をサポートしている場合、これを試すことができます:
StepA() && StepB() && StepC() && StepD();
DoAlways();
関数に引数を渡し、コードを以前の方法で記述できないように他の結果を取得する場合、他の多くの答えが問題に適しています。
なぜ誰も最も単純な解決策を与えないのですか? :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();
個々の条件変数が必要ないと仮定して、テストを反転し、「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();
失敗した変数を省略すると、コードが少しわかりにくくなります。
内部で変数を宣言するのは問題ありません。= 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();
これはステートマシンのように見えます。これは state-pattern で簡単に実装できるため便利です。
Javaでは、次のようになります。
interface StepState{
public StepState performStep();
}
実装は次のように機能します。
class StepA implements StepState{
public StepState performStep()
{
performAction();
if(condition) return new StepB()
else return null;
}
}
等々。次に、大きなif条件を次のように置き換えることができます。
Step toDo = new StepA();
while(toDo != null)
toDo = toDo.performStep();
executeThisFunctionInAnyCase();
Rommikが述べたように、これにデザインパターンを適用できますが、呼び出しを連鎖させたいので、StrategyではなくDecoratorパターンを使用します。コードが単純な場合、ネストを防ぐために、適切に構成された回答の1つを使用します。ただし、複雑な場合や動的チェーンが必要な場合は、Decoratorパターンが適しています。 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 です。
いくつかの答えは、特にネットワークプログラミングにおいて、私が何度も見たり使ったりしたパターンを示唆しています。ネットワークスタックでは、多くの場合、要求の長いシーケンスがあり、そのいずれかが失敗し、プロセスが停止します。
一般的なパターンは、do { } while (false);
を使用することでした
while(false)
にマクロを使用してdo { } once;
にしました一般的なパターンは次のとおりです。
do
{
bool conditionA = executeStepA();
if (! conditionA) break;
bool conditionB = executeStepB();
if (! conditionB) break;
// etc.
} while (false);
このパターンは比較的読みやすく、適切に破壊するオブジェクトの使用を許可し、複数のリターンを回避して、ステップ実行とデバッグを少し簡単にしました。
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;
Mathieuの答えと同じように、このソリューションは完全に例外に対して安全であり、すべての場合にexecuteThisFunctionInAnyCase
が呼び出されます。 executeThisFunctionInAnyCase
自体がスローされると、デストラクタは暗黙的にnoexcept
とマークされるため、スタックの巻き戻し中に例外がスローされる代わりに、std::terminate
の呼び出しが発行されます。
別のアプローチ-do - while
ループ、それがどのように見えるかを示す例はありませんでしたが、前に言及しましたが:
do
{
if (!executeStepA()) break;
if (!executeStepB()) break;
if (!executeStepC()) break;
...
break; // skip the do-while condition :)
}
while (0);
executeThisFunctionInAnyCase();
(まあ、while
ループには既に答えがありますが、do - while
ループは(開始時に)冗長にtrueをチェックせず、代わりにxDの最後にチェックします(ただし、これはスキップできます)。
単一のブロックからすべての呼び出しを行いたいようです。他の人が提案しているように、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();
興味深い方法は、例外を処理することです。
try
{
executeStepA();//function throws an exception on error
......
}
catch(...)
{
//some error handling
}
finally
{
executeThisFunctionInAnyCase();
}
このようなコードを書くと、どういうわけか間違った方向に進んでしまいます。そのようなコードを持つことは「問題」とは思わないが、そのような厄介な「アーキテクチャ」を持つことはない。
ヒント:これらのケースについて、信頼できるベテランの開発者と話し合ってください;-)
ちょっとしたメモ。 if
スコープが常にreturn
を引き起こす(またはループを中断する)場合は、else
ステートメントを使用しないでください。これにより、全体的なインデントを大幅に節約できます。
また、実行の間に[... block of code ...]があるため、メモリの割り当てまたはオブジェクトの初期化があると思います。この方法では、終了時にすでに初期化されているすべてのクリーニングに注意する必要があります。また、問題が発生し、いずれかの関数が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を呼び出すたびに何かを初期化したり、メモリを割り当てたりするのが最善の解決策だと思います。すべてが確実に消去されるようにしてください。
簡単な解決策は、条件ブール変数を使用することです。同じ変数を繰り返し再利用して、順番に実行されるステップのすべての結果を確認できます。
bool cond = executeStepA();
if(cond) cond = executeStepB();
if(cond) cond = executeStepC();
if(cond) cond = executeStepD();
executeThisFunctionInAnyCase();
事前にこれを行う必要はなかったというわけではありません:bool cond = true;
...そしてその後にif(cond)cond = executeStepA(); cond
変数をexecuteStepA()
の結果にすぐに割り当てることができるため、コードがさらに短くなり、読みやすくなります。
別のより独特で楽しいアプローチはこれでしょう(ただし、これはIOCCCの良い候補であると考える人もいるかもしれませんが、それでも):
!executeStepA() ? 0 :
!executeStepB() ? 0 :
!executeStepC() ? 0 :
!executeStepD() ? 0 : 1 ;
executeThisFunctionInAnyCase();
結果は、OPが投稿した内容とまったく同じです。
if(executeStepA()){
if(executeStepB()){
if(executeStepC()){
if(executeStepD()){
}
}
}
}
executeThisFunctionInAnyCase();
C-whateverとJavaの両方で、私が何度か使用したトリックを以下に示します。
do {
if (!condition1) break;
doSomething();
if (!condition2) break;
doSomethingElse()
if (!condition3) break;
doSomethingAgain();
if (!condition4) break;
doYetAnotherThing();
} while(FALSE); // Or until(TRUE) or whatever your language likes
特に各条件に明確なコメントを付けて適切にフォーマットされている場合、明確にするためにネストされたifよりもそれを好みます。
@Jefffreyが言ったように、ほとんどすべての言語で条件付き短絡機能を使用できますが、私は個人的には、スタイルの問題だけで、2つ以上の条件(単一の&&
または||
以上)を持つ条件ステートメントを嫌います。このコードは同じことを行い(おそらく同じものをコンパイルします)、私にとっては少しきれいに見えます。 executeStepX()
のすべての関数が、次のステートメントが次の場合にtrue
にキャストできる値を返す限り、中括弧、ブレーク、リターン、関数、ラムダ(c ++ 11のみ)、オブジェクトなどは必要ありません。実行されるか、そうでない場合はfalse
。
if (executeStepA())
if (executeStepB())
if (executeStepC())
//...
if (executeStepN()); // <-- note the ';'
executeThisFunctionInAnyCase();
関数のいずれかがfalse
を返すときはいつでも、次の関数は呼び出されません。
実行時に呼び出される関数(およびその順序)を変えることができるので、@ Mayerzの答えが気に入りました。この種の感覚は observer pattern のように感じられます。ここでは、特定の任意の条件が満たされるたびに呼び出されて実行されるサブスクライバー(関数、オブジェクトなど)のグループがあります。これは多くの場合、やりすぎになる可能性があるため、賢明に使用してください:)
[&]{
bool conditionA = executeStepA();
if (!conditionA) return; // break
bool conditionB = executeStepB();
if (!conditionB) return; // break
bool conditionC = executeStepC();
if (!conditionC) return; // break
}();
executeThisFunctionInAnyCase();
暗黙的な参照キャプチャを使用して匿名ラムダ関数を作成し、実行します。その中のコードはすぐに実行されます。
停止したい場合は、単にreturn
sです。
次に、実行後、executeThisFunctionInAnyCase
を実行します。
ラムダ内のreturn
は、ブロックの終わりまでのbreak
です。他の種類のフロー制御はすべて機能します。
例外はそのまま残されます。例外をキャッチしたい場合は、明示的に行ってください。例外がスローされる場合はexecuteThisFunctionInAnyCase
の実行に注意してください-例外ハンドラーで例外をスローできる場合、通常はexecuteThisFunctionInAnyCase
を実行したくないので、混乱が発生します(混乱は言語に依存します)。
このようなキャプチャベースのインライン関数の素晴らしい特性は、既存のコードを適切にリファクタリングできることです。関数が本当に長くなる場合、コンポーネント部分に分解することをお勧めします。
より多くの言語で動作するこのバリアントは次のとおりです。
bool working = executeStepA();
working = working && executeStepB();
working = working && executeStepC();
executeThisFunctionInAnyCase();
ここで、それぞれが短絡する個々の線を書きます。これらの行の間にコードを挿入して複数の「いずれの場合でも」、または実行ステップの間にif(working) { /* code */ }
を実行して、まだ救済していない場合にのみ実行するコードを含めることができます。
この問題に対する優れた解決策は、新しいフロー制御を追加する場合に堅牢である必要があります。
C++では、より簡単なソリューションは、迅速なscope_guardクラスを一緒に投げることです。
#ifndef SCOPE_GUARD_H_INCLUDED_
#define SCOPE_GUARD_H_INCLUDED_
template<typename F>
struct scope_guard_t {
F f;
~scope_guard_t() { f(); }
};
template<typename F>
scope_guard_t<F> scope_guard( F&& f ) { return {std::forward<F>(f)}; }
#endif
その後、問題のコードで:
auto scope = scope_guard( executeThisFunctionInAnyCase );
bool conditionA = executeStepA();
if (!conditionA) return;
bool conditionB = executeStepB();
if (!conditionB) return;
bool conditionC = executeStepC();
if (!conditionC) return;
そして、scope
のデストラクタは、executeThisFunctionInAnyCase
を自動的に実行します。クリーンアップが必要な非RAIIリソースを作成するたびに、「スコープの終わり」(それぞれ異なる名前を付ける)をますます注入できます。また、ラムダを使用できるため、ローカル変数を操作できます。
より洗練されたスコープガードは、デストラクタでの呼び出しの中止(bool
ガードを使用)、コピー/移動のブロック/許可、および内部コンテキストから返される型消去された「ポータブル」スコープガードをサポートできます。
特定の特別な状況では、仮想継承ツリーと仮想メソッド呼び出しが決定ツリーロジックを処理する場合があります。
objectp -> DoTheRightStep();
これが魔法の杖のように機能する状況に遭遇しました。もちろん、ConditionXを一貫して「object Is A」条件に変換できる場合、これは理にかなっています。
すべての答えを読んだ後、1つの新しいアプローチを提供したいと思います。これは、適切な状況で非常に明確で読みやすいものである可能性があります:状態パターン。
すべてのメソッド(executeStepX)をオブジェクトクラスにパックすると、Attribute getState()を持つことができます
class ExecutionChain
{
public:
enum State
{
Start,
Step1Done,
Step2Done,
Step3Done,
Step4Done,
FinalDone,
};
State getState() const;
void executeStep1();
void executeStep2();
void executeStep3();
void executeStep4();
void executeFinalStep();
private:
State _state;
};
これにより、実行コードを次のようにフラット化できます。
void execute
{
ExecutionChain chain;
chain.executeStep1();
if ( chain.getState() == Step1Done )
{
chain.executeStep2();
}
if ( chain.getState() == Step2Done )
{
chain.executeStep3();
}
if ( chain.getState() == Step3Done )
{
chain.executeStep4();
}
chain.executeFinalStep();
}
このように、読みやすく、デバッグが簡単で、明確なフロー制御があり、新しいより複雑な動作を挿入することもできます(たとえば、少なくともStep2が実行された場合にのみ特別なステップを実行します)...
Ok = execute();のような他のアプローチに関する私の問題そして(execute())があなたのコードが何が起こっているかのフロー図のように明確で読みやすいものでなければならない場合。フロー図には次の2つのステップがあります。1.実行2.結果に基づく決定
そのため、if文などに重要な重いメソッドを隠さないでください。
とても簡単です。
if ((bool conditionA = executeStepA()) &&
(bool conditionB = executeStepB()) &&
(bool conditionC = executeStepC())) {
...
}
executeThisFunctionInAnyCase();
これにより、ブール変数conditionAL、conditionS、conditionSも保持されます。
「switchステートメント」を使用できます
switch(x)
{
case 1:
//code fires if x == 1
break;
case 2:
//code fires if x == 2
break;
...
default:
//code fires if x does not match any case
}
以下と同等です:
if (x==1)
{
//code fires if x == 1
}
else if (x==2)
{
//code fires if x == 2
}
...
else
{
//code fires if x does not match any of the if's above
}
ただし、if-else-chainsを回避する必要はないと主張します。 switchステートメントの1つの制限は、正確な等価性のみをテストすることです。つまり、「case x <3」をテストすることはできません--- C++ではエラーがスローされ、Cでは動作する可能性がありますが、予期しない方法で動作します。方法。
while(executeStepA() && executeStepB() && executeStepC() && 0);
executeThisFunctionInAnyCase();
executeThisFunctionInAnyCase()は、他の機能が完了していなくても、どのような場合でも実行する必要がありました。
Whileステートメント:
while(executeStepA() && executeStepB() && executeStepC() && 0)
すべての関数を実行し、ループしないが間違いの偽のステートメントとして実行されます。これは、終了する前に特定の回数を再試行することもできます。
OOPを使用する理由擬似コードで:
abstract class Abstraction():
function executeStepA(){...};
function executeStepB(){...};
function executeStepC(){...};
function executeThisFunctionInAnyCase(){....}
abstract function execute():
class A(Abstraction){
function execute(){
executeStepA();
executeStepB();
executeStepC();
}
}
class B(Abstraction){
function execute(){
executeStepA();
executeStepB();
}
}
class C(Abstraction){
function execute(){
executeStepA();
}
}
これであなたのifsが消えます
item.execute();
item.executeThisFunctionInAnyCase();
通常、OOPを使用してifsを回避できます。
次のように条件付きのものを他のものに移動するだけではどうですか:
if (!(conditionA = executeStepA()){}
else if (!(conditionB = executeStepB()){}
else if (!(conditionC = executeStepC()){}
else if (!(conditionD = executeStepD()){}
これにより、インデントの問題が解決されます。
偽のループについては既に言及しましたが、これまでの回答には次のトリックはありませんでした。do { /* ... */ } while( evaulates_to_zero() );
を使用して、双方向の早出しブレーカーを実装できます。 break
を使用すると、条件ステートメントを評価せずにループが終了しますが、continue
は条件ステートメントを評価します。
2種類のファイナライズがあり、一方のパスがもう一方のパスよりも少し多くの作業を行う必要がある場合は、これを使用できます。
#include <stdio.h>
#include <ctype.h>
int finalize(char ch)
{
fprintf(stdout, "read a character: %c\n", (char)toupper(ch));
return 0;
}
int main(int argc, char *argv[])
{
int ch;
do {
ch = fgetc(stdin);
if( isdigit(ch) ) {
fprintf(stderr, "read a digit (%c): aborting!\n", (char)ch);
break;
}
if( isalpha(ch) ) {
continue;
}
fprintf(stdout, "thank you\n");
} while( finalize(ch) );
return 0;
}
これを実行すると、次のセッションプロトコルが得られます。
dw@narfi ~/misc/test/fakeloopbreak $ ./fakeloopbreak
-
thank you
read a character: -
dw@narfi ~/misc/test/fakeloopbreak $ ./fakeloopbreak
a
read a character: A
dw@narfi ~/misc/test/fakeloopbreak $ ./fakeloopbreak
1
read a digit (1): aborting!
まあ、これまでに50以上の回答があり、この状況で私が通常行うことについて誰も言及していません! (つまり、いくつかのステップで構成される操作ですが、ステートマシンまたは関数ポインターテーブルを使用するのはやり過ぎです):
if ( !executeStepA() )
{
// error handling for "A" failing
}
else if ( !executeStepB() )
{
// error handling for "B" failing
}
else if ( !executeStepC() )
{
// error handling for "C" failing
}
else
{
// all steps succeeded!
}
executeThisFunctionInAnyCase();
利点:
短所: