Когда я запустил ReSharper для моего кода, например:
if (some condition)
{
Some code...
}
ReSharper дал мне вышеупомянутое предупреждение (оператор Invert «if» для уменьшения вложенности) и предложил следующее исправление:
if (!some condition) return;
Some code...
Я хотел бы понять, почему это лучше. Я всегда думал, что использование «возврата» в середине метода проблематично, что-то вроде «goto».
ASSERT( exampleParam > 0 )
.Ответы:
Возвращение в середине метода не обязательно плохо. Может быть, лучше сразу вернуться, если это прояснит смысл кода. Например:
В этом случае, если
_isDead
это правда, мы можем немедленно выйти из метода. Может быть, лучше структурировать это так:Я выбрал этот код из каталога рефакторинга . Этот конкретный рефакторинг называется: Заменить вложенные условные выражения на охранные.
источник
Это не только эстетично , но и снижает максимальный уровень вложенности внутри метода. Как правило, это считается плюсом, потому что облегчает понимание методов (и, действительно, многие инструменты статического анализа предоставляют меру этого как один из показателей качества кода).
С другой стороны, это также дает вашему методу несколько точек выхода, что, по мнению другой группы людей, - нет-нет.
Лично я согласен с ReSharper и первой группой (на языке, в котором есть исключения, я нахожу глупым обсуждать «множественные точки выхода»; почти все может бросить, поэтому во всех методах есть множество потенциальных точек выхода).
Что касается производительности : обе версии должны быть эквивалентны (если не на уровне IL, то, конечно, после того, как джиттер закончен с кодом) на каждом языке. Теоретически это зависит от компилятора, но практически любой широко используемый сегодня компилятор способен обрабатывать гораздо более сложные случаи оптимизации кода, чем этот.
источник
Это немного религиозный аргумент, но я согласен с ReSharper, что вы должны предпочитать меньше вложенности. Я полагаю, что это перевешивает недостатки наличия нескольких путей возврата из функции.
Основная причина меньшего количества вложений заключается в улучшении читабельности и удобства сопровождения кода . Помните, что многим другим разработчикам нужно будет читать ваш код в будущем, и код с меньшими отступами, как правило, гораздо легче читать.
Предварительные условия - отличный пример того, где можно вернуться рано в начале функции. Почему наличие проверки предусловия должно влиять на читабельность остальной функции?
Что касается отрицательных результатов при многократном возврате из метода - отладчики сейчас довольно мощные, и очень легко точно определить, где и когда возвращается конкретная функция.
Наличие нескольких возвратов в функции не повлияет на работу программиста по сопровождению.
Плохая читаемость кода будет.
источник
Как уже упоминали другие, не должно быть падения производительности, но есть и другие соображения. Помимо этих действительных проблем, это также может открыть для вас при некоторых обстоятельствах. Предположим, вы имели дело с
double
:Сравните это с, казалось бы, эквивалентной инверсией:
Таким образом, при определенных обстоятельствах то, что кажется правильно перевернутым,
if
может не бытьисточник
Идея возврата только в конце функции возникла еще в те дни, когда языки поддерживали исключения. Это позволило программам полагаться на возможность помещать код очистки в конец метода, а затем быть уверенным, что он будет вызван, и какой-то другой программист не будет скрывать возврат в метод, который вызвал пропуск кода очистки , Пропущенный код очистки может привести к утечке памяти или ресурсов.
Однако на языке, который поддерживает исключения, он не предоставляет таких гарантий. В языке, который поддерживает исключения, выполнение любого оператора или выражения может вызвать поток управления, который вызывает завершение метода. Это означает, что очистка должна производиться с помощью ключевых слов finally или using.
Во всяком случае, я говорю, что думаю, что многие люди цитируют рекомендацию «только возврат в конце метода», не понимая, почему это когда-либо было полезно, и что сокращение вложенности для улучшения читабельности, вероятно, является лучшей целью.
источник
If you've got deep nesting, maybe your function is trying to do too many things.
=> Это не правильное следствие вашей предыдущей фразы. Потому что непосредственно перед тем, как вы скажете, что вы можете изменить поведение A с кодом C в поведение A с кодом D. код D более чистый, предоставленный, но «слишком много вещей» относится к поведению, которое НЕ изменилось. Поэтому вы не имеете никакого смысла с этим выводом.Я хотел бы добавить, что есть название для тех, кто перевернут, если это - Guard Clause. Я использую это всякий раз, когда могу.
Я ненавижу читать код там, где есть, если в начале, два экрана кода и больше ничего. Просто инвертируй если и верни. Таким образом, никто не будет тратить время на прокрутку.
http://c2.com/cgi/wiki?GuardClause
источник
if
с или нет. LOLЭто не только влияет на эстетику, но и предотвращает вложение кода.
Это может фактически служить предварительным условием, чтобы гарантировать, что ваши данные также действительны.
источник
Это, конечно, субъективно, но я думаю, что это сильно улучшается по двум пунктам:
condition
выполняется.источник
Множественные точки возврата были проблемой в C (и в меньшей степени в C ++), потому что они заставляли вас дублировать код очистки перед каждой из точек возврата. С сборкой мусора,
try
|finally
построить иusing
блоки, нет никаких причин, почему вы должны их бояться.В конечном итоге все сводится к тому, что вам и вашим коллегам легче читать.
источник
a loop that is not vectorizable due to a second data-dependent exit
С точки зрения производительности между этими двумя подходами не будет заметной разницы.
Но кодирование - это больше, чем производительность. Ясность и ремонтопригодность также очень важны. И в таких случаях, когда это не влияет на производительность, это единственное, что имеет значение.
Есть конкурирующие школы мысли о том, какой подход предпочтительнее.
Одно представление - это то, о котором упоминали другие: второй подход снижает уровень вложенности, что улучшает ясность кода. Это естественно в императивном стиле: когда вам нечего делать, вы также можете вернуться рано.
Другая точка зрения, с точки зрения более функционального стиля, состоит в том, что метод должен иметь только одну точку выхода. Все на функциональном языке является выражением. Так что если в утверждениях всегда должны быть предложения else. В противном случае выражение if не всегда будет иметь значение. Так что в функциональном стиле первый подход более естественный.
источник
Охранные предложения или предварительные условия (как вы, вероятно, можете видеть) проверяют, выполнено ли определенное условие, а затем нарушают ход программы. Они отлично подходят для мест, где вас действительно интересует только один результат
if
заявления. Так что вместо того, чтобы сказать:Вы отменяете условие и нарушаете его, если выполнено обратное условие
return
далеко не так грязно, какgoto
. Это позволяет вам передать значение, чтобы показать остальную часть вашего кода, что функция не может работать.Вы увидите лучшие примеры того, как это может быть применено во вложенных условиях:
против:
Вы найдете несколько человек, которые утверждают, что первое - чище, но, конечно, оно абсолютно субъективно. Некоторым программистам нравится знать, в каких условиях что-то работает с помощью отступов, в то время как я бы предпочел сохранить линейность методов.
Я не буду на мгновение предположить, что предконфигурации изменят вашу жизнь или уложат вас, но вам может показаться, что ваш код немного проще для чтения.
источник
Здесь есть несколько хороших моментов, но несколько точек возврата также могут быть нечитаемыми , если метод очень длинный. При этом, если вы собираетесь использовать несколько точек возврата, просто убедитесь, что ваш метод является коротким, в противном случае бонус читаемости нескольких точек возврата может быть потерян.
источник
Спектакль состоит из двух частей. У вас есть производительность, когда программное обеспечение находится в производстве, но вы также хотите иметь производительность при разработке и отладке. Последнее, чего хочет разработчик - это ждать чего-то тривиального. В конце концов, компиляция с включенной оптимизацией приведет к аналогичному коду. Так что хорошо знать эти маленькие хитрости, которые окупаются в обоих сценариях.
Дело в вопросе ясно, ReSharper правильно. Вместо того, чтобы вкладывать
if
операторы и создавать новую область в коде, вы устанавливаете четкое правило в начале вашего метода. Это повышает удобочитаемость, его будет легче поддерживать, и это уменьшает количество правил, которые нужно просеять, чтобы найти, куда они хотят пойти.источник
Лично я предпочитаю только 1 точку выхода. Этого легко добиться, если вы придерживаетесь коротких и точных методов, и это обеспечивает предсказуемый шаблон для следующего человека, который работает над вашим кодом.
например.
Это также очень полезно, если вы просто хотите проверить значения определенных локальных переменных внутри функции до ее выхода. Все, что вам нужно сделать, это поставить точку останова на окончательном возврате, и вы гарантированно достигнете ее (если не выдается исключение).
источник
Много веских причин о том, как выглядит код . Но как насчет результатов ?
Давайте посмотрим на код C # и его скомпилированную форму IL:
Этот простой фрагмент может быть скомпилирован. Вы можете открыть сгенерированный файл .exe с помощью ildasm и проверить результат. Я не буду публиковать все вещи ассемблера, но я опишу результаты.
Сгенерированный код IL делает следующее:
Таким образом, кажется, что код будет прыгать до конца. Что делать, если мы делаем нормальный, если с вложенным кодом?
Результаты очень похожи в инструкциях по IL. Разница заключается в том, что раньше выполнялись переходы по условию: если ложь, переходите к следующему фрагменту кода, если истина, то переход к концу. И теперь IL-код работает лучше и имеет 3 перехода (компилятор немного его оптимизировал): 1. Первый переход: когда Length равен 0, до части, где код снова переходит (третий переход) до конца. 2. Второе: в середине второго условия избегать одной инструкции. 3. Третье: если второе условие ложно, прыгайте до конца.
В любом случае, счетчик программы всегда будет прыгать.
источник
Теоретически, инвертирование
if
может привести к повышению производительности, если оно увеличивает частоту предсказания ветвлений. На практике я думаю, что очень трудно точно знать, как будет вести себя прогноз ветвления, особенно после компиляции, поэтому я бы не стал делать это в своей повседневной разработке, кроме случаев, когда я пишу ассемблерный код.Подробнее о прогнозе отрасли здесь .
источник
Это просто спорное. Не существует «соглашения между программистами» по вопросу о досрочном возвращении. Насколько я знаю, это всегда субъективно.
Можно сделать аргумент производительности, так как лучше иметь условия, которые написаны так, что они чаще всего выполняются; Можно также утверждать, что это яснее. С другой стороны, он создает вложенные тесты.
Я не думаю, что вы получите окончательный ответ на этот вопрос.
источник
Там уже есть много проницательных ответов, но, тем не менее, я бы хотел указать на немного иную ситуацию: вместо предусловия, которое действительно должно быть поставлено поверх функции, подумайте о пошаговой инициализации, где вы Нужно проверить каждый шаг, чтобы выполнить его, а затем перейти к следующему. В этом случае вы не можете проверить все наверху.
Я обнаружил, что мой код действительно не читается при написании хост-приложения ASIO с помощью ASIOSDK от Steinberg, поскольку я следовал парадигме вложенности. Он прошел глубиной в восемь уровней, и я не вижу там недостатка дизайна, как упомянул Эндрю Баллок выше. Конечно, я мог бы упаковать некоторый внутренний код в другую функцию, а затем вложить туда оставшиеся уровни, чтобы сделать его более читабельным, но мне это кажется довольно случайным.
Заменив вложение на элементы защиты, я даже обнаружил свое неправильное представление о части кода очистки, которая должна была произойти намного раньше в функции, а не в конце. С вложенными ветвями я бы никогда этого не увидел, можно даже сказать, что они привели к моему заблуждению.
Так что это может быть другая ситуация, когда инвертированные ifs могут внести свой вклад в более четкий код.
источник
Избегание нескольких точек выхода может привести к повышению производительности. Я не уверен насчет C #, но в C ++ оптимизация именованных возвращаемых значений (Copy Elision, ISO C ++ '03 12.8 / 15) зависит от наличия единой точки выхода. Эта оптимизация позволяет избежать создания копии возвращаемого значения (в вашем конкретном примере это не имеет значения). Это может привести к значительному увеличению производительности в тесных циклах, так как вы сохраняете конструктор и деструктор каждый раз, когда вызывается функция.
Но в 99% случаев сохранение дополнительных вызовов конструктора и деструктора не стоит потери читаемости, которую представляют вложенные
if
блоки (как указывали другие).источник
Это вопрос мнения.
Мой обычный подход заключается в том, чтобы избежать однострочных ifs и возвратов в середине метода.
Вы не хотели бы, чтобы такие строки были предложены везде в вашем методе, но есть что сказать, чтобы проверить кучу предположений в верхней части вашего метода и выполнить вашу реальную работу, только если они все пройдут.
источник
По моему мнению, ранний возврат - это хорошо, если вы просто возвращаете void (или какой-то бесполезный код возврата, который вы никогда не будете проверять), и это может улучшить читабельность, потому что вы избегаете вложения и в то же время делаете явным, что ваша функция выполнена.
Если вы на самом деле возвращаете returnValue, обычно лучше использовать вложение, потому что вы возвращаете returnValue только в одном месте (в конце концов), и это может сделать ваш код более понятным в большинстве случаев.
источник
Я не уверен, но я думаю, что R # пытается избежать прыжков в длину. Когда у вас есть IF-ELSE, компилятор делает что-то вроде этого:
Условие false -> дальний переход к false_condition_label
true_condition_label: инструкция1 ... инструкция_н
false_condition_label: инструкция1 ... инструкция_н
конец блока
Если условие истинно, переход и кэш L1 не происходит, но переход к false_condition_label может быть очень большим, и процессор должен развернуть свой собственный кэш. Синхронизация кеша стоит дорого. R # пытается заменить прыжки в длину на короткие прыжки, и в этом случае существует большая вероятность того, что все инструкции уже находятся в кеше.
источник
Я думаю, что это зависит от того, что вы предпочитаете, как уже упоминалось, нет никакого общего согласия. Чтобы уменьшить раздражение, вы можете уменьшить этот вид предупреждения до «Подсказка»
источник
Моя идея заключается в том, что возвращение «в середине функции» не должно быть таким «субъективным». Причина довольно проста, возьмите этот код:
Может быть, первое «возвращение» не ТАК интуитивно понятно, но это действительно экономит. Существует слишком много «идей» о чистых кодах, которым просто нужно больше практики, чтобы потерять свои «субъективные» плохие идеи.
источник
У такого рода кодирования есть несколько преимуществ, но для меня большой выигрыш в том, что если вы можете быстро вернуться, вы можете повысить скорость своего приложения. Т.е. я знаю, что из-за предусловия X я могу быстро вернуться с ошибкой. Это избавляет от ошибок в первую очередь и уменьшает сложность вашего кода. Во многих случаях, поскольку конвейер ЦП теперь может быть чище, он может остановить аварийное завершение конвейера или его переключение. Во-вторых, если вы зацикливаетесь, быстрое прерывание или возврат может сэкономить вам много ресурсов процессора. Некоторые программисты используют инварианты цикла для быстрого выхода из этого типа, но в этом случае вы можете разорвать конвейер процессора и даже создать проблему поиска памяти, что означает, что процессор должен загружаться из внешнего кэша. Но в основном я думаю, что вы должны делать то, что вы хотели, то есть конец цикла или функции не создает сложный путь к коду только для реализации некоторого абстрактного понятия правильного кода. Если единственный инструмент, который у вас есть, это молоток, то все выглядит как гвоздь.
источник