如何避免“如果”;链吗?

假设我有这样的伪代码:

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


executeThisFunctionInAnyCase();

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

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

bool conditionA = executeStepA();
if (!conditionA) return;
bool conditionB = executeStepB();
if (!conditionB) return;
bool conditionC = executeStepC();
if (!conditionC) return;
但是约束是executeThisFunctionInAnyCase函数调用。 break语句可以以某种方式使用吗?< / p >
47853 次浏览

break语句可以以某种方式使用吗?

也许不是最好的解决方案,但你可以把你的语句放在do .. while (0)循环中,并使用break语句而不是return

你可以把所有的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 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();

你可以使用&&(逻辑与):

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

这将满足你的两个要求:

  • executeStep<X>()应该只在前一个成功时才计算(这被称为短路评估)
  • executeThisFunctionInAnyCase()将在任何情况下执行

在这种情况下,老式的C程序员使用goto。这是Linux样式指南鼓励使用goto的一种用法,它被称为集中函数exit:

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的方法是将body包装成一个循环并从中中断,但实际上这两种方法做的是同一件事。如果你只在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;
}

使用循环方法,在这种情况下,您将得到两级循环。

在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

你也可以这样做:

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行,并且易于维护。


编辑:(谢谢@Unda)我不太喜欢你,因为我觉得你的能见度很低:

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();

因为你在执行之间也有[…代码块…],我猜你有内存分配或对象初始化。通过这种方式,你必须关心在退出时你已经初始化的所有东西,如果你遇到问题,任何函数都会返回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初始化一些东西,分配内存等。最好确保所有东西都被清理干净。

关于你的当前的代码例子,本质上是一个问题#2,

[...block of code...]
bool conditionA = executeStepA();
if (conditionA){
[...block of code...]
bool conditionB = executeStepB();
if (conditionB){
[...block of code...]
bool conditionC = executeStepC();
if (conditionC){
...other checks again...
}
}
}


executeThisFunctionInAnyCase();

除了将函数结果存储在变量中之外,这是典型的C代码。

如果布尔函数导致信号失败,则c++方式将使用异常,并将其编码为

struct Finals{ ~Finals() { executeThisFunctionInAnyCase(); } };


Finals finals;
// [...block of code...]
executeStepA();
// [...block of code...]
executeStepB();
// [...block of code...]
executeStepC();
//...other checks again...

然而,根据实际问题的不同,细节可能会有很大差异。

当我需要这种通用的final actions时,我通常使用通用的scope guard类,而不是当场定义一个自定义struct。作用域保护是由Petru Marginean发明的 for c++ 98,然后使用临时生命周期扩展技巧。在c++ 11中,一般的范围保护类可以基于提供lambda表达式的客户端代码简单地实现。

在问题的最后,你建议使用一个好的C的方法来做到这一点,即使用break语句:

for( ;; ) // As a block one can 'break' out of.
{
// [...block of code...]
if( !executeStepA() ) { break; }
// [...block of code...]
if( !executeStepB() ) { break; }
// [...block of code...]
if( !executeStepC() ) { break; }
//...other checks again...
break;
}
executeThisFunctionInAnyCase();

或者,对于C,将代码块中的代码重构为一个单独的函数,并使用return而不是break。因为它支持嵌套循环或开关,所以这更清楚也更通用。然而,你问的是break

与基于异常的c++方法相比,这种方法依赖于程序员记得检查每个函数的结果,并做正确的事情,这两者在c++中都是自动化的。

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

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

似乎你想在一个block中完成所有的调用。 正如其他人所建议的那样,你应该使用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();

让执行函数在失败时抛出异常,而不是返回false。然后你的调用代码看起来像这样:

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

当然,我假设在您最初的示例中,执行步骤只会在步骤内发生错误的情况下返回false ?

有一种很好的技术,它不需要使用return语句的额外包装器函数(Itjax规定的方法)。它使用了do while(0)伪循环。while (0)确保它实际上不是一个循环,而是只执行一次。但是,循环语法允许使用break语句。

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

把有条件的东西移到else中怎么样?

if (!(conditionA = executeStepA()){}
else if (!(conditionB = executeStepB()){}
else if (!(conditionC = executeStepC()){}
else if (!(conditionD = executeStepD()){}

这确实解决了缩进问题。

假设所需的代码是我目前看到的:

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();

这避免了任何需要__abc0、异常、虚拟while循环或其他困难的构造,并简单地继续手头的简单工作。

在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();

只做

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

就是这么简单。


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

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


executeThisFunctionInAnyCase();

代码中的IF/ELSE链不是语言的问题,而是程序设计的问题。如果你能够重构或重写你的程序,我想建议你在设计模式(http://sourcemaking.com/design_patterns)中找到一个更好的解决方案。

通常,当你看到很多IF &如果在你的代码中存在else,那么就有机会实现策略设计模式(http://sourcemaking.com/design_patterns/strategy/c-sharp-dot-net)或其他模式的组合。

我确信有替代方案来编写一长串if/else,但我怀疑它们除了对你来说看起来很漂亮之外不会改变任何东西(然而,情人眼里出佳人仍然适用于代码:-))。你应该关心这样的事情(6个月后,当我有一个新的条件,我不记得关于这个代码的任何事情,我是否能够轻松地添加它?)或者如果链发生变化,我将如何快速和无错误地实现它)

这是一种常见的情况,有许多常见的方法来处理它。以下是我对一个权威答案的尝试。请评论,如果我错过了什么,我会保持这篇文章的最新。

这是一个箭头

你正在讨论的东西被称为箭头反模式。它之所以被称为箭头,是因为嵌套的if链形成的代码块会越来越向右扩展,然后再向左扩展,形成一个可视的箭头,“指向”代码编辑器窗格的右侧。

用守卫压平箭

一些避免箭头的常见方法被讨论在这里。最常见的方法是使用警卫模式,其中代码首先处理异常流,然后处理基本流,例如,而不是

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条件相关的全部逻辑更有可能出现在一页上
  4. 编码器滚动页面/眼球轨迹的需要大大减少了

如何在末尾添加公共代码

这种防范模式的问题在于,它依赖于所谓的“机会主义回归”或“机会主义退出”。换句话说,它打破了每个函数都应该只有一个退出点的模式。这是一个问题,有两个原因:

  1. 它惹恼了一些人,例如,在Pascal上学习编码的人已经知道一个函数=一个出口点。
  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();
}

第二个选项。避免这个问题:重组你的职能

可以通过将代码分解为两个函数来避免这个问题。这种解决方案的好处是适用于任何语言,此外,它可以减少圈复杂度,这是一种经过验证的降低缺陷率的方法,并提高任何自动化单元测试的特异性。

这里有一个例子:

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。利用对象生命周期

另一种方法利用对象生命周期。使用context对象来携带参数(这是我们简单的例子所缺乏的),并在完成后处理它。

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,右边永远不求值。

当需要紧凑的代码时,当代码不太可能看到太多维护时,这个技巧是最有用的,例如你正在实现一个众所周知的算法。对于更通用的编码,这段代码的结构太脆弱了;即使是对逻辑的微小更改也可能引发完全重写。

这样能行吗?我认为这与您的代码是等价的。

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

已经有很多不错的答案了,但大多数答案似乎都牺牲了一些(诚然,很少)灵活性。一个不需要这种权衡的常见方法是添加状态/继续变量。当然,价格是需要跟踪的额外价值:

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;

有几个回答暗示了我看到并使用过很多次的模式,尤其是在网络编程中。在网络堆栈中,经常有很长的请求序列,其中任何一个都可能失败并停止进程。

常见的模式是使用do { } while (false);

我使用while(false)的宏使其do { } once;。常见的模式是:

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

这种模式相对容易阅读,并且允许使用能够正确销毁的对象,还避免了多次返回,从而使步进和调试更容易一些。

< p >没有。有时候你需要复杂性。关键在于你怎么做。使用“当条件存在时您要做什么”可能会占用一些空间,使if语句树看起来比实际更大。因此,与其在设置条件时执行操作,不如将变量设置为该情况的特定值(枚举或数字,如10014)。在if树之后,然后有一个case语句,对于那个特定的值,执行在if树中所做的任何操作。它会使树变亮。 如果x1 如果x2 如果x3 Var1: = 100016; endif endif 如果 情况下 var = 100016

以下是我在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,因为它的清晰度,特别是当每个条件都有明确的注释时。

假设你不需要单独的条件变量,反转测试并使用else-falthrough作为“ok”路径,将允许你获得更垂直的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();

正如Rommik提到的,您可以为此应用设计模式,但我将使用Decorator模式而不是Strategy,因为您想要链式调用。如果代码很简单,那么我会选择一个结构良好的答案来防止嵌套。但是,如果它很复杂或者需要动态链接,那么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;
}
}

恕我直言,关于设计模式最好的书是头部优先的设计模式

对于c++ 11及以上版本,一个很好的方法可能是实现一个类似于D的范围(退出)机制的出口范围系统。

实现它的一种可能的方法是使用c++ 11 lambdas和一些helper宏:

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()可以直接使用。

如果你不喜欢gotodo { } while (0);循环,而喜欢使用c++,你也可以使用临时lambda来达到同样的效果。

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


executeThisFunctionInAnyCase();

在某些特殊情况下,虚拟继承树和虚拟方法调用可以处理决策树逻辑。

objectp -> DoTheRightStep();
我遇到过这样的情况,它就像魔杖一样有效。 当然,如果你的ConditionX可以始终如一地翻译成“object Is A”条件,这是有意义的

你可以使用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语句的一个限制是它们只测试完全相等;也就是说,你不能测试“case x<3”——在c++中,它会抛出一个错误。在C语言中,它可能工作,但表现出意想不到的方式,这比抛出错误更糟糕,因为你的程序会以意想不到的方式故障。

这看起来像一个状态机,这很方便,因为你可以很容易地用状态模式实现它。

在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();

如果你的代码像你的例子一样简单,并且你的语言支持短路计算,你可以尝试这样做:

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

如果您将参数传递给函数并返回其他结果,因此您的代码不能以以前的方式编写,那么许多其他答案将更适合解决这个问题。

给定函数:

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 ();
}

正如@Jefffrey所说,你可以在几乎每一种语言中使用条件短路特性,我个人不喜欢有超过2个条件的条件语句(超过一个&&||),这只是风格问题。这段代码执行相同的操作(可能也会编译相同的代码),对我来说它看起来更清晰一些。你不需要花括号、换行符、返回值、函数、lambdas(只适用于c++11)、对象等等,只要executeStepX()中的每个函数返回的值可以在执行下一条语句时转换为true,否则转换为false

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


executeThisFunctionInAnyCase();

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

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

一种有趣的方法是处理异常。

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

如果您编写这样的代码,那么您就在某种程度上走错了方向。我不认为拥有这样的代码是“问题”,而是拥有如此混乱的“架构”。

提示:与您信任的经验丰富的开发人员讨论这些情况;-)

只是一个边注;当if作用域总是导致return(或循环中断)时,则不要使用else语句。这可以节省大量的缩进。

你只要这样做。

coverConditions();
executeThisFunctionInAnyCase();


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

99乘以100,这是唯一的方法。

永远,永远,永远不要试图做一些“诡计”;在计算机代码中。


顺便说一下,我很确定下面是你想到的实际解决方案…

继续语句在算法编程中非常重要。(同样,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)中有一个可笑的功能,它检查封闭块是否是一个循环。

enter image description here

下面是你如何解决这个问题的方法……

-(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");
}

所以不要忘记…

Do {} while(false);

意思是“做这个方块一次”。

也就是说,写do{}while(false);和简单地写{}完全没有区别。

这现在工作完美,因为你想要…这是输出…

enter image description here

所以,这可能就是你在脑海中看到的算法。你应该试着把脑子里的东西写下来。(尤其是你不清醒的时候,因为那个时候你的美就出来了!:))

在“algorithmic"这种情况经常发生的项目,在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");
}

有两点:

首先,尽管objective-c检查continue语句所在的block类型很愚蠢,但“fight that”很麻烦。所以这是一个艰难的决定。

第二,在这个例子中,你是否应该缩进那个block?这样的问题会让我失眠,所以我不能给你建议。

希望能有所帮助。

while(executeStepA() && executeStepB() && executeStepC() && 0);
executeThisFunctionInAnyCase();

executeThisFunctionInAnyCase()必须在任何情况下执行,即使其他函数没有完成。

while语句:

while(executeStepA() && executeStepB() && executeStepC() && 0)

将执行所有函数,不会循环是一个明确的假语句。 这也可以在退出前重试一定次数

为了改进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本身抛出,析构函数将隐式标记为__abc2,因此将发出对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(尽管可以跳过))。

在阅读了所有的答案之后,我想提供一种新的方法,它在适当的情况下可能非常清晰易读:状态模式。

如果你将所有的方法(executeStepX)打包到一个对象类中,它可以有一个属性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时才执行Special Step)……

我的问题与其他方法,如ok = execute();如果(execute()),则代码应该清晰易读,就像正在发生的事情的流程图一样。在流程图中有两个步骤:1。2执行。基于结果的决定

因此,您不应该将重要的重载方法隐藏在if语句或类似语句中,它们应该独立存在!

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

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

当它想要停止时,它简单地returns。

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

lambda中的return是一个break到end的块。任何其他类型的流控制都可以工作。

异常被单独保留——如果您想捕获它们,请显式地执行。在抛出异常时要小心运行executeThisFunctionInAnyCase——如果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保护),阻止/允许复制和移动,并支持可以从内部上下文返回的类型擦除的“便携式”范围保护。

假循环已经被提到了,但到目前为止,我没有在给出的答案中看到以下技巧:你可以使用do { /* ... */ } while( evaulates_to_zero() );来实现双向早出中断符。使用break终止循环而不需要对条件语句求值,而continue将对条件语句求值。

你可以使用它,如果你有两种类型的终结,其中一条路径必须比另一条路径做更多的工作:

#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!

为什么没有人给出最简单的解决方案?: D

如果你所有的函数都有相同的签名,那么你可以这样做(对于C语言):

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


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


if (!condition) {
break;
}
}


executeThisFunctionInAnyCase();
对于简洁的c++解决方案,您应该创建一个接口类,其中包含执行方法并将步骤包装在对象中 然后,上面的解将是这样的:

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


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


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


executeThisFunctionInAnyCase();

到目前为止,有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();

优点:

  • 最终不会有巨大的缩进水平
  • 错误处理代码(可选)出现在失败函数调用之后的那几行中

缺点:

  • 如果有一个步骤不是仅仅包含在一个函数调用中,会变得很糟糕吗
  • 如果除了“按顺序执行步骤,如果一个失败就终止”之外,还需要任何流,那么就会变得很糟糕。

在我看来,函数指针是最好的方法。

这种方法之前已经提到过,但是我想更深入地讨论一下对箭头类型的代码使用这种方法的优点。

根据我的经验,这种if链发生在程序的某个操作的初始化部分。程序在尝试启动之前需要确保一切正常。

通常情况下,在许多do stuff函数中一些东西可能会被分配,或者所有权可能会改变。如果你失败了,你会想要反转这个过程。

假设你有以下3个函数:

bool loadResources()
{
return attemptToLoadResources();
}
bool getGlobalMutex()
{
return attemptToGetGlobalMutex();
}
bool startInfernalMachine()
{
return attemptToStartInfernalMachine();
}

所有函数的原型将是:

typdef bool (*initializerFunc)(void);

如上所述,您将使用push_back将指针添加到一个向量中,并按顺序运行它们。但是,如果您的程序在startInfernalMachine上失败,您将需要手动返回互斥量并卸载资源。如果在RunAllways函数中执行此操作,则会遇到麻烦。

但是等等!函子是非常棒的(有时),你可以只改变原型如下:

typdef bool (*initializerFunc)(bool);

为什么?好的,新函数现在看起来像这样:

bool loadResources(bool bLoad)
{
if (bLoad)
return attemptToLoadResources();
else
return attemptToUnloadResources();
}
bool getGlobalMutex(bool bGet)
{
if (bGet)
return attemptToGetGlobalMutex();
else
return releaseGlobalMutex();
}
...

所以现在,整个代码看起来就像这样:

vector<initializerFunc> funcs;
funcs.push_back(&loadResources);
funcs.push_back(&getGlobalMutex);
funcs.push_back(&startInfernalMachine);
// yeah, i know, i don't use iterators
int lastIdx;
for (int i=0;i<funcs.size();i++)
{
if (funcs[i](true))
lastIdx=i;
else
break;
}
// time to check if everything is peachy
if (lastIdx!=funcs.size()-1)
{
// sad face, undo
for (int i=lastIdx;i>=0;i++)
funcs[i](false);
}
所以自动清理你的项目绝对是向前迈进了一步,并通过了这个阶段。 然而,实现有点尴尬,因为您需要反复使用这个推回机制。如果你只有一个这样的位置,我们说它是可以的,但如果你有10个位置,有一个振荡的函数数量……

幸运的是,还有另一种机制可以让你做出更好的抽象:可变函数。 毕竟,有许多不同数量的函数需要仔细研究。 变进函数是这样的:

bool variadicInitialization(int nFuncs,...)
{
bool rez;
int lastIdx;
initializerFunccur;
vector<initializerFunc> reverse;
va_list vl;
va_start(vl,nFuncs);
for (int i=0;i<nFuncs;i++)
{
cur = va_arg(vl,initializerFunc);
reverse.push_back(cur);
rez= cur(true);
if (rez)
lastIdx=i;
if (!rez)
break;
}
va_end(vl);


if (!rez)
{


for (int i=lastIdx;i>=0;i--)
{
reverse[i](false);
}
}
return rez;
}

现在你的代码将被缩减(在应用程序的任何地方)为:

bool success = variadicInitialization(&loadResources,&getGlobalMutex,&startInfernalMachine);
doSomethingAllways();

通过这种方式,你可以用一个函数调用来完成所有那些讨厌的if列表,并确保当函数退出时,你不会有任何初始化的残留物。

您的团队成员将非常感激您在1行代码中实现了100行代码。

< p >但等等! 还有更多! 箭头类型代码的主要特征之一是需要有特定的顺序! 并且这个特定的顺序在整个应用程序中需要是相同的(多线程死锁避免规则1:在整个应用程序中始终以相同的顺序接受互斥) 如果有一个新来者,把函数按随机顺序排列呢?更糟糕的是,如果要求您将其公开给java或c#,该怎么办?(是的,跨平台是个麻烦)

幸运的是,有一个方法可以解决这个问题。 在要点中,我的建议是:

  • 创建一个枚举,从第一个资源开始到最后一个资源

  • 定义一个pair,从枚举中获取一个值,并将其与函数指针配对

  • 把这些对放在一个向量中(我知道,我只是定义了一个映射的使用:),但我总是用向量表示小的数字)

  • 将可变宏从函数指针改为整数(这很容易在java或c#中公开;)))

  • 在变进函数中,对这些整数进行排序

  • 运行时,运行分配给该整数的函数。

最后,您的代码将确保以下内容:

  • 只要一行代码就可以初始化,不管需要多少东西

  • 强制执行调用顺序:你不能在loadResources之前调用startInfernalMachine,除非你(架构师)决定允许这样做

  • 如果在此过程中某些事情失败,则完成清理(考虑到您正确地进行了反初始化)

  • 改变整个应用程序中初始化的顺序只意味着改变枚举中的顺序

一个简单的解决方案是使用一个条件布尔变量,同一个变量可以重复使用,以检查顺序执行的所有步骤的结果:

    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();

另一种解决方案是通过宏hack定义习语。

 #define block for(int block = 0; !block; block++)

现在,一个“block”可以用break退出,就像for(;;)while()循环一样。例子:

int main(void) {


block {
if (conditionA) {
// Do stuff A...
break;
}
if (conditionB) {
// Do stuff B...
break;
}
if (conditionC) {
// Do stuff C...
break;
}
else {
// Do default stuff...
}
} /* End of "block" statement */
/* --->   The "break" sentences jump here */


return 0;
}

尽管有“for(;;)”结构,“block”语句只执行一次 这些“块”可以用break语句退出 因此,if else if else if...语句链被避免 最多,一个else可以挂在“块”的末尾,以处理“默认”情况。< / p >

此技术旨在避免典型且丑陋的do { ... } while(0)方法 在宏block中,它定义了一个同样名为block的变量,该变量的定义方式恰好执行了一次for迭代。根据宏的替换规则,宏block定义中的标识符block不会被递归替换,因此block成为程序员无法访问的标识符,但在内部可以很好地控制“隐藏的”for(;;)循环。< / p >

此外:这些“块”可以嵌套,因为隐藏变量int block将具有不同的作用域。

如果条件被移动到单独的步骤下,条件可以被简化,这是一个c#伪代码,

其思想是使用编排而不是中央编排。

void Main()
{
Request request = new Request();
Response response = null;


// enlist all the processors
var processors = new List<IProcessor>() {new StepA() };


var factory = new ProcessorFactory(processors);


// execute as a choreography rather as a central orchestration.
var processor = factory.Get(request, response);
while (processor != null)
{
processor.Handle(request, out response);
processor = factory.Get(request, response);
}


// final result...
//response
}


public class Request
{
}


public class Response
{
}


public interface IProcessor
{
bool CanProcess(Request request, Response response);
bool Handle(Request request, out Response response);
}


public interface IProcessorFactory
{
IProcessor Get(Request request, Response response);
}


public class ProcessorFactory : IProcessorFactory
{
private readonly IEnumerable<IProcessor> processors;


public ProcessorFactory(IEnumerable<IProcessor> processors)
{
this.processors = processors;
}


public IProcessor Get(Request request, Response response)
{
// this is an iterator
var matchingProcessors = processors.Where(x => x.CanProcess(request, response)).ToArray();


if (!matchingProcessors.Any())
{
return null;
}


return matchingProcessors[0];
}
}


// Individual request processors, you will have many of these...
public class StepA: IProcessor
{
public bool CanProcess(Request request, Response response)
{
// Validate wether this can be processed -- if condition here
return false;
}


public bool Handle(Request request, out Response response)
{
response = null;
return false;
}
}

为什么使用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();
}
}

这样你的如果就消失了

item.execute();
item.executeThisFunctionInAnyCase();

通常,使用OOP可以避免if。

非常简单。

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

这将保留布尔变量conditionA, conditionB和conditionC。

我认为c++ 23的可选的一元操作会做得很好,尽管函数需要做一些改变。

and_then()方法执行break或调用下一个函数操作,链接该方法允许逐个调用函数,直到其中一个返回false。

给一个快速的&肮脏的例子:

#include <iostream>
#include <optional>
#include <cstdlib>


using namespace std;


optional<bool> func1() {
cout << "func1\n";


if (rand() % 2)
return true;
else
return nullopt;
}


optional<bool> func2(optional<bool> v) {
cout << "func2\n";


if (rand() % 2)
return true;
else
return nullopt;
}


optional<bool> func3(optional<bool> v) {
cout << "func3\n";


if (rand() % 2)
return true;
else
return nullopt;
}


void func4() {
cout << "func4\n";
}


int main() {
srand(time(NULL));


func1()
.and_then(func2)
.and_then(func3);


func4();


return 0;
}