If statement - оценка короткого замыкания относительно читаемости

Иногда оператор if может быть довольно сложным или длинным, поэтому для удобства чтения лучше извлечь сложные вызовы до if.

например. это:

if (SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall())
{
    // do stuff
}

в этот

bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();

if (b1 || b2)
{
    //do stuff
}

(приведенный пример не так уж плох, он просто для иллюстрации... представляет другие вызовы с несколькими аргументами и т.д.)

Но при этом извлечении я потерял оценку короткого замыкания (SCE).

  • Я действительно теряю SCE каждый раз? Есть ли какой-то сценарий, когда компилятору разрешено "оптимизировать его" и по-прежнему предоставлять SCE?
  • Есть ли способы сохранить улучшенную читаемость второго фрагмента без потери SCE?

Ответ 1

Одно естественное решение будет выглядеть так:

bool b1 = SomeCondition();
bool b2 = b1 || SomeOtherCondition();
bool b3 = b2 || SomeThirdCondition();
// any other condition
bool bn = bn_1 || SomeFinalCondition();

if (bn)
{
  // do stuff
}

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


Это было мое первоначальное решение: Хороший шаблон в вызовах методов и для тела цикла:

if (!SomeComplicatedFunctionCall())
   return; // or continue

if (!SomeOtherComplicatedFunctionCall())
   return; // or continue

// do stuff

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

Ответ 2

Я склонен разбивать условия на несколько строк, т.е.:

if( SomeComplicatedFunctionCall()
 || OtherComplicatedFunctionCall()
  ) {

Даже когда вы имеете дело с несколькими операторами (& &), вам просто нужно указать отступ с каждой парой скобок. SCE по-прежнему ногами - нет необходимости использовать переменные. Написание кода таким образом сделало его намного более доступным для меня уже много лет. Более сложный пример:

if( one()
 ||( two()> 1337
  &&( three()== 'foo'
   || four()
    )
   )
 || five()!= 3.1415
  ) {

Ответ 3

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

bool b = SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
if (b && some_other_expression) { ... }

Если у вас есть компилятор с поддержкой С++ 11, вы можете использовать выражения лямбда-выражения для объединения выражений в функции, аналогичные приведенным выше:

auto e = []()
{
    return SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
};

if (e() && some_other_expression) { ... }

Ответ 4

1) Да, у вас больше нет SCE. В противном случае у вас будет

bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();

работает так или иначе, в зависимости от того, есть ли инструкция if позже. Слишком сложно.

2) Это мнение основано, но для разумно сложных выражений вы можете сделать:

if (SomeComplicatedFunctionCall()
    || OtherComplicatedFunctionCall()) {

Если это слишком сложно, очевидным решением является создание функции, которая оценивает выражение и вызывает его.

Ответ 5

Вы также можете использовать:

bool b = someComplicatedStuff();
b = b || otherComplicatedStuff(); // it has to be: b = b || ...;  b |= ...; is bitwise OR and SCE is not working then 

и SCE будет работать.

Но это не намного читаемо, чем, например:

if (
    someComplicatedStuff()
    ||
    otherComplicatedStuff()
   )

Ответ 6

1) Я действительно теряю SCE каждый раз? Является ли компилятором какой-то сценарий, позволяющий "оптимизировать его" и по-прежнему предоставлять SCE?

Я не думаю, что такая оптимизация разрешена; особенно OtherComplicatedFunctionCall() могут иметь некоторые побочные эффекты.

2) Какова наилучшая практика в такой ситуации? Это единственная возможность (когда я хочу SCE) иметь все, что мне нужно непосредственно внутри, если и "просто отформатировать ее как можно более читаемую"?

Я предпочитаю реорганизовать его в одну функцию или одну переменную с описательным именем; который сохранит и оценку короткого замыкания и читаемость:

bool getSomeResult() {
    return SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
}

...

if (getSomeResult())
{
    //do stuff
}

И поскольку мы реализуем getSomeResult() на основе SomeComplicatedFunctionCall() и OtherComplicatedFunctionCall(), мы можем их рекурсивно разложить, если они все еще сложны.

Ответ 7

1) Я действительно теряю SCE каждый раз? Является ли компилятор каким-то сценарием разрешено "оптимизировать его" и по-прежнему предоставлять SCE?

Нет, нет, но он применяется по-другому:

if (SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall())
{
    // do stuff
}

Здесь компилятор даже не запускает OtherComplicatedFunctionCall(), если SomeComplicatedFunctionCall() возвращает true.

bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();

if (b1 || b2)
{
    //do stuff
}

Здесь обе функции будут выполняться, потому что их нужно сохранить в b1 и b2. Ff b1 == true, тогда b2 не будет оцениваться (SCE). Но OtherComplicatedFunctionCall() уже запущен.

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

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

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

Ответ 8

Другая возможность коротких замыканий и условий в одном месте:

bool (* conditions [])()= {&a, &b, ...}; // list of conditions
bool conditionsHold = true;
for(int i= 0; i < sizeOf(conditions); i ++){
     if (!conditions[i]()){;
         conditionsHold = false;
         break;
     }
}
//conditionsHold is true if all conditions were met, otherwise false

Вы можете поместить цикл в функцию и позволить функции принимать список условий и выводить логическое значение.

Ответ 9

Очень странно: вы говорите о удобочитаемости, когда никто не упоминает использование комментария в коде:

if (somecomplicated_function() || // let me explain what this function does
    someother_function())         // this function does something else
...

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

/*---------------------------*/
/*! interpolates between values
* @param[in] X_axis : contains X-values
* @param[in] Y_axis : contains Y-values
* @param[in] value  : X-value, input to the interpolation process
* @return[out]      : the interpolated value
* @example          : interpolate([2,0],[3,2],2.4) -> 0.8
*/
int interpolate(std::vector<int>& X_axis, std::vector<int>& Y_axis, int value)

Очевидно, что форматирование для ваших комментариев может зависеть от вашей среды разработки (Visual studio, JavaDoc под Eclipse,...)

Что касается SCE, я предполагаю, что вы подразумеваете следующее:

bool b1;
b1 = somecomplicated_function(); // let me explain what this function does
bool b2 = false;
if (!b1) {                       // SCE : if first function call is already true,
                                 // no need to spend resources executing second function.
  b2 = someother_function();     // this function does something else
}

if (b1 || b2) {
...
}

Ответ 10

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