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

10

Это то, чем я занимаюсь в последнее время.

Пример:

setCircle(circle, i, { current }) {
    if (i == current) {
        circle.src = 'images/25CE.svg'
        circle.alt = 'Now picking'
    } else if (i < current) {
        circle.src = 'images/25C9.svg'
        circle.alt = 'Pick failed'
    } else if (i > current) {
        circle.src = 'images/25CB.svg'
        circle.alt = 'Pick chance'
    }
}

Часто лестница if / else значительно сложнее, чем эта ...

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

setCircle(circle, i, { current }) {
    if (i == current) {
        circle.src = 'images/25CE.svg'
        circle.alt = 'Now picking'
    } else if (i < current) {
        circle.src = 'images/25C9.svg'
        circle.alt = 'Pick failed'
    } else {
        circle.src = 'images/25CB.svg'
        circle.alt = 'Pick chance'
    }
}

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

Однако:

  • Ясное написание окончательного исчерпывающего условия - моя собственная идея, и у меня плохой опыт с моими собственными идеями - обычно люди кричат ​​на меня о том, как ужасно то, что я делаю, - и (иногда очень) позже я узнаю, что это действительно было неоптимальным;
  • Один намек на то, что это может быть плохой идеей: неприменимо к Javascript, но в других языках компиляторы склонны выдавать предупреждения или даже ошибки об управлении, достигающем конца функции. Хинтинг, делающий что-то подобное, может быть не слишком популярным, или я делаю это неправильно.
    • Жалобы компилятора заставили меня иногда написать последнее условие в комментарии, но я думаю, что это ужасно, поскольку комментарии, в отличие от кода, не влияют на фактическую семантику программы:
    } else { // i > current
        circle.src = 'images/25CB.svg'
        circle.alt = 'Pick chance'
    }

Я что-то пропустил? Или это нормально делать то, что я описал, или это плохая идея?

gaazkam
источник
+1 за отличный ответ @ Кристофа, что избыточность в коде увеличивает шансы на дефекты. Есть также намного меньшая проблема эффективности. Чтобы расширить это в отношении рассматриваемого примера кода, мы могли бы написать серию if-else-if как просто отдельные if без каких-либо других, но мы, как правило, этого не сделаем, поскольку if-else дает читателю понятие об исключительных альтернативах. а не ряд независимых условий (которые также были бы менее эффективными, поскольку это говорит о том, что все условия должны оцениваться даже после одного совпадения).
Эрик Эйдт
@ErikEidt>, поскольку это говорит о том, что все условия должны быть оценены даже после совпадения +1, если вы не возвращаетесь из функции или не «прерываете» цикл (что не так в приведенном выше примере).
Дарек Нендза
1
+1, Хороший вопрос. Кроме того, вас может заинтересовать то, что в присутствии NaN пример не является исчерпывающим.
одноклеточный

Ответы:

6

Оба подхода действительны. Но давайте подробнее рассмотрим плюсы и минусы.

Для ifцепочки с такими тривиальными условиями, как здесь, это не имеет значения:

  • с финалом else, для читателя очевидно, чтобы выяснить, в каком состоянии вызвано остальное;
  • с финалом else if, для читателя также очевидно, что никаких дополнительных действий не elseтребуется, так как вы рассмотрели все это.

Однако существует множество ifцепочек, в которых используются более сложные условия, сочетающие состояния нескольких переменных, возможно, со сложным логическим выражением. В этом случае это менее очевидно. И здесь следствие каждого стиля:

  • Финал else: ты уверен, что одна ветка занята. Если случится, что вы забыли один случай, он пройдет через последнюю ветвь, поэтому во время отладки, если была выбрана последняя ветка и вы ожидали чего-то другого, вы быстро разберетесь.
  • final else if: вам нужно вывести избыточное условие для кода, и это создает потенциальный источник ошибок с риском не охватить все случаи. Кроме того, если вы пропустили случай, ничего не будет выполнено, и может быть сложнее обнаружить, что чего-то не хватает (например, если некоторые переменные, которые вы ожидали установить, сохраняют значения из предыдущих итераций).

Таким образом, последнее избыточное условие является источником риска. Вот почему я бы предпочел пойти в финал else.

Редактировать: высокая надежность кодирования

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

Это защитное кодирование. Рекомендуется некоторыми спецификациями безопасности, такими как SEI CERT или MISRA . Некоторые инструменты статического анализа даже реализуют это как правило , которое систематически проверяется (это может объяснить предупреждения вашего компилятора).

Christophe
источник
7
Что если после последнего условия избыточности и добавления я добавлю еще одно, которое всегда выдает исключение, которое говорит: «Вы не должны были достигать меня!» - Это облегчит некоторые проблемы моего подхода?
Гаазкам
3
@gaazkam Да, это очень оборонительный стиль кодирования. Поэтому вам все еще нужно вычислить конечное условие, но для сложных цепочек, подверженных ошибкам, вы, по крайней мере, быстро это выясните. Единственная проблема, которую я вижу с этим вариантом, состоит в том, что это очевидный случай.
Кристоф
@gaazkam Я отредактировал свой ответ, чтобы учесть и твою дополнительную идею, упомянутую в твоем комментарии
Кристоф
1
@gaazkam Бросок исключения - это хорошо. Если вы это сделаете, не забудьте включить неожиданное значение в сообщение. Это может быть трудно воспроизвести, и неожиданное значение может дать подсказку об источнике проблемы.
Мартин Маат
1
Еще один вариант - поставить assertв финал else if. Однако ваше руководство по стилю может отличаться от того, является ли это хорошей идеей. (Условие an assertникогда не должно быть ложным, если только программист не испортил это. Так что это, по крайней мере, использование этой функции по прямому назначению. Но так много людей злоупотребляют ею, что многие магазины прямо ее запретили.)
Кевин,
5

Что-то, чего не хватает в ответах до сих пор, является вопросом того, какой тип отказа менее вреден.

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

Вы опускаете последнее условие: последний вариант выполняется, даже если это не правильно.

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

Вы добавляете окончательное условное и исключение: оно бросает.

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

Еще одна вещь, чтобы рассмотреть - каковы ваши варианты восстановления? Иногда у вас нет пути восстановления. То, что я считаю лучшим примером этого, - это первый запуск ракеты Ariane V. Произошла ошибка uncaught / 0 (на самом деле переполнение деления), которая привела к разрушению усилителя. На самом деле код, который вышел из строя, не имел никакого смысла в тот момент, он стал спорным в тот момент, когда зажглись бустеры. Как только они освещают его орбиту или бум, вы делаете все возможное, ошибки не могут быть допущены. (Если из-за этого ракета сбивается с пути, парень безопасности на дальности поворачивает ключ.)

Лорен Печтель
источник
4

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

setCircle(circle, i, { current }) {
    if (i == current) {
        circle.src = 'images/25CE.svg'
        circle.alt = 'Now picking'
    } else if (i < current) {
        circle.src = 'images/25C9.svg'
        circle.alt = 'Pick failed'
    } else {
        assert i > current
        circle.src = 'images/25CB.svg'
        circle.alt = 'Pick chance'
    }
}

Или утверждение мертвого кода:

setCircle(circle, i, { current }) {
    if (i == current) {
        circle.src = 'images/25CE.svg'
        circle.alt = 'Now picking'
    } else if (i < current) {
        circle.src = 'images/25C9.svg'
        circle.alt = 'Pick failed'
    } else if (i > current) {
        circle.src = 'images/25CB.svg'
        circle.alt = 'Pick chance'
    } else {
        assert False, "Unreachable code"
    }
}

Инструмент покрытия кода часто можно настроить так, чтобы он игнорировал код, например «assert False», из отчета о покрытии.


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

Ли Райан
источник
1
Мне не нравится ваш первый вариант, второй гораздо яснее, что это случай, который должен быть невозможным.
Лорен Печтел
0

Я определил макрос «заявлено», который оценивает условие, и в отладочной сборке попадает в отладчик.

Поэтому, если я на 100 процентов уверен, что одно из трех условий должно быть выполнено, я пишу

If condition 1 ...
Else if condition 2 .,,
Else if asserted (condition3) ...

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

gnasher729
источник
-2

Я рекомендую избегать всего остального . Используйте, ifчтобы объявить, что блок кода должен обрабатывать, и завершить блок, выйдя из функции.

Это приводит к коду, который очень ясен:

setCircle(circle, i, { current })
{
    if (i == current)
    {
        circle.src = 'images/25CE.svg'
        circle.alt = 'Now picking'
        return
    }
    if (i < current)
    {
        circle.src = 'images/25C9.svg'
        circle.alt = 'Pick failed'
        return
    }
    if (i > current)
    {
        circle.src = 'images/25CB.svg'
        circle.alt = 'Pick chance'
        return
    }
    throw new Exception("Condition not handled.");
}

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

Джон Ву
источник
1
Это на самом деле очень неясно, потому что теперь мне нужно подумать, может ли выполнение первой ветви изменить результат второго теста. Плюс бессмысленные множественные возвраты. Плюс вероятность того, что ни одно условие не является истинным.
gnasher729
Я на самом деле пишу, если подобные заявления, когда я преднамеренно ожидаю, могут быть приняты несколько случаев. Это особенно полезно в языках с операторами switch, которые не допускают провала. Я думаю, что если варианты взаимоисключающие, их следует записать так, чтобы было очевидно, что случаи взаимоисключающие (используя другое). И если это не так, то подразумевается, что случаи не являются взаимоисключающими (возможен провал).
Джерри Иеремия
@ gnasher729 Я полностью понимаю твою реакцию. Я знаю некоторых очень умных людей, у которых были проблемы с "избегать других" и "досрочным возвращением" ... сначала. Я призываю вас потратить некоторое время, чтобы привыкнуть к идее и разобраться в ней - потому что эти идеи объективно и измеримо уменьшают сложность. Ранний возврат фактически учитывает вашу первую точку (необходимо учитывать, может ли ветвь изменить другой тест). И «вероятность того, что ни одно из условий не является истинным», все равно существует; с этим стилем вы получаете очевидное исключение, а не скрытый логический недостаток.
Джон Ву
Но разве цикломатическая сложность обоих стилей не одинакова? т.е. равный количеству условий
гаазкам