Откуда взялось понятие «только возвращение»?

Я часто говорю с программистами, которые говорят « Не ставьте несколько операторов возврата в один и тот же метод. « Когда я прошу их рассказать мне причины, почему все, что я получаю, это « Стандарт кодирования так говорит. "или" Это запутанно. "Когда они показывают мне решения с одним оператором return, код выглядит уродливее для меня. Например:

 if (condition)
   return 42;
else
   return 97;

" Это некрасиво, вам нужно использовать локальную переменную! "

 int result;
if (condition)
   result = 42;
else
   result = 97;
return result;

Как этот 50% -ный раздутый код делает программу более понятной? Лично мне это сложнее, потому что пространство состояний просто увеличилось на другую переменную, которая легко могла быть предотвращена.

Конечно, обычно я просто пишу:

 return (condition) ? 42 : 97;

Но многие программисты избегают условного оператора и предпочитают длинную форму.

Откуда взялось это понятие «одно возвращение»? Есть ли историческая причина, почему эта конвенция возникла?

953 голоса | спросил 11 revs, 10 users 40%
fredoverflow
1 Jam1000000amThu, 01 Jan 1970 03:00:00 +030070 1970, 03:00:00

8 ответов


976

«Single Entry, Single Exit» был написан, когда большинство программ было выполнено на языке ассемблера, FORTRAN или COBOL. Он был широко неверно истолкован, потому что современные языки не поддерживают практики, которые Дейкстра предупреждала.

«Единая запись» означает «не создавать альтернативные точки входа для функций». На языке ассемблера, конечно, можно ввести функцию в любую команду. FORTRAN поддерживал несколько записей в функциях с помощью инструкции ENTRY:

      SUBROUTINE S(X, Y)
      R = SQRT(X*X + Y*Y)
C ALTERNATE ENTRY USED WHEN R IS ALREADY KNOWN
      ENTRY S2(R)
      ...
      RETURN
      END

C USAGE
      CALL S(3,4)
C ALTERNATE USAGE
      CALL S2(5)

«Single Exit» означает, что функция должна возвращать в только одно место: оператор сразу после вызова. Это означало не , что функция должна возвращать из только одно место. Когда было написано Структурированное программирование , для функции указывать ошибку, возвращаясь к альтернативному местоположению, было обычной практикой. FORTRAN поддерживал это через «альтернативный возврат»:

C SUBROUTINE WITH ALTERNATE RETURN.  THE '*' IS A PLACE HOLDER FOR THE ERROR RETURN
      SUBROUTINE QSOLVE(A, B, C, X1, X2, *)
      DISCR = B*B - 4*A*C
C NO SOLUTIONS, RETURN TO ERROR HANDLING LOCATION
      IF DISCR .LT. 0 RETURN 1
      SD = SQRT(DISCR)
      DENOM = 2*A
      X1 = (-B + SD) / DENOM
      X2 = (-B - SD) / DENOM
      RETURN
      END

C USE OF ALTERNATE RETURN
      CALL QSOLVE(1, 0, 1, X1, X2, *99)
C SOLUTION FOUND
      ...
C QSOLVE RETURNS HERE IF NO SOLUTIONS
99    PRINT 'NO SOLUTIONS'

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

ответил Dan Diplo 10 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowFri, 10 Sep 2010 22:42:22 +0400 2010, 22:42:22
868

Это понятие Single Entry, Single Exit (SESE) происходит от языков с явным управлением ресурсами , таких как C и сборка. В C код, подобный этому, будет утечка ресурсов:

 void f()
{
  resource res = acquire_resource();  // think malloc()
  if( f1(res) )
    return; // leaks res
  f2(res);
  release_resource(res);  // think free()
}

В таких языках у вас есть три варианта:

  • Репликация кода очистки.
    Тьфу. Избыточность всегда плохая.

  • Используйте 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 (где они бесполезны или совершенно плохи), где он теперь придерживается их, независимо от того, какие расходы. (Как и соглашение, чтобы определить все ваши переменные в начале области.)

Программисты придерживаются всех странных обозначений по иррациональным причинам. (Глубоко вложенные структурные утверждения - «наконечники стрел» - были на таких языках, как Паскаль, когда-то рассматривались как прекрасный код.) Применение чисто логических рассуждений к этому, похоже, не убеждает большинство из них отклоняться от своих установленных способов. Лучший способ изменить такие привычки - это, вероятно, научить их на раннем этапе делать то, что лучше, а не то, что принято. Вы, будучи учителем программирования, держите его в своих руках. :)

ответил Dan Diplo 10 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowFri, 10 Sep 2010 22:42:22 +0400 2010, 22:42:22
75

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

   int function() {
     if (bidi) { print("return 1"); return 1; }
     for (int i = 0; i < n; i++) {
       if (vidi) { print("return 2"); return 2;}
     }
     print("return 3");
     return 3;
  }

С другой стороны, вы можете реорганизовать это в функцию function(), которая вызывает _function() и регистрирует результат.

ответил Dan Diplo 10 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowFri, 10 Sep 2010 22:42:22 +0400 2010, 22:42:22
50

«Единый вход, одиночный выход» возник из-за революции структурированного программирования в начале 1970-х годов, которая была выпущена письмом Эдсгера У. Дейкстры редактору « Заявление GOTO считается вредоносным ". Концепции структурированного программирования подробно изложены в классической книге «Структурированное программирование» Оле Йохана-Даля, Эдсера У. Дейкстры и Чарльза Энтони Ричарда Хоар.

«Заявление GOTO считается вредным» требуется читать, даже сегодня. «Структурированное программирование» датируется, но все же очень, очень полезно, и должно быть на вершине списка «Must Read» любого разработчика, намного превосходящего все, например, от. Стив Макконнелл. (В разделе Даля излагаются основы классов в Simula 67, которые являются технической основой для классов на C ++ и все объектно-ориентированное программирование.)

ответил Dan Diplo 10 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowFri, 10 Sep 2010 22:42:22 +0400 2010, 22:42:22
29

Всегда легко связать Фаулера.

Одним из основных примеров, которые идут против SESE, являются предложения охраны:

Заменить вложенные условные выражения с главой кластера

  

Используйте Guard Clauses для всех особых случаев

 double getPayAmount() {
    double result;
    if (_isDead) result = deadAmount();
    else {
        if (_isSeparated) result = separatedAmount();
        else {
            if (_isRetired) result = retiredAmount();
            else result = normalPayAmount();
        };
    }
return result;
};  
     

   А А А А А А А А А А А А А А А А А А А А А А А А А А А А А А А А А А А А А А А А А А А А   А А А А А А А А А А А А А А А А А А А А А А А А А А А А А А А А А А А А А А А А А А А А А А А А        http://www.refactoring.com/catalog/arrow.gif

 double getPayAmount() {
    if (_isDead) return deadAmount();
    if (_isSeparated) return separatedAmount();
    if (_isRetired) return retiredAmount();
    return normalPayAmount();
};  
     

Подробнее см. стр. 250 из Рефакторинг ...

ответил Dan Diplo 10 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowFri, 10 Sep 2010 22:42:22 +0400 2010, 22:42:22
8

Я написал сообщение в блоге по этой теме некоторое время назад.

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

Этот вопрос также задан в Stackoverflow

ответил Dan Diplo 10 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowFri, 10 Sep 2010 22:42:22 +0400 2010, 22:42:22
5

Рассмотрим тот факт, что несколько операторов возврата эквивалентны тому, что GOTO имеет один оператор возврата. Это тот же случай с операторами break. Таким образом, некоторые, как и я, считают их GOTO для всех целей и целей.

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

Мое общее правило заключается в том, что GOTO предназначены только для управления потоком. Они никогда не должны использоваться для каких-либо циклов, и вы никогда не должны GOTO «вверх» или «назад». (как работает breaks /returns)

Как уже упоминалось, необходимо прочитать следующее Заявление GOTO считается вредоносным Однако имейте в виду, что это было написано в 1970 году, когда использование GOTO было чрезмерным. Не каждый GOTO вреден, и я не буду препятствовать их использованию, если вы не используете их вместо обычных конструктов, а скорее в нечетном случае, когда использование обычных конструкций будет очень неудобно.

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

ответил Dan Diplo 10 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowFri, 10 Sep 2010 22:42:22 +0400 2010, 22:42:22
2

Один возврат делает рефакторинг проще. Попробуйте выполнить «метод извлечения» во внутреннее тело цикла for, которое содержит возврат, разрыв или продолжение. Это приведет к сбою, поскольку вы нарушили свой поток управления.

Дело в том, что я думаю, что никто не претендует на создание совершенного кода. Таким образом, код постоянно реорганизуется, чтобы быть «улучшенным» и расширенным. Поэтому моя цель состояла бы в том, чтобы мой код как рефакторинг был дружественным, насколько это возможно.

Часто мне приходится сталкиваться с проблемой, что я должен полностью переформулировать функции, если они содержат ограничители потока управления, и если я хочу добавить только небольшую функциональность. Это очень подвержено ошибкам, поскольку вы меняете весь поток управления, а не вводите новые пути к изолированным гнездам. Если у вас есть только один возврат в конце или если вы используете защитники для выхода из цикла, вы, конечно, имеете больше гнездования и больше кода. Но вы получаете возможности рефакторинга, поддерживающие компилятор и IDE.

ответил Dan Diplo 10 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowFri, 10 Sep 2010 22:42:22 +0400 2010, 22:42:22

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132