Почему у функции должна быть только одна точка выхода? [закрыто]

97

Я всегда слышал о функции единственной точки выхода как о плохом способе кодирования, потому что вы теряете читаемость и эффективность. Я никогда не слышал, чтобы кто-нибудь возражал против другой стороны.

Я думал, что это как-то связано с CS, но этот вопрос был снят на cstheory stackexchange.

шестнадцатеричный боб-омб
источник
6
Ответ в том, что не существует всегда правильного ответа. Мне часто легче кодировать с несколькими выходами. Я также обнаружил (при обновлении кода выше), что изменение / расширение кода было сложнее из-за тех же самых множественных выходов. Наша работа - принимать такие индивидуальные решения. Когда на решение всегда есть «лучший» ответ, в нас нет необходимости.
JS.
1
@finnw фашистские модники удалили последние два вопроса, чтобы на них приходилось отвечать снова, и снова, и снова,
Маартен Бодевес,
Несмотря на слово «спорить» в вопросе, я действительно не думаю, что это вопрос, основанный на мнении. Это вполне актуально для хорошего дизайна и т. Д. Я не вижу причин для его закрытия, но ж / д.
Ungeheuer
1
Единая точка выхода упрощает отладку, чтение, измерение и настройку производительности, рефакторинг. Это объективно и существенно важно. Использование ранних возвратов (после простой проверки аргументов) позволяет разумно сочетать оба стиля. Учитывая преимущества единой точки выхода, засорение кода возвращаемыми значениями - просто свидетельство ленивого, небрежного, небрежного программиста - и, по крайней мере, возможно, не любящего щенков.
Рик О'Ши

Ответы:

108

Существуют разные школы мысли, и все зависит от личных предпочтений.

Во-первых, это менее запутанно, если есть только одна точка выхода - у вас есть единственный путь через метод и вы знаете, где искать выход. С другой стороны, если вы используете отступ для представления вложенности, ваш код в конечном итоге сильно смещается вправо, и становится очень трудно отслеживать все вложенные области.

Другой заключается в том, что вы можете проверить предварительные условия и выйти на раннем этапе в начале метода, чтобы вы знали в теле метода, что определенные условия истинны, без отступа всего тела метода на 5 миль вправо. Обычно это сводит к минимуму количество областей, о которых вам нужно беспокоиться, что значительно упрощает отслеживание кода.

В-третьих, вы можете выйти куда угодно. Раньше это было более запутанным, но теперь, когда у нас есть редакторы синтаксической раскраски и компиляторы, которые обнаруживают недоступный код, с этим намного легче справиться.

Я прямо в среднем лагере. Обеспечение единой точки выхода является бессмысленным или даже контрпродуктивным ограничением ИМХО, в то время как случайный выход по всему методу иногда может привести к запутанной сложной логике, когда становится трудно увидеть, будет ли данный бит кода или нет выполнен. Но «стробирование» вашего метода позволяет значительно упростить его тело.

Джейсон Уильямс
источник
1
Глубокой вложенности в singe exitпарадигму можно избежать с помощью go toутверждений. Кроме того, появляется возможность выполнить некоторую постобработку под локальной Errorметкой функции, что невозможно с несколькими returns.
Ant_222 07
2
Обычно существует хорошее решение, позволяющее избежать необходимости переходить к нему. Я предпочитаю «return (Fail (...))» и поместить общий код очистки в метод Fail. Это может потребовать передачи нескольких локальных переменных, чтобы позволить освободить память и т. Д., Но если вы не используете критический для производительности бит кода, это обычно гораздо более чистое решение, чем goto IMO. Это также позволяет нескольким методам совместно использовать аналогичный код очистки.
Джейсон Уильямс
Существует оптимальный подход, основанный на объективных критериях, но мы можем согласиться с тем, что существуют школы мысли (правильные и неправильные), и все сводится к личным предпочтениям (предпочтение или против правильного подхода).
Рик О'Ши
39

Моя общая рекомендация заключается в том, что операторы возврата должны, когда это возможно, располагаться либо перед первым кодом, который имеет какие-либо побочные эффекты, либо после последнего кода, который имеет какие-либо побочные эффекты. Я бы рассмотрел что-то вроде:

  if (! аргумент) // Проверяем, не равно ли нулю
    вернуть ERR_NULL_ARGUMENT;
  ... обработать ненулевой аргумент
  если (хорошо)
    возврат 0;
  еще
    вернуть ERR_NOT_OK;

яснее, чем:

  int return_value;
  if (аргумент) // не ноль
  {
    .. обработать ненулевой аргумент
    .. установите результат соответствующим образом
  }
  еще
    результат = ERR_NULL_ARGUMENT;
  вернуть результат;

Если определенное условие должно помешать функции что-либо делать, я предпочитаю досрочно выйти из функции в месте выше точки, в которой функция что-либо сделает. Однако после того, как функция предприняла действия с побочными эффектами, я предпочитаю вернуться снизу, чтобы прояснить, что все побочные эффекты должны быть устранены.

суперкар
источник
Ваш первый пример управления okпеременной мне кажется подходом с одним возвратом. Более того, блок if-else можно просто переписать на:return ok ? 0 : ERR_NOT_OK;
Melebius
2
В первом примере returnв начале перед всем кодом, который делает все. Что касается использования ?:оператора, то его запись в отдельных строках упрощает во многих IDE присоединение точки останова отладки к сценарию «не в порядке». Кстати, настоящий ключ к «единой точке выхода» заключается в понимании того, что важно то, что для каждого конкретного вызова нормальной функции точка выхода - это точка сразу после вызова . Сегодня программисты принимают это как должное, но так было не всегда. В некоторых редких случаях код может обходиться без пространства стека, что приводит к функциям ...
supercat
... которые выходят через условные или вычисленные переходы. Обычно все, у кого достаточно ресурсов для программирования на любом другом языке, кроме ассемблера, сможет поддерживать стек, но я написал код сборки, который должен был работать с некоторыми очень жесткими ограничениями (вплоть до НУЛЯ байтов ОЗУ в одном случае), и наличие нескольких точек выхода может быть полезным в таких случаях.
supercat 07
1
Так называемый более ясный пример гораздо менее ясен и труден для чтения. Одну точку выхода всегда легче читать, легче поддерживать, легче отлаживать.
GuidoG
8
@GuidoG: Любой шаблон может быть более читаемым, в зависимости от того, что появляется в пропущенных разделах. Используя "return x;" проясняет, что если оператор будет достигнут, возвращаемое значение будет x. Используя "result = x;" оставляет открытой возможность того, что что-то еще может изменить результат до его возврата. Это может быть полезно, если действительно возникнет необходимость изменить результат, но программисты, исследующие код, должны будут проверить его, чтобы увидеть, как результат может измениться, даже если ответ - «не может».
supercat
15

Единая точка входа и выхода была оригинальной концепцией структурированного программирования по сравнению с пошаговым спагетти-кодированием. Существует мнение, что несколько функций точки выхода требуют большего количества кода, поскольку вам необходимо правильно очистить пространство памяти, выделенное для переменных. Рассмотрим сценарий, в котором функция выделяет переменные (ресурсы), и ранний выход из функции без надлежащей очистки приведет к утечке ресурсов. Кроме того, очистка перед каждым выходом создаст много избыточного кода.

PSS
источник
Это действительно не проблема с RAII
BigSandwich
14

В большинстве случаев это сводится к потребностям результата. В «старые времена» спагетти-код с несколькими точками возврата приводил к утечкам памяти, поскольку кодеры, которые предпочитали этот метод, обычно плохо очищались. Также были проблемы с некоторыми компиляторами, которые «теряли» ссылку на возвращаемую переменную, когда стек был вытянут во время возврата, в случае возврата из вложенной области. Более общая проблема заключалась в повторном входе в код, который пытается сделать так, чтобы состояние вызова функции было точно таким же, как и состояние ее возврата. Мутаторы oop нарушили это, и концепция была отложена.

Есть результаты, в первую очередь ядра, которым требуется скорость, обеспечиваемая несколькими точками выхода. Эти среды обычно имеют собственную память и управление процессами, поэтому риск утечки сводится к минимуму.

Лично мне нравится иметь единую точку выхода, поскольку я часто использую ее для вставки точки останова в оператор return и выполнения проверки кода того, как код определил это решение. Я мог просто подойти к входу и пройти через него, что я делаю с помощью широко вложенных и рекурсивных решений. Как обозреватель кода, множественные возвраты в функции требуют более глубокого анализа - поэтому, если вы делаете это для ускорения реализации, вы ограбите Питера, чтобы спасти Пола. На обзоры кода потребуется больше времени, что сделает недействительной презумпцию эффективной реализации.

- 2 цента

Пожалуйста, смотрите этот документ для более подробной информации: NISTIR 5459

sscheider
источник
8
multiple returns in a function requires a much deeper analysisтолько если функция уже огромна (> 1 экран), иначе это упрощает анализ
dss539
2
множественные возвраты никогда не облегчают анализ, просто наоборот
GuidoG
1
ссылка мертвая (404).
fusi
1
@fusi - нашел на archive.org и обновил ссылку здесь
sscheider
4

На мой взгляд, совет выйти из функции (или другой структуры управления) только в одном месте часто бывает перепродан. Обычно приводятся две причины для выхода только в один момент:

  1. Код с одним выходом якобы легче читать и отлаживать. (Я признаю, что не очень думаю об этой причине, но она дана. Что существенно легче читать и отлаживать, так это код с однократной записью .)
  2. Код с одним выходом связывает и возвращает более четко.

Вторая причина тонкая и имеет некоторые достоинства, особенно если функция возвращает большую структуру данных. Однако я бы не стал слишком об этом беспокоиться, кроме ...

Если вы студент, вы хотите получить высшие оценки в своем классе. Делайте то, что предпочитает инструктор. У него, вероятно, есть веская причина с его точки зрения; так что, по крайней мере, вы узнаете его точку зрения. Это само по себе имеет ценность.

Удачи.

thb
источник
4

Раньше я был сторонником единого выхода. Мои рассуждения в основном исходили из боли ...

Однократный выход легче отлаживать.

С учетом техник и инструментов, которые у нас есть сегодня, это гораздо менее разумная позиция, поскольку модульные тесты и ведение журнала могут сделать единый выход ненужным. Тем не менее, когда вам нужно наблюдать за выполнением кода в отладчике, было намного сложнее понять и работать с кодом, содержащим несколько точек выхода.

Это стало особенно актуально, когда вам нужно было вставлять назначения, чтобы проверить состояние (заменено выражениями наблюдения в современных отладчиках). Также было слишком легко изменить поток управления таким образом, чтобы скрыть проблему или полностью нарушить выполнение.

Методы с одним выходом было легче выполнить в отладчике, и их легче было разделить, не нарушая логики.

Майкл Мерфри
источник
0

Ответ очень зависит от контекста. Если вы создаете графический интерфейс и у вас есть функция, которая инициализирует API и открывает окна в начале вашего main, он будет полон вызовов, которые могут вызывать ошибки, каждая из которых приведет к закрытию экземпляра программы. Если вы используете вложенные операторы IF и отступы, ваш код может быстро сильно смещаться вправо. Возврат при ошибке на каждом этапе может быть лучше и на самом деле более читаемым, при этом его так же легко отлаживать с помощью нескольких флагов в коде.

Однако, если вы тестируете разные условия и возвращаете разные значения в зависимости от результатов в вашем методе, может быть гораздо лучше иметь одну точку выхода. Раньше я работал над скриптами обработки изображений в MATLAB, которые могли стать очень большими. Множественные точки выхода могут затруднить отслеживание кода. Операторы переключения были гораздо более подходящими.

Лучше всего учиться на ходу. Если вы пишете код для чего-то, попробуйте найти код других людей и посмотреть, как они его реализуют. Решите, какие кусочки вам нравятся, а какие - нет.

Flash_Steel
источник
-6

Если вы чувствуете, что вам нужно несколько точек выхода в функции, функция слишком велика и делает слишком много.

Я бы рекомендовал прочитать главу о функциях в книге Роберта К. Мартина «Чистый код».

По сути, вы должны попытаться написать функции с 4 строками кода или меньше.

Некоторые заметки от блога Майка Лонга :

  • Первое правило функций: они должны быть небольшими
  • Второе правило функций: они должны быть меньше этого
  • Блоки внутри операторов if, операторов while, циклов for и т. Д. Должны быть длиной в одну строку.
  • … И эта строка кода обычно будет вызовом функции
  • Не должно быть более одного или двух уровней отступа.
  • Функции должны делать одно
  • Операторы функций должны быть на одном уровне абстракции.
  • Функция должна иметь не более 3 аргументов.
  • Выходные аргументы - это запах кода
  • Передавать в функцию логический флаг действительно ужасно. Вы по определению делаете в функции две вещи.
  • Побочные эффекты - ложь.
Роджер Даль
источник
29
4 строки? Что вы кодируете, что обеспечивает такую ​​простоту? Я действительно сомневаюсь, что ядро ​​Linux или git, например, это делают.
shinzou
7
«Передача логического флага в функцию действительно ужасна. Вы по определению делаете в функции две вещи». По определению? Нет ... это логическое значение потенциально может повлиять только на одну из ваших четырех строк. Кроме того, хотя я согласен с тем, чтобы размер функции был небольшим, четыре - это слишком строго. Это следует воспринимать как очень неопределенный ориентир.
Джесси
12
Добавление подобных ограничений неизбежно приведет к путанице в коде. Это больше о методах, которые должны быть лаконичными и делать только то, для чего они предназначены, без ненужных побочных эффектов.
Джесси
10
Один из редких ответов на SO, который я хотел бы несколько раз проголосовать против.
Стивен Рэндс,
8
К сожалению, этот ответ выполняет несколько задач и, вероятно, должен быть разделен на несколько других - все меньше четырех строк.
Eli