Еще или вернуть?
Какой из следующих двух лучше всего подходит для производительности и стандартной практики. Как.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, если вы работаете с многопоточными приложениями, то лучшим вариантом может быть ваш первый пример. В любом случае для небольших кусков кода это не будет иметь никакого значения (вероятно, даже для некоторых больших). Самое главное, что вы пишете, как вам удобно.