Какой из следующих двух является лучшим wrt к производительности и общепринятой практике. Как делает.NET, внутренне обрабатывает эти два фрагмента кода?
Code1
If(result)
{
process1();
}
else
{
process2();
}
Или код 2
If(result)
{
process1();
return;
}
process2();
Разница в производительности, если таковая имеется, в любом нормальном случае незначительна.
Одна из стандартных практик (среди прочих) - попытаться сохранить единственную точку выхода из метода, чтобы говорить в пользу первой альтернативы.
Фактическая реализация для return
в середине, скорее всего, сделает переход к концу метода, где код для завершения кадра стека для метода, поэтому вполне вероятно, что окончательный исполняемый код идентичен для двух кодов.
Это зависит от контекста.
Если это функция, которая при вычислении значения возвращает это значение, как только оно у вас есть (вариант 2). Вы показываете, что выполнили всю необходимую работу, и уходите.
Если это код, который является частью логики программы, то лучше быть как можно точнее (вариант 1). Кто-то, смотрящий на вариант 1, будет знать, что вы имеете в виду (сделайте это ИЛИ то), где вариант 2 может быть ошибкой (в этом состоянии делайте это ТОГДА ВСЕГДА делайте это - я просто исправлю это для вас!). Было это раньше.
При компиляции они обычно будут такими же, но нас не интересует производительность. Речь идет о удобочитаемости и ремонтопригодности.
Если бы мне пришлось сказать, я бы сказал Лучшая практика - это if-else, а не "подразумеваемое" else. Причина в том, что если кто-то другой изменяет ваш код, он может легко уловить это, взглянув на него.
Я напоминаю, что в мире программирования ведутся большие споры о том, следует ли иметь в коде несколько операторов возврата. Можно сказать, что это источник большой путаницы, потому что, если у вас есть несколько циклов в операторе «if» и есть условный возврат, это может вызвать некоторую путаницу.
Моя обычная практика - иметь оператор if-else и единственный оператор return.
Например,
type returnValue;
if(true)
{
returnValue = item;
}
else
returnValue = somethingElse;
return returnValue;
На мой взгляд, вышесказанное более читабельно. Однако это не всегда так. Иногда лучше иметь оператор return в середине оператора if, особенно если он требует сложного оператора return.
Я предпочитаю использовать единую точку выхода, что очень полезно при реализации блокировок в многопоточной среде, чтобы убедиться, что блокировки сняты . С первой реализацией это сделать сложнее.
Я бы использовал код 1
, потому что, если я добавлю что-то позже после оператора if
, я все равно уверен, что он выполнится, и мне не нужно будет помнить об удалении возвращает предложение
из того места, где оно находится в коде 2
.
Это зависит от того, что такое «результат», «процесс1» и «процесс2».
Если процесс2 является логическим следствием ложного результата "результат"; вам следует выбрать код 1. Это наиболее читаемый шаблон, если процессы process1 и process2 являются эквивалентными альтернативами.
if (do_A_or_B = A)
A()
else
B()
Если результатом является какое-то условие «запретить выполнение», а «процесс1» - это просто очистка, необходимая при таком условии, вы должны выбрать Код 2. Это наиболее читаемый шаблон, если «процесс2» является основным действием функции.
if (NOT can_do_B)
{
A()
return
}
B()
У меня есть несколько точек выхода из функции. Лично я считаю, что это понятнее, а в некоторых случаях может быть быстрее. Если вы что-то проверите, а затем вернетесь, программа не будет выполнять никаких левых команд. Опять же, как сказал HZC, если вы работаете с многопоточными приложениями, то лучшим вариантом будет ваш первый пример. В любом случае для небольших фрагментов кода это не будет иметь никакого значения (возможно, даже для некоторых более крупных). Самое главное, чтобы вы писали так, как вам удобно.
Думаю, неважно. Вы должны стремиться к удобочитаемости, и я думаю, что чем меньше скобок, тем лучше. Поэтому я бы вернулся без двух последних скобок (см. Образец ниже). Пожалуйста, не думайте о производительности, когда пишете что-то подобное, это не принесет вам ничего, кроме слишком большой сложности на слишком ранней стадии.
if(result)
{
process 1
return;
}
process 2
Думаю, вам не стоит беспокоиться о производительности здесь. В этом случае важнее удобочитаемость и ремонтопригодность.
Хорошая практика - придерживаться одной точки выхода из рутины.
Однако иногда множественные возвраты просто делают код более понятным, особенно когда у вас есть несколько тестов в начале кода (то есть проверка того, все ли входные параметры находятся в правильном формате), для которых используется 'if -true 'должен привести к возврату.
то есть:
if (date not set) return false;
age = calculateAgeBasedOnDate();
if (age higher than 100) return false;
...lots of code...
return result;
Возврат приведет к возврату кода из любого метода, в котором находится оператор if, дальнейший код не будет быть казненным. Оператор без возврата просто выпадет из оператора if.
Вот дополнительное чтение о предложении guard: http://www.c2.com/cgi/wiki?GuardClause
Я не увидел упоминания одного термина, который я считаю важным - цель guard clause состоит в том, чтобы повысить читабельность. Методы с одним выходом могут иметь тенденцию к "стрелочному" коду (где вложенность утверждений создает наконечник стрелы).
Не могу сказать о производительности, но Код 1 кажется мне намного понятнее и логичнее. Переход из функции в середине блока if
выглядит довольно запутанным, и его легко упустить.
Они оба будут компилироваться в один и тот же IL для режима выпуска (в отладке может быть несколько разных операндов Nop ..), и поэтому разницы в производительности не будет. Это полностью зависит от того, как вы и ваша команда считаете, что код легче читать.
Раньше я был сторонником раннего выхода, но теперь я чувствую, что это может значительно упростить выполнение кода.
// 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
Если вы удалите фигурные скобки вокруг оставшегося кода 2-й версии, это именно то, что я использую. Я предпочитаю, чтобы ранняя проверка функции была очевидной, а затем я мог перейти к делу.
Тем не менее, это вопрос мнения. Если вы последовательны в этом, выберите одно и придерживайтесь его.
править: Что касается производительности, испускаемый IL точно такой же. Выберите тот или иной стиль, ни за один из них нет штрафа.
Я думаю, что "единственная точка выхода" переоценена. Слишком догматичное следование ей может привести к очень сложному коду, который на самом деле должен либо иметь несколько точек выхода, либо быть разбит на более мелкие методы.
Я бы сказал, что выбор между этими двумя вариантами зависит от семантики.
"Если некоторое условие истинно, то сделайте это, иначе сделайте то" прекрасно отображается на if-else.
if (isLoggedIn) {
RedirectToContent();
} else {
RedirectToLogin();
}
"Если некоторое условие истинно, то выполните некоторую очистку и выйдите" лучше подходит к Code 2. Это называется паттерном охраны. При этом тело кода остается нормальным, ясным и не загроможденным ненужными отступами. Он обычно используется для проверки параметров или состояния (проверка, не является ли что-то нулевым, или что-то кэшируется, и т.п.).
if (user == null) {
RedirectToLogin();
return;
}
DisplayHelloMessage(user.Name);
Нередко в одном проекте используются обе формы. Что использовать, как я уже сказал, зависит от того, что вы пытаетесь передать.
Если есть общий код, который нужно выполнить после блока if / else, то вариант 1.
Если блок if-else - последнее, что нужно сделать в функции , то вариант 2.
Лично я всегда использую вариант 1. Если есть состояние возврата, оно идет после блока else
Лично я всегда люблю возвращаться как можно скорее, поэтому я бы выбрал что-то вроде:
if (result)
{
// do something
return;
}
// do something if not result
Что касается производительности, я сомневаюсь, что у обоих есть преимущества друг перед другом, все сводится к читабельности и личному вкусу. Я предполагаю, что .NET оптимизирует ваш первый блок кода до чего-то вроде приведенного выше.
Я думаю, что второй вариант лучше для случаев со многими условиями (например, проверка). Если вы воспользуетесь первым в этих случаях, вы получите некрасивый отступ.
if (con){
...
return;
}
if (other con){
...
return;
}
...
return;
Оба стиля банальны, и из-за них велись религиозные войны. : -)
Обычно я делаю так:
Однако, возможно, более важным правилом является «что более читабельно и / или поддерживаемо для следующего разработчика, который просматривает код? ".
Как говорили другие, разница в производительности незначительна.