Чи слід дотримуватися звичайного шляху чи рано провалюватися?


73

З книги « Повна кодова книга» надходить така цитата:

"Поставте звичайний випадок після, ifа не після else"

Що означає, що у elseвипадку повинні бути винятки / відхилення від стандартного шляху .

Але Прагматичний програміст привчає нас до "краху рано" (стор. 120).

Якого правила слід дотримуватися?


15
@gnat не дублювати
BЈович

16
двоє не є взаємовиключними, двозначності немає.
jwenting

6
Я не дуже впевнений, що ціна коду - така чудова порада. Імовірно, це спроба підвищити читабельність, але, безумовно, є ситуації, коли ви хочете, щоб нечасті випадки були текстовими першими. Зауважте, що це не відповідь; існуючі відповіді вже пояснюють плутанину, звідки ви виникаєте з питань.
Еймон Нербонна

14
Збій рано, важко врізайся! Якщо одна з ваших ifгілок повернеться, спочатку скористайтеся нею. І уникайте elseрешти коду, який ви вже повернули, якщо попередні умови не виконали. Код легше читати, менше відступів ...
CodeAngry

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

Відповіді:


189

"Збій рано" - це не те, який рядок коду надходить текстуально раніше. Він каже вам , щоб виявити помилки в найкоротші стадії обробки , так що ви випадково не ухвалювати рішення і розрахунки , засновані на вже несправному стані.

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


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

7
@Encaitar, цей рівень деталізації набагато менший, ніж загалом призначено, коли використовується словосполучення "провалитись рано".
riwalk

2
@Encaitar Це мистецтво програмування. Якщо ви зіткнулися з декількома перевірками, ідея полягає в тому, щоб спробувати той, який, швидше за все, справдиться спочатку. Однак ця інформація може бути відома не під час розробки, а на етапі оптимізації, але остерігайтеся передчасної оптимізації.
BPugh

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

Мови сценаріїв, такі як JavaScript, Python, perl, PHP, bash тощо, є винятками, оскільки вони лінійно інтерпретуються. У невеликих if/elseконструкціях це, мабуть, не має значення. Але ті, що викликаються в циклі або з багатьма висловлюваннями в кожному блоці, можуть працювати швидше, перш за все, найпоширеніша умова
DocSalvager

116

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

Замість цього:

if file.exists() :
  if validate(file) :
    # do stuff with file...
  else :
    throw foodAtMummy
else :
  throw toysOutOfPram

зробити це

if not file.exists() :
  throw toysOutOfPram

if not validate(file) :
  throw foodAtMummy

# do stuff with file...

Ви не хочете глибоко вкладати свій код, щоб просто включити перевірку помилок.

І, як усі інші вже заявили, дві поради не суперечать один одному. Одне - про наказ про виконання , інше - про порядок виконання коду .


4
Варто чітко пояснити, що порада щодо нормального потоку в блоці після ifта винятковий потік у блоці після elseне застосовується, якщо у вас немає else! Такі заяви, як ця, є кращою формою для обробки умов помилок у більшості стилів кодування.
Жуль

+1 - це сприятливий момент і насправді дає щось відповідь на реальне питання, як замовити речі з умовами помилок.
ashes999

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

27

Ви повинні слідувати обом.

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

Порада для нормальної справи спочатку означає, що якщо ви перевірите на стан, то найвірогідніший шлях повинен бути першим. Це допомагає в продуктивності (оскільки частіше буде прогнозувати гілка процесора) і читабельності, тому що вам не доведеться пропускати блоки блоків коду, намагаючись зрозуміти, що ця функція виконує в звичайному випадку.
Ця порада насправді не застосовується, коли ви перевіряєте на наявність попередньої умови та негайно випускаєте їх з застави (використовуючи твердження чи if (!precondition) throwконструкції), оскільки немає помилок при пропуску, щоб пропустити під час читання коду.


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

@ user136712: У сучасних (швидких) процесорах отримуються інструкції до того, як попередня інструкція закінчиться обробкою. Прогнозування гілок використовується для збільшення ймовірності того, що інструкції, отримані під час виконання умовної гілки, також є правильними інструкціями для виконання.
Барт ван Інген Шенау

2
Я знаю, що таке галузеве передбачення. Якщо я правильно прочитав ваш пост, ви скажете, що він if(cond){/*more likely code*/}else{/*less likely code*/}працює швидше, ніж if(!cond){/*less likely code*/}else{/*more likely code*/}через передбачення галузей. Я думаю, що галузеве передбачення не є упередженим ані твердженням, ifані elseтвердженням, а враховує лише історію. Тож якщо elseбільш шанси на те, що це станеться, слід передбачити це так само добре. Чи це припущення помилкове?
Роман Райнер

18

Я думаю, @JackAidley вже сказав суть цього , але дозвольте сформулювати це так:

без винятків (наприклад, C)

У регулярному потоці коду у вас є:

if (condition) {
    statement;
} else if (less_likely_condition) {
    less_likely_statement;
} else {
    least_likely_statement;
}
more_statements;

У випадку "рано помилка" ваш код раптом звучить:

/* demonstration example, do NOT code like this */
if (condition) {
    statement;
} else {
    error_handling;
    return;
}

Якщо ви помітили цю модель - це returnв else(або навіть if) блоці, переробити його негайно , так що код в питанні зовсім НЕ має elseблоку:

/* only code like this at University, to please structured programming professors */
function foo {
    if (condition) {
        lots_of_statements;
    }
    return;
}

У реальному світі ...

/* code like this instead */
if (!condition) {
    error_handling;
    return;
}
lots_of_statements;

Це дозволяє уникнути занадто глибокого гніздування та виконує випадок «рано вирватися» (допомагає зберегти розум - і потік коду - чистим) і не порушує «поставити більш детальну річ у ifчастину», оскільки просто немає її elseчастини .

C та очищення

Ось натхненний відповіддю на аналогічне запитання (яке помилилось), ось як зробити очищення за допомогою C. Тут ви можете використовувати одну або дві точки виходу, ось одна для двох точок виходу:

struct foo *
alloc_and_init(size_t arg1, int arg2)
{
    struct foo *res;

    if (!(res = calloc(sizeof(struct foo), 1)))
        return (NULL);

    if (foo_init1(res, arg1))
        goto err;
    res.arg1_inited = true;
    if (foo_init2(&(res->blah), arg2))
        goto err;
    foo_init_complete(res);
    return (res);

 err:
    /* safe because we use calloc and false == 0 */
    if (res.arg1_inited)
        foo_dispose1(res);
    free(res);
    return (NULL);
}

Ви можете згорнути їх в одну точку виходу, якщо робити менше очищення:

char *
NULL_safe_strdup(const char *arg)
{
    char *res = NULL;

    if (arg == NULL)
        goto out;

    /* imagine more lines here */
    res = strdup(arg);

 out:
    return (res);
}

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

Винятки

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

<igli> exceptions: a truly awful implementation of quite a nice idea.
<igli> just about the worst way you could do something like that, afaic.
<igli> it's like anti-design.
<mirabilos> that too… may I quote you on that?
<igli> sure, tho i doubt anyone will listen ;)

Але ось пропозиція, як ви робите це добре мовою з винятками, і коли ви хочете їх добре використовувати:

повернення помилок перед винятками

Можна замінити більшу частину ранніх returnс викиданням винятку. Однак ваш нормальний потік програми, тобто будь-який потік коду, в якому програма не зустрічається, ну, виняток… Умова помилки або щось таке, не повинно викликати жодних винятків.

Це означає що…

# this page is only available to logged-in users
if not isLoggedIn():
    # this is Python 2.5 style; insert your favourite raise/throw here
    raise "eh?"

... це добре, але ...

/* do not code like this! */
try {
    openFile(xyz, "rw");
} catch (LockedException e) {
    return "file is locked";
}
closeFile(xyz);
return "file is not locked";

… не. В основному виняток не є елементом керуючого потоку . Це також робить операції дивними на вас ("ті програмісти Java ™ завжди говорять нам, що ці винятки є нормальними") і можуть перешкоджати налагодженню (наприклад, скажіть IDE просто зламати будь-який виняток). Винятки часто вимагають, щоб середовище виконання часу розмотувало стек для створення слідів тощо. Напевно, існує більше причин проти цього.

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

загальна рада wrt. Винятки

Спробуйте спочатку отримати все винятки в програмі, про які погодилася вся команда розробників. В основному, плануйте їх. Не використовуйте їх в достатку. Іноді, навіть у C ++, Java ™, Python, повернення помилок краще. Іноді це не так; використовувати їх з думкою.


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

1
@DanMan: моя стаття була написана на увазі С ... Я зазвичай не використовую винятків. Але я продовжив статтю (ой, вона отримала досить довгу) пропозицію wrt. Винятки; до речі, у нас було те саме запитання щодо внутрішнього списку розсилки компанії вчора ...
mirabilos

Також використовуйте дужки навіть на однорядних ifs і fors. Ви не хочете, щоб черговий goto fail;прихований в ідентичності.
Бруно Кім

1
@BrunoKim: це повністю залежить від стилю / кодування умов проекту, з яким ви працюєте. Я працюю з BSD, де це нахмуриться (більше оптичного захаращення та втрати вертикального простору); при $ dayjob, однак я розміщую їх так, як ми домовилися (менш важко для новачків, менше шансів помилок тощо, як ви вже говорили).
mirabilos

3

На мою думку, "Умова охорони" - це один з найкращих і найпростіших способів зробити код читабельним. Я дуже ненавиджу, коли бачу ifна початку методу і не бачу elseкод, оскільки він вимкнений з екрана. Я повинен прокрутити вниз, щоб побачити throw new Exception.

Поставте чеки на початку, щоб особа, що читає код, не повинна перескакувати метод, щоб прочитати, а натомість завжди сканувати його зверху вниз.


2

(@Mirabilos ' відповідь відмінно, але ось як я думаю про питання , щоб досягти такої ж висновок :)

Я думаю про себе (чи когось іншого), читаючи пізніше код своєї функції. Коли я читаю перший рядок, я не можу робити жодних припущень щодо свого введення (крім тих, які я все одно не перевірятиму). Тож моя думка: «Гаразд, я знаю, що я буду робити справи з моїми аргументами. Але спочатку давайте« очистимо їх »- тобто вб’ємо контрольні шляхи, в яких вони мені не подобаються». Але в той же час , Я не бачу нормального випадку як щось обумовлене; я хочу підкреслити його нормальність.

int foo(int* bar, int baz) {

   if (bar == NULL) /* I don't like you, leave me alone */;
   if (baz < 0) /* go away */;

   /* there, now I can do the work I came into this function to do,
      and I can safely forget about those if's above and make all 
      the assumptions I like. */

   /* etc. */
}

-3

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

Іншими словами:

A. критичний розділ і без за замовчуванням => Ранній помилки

B. Некритичний розділ та параметри за замовчуванням => Використовуйте параметри за замовчуванням в іншій частині

C. у проміжках між справами => вирішити за кожним випадком


це лише ваша думка, або ви можете якось пояснити / підкріпити?
гнат

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

Я б не хотів цього сказати, але голоси в моїй відповіді є поза контекстом :). це питання, яке поставив ОП, чи є у вас альтернативна відповідь - це зовсім інше питання
Нікос М.

Я чесно не бачу пояснення тут. Скажіть, якщо хтось пише іншу думку, наприклад "критичний розділ і не за замовчуванням => не відмовте рано" , як ця відповідь допоможе читачеві вибрати дві протилежні думки? Розглянемо редагування ІНГ його в кращій формі, щоб відповідати Як відповідати правилам.
гнат

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