Есть ли веские причины, по которым лучше иметь в функции только один оператор return?
Или можно вернуться из функции, как только это будет логически правильно, то есть в функции может быть много операторов возврата?
language-agnostic
coding-style
Tim Post
источник
источник
Ответы:
У меня часто есть несколько утверждений в начале метода, чтобы вернуться к «легким» ситуациям. Например, это:
... можно сделать более читабельным (ИМХО) вот так:
Так что да, я думаю, что хорошо иметь несколько «точек выхода» из функции / метода.
источник
DoStuff() { DoStuffInner(); IncreaseStuffCallCounter(); }
Никто не упомянул или не процитировал Code Complete, поэтому я сделаю это.
17.1 возврат
Минимизируйте количество возвратов в каждой программе . Труднее понять рутину, если, читая ее внизу, вы не подозреваете о возможности ее возвращения где-то выше.
Используйте возврат, когда он улучшает читабельность . В некоторых подпрограммах, когда вы знаете ответ, вы хотите немедленно вернуть его в вызывающую подпрограмму. Если подпрограмма определена таким образом, что она не требует какой-либо очистки, то немедленный возврат не означает, что вам придется писать больше кода.
источник
Я бы сказал, что было бы невероятно неразумно принимать произвольные решения против нескольких точек выхода, поскольку я обнаружил, что эта техника на практике оказывается полезной снова и снова , на самом деле, для ясности я часто рефакторинг существующего кода для нескольких точек выхода. Мы можем сравнить два подхода таким образом:
Сравните это с кодом , где несколько точек выхода являются разрешенными: -
Я думаю, что последнее значительно яснее. Насколько я могу судить, критика нескольких точек выхода является довольно архаичной точкой зрения в наши дни.
источник
В настоящее время я работаю над базой кода, где двое из людей, работающих над ней, слепо подписываются на теорию «единой точки выхода», и я могу сказать вам, что по опыту это ужасная ужасная практика. Это делает код чрезвычайно сложным в обслуживании, и я покажу вам, почему.
С теорией «единой точки выхода» вы неизбежно получите код, который выглядит следующим образом:
Мало того, что это делает код очень трудным для отслеживания, но теперь скажем, что вам нужно вернуться назад и добавить операцию между 1 и 2. Вы должны сделать отступ практически для всей функции, и удачи, убедившись, что все Ваши условия if / else и фигурные скобки соответствуют друг другу.
Этот метод делает обслуживание кода чрезвычайно сложным и подверженным ошибкам.
источник
Структурированное программирование говорит, что вы должны иметь только один оператор возврата для каждой функции. Это для ограничения сложности. Многие люди, такие как Мартин Фаулер, утверждают, что проще писать функции с несколькими операторами возврата. Он представляет этот аргумент в классической книге рефакторинга, которую он написал. Это хорошо работает, если вы будете следовать его другим советам и писать небольшие функции. Я согласен с этой точкой зрения, и только пуристы строгого структурированного программирования придерживаются единственного оператора return для каждой функции.
источник
GOTO
для перемещения потока управления, даже когда функция существовала. Он никогда не говорит "никогда не использоватьGOTO
".Как отмечает Кент Бек, обсуждая защитные предложения в шаблонах реализации, у подпрограммы есть одна точка входа и выхода ...
Я нахожу функцию, написанную с защитными предложениями, намного более легкой, чем одна длинная вложенная группа
if then else
утверждений.источник
В функции, которая не имеет побочных эффектов, нет веской причины иметь более одного возврата, и вы должны написать их в функциональном стиле. В методе с побочными эффектами вещи более последовательны (индексируются по времени), поэтому вы пишете в императивном стиле, используя инструкцию return в качестве команды для прекращения выполнения.
Другими словами, по возможности, поддерживайте этот стиль
через это
Если вы обнаружите, что пишете несколько слоев вложенных условий, возможно, есть способ реорганизовать это, например, используя список предикатов. Если вы обнаружите, что ваши if и elses синтаксически далеко друг от друга, вы можете разбить это на более мелкие функции. Условный блок, который занимает больше, чем экранный текст, трудно читать.
Там нет жесткого и быстрого правила, которое применяется к каждому языку. Что-то вроде наличия одного оператора return не сделает ваш код хорошим. Но хороший код позволит вам писать свои функции таким образом.
источник
Я видел это в стандартах кодирования для C ++, которые были пережитком C, как если бы у вас не было RAII или другого автоматического управления памятью, тогда вы должны очищать для каждого возврата, что либо означает вырезать и вставить очистки или перехода (логически то же самое, что «наконец» в управляемых языках), оба из которых считаются дурным тоном. Если вы практикуете использование умных указателей и коллекций в C ++ или другой автоматической системе памяти, то для этого нет веских оснований, и все сводится к удобочитаемости и, скорее, к суждению.
источник
auto_ptr
, вы можете использовать простые указатели параллельно. Хотя было бы странно писать «оптимизированный» код с неоптимизирующим компилятором.try
...finally
в Java), и вам нужно выполнить обслуживание ресурсов, которое вы могли бы сделать с одним возврат в конце метода. Прежде чем сделать это, вы должны серьезно подумать о рефакторинге кода, чтобы избавиться от ситуации.Я склоняюсь к мысли, что операторы return в середине функции плохие. Вы можете использовать return для создания нескольких защитных предложений в верхней части функции и, конечно, сообщить компилятору, что возвращать в конце функции без проблем, но возврат в середине функции может быть легко пропущен и может усложнить интерпретацию функции.
источник
Да , есть:
Вопрос часто ставится как ложная дихотомия между множественными возвратами или глубоко вложенными операторами if. Почти всегда существует третье решение, которое является очень линейным (без глубокого вложения) с единственной точкой выхода.
Обновление : очевидно, руководящие принципы MISRA также предусматривают единый выход .
Чтобы быть ясным, я не говорю, что это всегда неправильно иметь несколько возвратов. Но, учитывая иные эквивалентные решения, есть много веских причин, чтобы предпочесть тот, с одним возвратом.
источник
Contract.Ensures
несколько точек возврата.goto
для доступа к общему коду очистки, вы, вероятно, упростили функцию, чтобыreturn
в конце кода очистки был один. Таким образом, можно сказать, что вы решили проблемуgoto
, но я бы сказал, что вы решили ее, упростив до единогоreturn
.Наличие единственной точки выхода действительно дает преимущество в отладке, поскольку позволяет установить единственную точку останова в конце функции, чтобы увидеть, какое значение на самом деле будет возвращено.
источник
В общем, я стараюсь иметь только одну точку выхода из функции. Однако бывают случаи, когда это фактически приводит к созданию более сложного тела функции, чем необходимо, и в этом случае лучше иметь несколько точек выхода. Это действительно должен быть «призыв к суждению», основанный на результирующей сложности, но цель должна состоять в том, чтобы как можно меньше точек выхода без ущерба для сложности и понятности.
источник
Нет, потому что мы больше не живем в 1970-х годах . Если ваша функция достаточно длинная, чтобы множественные возвраты были проблемой, она слишком длинная.
(Совершенно независимо от того факта, что любая многострочная функция в языке с исключениями в любом случае будет иметь несколько точек выхода.)
источник
Я бы предпочел единый выход, если он действительно не усложнит ситуацию. Я обнаружил, что в некоторых случаях несколько существующих точек могут маскировать другие более важные проблемы проектирования:
Увидев этот код, я бы сразу спросил:
В зависимости от ответов на эти вопросы может быть
В обоих вышеупомянутых случаях код, вероятно, может быть переработан с утверждением, гарантирующим, что 'foo' никогда не будет нулевым, а соответствующие вызывающие абоненты изменены.
Есть две другие причины (которые, я думаю, специфичны для кода C ++), когда множественность существует, может оказать негативное влияние. Это размер кода и оптимизация компилятора.
У не-POD C ++ объекта в области видимости при выходе из функции будет вызван деструктор. Там, где есть несколько операторов возврата, это может быть случай, когда существуют разные объекты в области видимости, поэтому список вызываемых деструкторов будет другим. Поэтому компилятор должен генерировать код для каждого оператора возврата:
Если размер кода является проблемой, то этого стоит избегать.
Другая проблема связана с «Оптимизацией именованных возвращаемых значений» (также известной как «Копия Elision», ISO C ++ '03 12.8 / 15). C ++ позволяет реализации пропустить вызов конструктора копирования, если он может:
Просто принимая код как есть, объект «a1» создается в «foo», и затем вызывается его конструкция копирования для создания «a2». Однако, elision copy позволяет компилятору создать «a1» в том же месте в стеке, что и «a2». Поэтому нет необходимости «копировать» объект при возврате функции.
Многочисленные точки выхода усложняют работу компилятора при попытке обнаружить это, и, по крайней мере, для сравнительно недавней версии VC ++ оптимизация не имела места, когда тело функции имело несколько возвращений. Посмотрите Оптимизацию Именованного Возвращаемого значения в Visual C ++ 2005 для получения дополнительной информации.
источник
throw new ArgumentNullException()
в данном случае в C #), мне очень понравились ваши другие соображения, они все действительны для меня и могут быть критическими в некоторых Нишевые контексты.foo
тестируется, не имеет ничего общего с предметом, а именно, делать ли этоif (foo == NULL) return; dowork;
илиif (foo != NULL) { dowork; }
Наличие единой точки выхода уменьшает цикломатическую сложность и, следовательно, теоретически снижает вероятность того, что вы будете вносить ошибки в свой код при его изменении. Однако практика, как правило, предполагает, что необходим более прагматичный подход. Поэтому я стремлюсь иметь одну точку выхода, но позволяю моему коду иметь несколько, если это более читабельно.
источник
Я заставляю себя использовать только одно
return
утверждение, так как оно в некотором смысле вызывает запах кода. Позволь мне объяснить:(Условия являются произвольными ...)
Чем больше условий, тем больше становится функция, тем сложнее ее прочитать. Так что, если вы настроены на запах кода, вы поймете это и захотите изменить код. Два возможных решения:
Многократный возврат
Отдельные функции
Конечно, это длиннее и немного грязно, но в процессе рефакторинга функции таким образом, мы
источник
Я бы сказал, что у вас должно быть столько, сколько требуется, или любое, что делает код чище (например, пункты охраны ).
Лично я никогда не слышал / не видел, чтобы "лучшие практики" говорили, что у вас должно быть только одно ответное заявление.
По большей части я стремлюсь как можно скорее выйти из функции, основываясь на логическом пути (пункты охраны - отличный пример этого).
источник
Я считаю, что множественные возвраты обычно хороши (в коде, который я пишу на C #). Стиль единственного возврата - это удержание от C. Но вы, вероятно, не программируете на C.
Нет закона, требующего только одну точку выхода для метода на всех языках программирования . Некоторые люди настаивают на превосходстве этого стиля, и иногда они возводят его в «правило» или «закон», но это убеждение не подтверждается никакими доказательствами или исследованиями.
Несколько кодов возврата могут быть плохой привычкой в коде C, где ресурсы должны быть явно выделены, но такие языки, как Java, C #, Python или JavaScript, имеют такие конструкции, как автоматический сбор мусора и
try..finally
блоки (иusing
блоки в C # ), и этот аргумент неприменим - в этих языках очень редко требуется централизованное ручное освобождение ресурсов.Есть случаи, когда одно возвращение является более читабельным, и случаи, когда это не так. Посмотрите, уменьшает ли это количество строк кода, делает логику более понятной или уменьшает количество фигурных скобок и отступов или временных переменных.
Поэтому используйте столько возвратов, сколько соответствует вашим художественным способностям, потому что это вопрос макета и читаемости, а не технический.
Я говорил об этом более подробно в своем блоге .
источник
Есть хорошие вещи, которые нужно сказать о наличии единой точки выхода, так же как и плохие вещи, которые нужно сказать о неизбежном программировании «стрелок», которое в результате получается.
Если при проверке входных данных или распределении ресурсов используются несколько точек выхода, я стараюсь очень четко поместить все «выходы с ошибками» в верхнюю часть функции.
Как в статье о спартанском программировании "SSDSLPedia", так и в статье о точках выхода из одной функции "Wiki репозитория шаблонов Portland" есть несколько проницательных аргументов. Также, конечно, есть этот пост для рассмотрения.
Если вам действительно нужна единая точка выхода (на любом языке без исключений), например, для того, чтобы высвободить ресурсы в одном месте, я считаю, что осторожное применение goto будет хорошим; посмотрите, например, этот довольно надуманный пример (сжатый, чтобы сохранить экранную недвижимость):
Лично я вообще не люблю программирование стрелок больше, чем несколько точек выхода, хотя обе они полезны при правильном применении. Лучше всего, конечно, структурировать вашу программу так, чтобы она не требовала ни того, ни другого. Разбивка вашей функции на несколько частей обычно помогает :)
Хотя при этом я все равно получаю несколько точек выхода, как в этом примере, где некоторая более крупная функция разбита на несколько более мелких функций:
В зависимости от проекта или руководящих принципов кодирования большая часть кода котельной плиты может быть заменена макросами. В качестве примечания, если разбить его таким образом, функции g0, g1, g2 очень легко тестировать по отдельности.
Очевидно, что в языке OO и с поддержкой исключений я бы не использовал подобные операторы if (или вообще, если бы мне это удавалось без особых усилий), и код был бы намного более простым. И без стрелок. И большинство не окончательных результатов, вероятно, будут исключениями.
Короче говоря;
источник
Вы знаете пословицу - красота в глазах смотрящего .
Некоторые люди клянутся NetBeans, а некоторые IntelliJ IDEA , некоторые Python, а некоторые PHP .
В некоторых магазинах вы можете потерять работу, если будете настаивать на этом:
Все дело в наглядности и удобстве обслуживания.
Я пристрастился к использованию булевой алгебры для сокращения и упрощения логики и использования конечных автоматов. Однако, в прошлом были коллеги, которые полагали, что мое использование «математических методов» в кодировании не подходит, потому что оно не будет видимым и поддерживаемым. И это было бы плохой практикой. Извините, люди, которые я использую, для меня очень наглядны и понятны, потому что, когда я вернусь к коду шесть месяцев спустя, я ясно пойму код, а не беспорядочные спагетти.
Эй, приятель (как обычно говорил бывший клиент), делай что хочешь, если знаешь, как это исправить, когда мне нужно, чтобы ты это исправил.
Я помню, 20 лет назад мой коллега был уволен за использование того, что сегодня будет называться стратегией гибкого развития . У него был дотошный пошаговый план. Но его менеджер кричал на него: «Вы не можете постепенно предоставлять функции пользователям! Вы должны придерживаться водопада ». Он ответил менеджеру, что постепенное развитие будет более точным в соответствии с потребностями клиента. Он верил в разработку для нужд клиентов, но менеджер верил в кодирование по «требованию клиента».
Мы часто виновны в нарушении нормализации данных, границ MVP и MVC . Мы встраиваем, а не строим функцию. Мы берем ярлыки.
Лично я считаю, что PHP - плохая практика, но что я знаю. Все теоретические аргументы сводятся к попытке выполнить один набор правил
Все остальные правила уходят на второй план. И, конечно, это правило никогда не исчезает:
источник
Я склоняюсь к использованию охранных предложений, чтобы вернуться рано и в противном случае выйти в конце метода. Единственное правило входа и выхода имеет историческое значение и было особенно полезно при работе с устаревшим кодом, который занимал до 10 страниц А4 для одного метода C ++ с множественными возвратами (и многими дефектами). В последнее время общепринятой практикой является сохранение небольших методов, что делает множественные выходы менее значительными для понимания. В следующем примере Kronoz, скопированном сверху, вопрос в том, что происходит в // Остальном коде ... ?:
Я понимаю, что пример несколько надуманный, но у меня возникнет соблазн реорганизовать цикл foreach в оператор LINQ, который затем можно будет рассматривать как защитное предложение. Опять же , в надуманный пример намерение кода не является очевидным и SomeFunction () может иметь какой - либо другой побочный эффект или результат может быть использован в // Остальной код ... .
Предоставление следующей рефакторированной функции:
источник
null
вместо того, чтобы выдавать исключение, указывающее, что аргумент не принят?Одна хорошая причина, которую я могу придумать, заключается в обслуживании кода: у вас есть единственная точка выхода. Если вы хотите изменить формат результата, ... это гораздо проще реализовать. Также для отладки вы можете просто поставить точку останова там :)
Сказав это, мне когда-то приходилось работать в библиотеке, где стандарты кодирования накладывали «один оператор возврата на функцию», и я нашел это довольно жестким. Я пишу много кода для числовых вычислений, и часто бывают «особые случаи», так что в конечном итоге за кодом было довольно трудно следовать ...
источник
Несколько точек выхода подходят для достаточно небольших функций, то есть функции, которую можно просматривать на одном экране в целом. Если длинная функция также включает в себя несколько точек выхода, это признак того, что эту функцию можно продолжить.
Тем не менее, я избегаю функций множественного выхода, если это абсолютно необходимо . Я чувствовал боль от ошибок, которые происходят из-за некоторого случайного возврата в какой-то непонятной строке в более сложных функциях.
источник
Я работал с ужасными стандартами кодирования, которые навязывали вам единый путь выхода, и результатом почти всегда были неструктурированные спагетти, если функция не тривиальна - вы в конечном итоге получаете множество разрывов и продолжений, которые просто мешают.
источник
if
оператор перед каждым вызовом метода, который возвращает успех или нет :(Единая точка выхода - при прочих равных условиях - делает код значительно более читабельным. Но есть подвох: популярная конструкция
является подделкой, "Res =" не намного лучше, чем "возврат". Он имеет один оператор возврата, но несколько точек, где функция фактически заканчивается.
Если у вас есть функция с несколькими возвратами (или "res =" s), часто неплохо разбить ее на несколько меньших функций с одной точкой выхода.
источник
Моя обычная политика - иметь только один оператор return в конце функции, если сложность кода не будет значительно уменьшена путем добавления большего количества. На самом деле, я скорее фанат Eiffel, который применяет единственное правило возврата, не имея оператора return (есть просто автоматически созданная переменная «result», в которую можно поместить ваш результат).
Конечно, есть случаи, когда код можно сделать более понятным с множественными возвратами, чем очевидная версия без них. Можно утверждать, что требуется дополнительная доработка, если у вас есть функция, которая слишком сложна, чтобы ее можно было понять без множественных операторов возврата, но иногда полезно быть прагматичным в таких вещах.
источник
Если вы получите более нескольких возвратов, возможно, что-то не так с вашим кодом. В противном случае я бы согласился, что иногда приятно иметь возможность возвращаться из нескольких мест в подпрограмме, особенно когда это делает код чище.
Perl 6: плохой пример
было бы лучше написать так
Perl 6: хороший пример
Обратите внимание, что это был только быстрый пример
источник
Я голосую за Единственное возвращение в конце как руководство. Это помогает обычной обработке очистки кода ... Например, взгляните на следующий код ...
источник
Вероятно, это необычная перспектива, но я думаю, что любому, кто считает, что нужно использовать несколько операторов return, никогда не приходилось использовать отладчик на микропроцессоре, который поддерживает только 4 аппаратных точки останова. ;-)
Хотя проблемы с «кодом стрелки» полностью верны, одна проблема, которая, похоже, исчезает при использовании нескольких операторов return, - это ситуация, когда вы используете отладчик. У вас нет удобной универсальной позиции для установки точки останова, чтобы гарантировать, что вы увидите выход и, следовательно, условие возврата.
источник
Чем больше операторов возврата у вас есть в функции, тем выше сложность в этом одном методе. Если вам интересно, есть ли у вас слишком много возвращаемых операторов, вы можете спросить себя, не слишком ли много строк кода в этой функции.
Но нет, нет ничего плохого в одном / многих операторах возврата. В некоторых языках это лучше (C ++), чем в других (C).
источник