Как избежать цепей "если"?

Предполагая, что у меня есть этот псевдокод:

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

executeThisFunctionInAnyCase();

Функции executeStepX должны выполняться тогда и только тогда, когда предыдущее выполняется. В любом случае функцию executeThisFunctionInAnyCase следует вызывать в конце. Я новичок в программировании, поэтому извините за самый простой вопрос: есть ли способ (например, в C/С++), чтобы избежать этой длинной цепочки if, производящей такую ​​ "пирамиду кода", за счет четкость кода?

Я знаю, что если бы мы могли пропустить вызов функции executeThisFunctionInAnyCase, код можно было бы упростить как:

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

Но ограничение - вызов функции executeThisFunctionInAnyCase. Может ли оператор break использоваться каким-то образом?

Ответ 1

Вы можете использовать && (логика 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();
}

Использование глубоко вложенных ifs (ваш первый вариант) или желание вырваться из "части функции" обычно означает, что вам нужна дополнительная функция.

Ответ 3

В этом случае программисты старой школы C используют goto. Это единственное использование goto, которое действительно поощряется Linux styleguide, оно называется централизованным выходом функции:

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, обертывая тело в цикл и ломая его, но фактически оба подхода делают то же самое. Подход goto лучше, если вам нужна другая очистка, только если executeStepA() был успешным:

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

При использовании петлевого подхода в этом случае вы получите два уровня циклов.

Ответ 4

Это обычная ситуация, и есть много общих способов борьбы с ней. Вот моя попытка канонического ответа. Прошу прокомментировать, если я пропустил что-нибудь, и я сохраню это сообщение в актуальном состоянии.

Это стрелка

То, что вы обсуждаете, называется anti-pattern. Он называется стрелкой, потому что цепочка вложенных ifs формирует кодовые блоки, которые расширяются дальше и дальше вправо, а затем назад влево, образуя визуальную стрелку, которая "указывает" на правую сторону панели редактора кода.

Сгладить стрелку с помощью Guard

Некоторые общие способы избежать стрелки обсуждаются здесь. Наиболее распространенным методом является использование шаблона 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

Когда есть длинная серия охранников, это значительно выравнивает код, поскольку все охранники появляются полностью влево, а ваши ifs не вложены. Кроме того, вы визуально соединяете логическое условие со своей связанной ошибкой, что значительно облегчает рассказ о том, что происходит:

Стрелка:

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

Guard:

ok = DoSomething1();
if (!ok)
{
    _log.Error("oops");
    return;
} 
ok = DoSomething2();
if (!ok)
{
    _log.Error("oops");
    return;
} 
ok = DoSomething3();
if (!ok)
{
    _log.Error("oops");
    return;
} 
ok = DoSomething4();
if (!ok)
{
    _log.Error("oops");
    return;
} 

Это объективно и количественно легче читать, потому что

  • Символы {и} для данного логического блока ближе друг к другу
  • Количество ментального контекста, необходимого для понимания конкретной линии, меньше
  • Вся логика, связанная с условием if, скорее всего, будет на одной странице
  • Необходимость кодера для прокрутки дорожки страницы/глаз значительно уменьшается

Как добавить общий код в конец

Проблема с шаблоном охраны заключается в том, что он полагается на то, что называется "оппортунистическим возвратом" или "оппортунистическим выходом". Другими словами, он разбивает шаблон, что каждая функция должна иметь ровно одну точку выхода. Это проблема по двум причинам:

  • Это раздражает некоторых людей, например. люди, которые научились программировать на Паскале, узнали, что одна функция = одна точка выхода.
  • Он не предоставляет раздел кода, который выполняется при выходе, независимо от того, что, которое является предметом под рукой.

Ниже я предоставил некоторые опции для работы с этим ограничением либо с помощью языковых функций, либо вообще без проблем.

Вариант 1. Вы не можете этого сделать: используйте finally

К сожалению, как разработчик С++, вы не можете этого сделать. Но это ответ номер один для языков, содержащих ключевое слово 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();
}

Вариант 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
}

Примечание. Обязательно ознакомьтесь с жизненным циклом объекта на выбранном вами языке. Для этого вам нужна какая-то детерминированная сборка мусора, т.е. Вы должны знать, когда будет вызван деструктор. На некоторых языках вам нужно будет использовать 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, правая сторона никогда не оценивается.

Этот трюк наиболее полезен, когда требуется компактный код и когда код вряд ли увидит много обслуживания, например, вы реализуете хорошо известный алгоритм. Для более общего кодирования структура этого кода слишком хрупкая; даже незначительное изменение логики может вызвать полную перезапись.

Ответ 5

Просто сделай

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

Это просто.


Из-за трех изменений, каждый из которых коренным образом изменил вопрос (четыре, если пересчитать версию до версии №1), я включаю пример кода, на который я отвечаю:

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

executeThisFunctionInAnyCase();

Ответ 6

Фактически существует способ отложить действия в С++: использование деструктора объекта.

Предполагая, что у вас есть доступ к С++ 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

Ответ 7

Там хороший метод, который не требует дополнительной функции-обертки с операторами return (метод, предписанный Itjax). Он использует псевдо-цикл do while(0). while (0) гарантирует, что он фактически не является циклом, но выполняется только один раз. Однако синтаксис цикла позволяет использовать оператор break.

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

Ответ 8

Вы также можете сделать это:

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: (Спасибо @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();

Ответ 9

Будет ли это работать? Я думаю, что это эквивалентно вашему коду.

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

Ответ 10

Предполагая, что желаемый код выглядит так, как я его сейчас вижу:

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

Это позволяет избежать необходимости goto s, исключений, фиктивных while циклов или других сложных конструкций и просто переходит к простой задаче.

Ответ 11

Можно ли каким-либо образом разбить оператор?

Возможно, это не самое лучшее решение, но вы можете поместить свои операторы в цикл do .. while (0) и использовать операторы break вместо return.

Ответ 12

Вы можете поместить все условия 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();

Если доступны доступные lambdas С++ 11 (в OP не было тега С++ 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();

Ответ 13

Если вам не нравятся goto и не нравятся циклы do { } while (0); и, как использование С++, вы также можете использовать временную лямбду, чтобы иметь тот же эффект.

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

executeThisFunctionInAnyCase();

Ответ 14

Цепочки IF/ELSE в вашем коде не являются проблемой языка, а дизайном вашей программы. Если вы можете повторно использовать или перезаписать свою программу, я хотел бы предложить вам посмотреть в шаблонах проектирования (http://sourcemaking.com/design_patterns), чтобы найти лучшее решение.

Обычно, когда вы видите много IF и еще в своем коде, это возможность реализовать шаблон разработки стратегии (http://sourcemaking.com/design_patterns/strategy/c-sharp-dot-net) или, может быть, сочетание других паттернов.

Я уверен, что есть альтернативы для написания длинного списка if/else, но я сомневаюсь, что они изменят что-либо, кроме того, что цепочка будет выглядеть вам симпатично (Тем не менее, красота в глазах смотрящего все еще также относится к коду:-)). Вы должны быть обеспокоены такими вещами, как (через 6 месяцев, когда у меня появилось новое условие, и я ничего не помню об этом коде, могу ли я легко добавить его? Или что, если цепочка изменится, как быстро и безошибочно я его реализую)

Ответ 15

Вы просто делаете это.

coverConditions();
executeThisFunctionInAnyCase();

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

99 раз 100, это единственный способ сделать это.

Никогда, никогда, никогда не пытайтесь сделать что-то "сложное" в компьютерном коде.


Кстати, я уверен, что следующее фактическое решение , которое вы имели в виду...

Оператор 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, извините), существует понятная функция, где он проверяет, является ли охватывающий блок циклом.

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

Итак, возможно, что вы видите алгоритм в своей голове. Вы всегда должны пытаться написать, что у вас в голове. (В частности, если вы не трезвы, потому что, когда симпатичный выходит!:))

В "алгоритмических" проектах, где это происходит много, в 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");
    }

Есть две точки:

a, хотя и глупо, что objective-c проверяет тип блока, в котором находится оператор continue, и он беспокоится о "борьбе с этим". Так что это жесткое решение.

b, возникает вопрос, должен ли вы отступать, в примере, что этот блок? Я теряю сон над такими вопросами, поэтому не могу советовать.

Надеюсь, что это поможет.

Ответ 16

Пусть ваши функции выполнения выдают исключение, если они не работают, а не возвращают false. Тогда ваш код вызова может выглядеть так:

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

Конечно, я предполагаю, что в вашем исходном примере шаг выполнения вернет false только в случае ошибки, возникающей внутри шага?

Ответ 17

Уже много хороших ответов, но большинство из них, похоже, компромиссны с некоторыми (правда, очень мало) гибкостью. Общий подход, который не требует этого компромисса, заключается в добавлении переменной состояния/сохранения. Цена, конечно, одна дополнительная ценность для отслеживания:

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;

Ответ 18

В С++ (вопрос отмечен как 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();

Ответ 19

Для С++ 11 и выше хорошим подходом может быть реализация системы выхода области, подобной D scope (exit) механизм.

Один из возможных способов его реализации - использовать lambdas С++ 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() можно использовать напрямую.

Ответ 20

Если ваш код так же прост, как ваш пример, и ваш язык поддерживает оценки короткого замыкания, вы можете попробовать следующее:

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

Если вы передаете аргументы своим функциям и возвращаете другие результаты, чтобы ваш код не мог быть написан ранее, многие другие ответы лучше подходят для проблемы.

Ответ 21

Предполагая, что вам не нужны отдельные переменные условия, инвертируя тесты и используя 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();

Опустить неудачную переменную делает код слишком скрытым IMO.

Объявление переменных внутри прекрасно, не беспокойтесь о = 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();

Ответ 22

Как упоминал Ромик, вы можете применить шаблон дизайна для этого, но я бы использовал шаблон Decorator, а не Strategy, так как вы хотите цеплять вызовы. Если код прост, то я бы пошел с одним из хорошо структурированных ответов, чтобы предотвратить вложенность. Однако, если он сложный или требует динамической цепочки, то шаблон Decorator является хорошим выбором. Вот диаграмма классов yUML:

yUML class diagram

Вот пример LinqPad Программа С#:

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, Головка первых шаблонов дизайна.

Ответ 23

Это выглядит как конечный автомат, что удобно, потому что вы можете легко реализовать его с помощью шаблона состояния.

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

Ответ 24

Почему никто не дает простейшего решения?: D

Если все ваши функции имеют одну и ту же подпись, вы можете сделать это так (для языка C):

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

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

    if (!condition) {
        break;
    }
}

executeThisFunctionInAnyCase();

Для чистого решения на С++ вы должны создать класс интерфейса, который содержит метод execute и обертывает ваши шаги в объектах.
Затем решение выше будет выглядеть так:

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

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

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

executeThisFunctionInAnyCase();

Ответ 25

Несколько ответов были намечены на шаблон, который я видел и использовал много раз, особенно в сетевом программировании. В сетевых стеках часто бывает длинная последовательность запросов, любая из которых может завершиться неудачей и остановит процесс.

Общая картина заключалась в использовании do { } while (false);

Я использовал макрос для while(false), чтобы сделать его do { } once;. Общий шаблон:

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

Этот шаблон был относительно прост для чтения и позволял использовать объекты, которые должным образом разрушали бы, а также избегали множественных возвратов, что упрощало бы отладку и отладку.

Ответ 26

Чтобы улучшить ответ Mathieu С++ 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));
}

Этот простой класс шаблонов будет принимать любой функтор, который можно вызывать без каких-либо параметров, и делает это без каких-либо распределений динамической памяти и поэтому лучше соответствует цели абстракции С++ без лишних накладных расходов. Дополнительный шаблон функции предназначен для упрощения использования путем вычитания параметра шаблона (который недоступен для параметров шаблона класса)

Пример использования:

auto guard = defer(executeThisFunctionInAnyCase);
bool conditionA = executeStepA();
if (!conditionA) return;
bool conditionB = executeStepB();
if (!conditionB) return;
bool conditionC = executeStepC();
if (!conditionC) return;

Так же, как Матье отвечает, это решение полностью безопасно для всех, и executeThisFunctionInAnyCase будет вызываться во всех случаях. Если сам executeThisFunctionInAnyCase бросает, деструкторы неявно помечены noexcept, и поэтому вызывается вызов std::terminate вместо того, чтобы вызывать исключение во время разматывания стека.

Ответ 27

Кажется, что вы хотите выполнить весь свой вызов из одного блока. Как и предлагали другие, вы должны использовать либо цикл 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();

Ответ 28

интересным способом является работа с исключениями.

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

Если вы напишете такой код, вы пойдете как-то не в том направлении. Я не буду рассматривать это как "проблему", чтобы иметь такой код, но иметь такую ​​грязную "архитектуру".

Совет: обсудите эти случаи с опытным разработчиком, которому вы доверяете; -)

Ответ 29

Другой подход - цикл 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 (это может быть пропущено, хотя)).суб >

Ответ 30

Поскольку у вас также есть [... блок кода...] между исполнениями, я предполагаю, что у вас есть выделение памяти или инициализация объекта. Таким образом, вы должны заботиться о том, чтобы очистить все, что вы уже инициализировали при выходе, а также очистить его, если вы встретите проблему, и любая из функций вернет 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 инициализирует что-то, выделяет память и т.д. Лучше всего убедиться, что все будет очищено.