Когда я запускал ReSharper в своем коде, например:
if (some condition)
{
Some code...
}
ReSharper дал мне указанное выше предупреждение (инвертировать оператор if для уменьшения вложенности) и предложил следующее исправление:
if (!some condition) return;
Some code...
Хотелось бы понять, почему так лучше. Я всегда думал, что использование return в середине метода проблематично, что-то вроде goto.
Нет, это не повлияет на производительность.
Это субъективно и зависит от того, какой случай вам больше нравится. Результирующий код IL будет очень похож.
У меня возникло бы искушение выбросить исключение ArgumentException, если бы мой метод вместо этого передавал неверные данные.
@asawyer Да, здесь есть целая сторонняя дискуссия о функциях, которые слишком снисходительны к бессмысленным вводам - в отличие от использования ошибки утверждения. Написание твердого кода открыл мне глаза на это. В этом случае это будет что-то вроде ASSERT( exampleParam > 0 ).
Утверждения предназначены для внутреннего состояния, а не для параметров. Вы должны начать с проверки параметров, подтверждения того, что ваше внутреннее состояние правильное, а затем выполнить операцию. В сборке выпуска вы можете либо опустить утверждения, либо сопоставить их с отключением компонента.
Приятно спросить об этом, что, как я полагал, было серьезным предубеждением со стороны разработчиков resharper. Я не считаю их рефакторинг более чистым, чем мой вложенный if. Они также слишком сильно применяют DRY. например, поставьте var здесь и там или удалите лишние квалификаторы, когда мне нравится моя избыточная квалификация, как они ...
возможный дубликат Повышает ли инвертирование «если» производительность?
"уменьшить вложенность" - вот объяснение, почему это предлагается :-)





Это просто спорное. По вопросу досрочного возврата «соглашения между программистами» нет. Насколько я знаю, это всегда субъективно.
Можно привести аргумент о производительности, поскольку лучше иметь записанные условия, которые чаще всего выполняются; можно также утверждать, что это яснее. С другой стороны, он создает вложенные тесты.
Не думаю, что вы получите однозначный ответ на этот вопрос.
Я не понимаю ваших аргументов в пользу производительности. Условия являются логическими, поэтому независимо от результата всегда есть два результата ... Я не понимаю, почему изменение оператора (или нет) изменило бы результат. (Если вы не говорите, что добавление «НЕ» к условию добавляет измеримый объем обработки ...
Оптимизация работает следующим образом: если вы гарантируете, что наиболее распространенный случай находится в блоке if, а не в блоке else, то ЦП обычно уже будет иметь операторы из блока if, загруженные в его конвейер. Если условие возвращает false, ЦП должен очистить свой конвейер и ...
Мне также нравится делать то, что говорят другие, просто для удобства чтения, я ненавижу отрицательные случаи в блоке «then», а не в «else». Имеет смысл сделать это, даже не зная и не задумываясь о том, что делает ЦП.
Это вопрос мнения.
Мой нормальный подход - избегать однострочных ifs и возвратов в середине метода.
Вам не нужны строки, подобные предложению, повсюду в вашем методе, но есть что сказать, чтобы проверить кучу предположений в верхней части вашего метода и выполнять свою фактическую работу только в том случае, если все они проходят.
Это немного религиозный аргумент, но я согласен с ReSharper в том, что вам следует предпочесть меньше вложенности. Я считаю, что это перевешивает недостатки наличия нескольких путей возврата из функции.
Основная причина уменьшения вложенности - улучшение читаемость кода и ремонтопригодность. Помните, что многим другим разработчикам потребуется читать ваш код в будущем, а код с меньшим отступом, как правило, читать намного легче.
Предварительные условия - отличный пример того, где можно вернуться в начале функции. Почему на удобочитаемость остальной части функции должно влиять наличие проверки предварительного условия?
Что касается минусов, связанных с многократным возвратом из метода, отладчики теперь довольно мощные, и очень легко узнать, где и когда возвращается конкретная функция.
Наличие нескольких возвратов в функции не повлияет на работу программиста по обслуживанию.
Плохая читабельность кода будет.
Кроме того, при использовании такого «инвертировать, если» у вас есть проверка ошибочного состояния и действия при ошибке, близкие друг к другу. Если бы вы поступили иначе, то было бы, если бы (хорошо) что-то сделали; остальное поправьте / сообщите. Таким образом, сразу видна ошибка - действие.
Множественные возвраты в функции действительно влияют на программиста обслуживания. При отладке функции в поисках неверного возвращаемого значения сопровождающий должен установить точки останова во всех местах, которые могут возвращаться.
Разве вы не можете поставить точку останова на закрывающей скобке метода?
@g: Я предполагаю, что проблема с этим подходом заключается в том, что если у вас есть несколько возвратов в методе, вы не узнаете, через какой из них он вернулся, если вы только поместите точку останова на закрывающую скобку.
Я бы поставил точку останова на открывающую скобку. Если вы пройдете через функцию, вы не только сможете увидеть, какие проверки выполняются по порядку, но и будет совершенно ясно, какая проверка выполнялась функцией для этого запуска.
Согласитесь с Джоном здесь ... Я не вижу проблемы в том, чтобы просто пройти через всю функцию.
@Nailer Очень поздно, я особенно согласен, потому что, если функция слишком велика, чтобы пройти через нее, ее все равно нужно разделить на несколько функций!
Согласитесь также с Джоном. Может быть, старая школьная мысль помещает точку останова внизу, но если вам интересно, что функция возвращает, вы пройдете через указанную функцию, чтобы увидеть, почему она все равно возвращает то, что возвращает. Если вы просто хотите увидеть, что он вернет, поместите его в последнюю скобку.
«Основная причина уменьшения вложенности заключается в улучшении читабельности кода и удобства обслуживания» Наличие нескольких точек возврата снижает это, а не очень структурированные вложенные операторы if.
Я думаю, это зависит от того, что вы предпочитаете, как уже упоминалось, на этот счет нет общего согласия. Чтобы уменьшить раздражение, вы можете уменьшить этот вид предупреждения до "Подсказки".
Это, конечно, субъективно, но я думаю, что это сильно улучшает два момента:
condition держится.Защитные предложения или предварительные условия (как вы, вероятно, видите) проверяют, выполняется ли определенное условие, а затем прерывают выполнение программы. Они отлично подходят для мест, где вас действительно интересует только один результат заявления if. Так что вместо того, чтобы говорить:
if (something) {
// a lot of indented code
}
Вы меняете условие и прерываете его, если это обратное условие выполняется.
if (!something) return false; // or another value to show your other code the function did not execute
// all the code from before, save a lot of tabs
return далеко не такой грязный, как goto. Это позволяет вам передать значение, чтобы показать остальную часть вашего кода, которую функция не может запустить.
Вы увидите лучшие примеры того, как это можно применить во вложенных условиях:
if (something) {
do-something();
if (something-else) {
do-another-thing();
} else {
do-something-else();
}
}
против:
if (!something) return;
do-something();
if (!something-else) return do-something-else();
do-another-thing();
Вы найдете немного людей, которые будут утверждать, что первое чище, но, конечно, это полностью субъективно. Некоторым программистам нравится знать, в каких условиях что-то работает, с помощью отступов, в то время как я бы предпочел, чтобы поток метода был линейным.
Я не буду на мгновение утверждать, что предварительные требования изменят вашу жизнь или заставят вас замолчать, но вы можете найти свой код немного более легким для чтения.
Думаю, я тогда один из немногих. Мне легче читать первую версию. Вложенные if делают дерево решений более очевидным. С другой стороны, если есть несколько предварительных условий, я согласен, что лучше поместить их все в начало функции.
@Otherside: полностью согласен. Последовательно записанные возвраты заставляют ваш мозг сериализовать возможные пути. Когда если-дерево может быть отображено непосредственно в какую-то временную логику, например, оно может быть скомпилировано для лиспа. но способ последовательного возврата потребовал бы гораздо более сложного компилятора. Дело здесь, очевидно, не имеет ничего общего с этим гипотетическим сценарием, оно связано с анализом кода, шансами на оптимизацию, корректирующими доказательствами, доказательствами завершения и обнаружением инвариантов.
Никому не будет легче читать код с большим количеством гнезд. Чем больше блоков похоже на что-то, тем легче это читать с первого взгляда. Нет никакого способа, которым первый пример здесь легче читать, и он включает только два гнезда, когда большая часть кода, подобного этому в реальном мире, идет глубже, и с каждым гнездом требуется больше умственных способностей, чтобы следовать.
Если бы вложение было вдвое глубже, сколько времени вам понадобилось бы, чтобы ответить на вопрос: «Когда вы« делаете-то-еще »?» Думаю, вам будет сложно ответить на него без ручки и бумаги. Но вы могли бы довольно легко ответить на него, если бы все это было оговорено в оговорках.
Я считаю, что возврат «в середине функции» не должен быть таким «субъективным». Причина довольно проста, возьмите этот код:
function do_something( data ){
if (!is_valid_data( data ))
return false;
do_something_that_take_an_hour( data );
istance = new object_with_very_painful_constructor( data );
if ( istance is not valid ) {
error_message( );
return ;
}
connect_to_database ( );
get_some_other_data( );
return;
}
Может быть, первое «возвращение» не НАСТОЛЬКО интуитивно понятно, но это действительно экономия. Слишком много «идей» о чистых кодах, которым просто нужно больше практики, чтобы избавиться от «субъективных» плохих идей.
За исключением языка, у вас нет этих проблем.
Возврат в середине метода не обязательно плох. Может быть, лучше вернуться немедленно, если это проясняет цель кода. Например:
double getPayAmount() {
double result;
if (_isDead) result = deadAmount();
else {
if (_isSeparated) result = separatedAmount();
else {
if (_isRetired) result = retiredAmount();
else result = normalPayAmount();
};
}
return result;
};
В этом случае, если _isDead верен, мы можем сразу выйти из метода. Возможно, лучше было бы структурировать его так:
double getPayAmount() {
if (_isDead) return deadAmount();
if (_isSeparated) return separatedAmount();
if (_isRetired) return retiredAmount();
return normalPayAmount();
};
Я выбрал этот код из каталог рефакторинга. Этот конкретный рефакторинг называется: Заменить вложенные условные предложения на защитные предложения.
Это действительно хороший пример! Реорганизованный код больше похож на оператор case.
Вероятно, дело вкуса: я предлагаю изменить 2-е и 3-е «если» на «иначе, если», чтобы еще больше повысить удобочитаемость. Если упустить из виду оператор «return», все равно будет ясно, что следующий случай проверяется только в том случае, если предыдущий не удался, т.е. что порядок проверок важен.
В этом простом примере я согласен, что второй способ лучше, но только потому, что он ТАК очевиден, что происходит.
@jop +1 за ссылку на каталог рефакторинга, это довольно мощный ресурс
Ваше последнее заявление следует выделить жирным шрифтом. Заменить вложенные условия на защитные предложения, потому что это правда. в то время как ваше первое утверждение неверно: A return in the middle of the method is not necessarily bad -> нет-нет! Мы можем только сказать, что охранник закрывается - единственное допустимое исключение.
он также называется защитный подход
@jop what if 'second if Зависимость от первого if? ex: if (object.firstName.Value) {if (Regex.IsMatch (object.firstName, strNameRegex)) {// dosomething}}
Идея возврата только в конце функции возникла в те дни, когда языки не поддерживали исключения. Это позволило программам полагаться на возможность поместить код очистки в конец метода, а затем быть уверенным, что он будет вызван, и какой-то другой программист не скроет возврат в методе, который вызвал пропуск кода очистки. . Пропуск кода очистки может привести к утечке памяти или ресурсов.
Однако в языке, поддерживающем исключения, таких гарантий не предоставляется. В языке, поддерживающем исключения, выполнение любого оператора или выражения может вызвать поток управления, который приводит к завершению метода. Это означает, что очистка должна выполняться с помощью ключевых слов finally или using.
Во всяком случае, я говорю, что я думаю, что многие люди цитируют рекомендацию «единственный возврат в конце метода», не понимая, почему это когда-либо было хорошо, и что сокращение вложенности для улучшения читаемости, вероятно, является лучшей целью.
Вы только что поняли, почему исключения - это UglyAndEvil [tm] ... ;-) Исключения - это gotos в причудливой, дорогой маскировке.
@Eric, вы, должно быть, столкнулись с исключительно плохим кодом. Когда они используются неправильно, это довольно очевидно, и они обычно позволяют писать более качественный код.
Это тот ответ, который я действительно искал! Здесь есть отличные ответы, но большинство из них сосредоточено на примерах, а не на реальной причине, по которой появилась эта рекомендация.
Это лучший ответ, поскольку он объясняет, почему вообще возник весь этот спор.
Множественные точки возврата были проблемой в C (и, в меньшей степени, C++), потому что они заставляли вас дублировать код очистки перед каждой из точек возврата. Со сборкой мусора try | Конструкция finally и блоки using, на самом деле нет причин, по которым вам стоит их бояться.
В конечном итоге все сводится к тому, что вам и вашим коллегам легче читать.
Это не единственная причина. Существует академическая причина, по которой псевдокод не имеет ничего общего с практическими соображениями, такими как очистка. Эта академическая причина связана с уважением к основным формам императивных конструкций. Точно так же вы не помещаете средства выхода из цикла в его середину. Таким образом, инварианты могут быть точно обнаружены и поведение может быть доказано. Или прекращение может быть доказано.
новости: на самом деле я нашел очень практическую причину, по которой ранний разрыв и возврат - это плохо, и это прямое следствие того, что я сказал, статического анализа. А вот и руководство по использованию компилятора Intel C++: d3f8ykwhia686p.cloudfront.net/1live/intel/…. Ключевая фраза: a loop that is not vectorizable due to a second data-dependent exit
Здесь есть несколько хороших моментов, но есть и несколько точек возврата может быть нечитаемым, если метод очень длинный. При этом, если вы собираетесь использовать несколько точек возврата, просто убедитесь, что ваш метод короткий, иначе бонус читабельности в виде нескольких точек возврата может быть потерян.
У такого типа кодирования есть несколько преимуществ, но для меня большой выигрыш в том, что если вы сможете быстро вернуться, вы сможете улучшить скорость своего приложения. Т.е. я знаю, что благодаря Предварительному условию X я могу быстро вернуться с ошибкой. Это сначала избавляет от ошибок и снижает сложность вашего кода. Во многих случаях, поскольку конвейер процессора теперь может быть чище, он может остановить сбои конвейера или переключение. Во-вторых, если вы зациклились, быстрое отключение или выход из строя может сэкономить вам много процессора. Некоторые программисты используют инварианты цикла для такого рода быстрого выхода, но в этом случае вы можете сломать конвейер процессора и даже создать проблему поиска в памяти, что означает, что процессор должен загружаться из внешнего кеша. Но в основном я думаю, что вы должны делать то, что планировали, а именно завершать цикл или функцию, а не создавать сложный путь кода, просто чтобы реализовать какое-то абстрактное понятие правильного кода. Если единственный инструмент, который у вас есть, - это молоток, то все будет похоже на гвоздь.
Читая ответ graffic, это скорее похоже на то, что вложенный код был оптимизирован компилятором таким образом, что выполняемый код выполняется быстрее, чем использование нескольких возвратов. Однако, даже если бы несколько возвратов были быстрее, эта оптимизация, вероятно, не сильно ускорит ваше приложение ... :)
Я не согласен - Первый закон - это правильный алгоритм. Но мы все инженеры, а это решение правильной проблемы с использованием ресурсов, которые у нас есть и которые мы делаем в рамках доступного бюджета. Слишком медленное приложение не подходит для использования.
На мой взгляд, ранний возврат - это нормально, если вы просто возвращаете void (или какой-то бесполезный код возврата, который вы никогда не будете проверять), и это может улучшить читаемость, потому что вы избегаете вложенности и в то же время явно указываете, что ваша функция выполнена.
Если вы на самом деле возвращаете returnValue - вложение обычно является лучшим способом, потому что вы возвращаете свое returnValue только в одном месте (в конце - да), и это может сделать ваш код более удобным в сопровождении во многих случаях.
Много веских причин о как выглядит код. А как насчет полученные результаты?
Давайте посмотрим на код C# и его скомпилированную форму IL:
using System;
public class Test {
public static void Main(string[] args) {
if (args.Length == 0) return;
if ((args.Length+2)/3 == 5) return;
Console.WriteLine("hey!!!");
}
}
Этот простой фрагмент можно скомпилировать. Вы можете открыть сгенерированный файл .exe с помощью ildasm и проверить результат. Я не буду выкладывать все, что касается ассемблера, но опишу результаты.
Сгенерированный код IL выполняет следующие действия:
false, переходит к коду, где находится второе.true, то переходит к последней инструкции. (Примечание: последняя инструкция - возврат).Console.WriteLine, если это false, или до конца, если это true.Так что вроде код перескочит до конца. Что, если мы сделаем нормальный if с вложенным кодом?
using System;
public class Test {
public static void Main(string[] args) {
if (args.Length != 0 && (args.Length+2)/3 != 5)
{
Console.WriteLine("hey!!!");
}
}
}
В инструкциях IL результаты очень похожи. Разница в том, что раньше было два перехода на каждое условие: если false перейти к следующему фрагменту кода, если true перейти к концу. И теперь код IL течет лучше и имеет 3 перехода (компилятор немного оптимизировал это):
false, переходите до конца.В любом случае счетчик программ всегда будет прыгать.
Это хорошая информация, но лично мне все равно, что IL "течет лучше", потому что люди, которые будут управлять кодом, не увидят никакого IL.
Вы действительно не можете предвидеть, как будет работать этап оптимизации в следующем обновлении компилятора C# по сравнению с тем, что вы видите сегодня. Лично я бы не тратил НИКАКОГО времени на попытки настроить код C#, чтобы получить другой результат в IL, если только тестирование не покажет, что у вас где-то СЕРЬЕЗНАЯ проблема с производительностью в каком-то жестком цикле и у вас закончились другие идеи. . Даже тогда я бы больше подумал о других вариантах. В том же духе я сомневаюсь, что вы хоть представляете, какие виды оптимизаций JIT-компилятор делает с IL, который ему передается для каждой платформы ...
Лично я предпочитаю только одну точку выхода. Этого легко достичь, если вы будете использовать короткие и точные методы, и это обеспечит предсказуемый шаблон для следующего человека, который будет работать над вашим кодом.
например.
bool PerformDefaultOperation()
{
bool succeeded = false;
DataStructure defaultParameters;
if ((defaultParameters = this.GetApplicationDefaults()) != null)
{
succeeded = this.DoSomething(defaultParameters);
}
return succeeded;
}
Это также очень полезно, если вы просто хотите проверить значения определенных локальных переменных в функции перед ее выходом. Все, что вам нужно сделать, это установить точку останова на последнем возврате, и вы гарантированно попадете в нее (если не возникнет исключение).
bool PerformDefaultOperation () {DataStructure defaultParameters = this.GetApplicationDefaults (); return (defaultParameters! = NULL && this.DoSomething (defaultParameters);} Вот, исправил для вас. :)
Этот пример очень простой и упускает из виду суть этого шаблона. Проведите около 4 других проверок внутри этой функции, а затем сообщите нам, что она более читабельна, потому что это не так.
Там уже есть много проницательных ответов, но все же я хотел бы обратиться к немного другой ситуации: вместо предварительного условия, которое действительно должно быть помещено поверх функции, подумайте о пошаговой инициализации, где вы нужно проверять каждый шаг, чтобы он был успешным, а затем переходить к следующему. В этом случае вы не можете проверить все наверху.
Я обнаружил, что мой код действительно нечитаем при написании хост-приложения ASIO с помощью ASIOSDK Steinberg, поскольку я следовал парадигме вложенности. Он прошел примерно на восемь уровней, и я не вижу здесь недостатка дизайна, как упоминал Эндрю Баллок выше. Конечно, я мог бы упаковать некоторый внутренний код в другую функцию, а затем вложить туда оставшиеся уровни, чтобы сделать его более читабельным, но мне это кажется довольно случайным.
Заменив вложенность защитными предложениями, я даже обнаружил свое заблуждение относительно части кода очистки, которая должна была произойти гораздо раньше внутри функции, а не в конце. С вложенными ветками я бы никогда этого не увидел, можно даже сказать, они привели к моему заблуждению.
Так что это может быть другая ситуация, когда инвертированные if могут способствовать более ясному коду.
Это не только эстетично, но он также уменьшает максимальный уровень вложенности внутри метода. Это обычно считается плюсом, потому что это упрощает понимание методов (и действительно, многостатическийанализинструменты дает меру этого как один из индикаторов качества кода).
С другой стороны, это также заставляет ваш метод иметь несколько точек выхода, что, по мнению другой группы людей, является недопустимым.
Лично я согласен с ReSharper и первой группой (на языке, в котором есть исключения, я считаю глупым обсуждать «множественные точки выхода»; почти все может бросить вызов, поэтому существует множество потенциальных точек выхода во всех методах).
Что касается производительности: обе версии должен эквивалентны (если не на уровне IL, то обязательно после того, как джиттер исчезнет с кодом) на всех языках. Теоретически это зависит от компилятора, но практически любой широко используемый сегодня компилятор способен обрабатывать гораздо более сложные случаи оптимизации кода, чем этот.
единые точки выхода? Кому это нужно?
@ sq33G: Этот вопрос о SESE (и, конечно, ответы) просто фантастичен. Спасибо за ссылку!
Я все время слышу в ответах, что есть люди, которые выступают за единую точку выхода, но я, никогда и никогда, видел, как кто-то действительно выступает за это, особенно в таких языках, как C#.
@AndreasBonini: Отсутствие доказательств не является доказательством отсутствия. :-)
Да, конечно, мне просто странно, что каждый чувствует необходимость сказать, что некоторым людям нравится другой подход, если эти люди не чувствуют необходимости говорить это сами =)
@ sq33G Кого волнует / нужна единственная точка выхода? На это может ответить любой, кто является опытным разработчиком многопоточных приложений. Это не означает единого выхода в канон разработчика или что-то подобное, это просто ценная практика многих ветеранов-разработчиков. (и да, в наши дни это гораздо менее актуально).
@Robert Altman, забавно, я не припомню, чтобы что-нибудь встречал о многопоточности в этом исчерпывающем посте. Вы можете уточнить?
@ sq33G Просто пример того, почему некоторые люди настаивают на единой точке выхода. ReSharper, скорее всего, этим не занимается. Если в моем комментарии есть что-то достойное упоминания, так это то, что «стиль» необходимо рассматривать в контексте того, что оформляется. (Прочтите вашу ссылку после Я разместил свой комментарий, поэтому, возможно, я пропустил какую-либо умышленную иронию или объяснение с вашей стороны.)
Это не только влияет на эстетику, но также предотвращает вложение кода.
Фактически это может служить предварительным условием для проверки достоверности ваших данных.
С точки зрения производительности заметной разницы между двумя подходами не будет.
Но кодирование - это больше, чем просто производительность. Также очень важны четкость и ремонтопригодность. И в таких случаях, когда это не влияет на производительность, это единственное, что имеет значение.
Существуют конкурирующие школы мысли относительно того, какой подход предпочтительнее.
Одно из представлений - это то, о чем упоминали другие: второй подход снижает уровень вложенности, что улучшает ясность кода. Это естественно для императивного стиля: когда вам нечего делать, вы можете вернуться раньше.
Другая точка зрения, с точки зрения более функционального стиля, заключается в том, что метод должен иметь только одну точку выхода. Все в функциональном языке - это выражение. Таким образом, операторы if всегда должны иметь предложения else. В противном случае выражение if не всегда имело бы значение. Так что в функциональном стиле первый подход более естественен.
Спектакль состоит из двух частей. У вас есть производительность, когда программное обеспечение находится в производстве, но вы также хотите иметь производительность при разработке и отладке. Меньше всего разработчику хочется «ждать» чего-то тривиального. В конце концов, компиляция этого с включенной оптимизацией приведет к аналогичному коду. Так что хорошо знать эти маленькие уловки, которые окупаются в обоих сценариях.
Дело в вопросе ясное, ReSharper прав. Вместо того, чтобы вкладывать операторы if и создавать новую область видимости в коде, вы устанавливаете четкое правило в начале своего метода. Это увеличивает удобочитаемость, его будет легче поддерживать, и он сокращает количество правил, которые нужно просеивать, чтобы найти, куда они хотят пойти.
Как отмечали другие, снижения производительности не должно быть, но есть и другие соображения. Помимо этих обоснованных опасений, в некоторых обстоятельствах это также может привести к ошибкам. Предположим, вы вместо этого имеете дело с double:
public void myfunction(double exampleParam){
if (exampleParam > 0){
//Body will *not* be executed if Double.IsNan(exampleParam)
}
}
Сравните это с эквивалентной инверсией по-видимому:
public void myfunction(double exampleParam){
if (exampleParam <= 0)
return;
//Body *will* be executed if Double.IsNan(exampleParam)
}
Так что при определенных обстоятельствах то, что кажется правильно перевернутым if, может не быть.
Также следует отметить, что быстрое исправление resharper инвертирует exampleParam> 0 в exampleParam <0 нет exampleParam <= 0, что меня поймало.
И именно поэтому этот чек должен быть авансом и также возвращен, поскольку разработчик считает, что наниматель должен привести к спасению.
Я хотел бы добавить, что есть название для этих перевернутых if - Guard Clause. Я использую его, когда могу.
Я ненавижу читать код там, где в начале есть два экрана кода и ничего больше. Просто переверните if и вернитесь. Так никто не будет тратить время на прокрутку.
http://c2.com/cgi/wiki?GuardClause
Точно. Это скорее рефакторинг. Как вы упомянули, это помогает легче читать код.
«возврат» недостаточно и ужасен для охранной оговорки. Я бы сделал: if (ex <= 0) throw WrongParamValueEx ("[MethodName] Неверное значение входного параметра 1 {0} ... и вам нужно будет перехватить исключение и записать в журнал вашего приложения
@Джон. Вы правы, если это действительно ошибка. Но часто это не так. И вместо того, чтобы что-то проверять в каждом месте, где вызывается метод, метод просто проверяет это и ничего не возвращает.
Я бы не хотел читать метод, состоящий из двух экранов кода, точки, if или нет. ржу не могу
Я тоже это ненавижу! Вы не поверите, но я видел 11к строк кодовых методов на C# :)
Я лично предпочитаю ранний возврат именно по этой причине (также: избегая вложенности). Однако это не связано с исходным вопросом о производительности и, вероятно, должно быть комментарием.
Избегание нескольких точек выхода может приводит к увеличению производительности. Я не уверен насчет C#, но в C++ оптимизация именованного возвращаемого значения (Copy Elision, ISO C++ '03 12.8 / 15) зависит от наличия единственной точки выхода. Эта оптимизация позволяет избежать копирования, создавая ваше возвращаемое значение (в вашем конкретном примере это не имеет значения). Это может привести к значительному увеличению производительности в узких циклах, поскольку вы сохраняете конструктор и деструктор при каждом вызове функции.
Но в 99% случаев сохранение дополнительных вызовов конструктора и деструктора не стоит потери читаемости вложенных блоков if (как указывали другие).
там. мне потребовалось 3 длинных чтения десятков ответов на 3 вопроса, задающих одно и то же (2 из которых заморожены), чтобы наконец найти кого-то, кто упомянул NRVO. гы ... спасибо.
Теоретически инвертирование if может привести к повышению производительности, если оно увеличит процент попаданий предсказания ветвления. На практике я думаю, что очень сложно точно знать, как будет вести себя предсказание ветвления, особенно после компиляции, поэтому я бы не стал делать это в своей повседневной разработке, за исключением случаев, когда я пишу ассемблерный код.
Подробнее о предсказании ветвлений здесь.
Не уверен, но думаю, что R # старается избегать дальних прыжков. Когда у вас есть IF-ELSE, компилятор делает что-то вроде этого:
Условие false -> дальний переход к false_condition_label
true_condition_label: инструкция1 ... инструкция_n
false_condition_label: инструкция1 ... инструкция_n
конец блока
Если условие истинно, перехода и развертывания кэша L1 нет, но переход к метке false_condition_label может быть очень далеким, и процессор должен развернуть свой собственный кеш. Синхронизация кеша стоит дорого. R # пытается заменить дальние переходы на короткие, и в этом случае с большей вероятностью все инструкции уже находятся в кеше.
Я считаю, что проверка исключения и возврат в начале - это нормально, но я бы изменил условие, чтобы вы проверяли исключение напрямую, а не что-то (то есть, если (какое-то условие) вернется).