Какая польза от прерывания, если ... else if конструирует с предложением else?

136

В нашей организации есть обязательное правило кодирования (без каких-либо объяснений), которое:

if… else if конструкции должны быть завершены с помощью условия else

Пример 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")может иметь смысл
Thilo
2
В нашей организации есть похожие правила, но не такие гранулярные. Цель в нашем случае двоякая. Во-первых, последовательность кода. Во-вторых, нет свободных строк / читаемость. Требование else, даже когда оно не нужно, добавляет ясности к коду, даже если он не задокументирован. Требование else - это то, что я делал в прошлом, даже когда он не нужен, просто чтобы убедиться, что я учел все возможные результаты в приложении.
LuvnJesus
4
Вы всегда можете победить, если с if (x < 0) { x = 0; } else { if (y < 0) { x = 3; }}. Или вы можете просто следовать таким правилам, многие из которых глупы, просто потому, что вы обязаны.
Джим Балтер
3
@Thilo Я немного опоздал, но все же никто не поймал ошибку: нет никаких признаков того, что другое никогда не должно произойти, только то, что у него не должно быть никаких побочных эффектов (что кажется нормальным при выполнении < 0проверок), так что assert происходит сбой программы в том, что, вероятно, является наиболее распространенным случаем, когда значения находятся в ожидаемых пределах.
Лодувийк

Ответы:

148

Как упоминалось в другом ответе, это из руководства по кодированию MISRA-C. Цель - защитное программирование, концепция, которая часто используется в критически важном программировании.

То есть каждый if - else ifдолжен заканчиваться на else, и каждый switchдолжен заканчиваться на default.

Для этого есть две причины:

  • Самодокументированный код. Если вы пишете, elseно оставляете это пустым, это означает: «Я определенно рассмотрел сценарий, когда ни то, ifни другое не else ifверно».

    Не писать elseтам означает: «либо я рассмотрел сценарий, где ни, ifни else ifне соответствует действительности, или я полностью забыл его рассмотреть, и в моем коде есть потенциальная ошибка».

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

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

    Этот код будет совершенно чужд ПК программистам и компьютерным ученым, но он имеет смысл в критически важном программном обеспечении, потому что он обнаруживает случай, когда «mybool» испортился по любой причине.

    Исторически сложилось так, что вы боялись бы повреждения оперативной памяти из-за EMI / шума. Это не большая проблема сегодня. Гораздо более вероятно, что повреждение памяти происходит из-за ошибок в другом месте кода: указатели на неправильные расположения, ошибки выхода за пределы массива, переполнение стека, прогон кода и т. Д.

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


РЕДАКТИРОВАТЬ

Относительно того, почему 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; тогда компилятор не сделал бы предположение без дополнительного статического анализа). А на тему «Если вы пишете else, но оставляете его пустым, это означает:« Я определенно рассмотрел сценарий, когда ни if, ни else не верны ».» Моя первая реакция - предположить, что программист забыл поместить код в блок else, иначе зачем просто сидеть там пустой блок else? // unusedКомментарий будет уместно, а не просто пустой блок.
JAB
5
@JAB Да, elseблок должен содержать какой-то комментарий, если нет кода. Обычная практика для пустого elseявляется одной точкой с запятой плюс комментарий: else { ; // doesn't matter }. Поскольку нет никакой причины, по которой кто-либо в противном случае просто набрал бы одиночную точку с запятой в отдельной строке. Подобная практика иногда используется в пустых петель: while(something) { ; // do nothing }. (код с разрывами строк, очевидно. ТАК комментарии не позволяют их)
Лундин
5
Я хотел бы отметить, что этот пример кода с двумя IF и другим может быть использован даже в обычном настольном программном обеспечении в многопоточных средах, поэтому значение mybool может быть изменено только между ними
Илья Бурсов
61

Ваша компания следовала руководству по кодированию MISRA. Существует несколько версий этих руководств, которые содержат это правило, но из MISRA-C: 2004 :

Правило 14.10 (обязательно): Все if… else if конструкции должны заканчиваться условием else.

Это правило применяется всякий раз, когда за оператором if следует один или несколько других операторов if; за последним остальным ifследует else заявление. В случае простого ifутверждения тогда else утверждение не должно быть включено. Требование к заключительному else утверждению - защитное программирование. elseЗаявление должно либо принять соответствующие меры или содержать соответствующий комментарий, почему не принимается никаких мер. Это согласуется с требованием иметь заключительное defaultпредложение в switchзаявлении. Например, этот код является простым оператором if:

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: в одном операторе оператора if может потребоваться проверить n условий и выполнить одну операцию.

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

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

Пример 2: Здесь программист проверяет количество условий и выполняет несколько операций. При регулярном использовании if..else ifэто, как switchвам может потребоваться выполнить операцию, как по умолчанию. Таким образом, использование elseнеобходимо согласно стандарту Мисры

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

Текущие и прошлые версии этих публикаций можно приобрести через интернет- магазин MISRA ( через ).

Встроенный C
источник
1
Спасибо, ваш ответ - все содержание правила Мисры, но я ожидаю ответа на мой вопрос о путанице в части редактирования.
Тревор
2
Хороший ответ. Из любопытства, говорят ли эти руководства что-нибудь о том, что делать, если вы ожидаете, что elseпункт будет недоступен? (Вместо этого оставьте окончательное условие, возможно, сгенерируйте ошибку?)
jpmc26
17
Я собираюсь отказаться от голосования за плагиат и ссылки, которые нарушают авторские права. Я отредактирую пост так, чтобы стало понятнее, каковы ваши слова и каковы слова MISRA. Первоначально этот ответ был не чем иным, как необработанным копированием / вставкой.
Лундин
8
@TrieuTheVan: Вопросы не должны быть движущимися целями . Убедитесь, что ваш вопрос завершен, прежде чем опубликовать его.
TJ Crowder
1
@ jpmc26 Нет, но цель MISRA не в том, чтобы дать вам точный код, а в том, чтобы дать вам безопасный код. Любой компилятор в наши дни оптимизирует недостижимый код в любом случае, так что в этом нет недостатка.
Грэм
19

Это эквивалентно требованию регистра по умолчанию в каждом коммутаторе.

Эта дополнительная возможность уменьшит покрытие кода вашей программы.


По моему опыту с портированием ядра Linux или кода Android на другую платформу мы много раз делали что-то не так, и в 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 лет назад.

Существует (с большинством языков) нет синтаксического требования для включения «нулевого» 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 if в виде переключателя, который должен охватывать весь диапазон мыслимых значений для выражения, отнюдь не гарантируется, что любой диапазон возможных условий будет полностью покрыт. То же самое можно сказать и о самой конструкции switch, отсюда и требование использовать предложение по умолчанию, которое перехватывает все оставшиеся значения и может, если в любом случае не требуется иное, использоваться в качестве гарантии подтверждения.

Сам вопрос имеет хороший контрпример: второе условие вообще не относится к x (именно поэтому я часто предпочитаю более гибкий вариант на основе if, чем вариант на основе коммутатора). Из примера очевидно, что если условие A выполнено, x должно быть установлено на определенное значение. Если A не выполняется, то условие B проверяется. Если оно выполнено, то x должен получить другое значение. Если ни A, ни B не выполняются, то x должен остаться без изменений.

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

С другой стороны, я не могу понять, почему должно быть предложение else, особенно для самого последнего и самого внутреннего оператора if. В C не существует такой вещи, как «else if». Есть только если и еще. Вместо этого, согласно MISRA, конструкция должна формально иметь отступ таким образом (и я должен был поставить открывающие фигурные скобки на их собственных строках, но мне это не нравится):

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

Когда MISRA просит поместить фигурные скобки вокруг каждой ветви, это противоречит самому себе, говоря «if ... else if constructs».

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

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». Вам все еще нужно пройти тест, где условие ложно.

TheBick
источник
5

Логически любой тест подразумевает две ветви. Что вы делаете, если это правда, и что вы делаете, если это ложно.

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

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

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

Ваш пробег может отличаться.

EvilTeach
источник
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 в предложении try-catch
Khaled.K
источник
2

Наше программное обеспечение не было критически важным, но мы также решили использовать это правило из-за защитного программирования. Мы добавили исключение throw в теоретически недоступный код (switch + if-else). И это спасло нас много раз, так как программное обеспечение быстро перестало работать, например, когда был добавлен новый тип, и мы забыли изменить один или два if-else или переключатель. В качестве бонуса было очень легко найти проблему.

holdfenytolvaj
источник
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заявление может предотвратить любые дальнейшие проблемы.

ST3
источник
1

В настоящее время я работаю с PHP. Создание формы регистрации и формы входа. Я просто использую if и else. Нет больше, если или что-нибудь, что не нужно.

Если пользователь нажимает кнопку «отправить» -> он переходит к следующему оператору if ... если имя пользователя меньше количества символов «X», тогда появляется предупреждение. В случае успеха проверьте длину пароля и так далее.

Нет необходимости в дополнительном коде, таком как else, если это может снизить надежность времени загрузки сервера для проверки всего дополнительного кода.

Porixify
источник