Я часто общаюсь с программистами, которые говорят: « Не помещайте несколько операторов return в один и тот же метод». Когда я прошу их рассказать мне причины, все, что я получаю, это « Стандарт кодирования говорит об этом » или « Это сбивает с толку ». Когда они показывают мне решения с помощью одного оператора return, код кажется мне более уродливым. Например:
if (condition)
return 42;
else
return 97;
« Это ужасно, вы должны использовать локальную переменную! »
int result;
if (condition)
result = 42;
else
result = 97;
return result;
Как это 50% -ое раздувание кода облегчает понимание программы? Лично мне сложнее, потому что пространство состояний только что увеличилось на другую переменную, которую можно было бы легко предотвратить.
Конечно, обычно я просто написал бы:
return (condition) ? 42 : 97;
Но многие программисты избегают условного оператора и предпочитают длинную форму.
Откуда пришло это понятие «только одно возвращение»? Есть ли историческая причина, по которой возникла эта конвенция?
источник
Ответы:
«Один вход, один выход» был написан, когда большинство программ было написано на ассемблере, FORTRAN или COBOL. Это было неправильно истолковано, потому что современные языки не поддерживают практики, против которых Дейкстра предупреждал.
«Один вход» означал «не создавать альтернативные точки входа для функций». На языке ассемблера, конечно, можно ввести функцию по любой инструкции. FORTRAN поддерживает несколько записей для функций с
ENTRY
оператором:«Единый выход» означал, что функция должна возвращаться только в одно место: оператор, следующий сразу за вызовом. Это не значит, что функция должна возвращаться только из одного места. Когда было написано структурированное программирование , для функции было обычной практикой указывать ошибку, возвращаясь в другое место. FORTRAN поддерживал это с помощью «альтернативного возврата»:
Оба эти метода были очень подвержены ошибкам. Использование альтернативных записей часто оставляло некоторую переменную неинициализированной. Использование альтернативных возвратов имело все проблемы с оператором GOTO, с дополнительным осложнением, состоящим в том, что условие ветвления было не смежным с ветвью, а где-то в подпрограмме.
источник
const
с тех пор, как многие из пользователей здесь родились, так что больше не нужны капитальные константы даже в Си. Но Java сохранила все эти старые дурные привычки .setjmp/longjmp
,?)Это понятие Single Entry, Single Exit (SESE) происходит от языков с явным управлением ресурсами , таких как C и ассемблер. В C такой код будет пропускать ресурсы:
На таких языках у вас есть три основных варианта:
Скопируйте код очистки.
Тьфу. Избыточность это всегда плохо.
Используйте,
goto
чтобы перейти к коду очистки.Это требует, чтобы код очистки был последним в функции. (И вот почему некоторые утверждают, что
goto
имеет свое место. И оно действительно - в C.)Введите локальную переменную и управляйте потоком управления через нее.
Недостатком является то, что управление потоком манипулируют с помощью синтаксиса (думаю
break
,return
,if
,while
) намного легче следить , чем поток управления манипулируют через состояние переменных (поскольку эти переменные не имеют состояния , когда вы смотрите на алгоритме).В сборке это даже страннее, потому что вы можете перейти к любому адресу в функции при вызове этой функции, что фактически означает, что у вас есть практически неограниченное количество точек входа в любую функцию. (Иногда это полезно. Такие приемы являются распространенным приемом для компиляторов для реализации
this
настройки указателя, необходимой для вызоваvirtual
функций в сценариях множественного наследования в C ++.)Когда вам приходится управлять ресурсами вручную, использование параметров входа или выхода из функции в любом месте приводит к более сложному коду и, следовательно, к ошибкам. Поэтому появилась школа мысли, которая распространяла SESE, чтобы получить более чистый код и меньше ошибок.
Однако, когда в языке есть исключения, (почти) любая функция может быть преждевременно закрыта в (почти) любой точке, поэтому вам все равно необходимо предусмотреть возможность преждевременного возврата. (Я думаю, что
finally
в основном используется для этого в Java иusing
(при реализацииIDisposable
, вfinally
противном случае) в C #; в C ++ вместо этого используется RAII .) После того, как вы это сделаете, вы не сможете не выполнить очистку после себя из-за раннегоreturn
утверждения, так что, вероятно, самый сильный аргумент в пользу SESE исчез.Это оставляет читабельность. Конечно, функция 200 LoC с полудюжиной
return
операторов, случайно разбросанных по ней, не является хорошим стилем программирования и не подходит для читабельного кода. Но такую функцию было бы нелегко понять без этих преждевременных возвратов.В тех языках, где ресурсы не управляются или не должны управляться вручную, нет смысла или нет смысла придерживаться старого соглашения SESE. OTOH, как я уже говорил выше, SESE часто делает код более сложным . Это динозавр, который (за исключением C) плохо вписывается в большинство современных языков. Вместо того, чтобы помочь пониманию кода, это мешает.
Почему Java-программисты придерживаются этого? Я не знаю, но из моего (вне) POV, Java взяла много соглашений из C (где они имеют смысл) и применила их к своему OO-миру (где они бесполезны или просто плохи), где теперь придерживается их, независимо от того, что стоит. (Как соглашение, чтобы определить все ваши переменные в начале области.)
Программисты придерживаются всевозможных странных обозначений по иррациональным причинам. (Глубоко вложенные структурные утверждения - «наконечники стрел» - в таких языках, как Паскаль, когда-то рассматривались как прекрасный код.) Применение чистых логических рассуждений к этому, кажется, не может убедить большинство из них отклониться от своих устоявшихся способов. Вероятно, лучший способ изменить такие привычки - это научить их делать то, что лучше, а не то, что принято. Вы, будучи учителем программирования, держите это в руках.
:)
источник
finally
разделам, где он выполняется независимо от раннихreturn
s или исключений.finally
большую часть времени.malloc()
иfree()
в комментарий в качестве примера), я говорил о ресурсах в целом. Я также не предполагал, что GC решит эти проблемы. (Я упомянул C ++, в котором нет GC из коробки.) Из того, что я понимаю, в Javafinally
используется для решения этой проблемы.С одной стороны, одиночные операторы возврата упрощают ведение журнала, а также формы отладки, основанные на ведении журнала. Я помню, как много раз мне приходилось сводить функцию к единому возврату, просто чтобы распечатать возвращаемое значение в одной точке.
С другой стороны, вы могли бы реорганизовать это в
function()
эти вызовы_function()
и записать результат.источник
_function()
, сreturn
s в соответствующих местах и оберткой с именем,function()
которая обрабатывает постороннее ведение журнала, чем иметь одинfunction()
с искаженной логикой, чтобы все возвраты помещались в один выход -точка, чтобы я мог вставить дополнительное утверждение до этой точки.«Один вход, один выход» возник в результате революции в области структурированного программирования в начале 1970-х годов, которая была начата письмом Эдсгера В. Дейкстры в редакцию « Заявление GOTO считается опасным ». Концепции структурного программирования были подробно изложены в классической книге «Структурное программирование» Оле Йохана-Даля, Эдсгера В. Дейкстры и Чарльза Энтони Ричарда Хоара.
"GOTO Заявление считается вредным" необходимо читать, даже сегодня. «Структурированное программирование» устарело, но все еще очень, очень полезно, и должно быть на вершине списка «Обязательно читать» любого разработчика, намного выше всего, например, от Стива Макконнелла. (В разделе Даля изложены основы классов в Simula 67, которые являются технической основой для классов в C ++ и всего объектно-ориентированного программирования.)
источник
goto
можно было буквально идти куда угодно , например, прямо в какую-то случайную точку в другой функции, обходя любое понятие процедур, функций, стека вызовов и т. Д. Ни один здравомыслящий язык не позволяет делать это в прямом смыслеgoto
. Сsetjmp
/longjmp
это единственный полуисключительный случай, о котором я знаю, и даже это требует сотрудничества с обеих сторон. (Полуиронично, что я использовал слово «исключительный», хотя, учитывая, что исключения делают почти одно и то же ...) По сути, статья не одобряет практику, которая давно умерла.Всегда легко связать Фаулера.
Одним из основных примеров, которые идут против SESE, являются пункты охраны:
Заменить вложенные условные выражения на охранные
источник
_isSeparated
и то и_isRetired
другое может быть правдой (и почему это невозможно?), Вы возвращаете неправильную сумму.Я написал пост в блоге на эту тему некоторое время назад.
Суть в том, что это правило относится к эпохе языков, в которых нет сборки мусора или обработки исключений. Нет формального исследования, которое показывает, что это правило приводит к улучшению кода на современных языках. Не стесняйтесь игнорировать это всякий раз, когда это приведет к более короткому или более читаемому коду. Парни из Java, которые настаивают на этом, слепо и беспрекословно следуют устаревшим, бессмысленным правилам.
Этот вопрос также задавался на Stackoverflow
источник
Одно возвращение облегчает рефакторинг. Попробуйте выполнить «метод извлечения» во внутреннем теле цикла for, который содержит возврат, разрыв или продолжение. Это не удастся, поскольку вы нарушили поток управления.
Дело в том, что я думаю, что никто не притворяется, что пишет идеальный код. Поэтому код регулярно подвергается рефакторингу, чтобы быть «улучшенным» и расширенным. Поэтому моя цель - сделать мой код максимально удобным для рефакторинга.
Часто я сталкиваюсь с проблемой, что мне нужно полностью переформулировать функции, если они содержат прерыватели потока управления и если я хочу добавить лишь небольшую функциональность. Это очень подвержено ошибкам, так как вы меняете весь поток управления вместо того, чтобы вводить новые пути в изолированные вложения. Если у вас есть только один возврат в конце или вы используете охрану для выхода из цикла, у вас, конечно, больше вложенности и больше кода. Но вы получаете возможности рефакторинга, поддерживаемые компилятором и IDE.
источник
Учтите, что множественные операторы возврата эквивалентны наличию GOTO для одного оператора возврата. Это то же самое, что и с операторами break. Таким образом, некоторые, как и я, считают их GOTO для всех намерений и целей.
Тем не менее, я не считаю, что эти типы GOTO вредны, и я без колебаний буду использовать реальный GOTO в моем коде, если найду вескую причину для этого.
Мое общее правило: GOTO предназначены только для управления потоком. Они никогда не должны использоваться для зацикливания, и вы никогда не должны идти «вверх» или «назад». (как работает перерыв / возврат)
Как уже упоминали другие, необходимо прочитать следующее заявление GOTO, которое считается вредным.
Однако имейте в виду, что оно было написано в 1970 году, когда GOTO слишком злоупотребляли. Не все GOTO вредны, и я не стал бы препятствовать их использованию, если вы не используете их вместо обычных конструкций, а скорее в странном случае, когда использование нормальных конструкций будет крайне неудобно.
Я считаю, что их использование в случаях ошибок, когда вам нужно покинуть область из-за сбоя, который никогда не должен происходить в обычных случаях, иногда полезен. Но вам также следует подумать о том, чтобы поместить этот код в отдельную функцию, чтобы вы могли просто вернуться раньше, чем использовать GOTO ... но иногда это также неудобно.
источник
Цикломатическая Сложность
Я видел, как SonarCube использует оператор множественного возврата для определения цикломатической сложности. Чем больше операторов возврата, тем выше цикломатическая сложность
Изменение типа возврата
Многократные возвраты означают, что нам нужно изменить в нескольких местах функции, когда мы решим изменить наш тип возврата.
Множественный выход
Его сложнее отладить, поскольку необходимо тщательно изучить логику в сочетании с условными операторами, чтобы понять, что вызвало возвращаемое значение.
Рефакторированный раствор
Решение нескольких операторов возврата состоит в том, чтобы заменить их полиморфизмом, имеющим один возврат, после разрешения требуемого объекта реализации.
источник