Должен ли я вернуться из функции раньше или использовать оператор if? [закрыто]

303

Я часто писал такого рода функции в обоих форматах, и мне было интересно, если один формат предпочтительнее другого и почему.

public void SomeFunction(bool someCondition)
{
    if (someCondition)
    {
        // Do Something
    }
}

или же

public void SomeFunction(bool someCondition)
{
    if (!someCondition)
        return;

    // Do Something
}

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

Рэйчел
источник
9
Я немного опоздал на эту дискуссию, поэтому не буду ставить это в ответ; Я также думал об этом два года назад: lecterror.com/articles/view/code-formatting-and-readability Я считаю, что второе легче читать, изменять, поддерживать и отлаживать. Но, возможно, это только я :)
доктор Ганнибал Лектер
4
Теперь этот вопрос является прекрасным примером основанного на мнении вопроса
Рудольф Олах
2
ну и что, если нет абсолютного доказательства в том или ином направлении? Когда в том и другом направлении предоставляется достаточное количество аргументов, и если голос правильный, проводится голосование, это делает его очень полезным. Я считаю, что закрывающие вопросы, подобные этому, вредны для ценности этого сайта.
GSF
9
И мне нравятся основанные на мнении вопросы и ответы. Они говорят мне, что предпочитает большинство, и это позволяет мне писать код для чтения другими.
Сигизмунд

Ответы:

401

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

Мейсон Уилер
источник
7
Smalltalk называет эти «пункты охраны». По крайней мере, это то, что Кент Бек называет их в шаблонах Besttalk Best Practice; Я не знаю, является ли это обычным языком.
Фрэнк Шиарар
154
Ранний выход позволяет выкинуть вещи из вашего ограниченного умственного стека. :)
Joren
50
Ранее я слышал, что это называется «Образец вышибала» - избавьтесь от плохих дел, прежде чем они попадут в дверь.
RevBingo
14
Я бы даже зашел так далеко и сказал, что это, безусловно, ЕДИНСТВЕННО правильная вещь.
Оливер Вейлер
38
Кроме того, вы не будете добавлять отступы, если вначале избавитесь от пограничных случаев.
doppelgreener
170

Определенно последнее. Первый не выглядит плохо сейчас, но когда вы получаете более сложный код, я не могу себе представить, что кто-то подумает так:

public int SomeFunction(bool cond1, string name, int value, AuthInfo perms)
{
    int retval = SUCCESS;
    if (someCondition)
    {
        if (name != null && name != "")
        {
            if (value != 0)
            {
                if (perms.allow(name)
                {
                    // Do Something
                }
                else
                {
                    reval = PERM_DENY;
                }
            }
            else
            {
                retval = BAD_VALUE;
            }
        }
        else
        {
            retval = BAD_NAME;
        }
    }
    else
    {
        retval = BAD_COND;
    }
    return retval;
}

более читабельно, чем

public int SomeFunction(bool cond1, string name, int value, AuthInfo perms)
{
    if (!someCondition)
        return BAD_COND;

    if (name == null || name == "")
        return BAD_NAME;

    if (value == 0)
        return BAD_VALUE;

    if (!perms.allow(name))
        return PERM_DENY;

    // Do something
    return SUCCESS;
}

Я полностью признаю, что никогда не понимал преимущества единых точек выхода.

Jason Viers
источник
20
Преимущество единой точки выхода в том, что ... есть единственная точка выхода! В вашем примере есть несколько моментов, которые могут вернуться. С более сложной функцией, она может превратиться в точку поиска, когда меняется формат возвращаемого значения. Конечно, бывают случаи, когда форсирование одной точки выхода не имеет смысла.
JohnL
71
@JohnL большие функции - это проблема, а не множественные точки выхода. Если вы не работаете в контексте, где дополнительный вызов функции, конечно,
сильно
4
@ Яр: Верно, но точка зрения стоит. Я не собираюсь никого пытаться преобразовать, и я просто указывал на преимущество в том, чтобы использовать как можно меньше точек выхода (и в любом случае пример Джейсона был соломенным человеком). Просто в следующий раз, когда вы пытаетесь понять, почему SomeFunction иногда возвращает нечетные значения, возможно, вы захотите добавить вызов регистрации непосредственно перед возвратом. Намного легче отлаживать, если есть только один из ошибок! :)
JohnL
76
@ Джейсон Вирс Как будто один return value;помогает!?! Затем нужно охотиться на полдюжины value = ..., с тем недостатком, что вы никогда не уверены, что значение не изменится между этим заданием и окончательным возвратом. По крайней мере, немедленное возвращение ясно, что результат больше ничего не изменит.
Sjoerd
5
@Sjoed: я второй Sjoerd здесь. Если вы хотите записать результат, вы можете войти на сайт звонящего. Если вы хотите узнать причину, вы должны регистрироваться в каждой точке выхода / назначении, чтобы оба идентичных в этом аспекте.
Матье М.
32

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

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

public int myFunction(string parameterOne, string parameterTwo) {
  // Can't work without a value
  if (string.IsNullOrEmpty(parameterOne)) {
    throw new ArgumentNullException("parameterOne");
  } 
  if (string.IsNullOrEmpty(parameterTwo)) {
    throw new ArgumentNullException("parameterTwo");
  }

  // ...      
  // Do some work
  // ...

  return value;
}
rjzii
источник
9
И если конечный результат является ремонтопригодным, кого волнует, какой стиль был выбран?
Джефф Сивер
3
@Jeff Siver - Таким образом, почему это, как правило, вопрос стиля «священной войны», в конце концов, он сводится к личным предпочтениям и тому, что говорит внутренний гид по стилю.
rjzii
1
Ключевым моментом здесь является то, что он выбрасывает исключение, а не возвращается рано. Возвращаемое значение не должно быть переопределено для проверок достоверности. Что если у вас возникли различные условия и вы хотите, чтобы код, использующий метод, знал, почему он не работает? Внезапно вы можете вернуть фактические бизнес-данные, ничего (пустой результат) или много разных строк, кодов, чисел, ... из одного единственного метода. Просто чтобы описать, почему это не удалось. Спасибо, не надо.
DanMan
как насчет цикломатической сложности, как подсказывает мой компилятор? не лучше ли не вкладывать код, если можно в этом помочь?
l --''''''--------- '' '' '' '' '' ''
24

Я предпочитаю раннее возвращение.

Если у вас есть одна точка входа и одна точка выхода, то вы всегда должны отслеживать весь код в своей голове вплоть до точки выхода (вы никогда не узнаете, делает ли какой-то другой фрагмент кода ниже что-то еще с результатом, поэтому вы надо отследить, пока не появятся). Вы делаете это независимо от того, какая ветвь определяет конечный результат. Это трудно понять.

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

оборота user7197
источник
13

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

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

Я недостаточно осведомлен о Java, будет ли вызываться блочный код finally, и смогут ли финализаторы справиться с ситуацией, когда нужно что-то гарантировать.

C # Я, конечно, не могу ответить на.

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

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

Дойная корова
источник
1
C ++ допускает то, что называется optmization возвращаемого значения, что позволяет компилятору по существу пропустить операцию копирования, которая обычно происходит при возврате значения. Однако при различных условиях это трудно сделать, и несколько возвращаемых значений - один из таких случаев. Другими словами, использование нескольких возвращаемых значений в C ++ может на самом деле замедлить ваш код. Это, безусловно, применимо к MSC, и, учитывая, что реализовать RVO с несколькими возможными возвращаемыми значениями было бы довольно сложно (если не невозможно), вероятно, это проблема всех компиляторов.
Имон Нербонн
1
В C просто используйте gotoи, возможно, двухточечный возврат. Пример (форматирование кода в комментариях невозможно):foo() { init(); if (bad) goto err; bar(); if (bad) goto err; baz(); return 0; err: cleanup(); return 1; }
mirabilos
1
Вместо goto я предпочитаю «Метод извлечения». Когда вы думаете, что вам нужно реализовать переменную возвращаемого значения или goto, чтобы гарантировать, что ваш код очистки всегда вызывается, это запах, который вам нужно разбить на несколько функций. Это позволяет вам упростить ваш сложный условный код, используя пункты охраны и другие методы раннего возврата, при этом гарантируя, что ваш код очистки всегда выполняется.
BrandonLWhite
«Кто-то может отредактировать вашу функцию» - это такое нелепое объяснение для принятия решения. Любой может сделать что-нибудь в будущем. Это не значит, что вы должны сделать что-то конкретное сегодня, чтобы кто-то не сломал вещи в будущем.
Виктор Ярема
Вызывается сделать ваш код ремонтопригодным. В реальных условиях код пишется для деловых целей, и иногда разработчику необходимо его изменить. В то время, когда я писал этот ответ, я исправил CURL, чтобы ввести кеширование и вспомнить беспорядок в спагетти, который действительно нуждался в надлежащей переписке в современном C ++
CashCow
9

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

STW
источник
9

Я использую оба.

Если DoSomething3-5 строк кода, то код выглядит красиво, используя первый метод форматирования.

Но если в нем много строк, я предпочитаю второй формат. Мне не нравится, когда открывающие и закрывающие скобки не на одном экране.

azheglov
источник
2
Красиво никак! Слишком много отступов!
JimmyKane
@JimmyKane В 3-5 строк может быть только столько отступов, тем более, что вам нужно 2 (?) Строки на уровень, остальные получают отступы: одну для управляющей структуры и начала блока, одну для конца блока.
дедупликатор
8

Классическая причина единственного входа-единственного выхода состоит в том, что в противном случае формальная семантика становится невыразимо уродливой в противном случае (та же самая причина, по которой GOTO считался вредным).

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

Обычно я минимизирую подход с ранним возвратом.

Пол Натан
источник
но для инструмента формального анализа вы можете синтезировать внешнюю функцию с семантикой, в которой нуждается инструмент, и сохранять код понятным для человека.
Тим Уиллискрофт
@Тим. Это зависит от того, сколько выпить ты хочешь и что анализируешь. Я нахожу SESE довольно читабельным, если кодер был в здравом уме.
Пол Натан
Мое отношение к анализу определяется оптимизацией проекта Self. 99% динамических вызовов методов могут быть статически разрешены и исключены. так что тогда вы можете проанализировать довольно прямой код. Как работающий программист, я могу с уверенностью предположить, что большая часть кода, над которым я буду работать, была написана обычными программистами. Поэтому они не будут писать очень хороший код.
Тим Уиллискрофт
@ Тим: достаточно справедливо. Хотя я чувствую себя обязанным указать, что статические языки все еще играют.
Пол Натан
Причина, которая не держится перед лицом исключений. Вот почему одиночный выход хорош в C, но ничего не дает в C ++.
peterchen
7

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

Натан
источник
6

По-разному.

Ранний возврат, если есть некоторое очевидное тупиковое условие, которое нужно проверить сразу, что сделает бессмысленным запуск остальной части функции. *

Установите Retval + single return, если функция более сложна и может иметь несколько точек выхода в противном случае (проблема читабельности).

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

Бобби Столы
источник
6
Когда я пишу код, которым можно поделиться, моя мантра звучит так: «Ничего не принимай. Не доверяй никому». Вы должны всегда проверять свои входные данные и любое внешнее состояние, от которого вы зависите. Лучше бросить исключение, чем возможно испортить что-то, потому что кто-то дал вам плохие данные.
TMN
@ TMN: Хороший вопрос.
Бобби Столы
2
Ключевым моментом здесь является то, что в ОО вы генерируете исключение , а не возврат . Многократные возвраты могут быть плохими, многократные исключения не обязательно являются запахом кода.
Майкл К
2
@MichaelK: метод должен быть исключением, если постусловие не может быть выполнено. В некоторых случаях метод должен завершиться досрочно, поскольку постусловие было достигнуто еще до запуска функции. Например, если вызвать метод «Установить метку элемента управления», чтобы изменить метку элемента управления, метка Fredокна уже установлена Fred, а установка имени элемента управления в его текущее состояние приведет к перерисовке (что, хотя в некоторых случаях потенциально полезно, может привести к надоедать одному), было бы разумно иметь метод set-name для досрочного выхода, если старые и новые имена совпадают.
суперкат
3

Используйте If

В книге Дона Кнута о GOTO я читал, как он объясняет причину, по которой наиболее вероятное условие стоит первым в утверждении if. При условии, что это все еще разумная идея (а не исключение из соображений скорости эпохи). Я бы сказал, что ранние возвраты не являются хорошей практикой программирования, особенно если учесть тот факт, что они чаще всего используются для обработки ошибок, если ваш код с большей вероятностью не сработает, чем не сработает :-)

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

Delphi Specific ...

Я считаю, что это хорошая практика программирования для программистов на Delphi, хотя у меня нет никаких доказательств. Pre-D2009, мы не имеем атомный способ вернуть значение, у нас есть exit;и result := foo;или мы могли бы просто выбросить исключение.

Если бы вам пришлось заменить

if (true) {
 return foo;
} 

за

if true then 
begin
  result := foo; 
  exit; 
end;

Вы можете просто устать от того, что видите это на вершине каждой из ваших функций и предпочитаете

if false then 
begin
  result := bar;

   ... 
end
else
   result := foo;

и просто избегать exitвообще.

Peter Turner
источник
2
С новым Delphis, это может быть сокращено до if true then Exit(foo);Я использую часто метод для первой инициализации resultдо nilили FALSEсоответственно, а затем проверить все условия ошибки и только Exit;если один удовлетворяются. Случай успеха result(обычно) устанавливается где-то в конце метода.
JensG
да, мне нравится эта новая функция, хотя кажется, что это просто конфетка, чтобы успокоить Java-программистов, следующее, что вы знаете, они позволят нам определять наши переменные внутри процедур.
Питер Тернер
И программисты на C #. Да, если честно, я нашел бы это действительно полезным, так как он уменьшает количество строк между объявлением и использованием (у IIRC есть даже метрика для этого, забыл название).
JensG
2

Я согласен со следующим утверждением:

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

Взято из этого вопроса в stackoverflow .

Тото
источник
+1 За охрану. Я также предпочитаю положительно ориентированную защиту, например: возвращение if (условие = false) вместо возврата if (! Условие)
JustinC
1

Я использую ранний возврат почти исключительно в эти дни, до крайности. Я пишу это

self = [super init];

if (self != nil)
{
    // your code here
}

return self;

как

self = [super init];
if (!self)
    return;

// your code here

return self;

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

Dan Rosenstark
источник
Я согласен. Отступ - это то, что вызывает проблемы при чтении. Чем меньше отступ, тем лучше. Ваш простой пример имеет такой же уровень отступов, но этот первый пример, безусловно, вырастет до большего количества углублений, которые требуют большей мощности мозга.
user441521
1

Я бы предпочел написать:

if(someCondition)
{
    SomeFunction();
}
Josh K
источник
2
EWW. Это предварительная проверка? Или это в методе, посвященном проверке DoSomeFunctionIfSomeCondition?
STW
Как это держит ваши проблемы отдельно?
Justkt
2
Вы нарушаете инкапсуляцию, делая реализацию функции (ее логику зависимости) внешней.
TMN
1
Если это выполняется в публичном методе, а SomeFunction () является частным методом в том же классе, это может быть нормально. Но если кто-то еще вызывает SomeFunction (), вам придется дублировать проверки там. Я считаю, что лучше, чтобы каждый метод позаботился о том, что ему нужно для своей работы, и никто другой не должен знать об этом.
В Викландере
2
Это определенно стиль, который Роберт С. Мартин предлагает в «Чистом коде». Функция должна делать только одно. Однако в «Образцах реализации», Кент Бек предлагает, из двух вариантов, предложенных в ОП, второй лучше.
Скотт Уитлок
1

Как и вы, я обычно пишу первый, но предпочитаю последний. Если у меня много вложенных проверок, я обычно рефакторинг ко второму методу.

Мне не нравится, как обработка ошибок удалена от проверки.

if not error A
  if not error B
    if not error C
      // do something
    else handle error C
  else handle error B
else handle error A

Я предпочитаю это:

if error A
  handle error A; return
if error B
  handle error B; return
if error C
  handle error C; return

// do something
толчка
источник
0

Условия наверху называются «предварительными условиями». Ставя if(!precond) return;, вы визуально перечисляете все предпосылки.

Использование большого блока if-else может увеличить накладные расходы на отступ (я забыл цитату о трехуровневых отступах).

мин-тан
источник
2
Какая? Вы не можете сделать ранний возврат в Java? C #, VB (.NET и 6) и, очевидно, Java (что я и предполагал, но должен был искать, так как я не использовал язык в течение 15 лет) - все допускают досрочный возврат. так что не обвиняйте «строго типизированные языки» в отсутствии такой возможности. stackoverflow.com/questions/884429/…
ps2goat
-1

Я предпочитаю сохранить, если заявления небольшие.

Итак, выбирая между:

if condition:
   line1
   line2
    ...
   line-n

а также

if not condition: return

line1
line2
 ...
line-n

Я бы выбрал то, что вы описали как «раннее возвращение».

Имейте в виду, я не забочусь о досрочном возврате или о чем-то другом, я просто очень хотел бы упростить код, сократить тела операторов if и т. Д.

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

hasen
источник
-2

Как говорят другие, это зависит. Для маленьких функций, которые возвращают значения, я могу закодировать ранние результаты. Но для значительных функций мне нравится всегда иметь место в коде, где я знаю, что я могу поместить что-то, что будет выполнено до его возвращения.

Майк Данлавей
источник
-2

Я практикую безотказно на функциональном уровне. Он сохраняет код непротиворечивым и чистым (для меня и тех, с кем я работал). Из-за этого я всегда возвращаюсь рано.

Для некоторых часто проверяемых условий вы можете реализовать аспекты для этих проверок, если используете AOP.

Стивен Эверс
источник