Оператор if - оценка короткого замыкания и читабельность

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

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

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

в это

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

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

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

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

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

10 ответов

Решение

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

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

if (bn)
{
  // do stuff
}

Преимущество этого заключается в простоте понимания, применимости ко всем случаям и коротком замыкании.


Это было мое первоначальное решение: Хорошая схема вызовов методов и тел цикла for выглядит следующим образом:

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

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

// do stuff

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

...

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

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

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

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

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

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

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

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

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

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

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

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

Еще одна возможность, которая замыкается и имеет условия в одном месте:

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

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

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

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) {
...
}

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

Другие вопросы по тегам