Должен ли я идти по обычному пути или рано провалиться?

73

Из книги « Полный код» приводится следующая цитата:

«Положите нормальный случай после, ifа не после else»

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

Но Прагматичный Программист учит нас «рано падать» (стр. 120).

Какому правилу я должен следовать?

Джао
источник
15
@gnat не дублируется
BЈовић
16
два не являются взаимоисключающими, нет никакой двусмысленности.
jwenting
6
Я не уверен, что цитата с полным кодом - такой хороший совет. Предположительно, это попытка улучшить читабельность, но, безусловно, существуют ситуации, когда вы хотите, чтобы необычные случаи были в первую очередь текстовыми. Обратите внимание, что это не ответ; Существующие ответы уже объясняют путаницу, с которой ваш вопрос хорошо связан.
Имон Нербонн
14
Сбой рано, с треском тяжело! Если одна из ваших ifветок вернется, сначала используйте ее. И избегайте elseостальной части кода, вы уже вернулись, если предварительные условия не удалось. Код легче читать, меньше отступов ...
CodeAngry
5
Только я думаю, что это не имеет никакого отношения к читабельности, а вместо этого было, вероятно, просто ошибочной попыткой оптимизации для статического предсказания ветвлений?
Мердад

Ответы:

189

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

В конструкции if/ выполняется elseтолько один из блоков, поэтому нельзя сказать, что ни один из них составляет «более ранний» или «более поздний» этап. Как их заказать, поэтому вопрос читабельности, и «ранний сбой» не влияет на решение.

Килиан Фот
источник
2
Ваше общее мнение замечательно, но я вижу одну проблему. Если у вас есть (if / else if / else), то выражение, вычисленное в «else if», вычисляется после выражения в выражении «if». Важным моментом, как вы указываете, является удобочитаемость по сравнению с концептуальной единицей обработки. Выполняется только один кодовый блок, но можно оценить несколько условий.
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, будет работать быстрее, чем код, который с большей вероятностью перейдет в случай else. Я имею в виду, в этом весь смысл предсказания ветвления, не так ли?
Роман Райнер
@ 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()в библиотеке также…) Но что бы вы ни делали, не, как правило, выдается исключение, если операция продолжается (более или менее) нормально после нее.

общий совет относительно Исключения

Постарайтесь сначала использовать все исключения в программе, согласованные всей командой разработчиков. В основном, планируйте их. Не используйте их в изобилии. Иногда даже в C ++, Java ™, Python возврат ошибок лучше. Иногда это не так; используйте их с мыслью.

mirabilos
источник
В общем, я рассматриваю ранний возврат как запах кода. Вместо этого я бы выдал исключение, если следующий код потерпит неудачу, потому что предварительное условие не выполнено. Просто
скажи
1
@DanMan: моя статья была написана с учетом C ... Я обычно не использую исключения. Но я расширил статью предложением (к сожалению, оно получилось довольно длинным). Исключения; кстати, у нас вчера был тот же самый вопрос во внутреннем списке рассылки компании…
mirabilos
Кроме того, используйте фигурные скобки даже в однострочных операторах if и for. Вы не хотите, чтобы другой goto fail;скрытый в обличье.
Бруно Ким
1
@BrunoKim: это полностью зависит от соглашения о стиле / кодировании проекта, с которым вы работаете. Я работаю с BSD, где это осуждается (более оптический беспорядок и потеря вертикального пространства); однако в $ dayjob я размещаю их, как мы договорились (менее сложно для новичков, меньше вероятность ошибок и т. д., как вы сказали).
Мирабилось
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. */
}
einpoklum - восстановить Монику
источник
-3

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

Другими словами:

A. критический раздел и нет значений по умолчанию => Fail Early

B. некритический раздел и значения по умолчанию => Использовать значения по умолчанию в остальной части

C. промежуточные дела => решать по каждому делу по мере необходимости

Никос М.
источник
это просто ваше мнение или вы можете как-то объяснить / поддержать это?
комнат
как именно это не резервное копирование, как каждый вариант объясняет (без многих слов), почему он используется?
Никос М.
Я не хотел бы говорить это, но отрицательные отзывы в (моем) этом ответе находятся вне контекста :). Это вопрос, который ОП задал, есть ли у вас альтернативный ответ - это совсем другое дело
Никос М.
Я, честно говоря, не вижу здесь объяснения. Скажем, если кто-то напишет другое мнение, например, «критическая секция и никаких значений по умолчанию => не терпеть неудачу рано» , как этот ответ поможет читателю выбрать два противоположных мнения? Попробуйте отредактировать его в лучшую форму, чтобы соответствовать рекомендациям « Как ответить» .
комнат
Хорошо, я понимаю, это действительно может быть другим объяснением, но, по крайней мере, вы понимаете слова «критический раздел» и «нет значений по умолчанию», которые могут означать, что стратегия проваливается рано, и это действительно расширение, хотя и минималистичное
Никос М.