Еще или вернуть?

Какой из следующих двух лучше всего подходит для производительности и стандартной практики. Как.NET внутренне обрабатывает эти два фрагмента кода?

Code1

If(result)
{
  process1();
}
else
{
  process2();
}

Или код 2

If(result)
{
   process1();
   return;
}
process2();

19 ответов

Решение

Разница в производительности, если таковая имеется, незначительна в любом нормальном случае.

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

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

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

if (result)
{
    // do something
    return;
}

// do something if not result

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

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

Я бы сказал, что выбор между ними зависит от семантики.

"Если какое-то условие истинно, тогда сделайте это, в противном случае сделайте это" отлично отображается на if-else.

if (isLoggedIn) {
    RedirectToContent();
} else {
    RedirectToLogin();
}

"Если какое-то условие, а затем выполнить некоторую очистку и спасение" лучше отображает код 2. Это называется шаблоном защиты. Это делает тело кода нормальным, ясным и не загроможденным ненужными отступами. Обычно он используется для проверки параметров или состояния (проверьте, не содержит ли что-то значение NULL или что-то в кэше, и тому подобное).

if (user == null) {
    RedirectToLogin();
    return;
}

DisplayHelloMessage(user.Name);

Нередко обе формы используются в одном проекте. Как использовать, как я уже сказал, зависит от того, что вы пытаетесь передать.

Если есть общий код, который необходимо выполнить после блока if/else, тогда вариант 1.

Если блок if-else является последним, что нужно сделать в функции, то вариант 2.

Лично я всегда использую опцию 1. Если есть возвращаемое состояние, оно идет после блока else

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

Это, как говорится, это вопрос мнения. Пока вы последовательны в этом, выберите один и придерживайтесь его.

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

Они оба будут компилироваться в один и тот же IL для режима Release (может быть несколько разных операндов Nop в Debug..) И, как таковые, не будут иметь разницы в производительности. Это полностью зависит от того, как вы и ваша команда чувствуете, что код легче читать.

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

// C#
public static void @elseif(bool isTrue)
{
    if (isTrue)
        Process1();
    else
        Process2();
}
// IL
.method public hidebysig static void  elseif(bool isTrue) cil managed
{
  // Code size       15 (0xf)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  brfalse.s  IL_0009
  IL_0003:  call       void elseif.Program::Process1()
  IL_0008:  ret
  IL_0009:  call       void elseif.Program::Process2()
  IL_000e:  ret
} // end of method Program::elseif


// C#
public static void @earlyReturn(bool isTrue)
{
    if (isTrue)
    {
        Process1();
        return;
    }
    Process2();
}
// IL
.method public hidebysig static void  earlyReturn(bool isTrue) cil managed
{
  // Code size       15 (0xf)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  brfalse.s  IL_0009
  IL_0003:  call       void elseif.Program::Process1()
  IL_0008:  ret
  IL_0009:  call       void elseif.Program::Process2()
  IL_000e:  ret
} // end of method Program::earlyReturn

Вот некоторые дополнительные материалы по пункту охраны: http://www.c2.com/cgi/wiki?GuardClause

Один термин, который я не видел, упомянул, что я считаю важным - цель пункта охраны - повысить удобочитаемость. Методы с одним выходом могут стремиться к коду "стрелки" (где вложение операторов создает стрелку).

Не могу сказать о производительности, но код 1 выглядит намного понятнее и логичнее для меня. Выпрыгнув из функции в середине if блок выглядит довольно запутанным и легко пропустить.

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

Придерживаться одной точки выхода для рутины - это хорошая практика.

Однако иногда множественные возвраты делают код намного более понятным, особенно когда у вас есть несколько тестов в начале кода (т. Е. Проверяется, все ли входные параметры имеют правильный формат), для которых 'if-true' должно привести к возвращению.

то есть:

if (date not set) return false;
age = calculateAgeBasedOnDate();
if (age higher than 100) return false;
...lots of code...
return result;

Я хотел бы использовать Code 1потому что, если я добавлю что-то позже после if заявление, я все еще уверен, что оно будет выполнено, без необходимости помнить, чтобы удалить return пункт от того, где он находится в Code 2,

Оба стиля являются обычным явлением, и за них велись религиозные войны.:-)

Я обычно делаю это:

  • Если тест выражает семантику контракта метода, например, проверяет правильность входных параметров, выберите вариант 2.
  • В противном случае выберите вариант 1.

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

Разница в производительности незначительна, как говорили другие.

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

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

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

Моя обычная практика - иметь оператор if-else и один оператор return.

Например,

type returnValue;
if(true)
{
 returnValue = item;
}
else
 returnValue = somethingElse;

return returnValue;

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

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

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

 if (con){
    ...
    return;
 }
 if (other con){
    ...
    return;
 }
 ...
 return;

Это зависит от того, что является "результатом", "процессом1" и "процессом2".

Если process2 является логическим следствием того, что "result" является ложным; вам следует перейти к коду 1. Это наиболее читаемый шаблон, если process1 и process2 являются эквивалентными альтернативами.

if (do_A_or_B = A)
  A()
else
  B()

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

if (NOT can_do_B)
{
  A() 
  return
}

B()

Это зависит от контекста.

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

Если это код, который является частью логики программы, то лучше быть максимально точным (вариант 1). Кто-то, взглянув на вариант 1, поймет, что вы имеете в виду (сделайте это ИЛИ это), где вариант 2 может быть ошибкой (в этом случае сделайте это, ТОГДА ВСЕГДА сделайте это - я просто исправлю это для вас!). Если бы это было раньше.

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

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

if(result)
{
  process 1
  return;
}

process 2

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

Другие вопросы по тегам