Яка користь від припинення, якщо… інше, якщо конструкції з іншим пунктом?


136

Наша організація має необхідне правило кодування (без будь-якого пояснення), яке:

якщо… інше, якщо конструкції слід припинити за допомогою іншого пункту

Приклад 1:

if ( x < 0 )
{
   x = 0;
} /* else not needed */

Приклад 2:

if ( x < 0 )
{
    x = 0;
}
else if ( y < 0 )
{
    x = 3;
}
else    /* this else clause is required, even if the */
{       /* programmer expects this will never be reached */
        /* no change in value of x */
}

Який край корпусу призначений для обробки?

Що також мене хвилює щодо причини, це те, що Приклад 1 не потребує, elseа приклад 2 . Якщо причиною є повторне використання та розширюваність, я думаю, що це elseслід використовувати в обох випадках.


30
Можливо, запитайте у своєї компанії причину та вигоду. На перший погляд, це змушує програміста задуматися над цим і додати коментар "не потрібно нічого робити". Ті ж міркування за (і принаймні настільки ж суперечливими, як) перевірені винятки Java.
Тіло

32
Приклад 2 насправді є хорошим прикладом того, де це assert(false, "should never go here")може мати сенс
Тіло,

2
Наша організація має подібні правила, але не зовсім це детально. Мета в нашому випадку двояка. По-перше, послідовність коду. По-друге, без вільних рядків / читабельності. Вимагаючи іншого, навіть коли це не потрібно, додає ясність коду навіть тоді, коли його не задокументовано. Вимагати іншого - це те, що я робив у минулому, навіть коли це не потрібно, просто щоб переконатися, що я врахував усі можливі результати в додатку.
LuvnJesus

4
Ви завжди могли перемогти, якщо з if (x < 0) { x = 0; } else { if (y < 0) { x = 3; }}. Або ви могли просто дотримуватися таких правил, багато з яких нерозумні, просто тому, що від вас вимагають.
Джим Балтер

3
@Thilo Я трохи запізнююся, але все ще ніхто не вловив помилку: Немає жодних ознак, що інше ніколи не повинно відбуватися, лише те, що воно не повинно мати побічних ефектів (що здається нормальним при проведенні < 0перевірок), так що затвердження буде збій програми на те, що, мабуть, найпоширеніший випадок, коли значення знаходяться в очікуваних межах.
Loduwijk

Відповіді:


148

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

Тобто, кожне if - else ifповинно закінчуватися символом а else, а кожне switchповинне закінчуватися символом adefault .

Для цього є дві причини:

  • Код самодокументації. Якщо ви пишете, elseале залишаєте його порожнім, це означає: "Я точно розглядав сценарій, коли ні ifніelse if є правдою".

    Якщо не писати elseтам, це означає: "або я розглядав сценарій, коли ні, ifні else ifправда, або я зовсім забув його розглядати, і тут, можливо, в моєму коді є жирна помилка".

  • Зупиніть утікаючий код. У критичному для місії програмному забезпеченні потрібно писати надійні програми, які враховують навіть дуже малоймовірні. Тож ви могли бачити подібний код

    if (mybool == TRUE) 
    {
    } 
    else if (mybool == FALSE) 
    {
    }
    else
    {
      // handle error
    }

    Цей код буде абсолютно чужий для програмістів ПК та вчених-комп’ютерів, але він має досконалий сенс у критичному для програмного забезпечення програмному забезпеченні, оскільки він сприймає випадок, коли "mybool" з будь-якої причини зіпсувався.

    Історично ви побоювалися б пошкодження оперативної пам'яті через EMI / шум. Сьогодні це не велика проблема. Набагато ймовірніше, що пошкодження пам’яті трапляється через помилки в іншому місці коду: вказівники на неправильні локації, помилки з виходу з-за меж, переповнення стека, втікаючий код тощо.

    Тому більшість часу подібний код повертається, щоб ляпати себе в обличчя, коли ви писали помилки на етапі впровадження. Це означає, що він також може бути використаний як метод налагодження: програма, яку ви пишете, повідомляє вам, коли ви пишете помилки.


EDIT

Щодо того, чому elseце не потрібно після кожного синглуif :

if-elseАбо if-else if-elseповністю покриває всі можливі значення , які змінна може мати. Але звичайне ifтвердження не обов'язково існує для покриття всіх можливих значень, воно має набагато ширше використання. Найчастіше ви просто хочете перевірити певну умову, і якщо вона не виконується, то нічого не робіть. Тоді писати оборонні програми для висвітлення elseсправи просто не має сенсу .

Плюс це повністю захаращує код, якби ви писали порожній elseпісля кожного if.

MISRA-C: 2012 15.7 не дає обґрунтування, чому elseвін не потрібен, він просто констатує:

Примітка: остаточне elseтвердження не потрібно для простого if твердження.


6
Якщо пам'ять пошкоджена, я очікую, що вона зруйнує набагато більше, ніж просто mybool, включаючи, можливо, сам перевіряючий код. Чому б вам не додати ще один блок, який підтверджує, що ваш if/else if/elseкомпільований, до того, що ви очікуєте? А потім ще один для перевірки попереднього верифікатора?
Олег Вікторович Волков

49
Зітхнути. Погляньте, хлопці, якщо у вас немає абсолютно іншого досвіду, крім програмування настільних ПК, немає необхідності робити коментарі, які ви знаєте, про щось, чого ви, очевидно, не маєте. Це завжди відбувається, коли обговорюється захисне програмування, і програміст ПК зупиняється. Я додав коментар "Цей код буде абсолютно чужий для програмістів ПК" чомусь. Ви не програмуєте критичне для безпеки програмне забезпечення для роботи на настільних комп'ютерах на базі ОЗУ . Період. Сам код знаходиться у флеш-диску з контрольними сумами ECC та / або CRC.
Лундін

6
@Deduplicator Дійсно (якщо myboolвін не має булевого типу, як це було до того, як C отримав своє власне bool; тоді компілятор не зробив би припущення без додаткового статичного аналізу). А на тему: "Якщо ви пишете інше, але залишаєте його порожнім, це означає:" Я, безумовно, розглядав сценарій, коли ні якщо, ні інше, якщо це правда ".": Моя перша реакція - припустити, що програміст забув поставити код інший блок, інакше навіщо порожній блок ще просто сидіти там? // unusedКоментар буде доречно, а не просто порожній блок.
JAB

5
@JAB Так, у elseблоці має бути якийсь коментар, якщо немає коду. Звичайна практика для порожнього elseє однією точкою з коми плюс коментар: else { ; // doesn't matter }. Оскільки немає причин, чому б хтось інакше просто вводив відступ, одинарну двокрапку на власній лінії. Подібна практика іноді використовується в порожніх петель: while(something) { ; // do nothing }. (очевидно, код з розривами рядків. Оскільки коментарі не дозволяють їм)
Лундін,

5
Я хотів би зазначити, що цей зразок коду з двома ІФ та іншим може перейти на інше, навіть у звичайному настільному програмному забезпеченні в багатопотокових середовищах, тому значення mybool можна змінити саме між ними
Іллія Бурсов

61

Ваша компанія дотримувалася інструкцій з кодування MISRA. Існує кілька версій цих вказівок, які містять це правило, але з MISRA-C: 2004 :

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

Це правило застосовується всякий раз, якщо за оператором if дотримується один або декілька інших, якщо заяви; остаточне інше ifсупроводжується else заявою. У випадку простого ifтвердження, тоді else заяву не потрібно включати. Вимогою до остаточного else твердження є захисне програмування. elseЗаява повинна або прийняти відповідні заходи або містити відповідний коментар, чому не вживається жодних заходів. Це узгоджується з вимогою мати заключний defaultпункт у switchвиписці. Наприклад, цей код є простим, якщо оператор:

if ( x < 0 )
{
 log_error(3);
 x = 0;
} /* else not needed */

в той час як наступний код демонструє if, else ifконструкцію

if ( x < 0 )
{
 log_error(3);
 x = 0;
}
else if ( y < 0 )
{
 x = 3;
}
else /* this else clause is required, even if the */
{ /* programmer expects this will never be reached */
 /* no change in value of x */
}

У програмі MISRA-C: 2012 , яка замінює версію 2004 року та є чинною рекомендацією щодо нових проектів, те саме правило існує, але воно має нумерацію 15.7 .

Приклад 1: в одному, якщо програмісту оператора може знадобитися перевірка n кількості умов і виконує одну операцію.

if(condition_1 || condition_2 || ... condition_n)
{
   //operation_1
}

У звичайному використанні виконання операції не потрібно весь час, коли ifвикористовується.

Приклад 2: Тут програміст перевіряє n кількість умов і виконує кілька операцій. У регулярному використанні if..else if- це як switchвам може знадобитися виконувати таку операцію, як за замовчуванням. Тому використання elseпотрібне відповідно до стандарту misra

if(condition_1 || condition_2 || ... condition_n)
{
   //operation_1
}
else if(condition_1 || condition_2 || ... condition_n)
{
  //operation_2
}
....
else
{
   //default cause
}

Поточні та попередні версії цих публікацій доступні для придбання через веб-магазин MISRA ( через ).


1
Дякую, ваша відповідь - це весь зміст правила Misra, але я очікую відповіді на мою плутанину в редагуванні частини запитання.
Тревор

2
Хороша відповідь. З цікавості, чи говорять ці довідники про те, що робити, якщо ви очікуєте, що це elseположення буде недосяжним? (Замість цього залиште остаточну умову, можливо, киньте помилку?)
jpmc26

17
Я буду голосувати за плагіат і посилання, які порушують авторські права. Я відредагую публікацію, щоб стало зрозуміліше, що ваші слова та які слова MISRA. Спочатку ця відповідь була не що інше, як необроблена копія / паста.
Лундін

8
@TrieuTheVan: Питання не повинні бути цілями, що рухаються . Переконайтеся, що ваше запитання закінчене, перш ніж його публікувати.
TJ Crowder

1
@ jpmc26 Ні, але сенс MISRA не в тому, щоб дати вам чіткий код, це дати безпечний код. Будь-який компілятор у ці дні оптимізує недоступний код так чи інакше, тому немає ніяких недоліків.
Грем

19

Це еквівалент вимагання випадку за замовчуванням у кожному комутаторі.

Це додаткове інше зменшить охоплення коду вашої програми.


У моєму досвіді перенесення ядра Linux або андроїд-коду на іншу платформу багато разів ми робимо щось не так, і в logcat ми бачимо помилку, наприклад

if ( x < 0 )
{
    x = 0;
}
else if ( y < 0 )
{
    x = 3;
}
else    /* this else clause is required, even if the */
{       /* programmer expects this will never be reached */
        /* no change in value of x */
        printk(" \n [function or module name]: this should never happen \n");

        /* It is always good to mention function/module name with the 
           logs. If you end up with "this should never happen" message
           and the same message is used in many places in the software
           it will be hard to track/debug.
        */
}

2
Саме тут __FILE__і __LINE__макроси корисні для полегшення пошуку місця розташування джерела, якщо повідомлення коли-небудь надруковане.
Пітер Кордес

9

Лише коротке пояснення, оскільки я робив це все близько 5 років тому.

Немає (з більшості мов) синтаксичних вимог включати "null" elseоператор (і зайвий){..} ), а в "простих маленьких програмах" немає потреби. Але справжні програмісти не пишуть "прості маленькі програми", і, що важливо, вони не пишуть програми, які будуть використані один раз, а потім відкидаються.

Коли ви пишете if / else:

if(something)
  doSomething;
else
  doSomethingElse;

все здається простим, і навряд чи можна побачити навіть сенс додавання {..}.

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

if(something)
  doSomething;
else
  doSomethingIForgot;
  doSomethingElse;

Раптом doSomethingElseякось забуває, що це повинно бути в elseнозі.

Отже, ти маленький хороший програміст і завжди використовуєш {..}. Але ви пишете:

if(something) {
  if(anotherThing) {
    doSomething;
  }
}

Все добре і добре, поки цей новий хлопець не зробить опівночі модифікації:

if(something) {
  if(!notMyThing) {
  if(anotherThing) {
    doSomething;
  }
  else {
    dontDoAnything;  // Because it's not my thing.
  }}
}

Так, він неправильно відформатований, але так це половина коду в проекті, і "автоматичний форматник" підкреслюється усіма #ifdefзаявами. І, звичайно, справжній код набагато складніший, ніж цей приклад іграшки.

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


7

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

Це хороша практика програмування, яка робить код багаторазовим і розширеним .


6

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

Саме запитання містить хороший зустрічний приклад: друга умова взагалі не стосується х (саме тому я часто віддаю перевагу більш гнучкому варіанту, який базується на комутаторах, ніж варіант на основі комутатора). З прикладу очевидно, що якщо виконується умова A, x слід встановити на певне значення. Якщо A не буде виконано, тоді умова B перевіряється. Якщо вона дотримана, то x має отримати інше значення. Якщо ні А, ні В не виконуються, х має залишатися незмінним.

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

З іншого боку, я не можу зрозуміти, чому має бути інше застереження, особливо для останнього та найпотаємнішого, якщо твердження. У С не існує такого поняття, як "інше якщо". Є лише якщо і ще. Натомість, на думку MISRA, конструкцію слід формально відкласти таким чином (і я повинен був поставити відкриваючі фігурні дужки на власні лінії, але мені це не подобається):

if (A) {
    // do something
}
else {
    if (B) {
        // do something else (no pun intended)
    }
    else {
        // don't do anything here
    }
}

Коли MISRA просить поставити фігурні дужки навколо кожної гілки, то це суперечить собі, згадуючи "якщо ... інше, якщо будує".

Будь-хто може уявити потворність глибоко вкладених, якщо ще дерев, див . А тепер уявіть, що цю конструкцію можна довільно поширити де завгодно. Тоді запитати про інше застереження врешті-решт, але ніде більше, стає абсурдом.

if (A) {
    if (B) {
        // do something
    }
    // you could to something here
}
else {
    // or here
    if (B) { // or C?
        // do something else (no pun intended)
    }
    else {
        // don't do anything here, if you don't want to
    }
    // what if I wanted to do something here? I need brackets for that.
}

Тож я впевнений, що люди, які розробляли настанови MISRA, мали на увазі, як інакше, якщо це було намір.

Зрештою, це зводиться до того, щоб точно визначити, що означає "якщо ... інакше, якщо сконструювати"


5

Основна причина - це, мабуть, висвітлення коду та неявна інша: як буде вести себе код, якщо умова не відповідає дійсності? Для справжнього тестування вам потрібен певний спосіб переконатися, що ви пройшли тест з умовою false. Якщо кожен ваш тестовий випадок проходить через пункт if, ваш код може мати проблеми в реальному світі через стан, який ви не тестували.

Однак деякі умови можуть бути належними прикладу 1, як у податковій декларації: "Якщо результат менше 0, введіть 0." Вам все одно потрібно пройти тест, коли умова помилкова.


5

За логікою, будь-який тест має на увазі дві гілки. Що ви робите, якщо це правда, і що ви робите, якщо це неправда.

У тих випадках, коли будь-яка філія не має функціоналу, доцільно додати коментар про те, чому вона не повинна мати функціональність.

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

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

Ваш пробіг може відрізнятися.


4

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

  • Функція перевірки охорони
  • Варіант ініціалізації
  • Необов’язкова галузь обробки

Приклад

void print (char * text)
{
    if (text == null) return; // guard check

    printf(text);
}

Але коли ви це робите if .. else if, це, мабуть, одна з таких причин, як:

  • Динамічний вимикач-корпус
  • Вилка для обробки
  • Обробка параметра обробки

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

Приклад

int absolute_value (int n)
{
    if (n == 0)
    {
        return 0;
    }
    else if (n > 0)
    {
        return n;
    }
    else /* if (n < 0) */ // redundant check
    {
        return (n * (-1));
    }
}

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

Приклад

#DEFINE SQRT_TWO   1.41421356237309504880
#DEFINE SQRT_THREE 1.73205080756887729352
#DEFINE SQRT_FIVE  2.23606797749978969641

double square_root (int n)
{
         if (n  > 5)   return sqrt((double)n);
    else if (n == 5)   return SQRT_FIVE;
    else if (n == 4)   return 2.0;
    else if (n == 3)   return SQRT_THREE;
    else if (n == 2)   return SQRT_TWO;
    else if (n == 1)   return 1.0;
    else if (n == 0)   return 0.0;
    else               return sqrt(-1); // error handling
}

Цей останній elseпункт дуже схожий на кілька інших речей в таких мовах, як Javaі C++, наприклад , як:

  • default випадку в операторі комутатора
  • catch(...)що з'являється після всіх конкретних catchблоків
  • finally у пункті про випробування

2

Наше програмне забезпечення не було критично важливим, але ми також вирішили використовувати це правило через оборонне програмування. Ми додали виняток кидка до теоретично недосяжного коду (switch + if-else). І це врятувало нас багато разів, коли програмне забезпечення швидко вийшло з ладу, наприклад, коли було додано новий тип, і ми забули змінити один-два if-else або переключитись. Як бонус було дуже легко знайти проблему.


2

Ну, мій приклад передбачає невизначеність поведінки, але іноді деякі люди намагаються бути фантазійними і не вдається, погляньте:

int a = 0;
bool b = true;
uint8_t* bPtr = (uint8_t*)&b;
*bPtr = 0xCC;
if(b == true)
{
    a += 3;
}
else if(b == false)
{
    a += 5;
}
else
{
    exit(3);
}

Напевно, ти ніколи не сподівався б мати те, boolчого немаєtrue ні false, однак це може статися. Особисто я вважаю, що це проблема, викликана людиною, яка вирішила зробити щось фантазійне, але додаткова elseзаява може запобігти будь-яким подальшим питанням.


1

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

Якщо користувач натискає кнопку "Надіслати" -> вона переходить до наступного, якщо заява ... якщо ім'я користувача менше, ніж "X", кількість символів, то попереджайте. Якщо це успішно, перевірте довжину пароля тощо.

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

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