如何避免“如果”链?
假设我有这个伪代码:
bool conditionA = executeStepA(); if (conditionA){ bool conditionB = executeStepB(); if (conditionB){ bool conditionC = executeStepC(); if (conditionC){ ... } } } executeThisFunctionInAnyCase();
函数executeStepX
应该被执行当且仅当前一个成功。 无论如何,应该在最后调用executeThisFunctionInAnyCase
函数。 我是一个编程的新手,所以对于一个非常基本的问题感到抱歉:有没有一种方法(例如C / C ++)来避免那些产生这种“代码金字塔”的链,代价是代码长可读性?
我知道,如果我们可以跳过executeThisFunctionInAnyCase
函数调用,代码可以简化为:
bool conditionA = executeStepA(); if (!conditionA) return; bool conditionB = executeStepB(); if (!conditionB) return; bool conditionC = executeStepC(); if (!conditionC) return;
但约束是executeThisFunctionInAnyCase
函数调用。 break
语句能以某种方式使用吗?
您可以使用&&
(逻辑与):
if (executeStepA() && executeStepB() && executeStepC()){ ... } executeThisFunctionInAnyCase();
这将满足您的两个要求:
-
executeStep<X>()
应该只在前一个成功(这称为短路评估 ) -
executeThisFunctionInAnyCase()
将在任何情况下被执行
只需使用一个额外的function,让您的第二个版本的工作:
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(你的第一个变体)或者打破“函数的一部分”的愿望通常意味着你需要一个额外的函数。
老式的C程序员在这种情况下使用goto
。 这是Linux风格引导实际鼓励的goto
的一个用法,它被称为集中式function出口:
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; }
使用循环方法,在这种情况下,您将最终得到两级循环。
这是一个普遍的情况,有很多常见的方法来处理它。 这是我在一个规范的答案的尝试。 请评论,如果我错过了什么,我会保持这个post是最新的。
这是一个箭头
你正在讨论的是被称为箭头反模式 。 它被称为箭头,因为嵌套的ifs链形成代码块,它们向右和向左扩展,形成一个可视箭头,指向代码编辑器窗格的右侧。
用卫兵展开箭头
这里讨论避免箭头的一些常见方法。 最常见的方法是使用一个保护模式,其中代码首先处理exceptionstream程,然后处理基本stream程,而不是
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
当有一系列的守卫时,所有的守卫都会出现在左边,而你的守护者并没有嵌套。 另外,你在视觉上将逻辑条件与其相关的错误进行配对,这使得更容易知道发生了什么:
箭头:
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条件相关的整个逻辑更可能在一个页面上
- 编码器滚动页面/眼睛轨迹的需要大大减less
如何在最后添加通用代码
守卫模式的问题在于它依赖于所谓的“机会主义的回报”或“机会主义的退出”。 换句话说,它打破了每一个function都应该有一个出口点的模式。 这是一个问题,有两个原因:
- 它以某种错误的方式磨擦了一些人,例如在帕斯卡学习编码的人已经知道一个function=一个退出点。
- 它没有提供一段代码,不pipe这个代码是否在退出时执行 ,这是目前的主题。
下面我提供了一些解决这个限制的方法,既可以使用语言特性,也可以完全避免这个问题。
选项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.避免此问题:重构您的function
你可以通过将代码分解成两个函数来避免这个问题。 这个解决scheme可以用于任何语言,另外还可以减less圈复杂度 ,这是一种经过validation的减less缺陷率的方法,可以提高任何自动化unit testing的特异性。
这是一个例子:
void OuterFunction() { DoSomethingIfPossible(); DoSomethingNoMatterWhat(); } void DoSomethingIfPossible() { if (!ok) { _log.Error("Oops"); return; } DoSomething(); }
选项3.语言技巧:使用假循环
我看到的另一个常见的技巧是使用while(true)和break,如其他答案所示。
while(true) { if (!ok) break; DoSomething(); break; //important } DoSomethingNoMatterWhat();
虽然这比使用goto
更“诚实”,但在重构时不太容易被搞乱,因为它清楚地标示了逻辑范围的边界。 一个天真的编码器,削减和粘贴您的标签或您的goto
语句可能会导致重大问题! (坦率地说,现在我认为这个模式很清楚地传达了这个意图,因此根本不是“不诚实的”)。
这个选项还有其他的变种。 例如,可以使用switch
而不是while
。 任何带有break
关键字的语言结构都可能工作。
选项4.利用对象生命周期
另一种方法利用了对象的生命周期。 使用上下文对象来携带你的参数(我们幼稚的例子中可疑的东西),当你完成的时候处理掉它。
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 }
注意:确保你了解你select的语言的对象生命周期。 你需要一些确定性的垃圾回收来完成这个工作,也就是说你必须知道何时会调用析构函数。 在某些语言中,您将需要使用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();
这个解决scheme利用&&运算符的工作方式。 当&&的左手边评估为假时,右手边永远不会被评估。
这个技巧是非常有用的,当需要紧凑的代码时,代码不可能看到太多的维护,例如你正在实现一个众所周知的algorithm。 对于更一般的编码,这个代码的结构太脆弱了; 即使对逻辑稍作改动也会触发整个重写。
做就是了
if( executeStepA() && executeStepB() && executeStepC() ) { // ... } executeThisFunctionInAnyCase();
就这么简单。
由于三个编辑,每个都已经从根本上改变了问题(如果将修订版本计为#1,则有四个编辑),我将包括我正在回答的代码示例:
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
有一个很好的技术,不需要额外的包装函数和返回语句(由Itjax规定的方法)。 它利用do while(0)
伪循环。 while (0)
确保它实际上不是一个循环,而只执行一次。 但是,循环语法允许使用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行,而且易于维护。
编辑 :(谢谢@昂达)不是一个很大的球迷,因为你松散的知名度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();
我想说,正确的方法是最简单的阅读和最容易维护的,缩进的水平将会更less,这是(目前)这个问题的目的。
// 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
,exception,假while
循环或其他困难的构造的任何需求, while
只需简单的工作即可完成。
可以打破声明以某种方式使用?
也许不是最好的解决scheme,但是你可以把你的语句放在do .. while (0)
循环中,并使用break
语句而不是return
。
你可以把所有的if
条件,格式化成你自己的函数,on返回执行executeThisFunctionInAnyCase()
函数。
从OP的基本例子来看,条件testing和执行可以被拆分;
void InitialSteps() { bool conditionA = executeStepA(); if (!conditionA) return; bool conditionB = executeStepB(); if (!conditionB) return; bool conditionC = executeStepC(); if (!conditionC) return; }
然后这样叫;
InitialSteps(); executeThisFunctionInAnyCase();
如果C ++ 11 lambda可用(OP中没有C ++ 11标签,但它们可能仍然是一个选项),那么我们可以放弃单独的函数并将其包装到lambda中。
// 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();
do { } while (0);
如果你不喜欢goto
和不喜欢do { } while (0);
循环和喜欢使用C ++,你也可以使用一个临时的lambda具有相同的效果。
[&]() { // create a capture all lambda if (!executeStepA()) { return; } if (!executeStepB()) { return; } if (!executeStepC()) { return; } }(); // and immediately call it executeThisFunctionInAnyCase();
代码中的IF / ELSE链不是语言问题,而是程序的devise。 If you're able to re-factor or re-write your program I'd like to suggest that you look in Design Patterns ( http://sourcemaking.com/design_patterns ) to find a better solution.
Usually, when you see a lot of IF's & else's in your code , it is an opportunity to implement the Strategy Design Pattern ( http://sourcemaking.com/design_patterns/strategy/c-sharp-dot-net ) or maybe a combination of other patterns.
I'm sure there're alternatives to write a long list of if/else , but I doubt they will change anything except that the chain will look pretty to you (However, the beauty is in the eye of the beholder still applies to code too:-) ) . You should be concerned about things like (in 6 months when I have a new condition and I don't remember anything about this code , will I be able to add it easily? Or what if the chain changes, how quickly and error-free will I be implement it)
You just do this..
coverConditions(); executeThisFunctionInAnyCase(); function coverConditions() { bool conditionA = executeStepA(); if (!conditionA) return; bool conditionB = executeStepB(); if (!conditionB) return; bool conditionC = executeStepC(); if (!conditionC) return; }
99 times of 100, this is the only way to do it.
Never, ever, ever try to do something "tricky" in computer code.
By the way, I'm pretty sure the following is the actual solution you had in mind…
The continue statement is critical in algorithmic programming. (Much as, the goto statement is critical in algorithmic programming.)
In many programming languages you can do this:
-(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"); }
(Note first of all: naked blocks like that example are a critical and important part of writing beautiful code, particularly if you are dealing with "algorithmic" programming.)
Again, that's exactly what you had in your head, right? And that's the beautiful way to write it, so you have good instincts.
However, tragically, in the current version of objective-c (Aside – I don't know about Swift, sorry) there is a risible feature where it checks if the enclosing block is a loop.
Here's how you get around that…
-(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"); }
So don't forget that ..
do { } while(false);
just means "do this block once".
ie, there is utterly no difference between writing do{}while(false);
and simply writing {}
.
This now works perfectly as you wanted…here's the output…
So, it's possible that's how you see the algorithm in your head. You should always try to write what's in your head. ( Particularly if you are not sober, because that's when the pretty comes out! 🙂 )
In "algorithmic" projects where this happens a lot, in objective-c, we always have a macro like…
#define RUNONCE while(false)
… so then you can do this …
-(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"); }
There are two points:
a, even though it's stupid that objective-c checks the type of block a continue statement is in, it's troubling to "fight that". So it's a tough decision.
b, there's the question should you indent, in the example, that block? I lose sleep over questions like that, so I can't advise.
希望能帮助到你。
Have your execute functions throw an exception if they fail instead of returning false. Then your calling code could look like this:
try { executeStepA(); executeStepB(); executeStepC(); } catch (...)
Of course I'm assuming that in your original example the execution step would only return false in the case of an error occuring inside the step?
A lot of good answers already, but most of them seem to tradeoff on some (admittedly very little) of the flexibility. A common approach which doesn't require this tradeoff is adding a status/keep-going variable. The price is, of course, one extra value to keep track of:
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;
In C++ (the question is tagged both C and C++), if you can't change the functions to use exceptions, you still can use the exception mechanism if you write a little helper function like
struct function_failed {}; void attempt(bool retval) { if (!retval) throw function_failed(); // or a more specific exception class }
Then your code could read as follows:
try { attempt(executeStepA()); attempt(executeStepB()); attempt(executeStepC()); } catch (function_failed) { // -- this block intentionally left empty -- } executeThisFunctionInAnyCase();
If you're into fancy syntax, you could instead make it work via explicit cast:
struct function_failed {}; struct attempt { attempt(bool retval) { if (!retval) throw function_failed(); } };
Then you can write your code as
try { (attempt) executeStepA(); (attempt) executeStepB(); (attempt) executeStepC(); } catch (function_failed) { // -- this block intentionally left empty -- } executeThisFunctionInAnyCase();
If your code is as simple as your example and your language supports short-circuit evaluations, you could try this:
StepA() && StepB() && StepC() && StepD(); DoAlways();
If you are passing arguments to your functions and getting back other results so that your code cannot be written in the previous fashion, many of the other answers would be better suited to the problem.
For C++11 and beyond, a nice approach might be to implement a scope exit system similar to D's scope(exit) mechanism.
One possible way to implement it is using C++11 lambdas and some helper macros:
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 })
This will allow you to return early from the function and ensure whatever cleanup code you define is always executed upon scope exit:
SCOPE_EXIT( delete pointerA; delete pointerB; close(fileC); ); if (!executeStepA()) return; if (!executeStepB()) return; if (!executeStepC()) return;
The macros are really just decoration. MakeScopeExit()
can be used directly.
Assuming you don't need individual condition variables, inverting the tests and using the else-falthrough as the "ok" path would allow you do get a more vertical set of if/else statements:
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();
Omitting the failed variable makes the code a bit too obscure IMO.
Declaring the variables inside is fine, no worry about = 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();
This is obscure, but compact:
// keep going if we don't fail if (!executeStepA()) {} else if (!executeStepB()) {} else if (!executeStepC()) {} else if (!executeStepD()) {} else { /* success */ } runThisFunctionInAnyCase();
This looks like a state machine, which is handy because you can easily implement it with a state-pattern .
In Java it would look something like this:
interface StepState{ public StepState performStep(); }
An implementation would work as follows:
class StepA implements StepState{ public StepState performStep() { performAction(); if(condition) return new StepB() else return null; } }
等等。 Then you can substitute the big if condition with:
Step toDo = new StepA(); while(toDo != null) toDo = toDo.performStep(); executeThisFunctionInAnyCase();
As Rommik mentioned, you could apply a design pattern for this, but I would use the Decorator pattern rather than Strategy since you are wanting to chain calls. If the code is simple, then I would go with one of the nicely structured answers to prevent nesting. However, if it is complex or requires dynamic chaining, then the Decorator pattern is a good choice. Here is a yUML class diagram :
Here is a sample LinqPad C# program:
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; } }
The best book to read on design patterns, IMHO, is Head First Design Patterns .
Why is nobody giving the simplest solution ? :d
If all your functions have the same signature then you can do it this way (for C language):
bool (*step[])() = { &executeStepA, &executeStepB, &executeStepC, ... }; for (int i = 0; i < numberOfSteps; i++) { bool condition = step[i](); if (!condition) { break; } } executeThisFunctionInAnyCase();
For a clean C++ solution, you should create an interface class that contains an execute method and wraps your steps in objects.
Then, the solution above will look like this:
Step *steps[] = { stepA, stepB, stepC, ... }; for (int i = 0; i < numberOfSteps; i++) { Step *step = steps[i]; if (!step->execute()) { break; } } executeThisFunctionInAnyCase();
Several answers hinted at a pattern that I saw and used many times, especially in network programming. In network stacks there is often a long sequence of requests, any of which can fail and will stop the process.
The common pattern was to use do { } while (false);
I used a macro for the while(false)
to make it do { } once;
The common pattern was:
do { bool conditionA = executeStepA(); if (! conditionA) break; bool conditionB = executeStepB(); if (! conditionB) break; // etc. } while (false);
This pattern was relatively easy to read, and allowed objects to be used that would properly destruct and also avoided multiple returns making stepping and debugging a bit easier.
It's seems like you want to do all your call from a single block. As other have proposed it, you should used either a while
loop and leave using break
or a new function that you can leave with return
(may be cleaner).
I personally banish goto
, even for function exit. They are harder to spot when debugging.
An elegant alternative that should work for your workflow is to build a function array and iterate on this one.
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();
an interesting way is to work with exceptions.
try { executeStepA();//function throws an exception on error ...... } catch(...) { //some error handling } finally { executeThisFunctionInAnyCase(); }
If you write such code you are going somehow in the wrong direction. I wont see it as "the problem" to have such code, but to have such a messy "architecture".
Tip: discuss those cases with a seasoned developer which you trust 😉
Another approach – do - while
loop, even though it was mentioned before there was no example of it which would show how it looks like:
do { if (!executeStepA()) break; if (!executeStepB()) break; if (!executeStepC()) break; ... break; // skip the do-while condition :) } while (0); executeThisFunctionInAnyCase();
(Well there's already an answer with while
loop but do - while
loop does not redundantly check for true (at the start) but instead at the end xD (this can be skipped, though)).
To improve on Mathieu's C++11 answer and avoid the runtime cost incurred through the use of std::function
, I would suggest to use the following
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)); }
This simple template class will accept any functor that can be called without any parameters, and does so without any dynamic memory allocations and therefore better conforms to C++'s goal of abstraction without unnecessary overhead. The additional function template is there to simplify use by template parameter deduction (which is not available for class template parameters)
Usage example:
auto guard = defer(executeThisFunctionInAnyCase); bool conditionA = executeStepA(); if (!conditionA) return; bool conditionB = executeStepB(); if (!conditionB) return; bool conditionC = executeStepC(); if (!conditionC) return;
Just as Mathieu's answer this solution is fully exception safe, and executeThisFunctionInAnyCase
will be called in all cases. Should executeThisFunctionInAnyCase
itself throw, destructors are implicitly marked noexcept
and therefore a call to std::terminate
would be issued instead of causing an exception to be thrown during stack unwinding.
Because you also have […block of code…] between executions, I guess you have memory allocation or object initializations. In this way you have to care about cleaning all you already initialized at exit, and also clean it if you will meet problem and any of functions will return false.
In this case, best what I had in my experience (when I worked with CryptoAPI) was creating small classes, in constructor you initialize your data, in destructor you uninitialize it. Each next function class have to be child of previous function class. If something went wrong – throw exception.
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; };
And then in your code you just need to call:
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();
I guess it is best solution if every call of ConditionX initialize something, allocs memory and etc. Best to be sure everything will be cleaned.