якщо твердження - оцінка короткого замикання проти читабельності


90

Іноді ifвисловлювання може бути досить складним або довгим, тому для читабельності краще витягувати складні дзвінки перед if.

наприклад це:

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

в це

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

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

( При умови , приклад не що погано, це просто для ілюстрації ... уявіть собі інші виклики з декількома аргументами, і т.д.)

Але завдяки цьому вилученню я програв оцінку короткого замикання (SCE).

  1. Чи я щоразу втрачаю SCE? Чи існує якийсь сценарій, коли компілятору дозволяється "оптимізувати його" і при цьому надавати SCE?
  2. Чи є способи зберегти покращену читабельність другого фрагмента без втрати SCE?

20
Практика показує, що більшість відповідей про ефективність, які ви побачите тут чи в інших місцях, в більшості випадків помилкові (4 неправильні 1 правильна). Моя порада - завжди робіть профілювання і перевіряйте це самостійно, ви уникнете «передчасної оптимізації» і дізнаєтесь нове.
Marek R

25
@MarekR - це не просто продуктивність, це можливі побічні ефекти в OtherCunctionCall ...
relaxxx

3
@David, посилаючись на інші веб-сайти, часто корисно зазначити, що перехресне розміщення не споглядається
gnat

7
Якщо читаність є вашим головним завданням, не викликайте функції з побічними ефектами всередині, якщо умовно
Morgen

3
Потенційні близькі виборці: прочитайте питання ще раз. Частина (1) не ґрунтується на думках, тоді як частина (2) може легко перестати бути заснованою на думках завдяки редагуванню, яке видаляє посилання на будь-яку передбачувану "найкращу практику", як я збираюся зробити.
дуплод

Відповіді:


119

Одне природне рішення могло б виглядати так:

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

Отримуються такі ж приємні переваги продуктивності оцінки короткого замикання, але код виглядає більш читабельним.


4
@relaxxx: Я розумію, але "ще багато справ, щоб зробити після if", також є ознакою того, що ваша функція чи метод занадто великі, і їх слід розділити на менші. Це не завжди найкращий спосіб, але дуже часто це так!
nperson325681

2
це порушує принцип білого списку
JoulinRouge

13
@JoulinRouge: Цікаво, я ніколи не чув про цей принцип. Я сам віддаю перевагу такому підходу "короткого замикання" для переваг читабельності: він зменшує відступи і виключає можливість того, що щось трапляється після блоку з відступами.
Матьє М.,

2
Це читабельніше? Назвіть b2правильно, і ви отримаєте someConditionAndSomeotherConditionIsTrue, не дуже змістовно. Крім того, я повинен тримати купу змінних на своєму розумовому стеці під час цієї вправи (і tbh, поки я не перестану працювати в цій області). Я б пішов із SJuan76рішенням номер 2 або просто поставив все це у функцію.
Натан Купер,

2
Я не прочитав усіх коментарів, але після швидкого пошуку я не знайшов великої переваги першого фрагмента коду, а саме налагодження. Розміщення матеріалів безпосередньо у операторі if замість того, щоб заздалегідь присвоїти його змінній, а потім використовувати змінну, робить налагодження складнішим, ніж це потрібно. Використання змінних також дозволяє групувати значення семантично разом, що підвищує читабельність.
rbaleksandar

31

Я схильний розбивати умови на кілька рядків, тобто:

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

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

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

28

Якщо у вас довгі ланцюги умов і що зберегти деякі короткі замикання, ви можете використовувати тимчасові змінні, щоб поєднати кілька умов. На вашому прикладі це можна було б зробити, наприклад

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

Якщо у вас є компілятор, що підтримує C ++ 11, ви можете використовувати лямбда-вирази для об'єднання виразів у функції, подібні до вищезазначених:

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

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

21

1) Так, у вас більше немає SCE. Інакше у вас це було б

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

працює так чи інакше, залежно від того, чи буде ifзаява пізніше. Занадто складний.

2) Це засновано на думках, але для досить складних виразів ви можете зробити:

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

Якщо воно занадто складне, очевидним рішенням є створення функції, яка оцінює вираз і викликає його.


21

Ви також можете використовувати:

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

3
Я не хочу поєднувати логічні значення з побітовим оператором. Це здається мені не надто набраним. Як правило, я використовую те, що виглядає найбільш читабельним, якщо не працюю на дуже низькому рівні і не враховуються цикли процесора.
Мураха

3
Я спеціально використовував b = b || otherComplicatedStuff();і @SargeBorsch вносив зміни для видалення SCE. Дякуємо, що помітили мене про цю зміну @Ant.
КИЇВ

14

1) Чи я щоразу втрачаю SCE? Чи дозволено компілятору якийсь сценарій "оптимізувати його" і при цьому забезпечити SCE?

Я не думаю, що така оптимізація дозволена; особливо OtherComplicatedFunctionCall()може мати деякі побічні ефекти.

2) Яка найкраща практика в такій ситуації? Чи це єдина можливість (коли я хочу SCE) мати все, що мені потрібно, безпосередньо всередині if і "просто відформатувати, щоб це було якомога читабельніше"?

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

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

...

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

І оскільки ми реалізуємо getSomeResult()на основі SomeComplicatedFunctionCall()і OtherComplicatedFunctionCall(), ми могли б їх рекурсивно розкласти, якщо вони все ще ускладнені.


2
мені це подобається, тому що ви можете отримати деяку читабельність, надавши функції обгортки описове ім'я (хоча, мабуть, не getSomeResult), занадто багато інших відповідей насправді не додають нічого цінного
aw04

9

1) Чи я щоразу втрачаю SCE? Чи дозволено компілятору якийсь сценарій "оптимізувати його" і при цьому забезпечити SCE?

Ні, ні, але застосовується інакше:

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

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

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

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

Тут обидві функції будуть працювати , тому що вони повинні зберігатися в b1і b2. b1 == trueТоді Ff b2не буде оцінено (SCE). АлеOtherComplicatedFunctionCall() вже запущений.

Якщо b2ніде не використовується, компілятор може бути досить розумним, щоб вбудувати виклик функції всередині if, якщо функція не має спостережуваних побічних ефектів.

2) Яка найкраща практика в такій ситуації? Чи це єдина можливість (коли я хочу SCE) мати все, що мені потрібно, безпосередньо всередині if і "просто відформатувати, щоб це було якомога читабельніше"?

Це залежить. Вам потрібно 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

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


1
@Erbureth Ні, вони не є. Елементи масиву є покажчиками на функції, вони не виконуються, поки функції не будуть викликані в циклі.
Бармар,

Дякую Бармару, але я зробив редагування, Ербурет мав рацію перед редагуванням (я думав, що моє редагування буде пропонуватися візуально більш прямо).
levilime

4

Дуже дивно: ви говорите про читабельність, коли ніхто не згадує про використання коментаря в коді:

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

-7

Читаність необхідна, якщо ви працюєте в компанії, і ваш код буде прочитаний кимось іншим. Якщо ви пишете програму для себе, це залежить від вас, якщо ви хочете пожертвувати продуктивністю заради зрозумілого коду.


23
Маючи на увазі, що "ти через півроку", безумовно, "хтось інший", а "ти завтра" іноді може бути. Я ніколи не пожертвував би читальністю заради продуктивності, поки не мав надійних доказів того, що проблема з продуктивністю є.
Мартін Боннер підтримує Моніку
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.