Якщо інша драбина, яка повинна відповідати всім умовам, - чи слід додавати надлишковий заключний пункт?


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'
    }

Я щось пропускаю? Або добре робити те, що я описав, або це погана ідея?


+1 для виконавця @ Крістофа відповів, що надмірність коду збільшує шанси на дефекти. Існує також набагато менша проблема ефективності. Для розширення цього питання, стосовно відповідного прикладу коду, ми могли б написати серію if-else-if як просто окрему, якщо без будь-яких ельз, але ми взагалі не хотіли б, оскільки if-else даємо читачеві поняття про ексклюзивні альтернативи а не ряд незалежних умов (які також були б менш ефективними, оскільки це говорить про те, що всі умови слід оцінювати навіть після одного матчу).
Ерік Ейдт

@ErikEidt> оскільки це говорить про те, що всі умови слід оцінювати навіть після одного відповідності +1, якщо ви не повертаєтеся з функції або не "вириваєте" з циклу (що у наведеному прикладі не відбувається).
Darek

1
+1, приємне запитання. З іншого боку, це може вас зацікавити, що при наявності NaNs приклад не є вичерпним.
одноклітинних

Відповіді:


6

Обидва підходи справедливі. Але давайте докладніше розглянемо плюси і мінуси.

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

  • з остаточним elseдля читача очевидно з'ясувати, в якому стані спрацьовує інше;
  • З фіналом else if, для читача так само очевидно, що додаткові додаткові elseне потрібні, оскільки ви все це висвітлювали.

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

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

Тож остаточна надлишкова умова є джерелом ризику. Ось чому я краще пропоную пройти до фіналу else.

Редагування: кодування з високою надійністю

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

Це захисне кодування. Рекомендується деякими специфікаціями безпеки, такими як SEI CERT або MISRA . Деякі інструменти статичного аналізу навіть реалізують це як правило, яке систематично перевіряється (це може пояснити попередження вашого компілятора).


7
Що робити, якщо після остаточної умови надмірності я додаю ще одне, яке завжди кидає виняток, який говорить: "Ти не повинен був до мене звернутися!" - Чи полегшить це деякі проблеми мого підходу?
gaazkam

3
@gaazkam так, це дуже захисний стиль кодування. Отже, вам все одно потрібно обчислити остаточну умову, але для складних ланцюгів, схильних до помилок, ви принаймні швидко дізнаєтесь. Єдине питання, що я бачу з цим варіантом, - це те, що це очевидно трохи.
Крістоф

@gaazkam Я відредагував свою відповідь, щоб вирішити також вашу додаткову ідею, згадану у вашому коментарі
Крістоф

1
@gaazkam Кидати виняток - це добре. Якщо ви це зробите, не забудьте включити у повідомлення несподіване значення. Відтворити це може бути важко, і несподіване значення може дати зрозуміти джерело проблеми.
Мартін

1
Ще один варіант - поставити assertфінал else if. Однак ваш посібник зі стилів може відрізнятися від того, чи є це хороша ідея. (Умова assertніколи не повинна бути помилковою, якщо програміст не накрутив. Отже, принаймні це функція використовує за призначенням. Але так багато людей зловживають ним, що багато магазинів заборонили це прямо.)
Кевін,

5

Щось цього не вистачає у відповідях - це питання про те, який тип невдач менш шкідливий.

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

Ви опускаєте остаточний умовний варіант: Остаточний варіант виконується, навіть якщо це не правильно.

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

Ви додаєте остаточний умовний і виняток: Він кидає.

Ви повинні вирішити, який із цих варіантів найкращий. У коді розробки я вважаю це безпроблемним - візьміть третій випадок. Однак, напевно, я встановив би cir.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"
    }
}

Інструмент покриття коду часто може бути налаштований на ігнорування коду типу "стверджувати помилкове" зі звіту про покриття.


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


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

0

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

Тож якщо я на 100 відсотків впевнений, що одна з трьох умов повинна бути істинною, я пишу

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

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


-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

Я фактично пишу, якщо такі заяви, коли я навмисно очікую, можуть бути прийняті кілька справ. Особливо корисно це для мов з операторами переключення, які не дозволяють пропуститись. Я думаю, що якщо вибір взаємно виключається, його слід писати так, щоб було очевидно, що справи є взаємовиключними (використовуючи інше). І якщо це не написано так, то наслідком є ​​те, що випадки не є взаємовиключними (можливий провал).
Джеррі Єремія

@ gnasher729 Я повністю розумію вашу реакцію. Я знаю деяких дуже розумних людей, які мали проблеми з "уникати іншого" та "дострокового повернення" ... спочатку. Я закликаю вас зайнятись деяким часом, щоб звикнути до ідеї та вивчити її - тому що ці ідеї об'єктивно і вимірно знижують складність. Раннє повернення насправді стосується вашої першої точки (потрібно врахувати, чи може гілка змінити інший тест). І "можливість того, що жодна умова не відповідає дійсності" все ще існує незалежно; з цим стилем ви отримуєте очевидний виняток, а не прихований недолік логіки.
Джон Ву

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