если заявление - оценка короткого замыкания против удобочитаемости


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

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

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

в этой

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

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

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

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

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

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-loop является следующее:

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) иметь все, что мне нужно непосредственно внутри, если и "просто отформатируйте его, чтобы быть как можно более читаемым" ?

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

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

...

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

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

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

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

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

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