假设我有这样的伪代码:

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

executeThisFunctionInAnyCase();

函数executeStepX当且仅当前一个成功时执行。 在任何情况下,executeThisFunctionInAnyCase函数都应该在最后被调用。 我在编程方面是一个新手,所以很抱歉提出一个非常基本的问题:有没有一种方法(例如在C/ c++中)以代码易读性为代价,避免长if链产生那种“金字塔式代码”?

我知道如果我们可以跳过executeThisFunctionInAnyCase函数调用,代码可以简化为:

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

但是约束是executeThisFunctionInAnyCase函数调用。 break语句可以以某种方式使用吗?


当前回答

As @Jefffrey said, you can use the conditional short-circuit feature in almost every language, I personally dislike conditional statements with more than 2 condition (more than a single && or ||), just a matter of style. This code does the same (and probably would compile the same) and it looks a bit cleaner to me. You don't need curly braces, breaks, returns, functions, lambdas (only c++11), objects, etc. as long as every function in executeStepX() returns a value that can be cast to true if the next statement is to be executed or false otherwise.

if (executeStepA())
 if (executeStepB())
  if (executeStepC())
   //...
    if (executeStepN()); // <-- note the ';'

executeThisFunctionInAnyCase();

任何时候,任何函数返回false,都不会调用下一个函数。

我喜欢@Mayerz的答案,因为你可以在运行时改变要调用的函数(以及它们的顺序)。这有点像观察者模式,其中有一组订阅者(函数、对象等),只要满足给定的任意条件,就会调用和执行这些订阅者。在许多情况下,这可能是一个过度杀戮,所以明智地使用它:)

其他回答

给定函数:

string trySomething ()
{
    if (condition_1)
    {
        do_1();
        ..
            if (condition_k)
            {
                do_K();

                return doSomething();
            }
            else
            {
                return "Error k";
            }
        ..
    }
    else
    {
        return "Error 1";
    }
}

我们可以通过反转验证过程来摆脱语法嵌套:

string trySomething ()
{
    if (!condition_1)
    {
        return "Error 1";
    }

    do_1();

    ..

    if (!condition_k)
    {
        return "Error k";
    }

    do_K();

    return doSomething ();
}

为了改进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的调用,而不是在堆栈展开期间引发异常。

[&]{
  bool conditionA = executeStepA();
  if (!conditionA) return; // break
  bool conditionB = executeStepB();
  if (!conditionB) return; // break
  bool conditionC = executeStepC();
  if (!conditionC) return; // break
}();
executeThisFunctionInAnyCase();

我们创建一个带有隐式引用捕获的匿名lambda函数,并运行它。其中的代码立即运行。

当它想要停止时,它只是返回。

然后,在它运行之后,我们运行executeThisFunctionInAnyCase。

在lambda内的Return是到块结束的断点。任何其他类型的流控制都可以工作。

异常被单独保留——如果您想捕获它们,请显式地执行。在抛出异常时要小心运行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资源时,您可以在作用域末端注入更多这样的“资源”(给每个资源一个不同的名称)。它也可以取lambda,所以你可以操作局部变量。

更花哨的作用域保护可以支持终止析构函数中的调用(使用bool保护),阻止/允许复制和移动,并支持可以从内部上下文返回的类型擦除的“便携式”作用域保护。

只做

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

就是这么简单。


由于三次编辑都从根本上改变了问题(如果算上版本1的修订,则是四次),我包括了我正在回答的代码示例:

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

executeThisFunctionInAnyCase();

只需使用一个额外的函数来让你的第二个版本工作:

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(你的第一个变体)或者想要跳出“函数的一部分”通常意味着你确实需要一个额外的函数。