Чому функція повинна мати лише одну точку виходу? [зачинено]


97

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

Я думав, що це має щось спільне з CS, але це питання було збито на cceheory stackexchange.



6
Відповідь полягає в тому, що немає відповіді, яка завжди була б правильною. Мені часто стає простіше кодувати безліч виходів. Я також виявив (під час оновлення коду вище), що модифікація / розширення коду було складнішим через ті самі декілька виходів. Прийняття таких рішень у кожному конкретному випадку - це наша робота. Коли рішення завжди має "найкращу" відповідь, ми не маємо потреби.
Й.С.

1
@finnw фашистські моди видалили два останні питання, щоб переконатися, що на них доведеться відповідати знову і знову, і знову
Маартен Бодеус

Незважаючи на слово "сперечатися" у питанні, я справді не думаю, що це питання, засноване на думках. Це цілком стосується хорошого дизайну і т. Д. Я не бачу причини, щоб його закривали, але ж / без.
Ungeheuer

1
Одна точка виходу спрощує налагодження, зчитування, вимірювання та налаштування продуктивності, рефакторинг. Це об’єктивно та істотно важливо. Використання ранніх повернень (після простих перевірок аргументів) створює розумне поєднання обох стилів. Враховуючи переваги однієї точки виходу, засипання коду поверненими значеннями є просто свідченням ледачого, недбалого, недбалого програміста - і принаймні, можливо, не подобається цуценят.
Рік О'Ші

Відповіді:


108

Існують різні школи мислення, і це здебільшого зводиться до особистих переваг.

Одне з них полягає в тому, що це менш заплутано, якщо є лише одна точка виходу - у вас є єдиний шлях через метод і ви знаєте, де шукати вихід. З мінусової сторони, якщо ви використовуєте відступ для представлення вкладеності, ваш код закінчується масовим відступом праворуч, і стає дуже важко відстежувати всі вкладені області.

Іншим є те, що ви можете перевірити передумови та вийти на початку на початку методу, щоб ви знали в тілі методу, що певні умови відповідають дійсності, не відхиляючи все тіло методу на відстані 5 миль праворуч. Зазвичай це мінімізує кількість областей, про які вам доведеться турбуватися, що робить код набагато легшим для дотримання.

Третя - ви можете вийти куди завгодно. Раніше це було більш заплутаним, але зараз, коли у нас є редактори та компілятори, що розфарбовують синтаксис, які виявляють недосяжний код, з цим набагато простіше мати справу.

Я прямо в середньому таборі. Застосування однієї точки виходу є безглуздим або навіть контрпродуктивним обмеженням IMHO, тоді як випадковий вихід по всьому методу іноді може призвести до безладної важкої для дотримання логіки, де стає важко зрозуміти, буде чи не буде заданий біт коду. страчений. Але "встановлення" вашого методу дозволяє значно спростити суть методу.


1
Глибоке вкладання може бути усунене в singe exitпарадигмі відтінками go toтверджень. Крім того, ми отримуємо можливість виконати певну обробку під локальною Errorміткою функції, що неможливо з кількома returns.
Ant_222

2
Зазвичай є хороше рішення, яке дозволяє уникнути необхідності відвідувати. Я набагато волію 'повернути (Fail (...))' і ввести загальний код очищення в метод Fail. Для цього може знадобитися передача кількох місцевих жителів, щоб звільнити пам’ять тощо, але якщо у вас немає критичного біту коду, це, як правило, набагато чистіше рішення, ніж goto IMO. Це також дозволяє декільком методам також використовувати спільний код очищення.
Джейсон Вільямс,

Існує оптимальний підхід, заснований на об’єктивних критеріях, але ми можемо погодитись, що існують школи думок (правильні та неправильні), і це зводиться до особистих переваг (перевагу правильному підходу чи проти).
Рік О'Ші

39

Моя загальна рекомендація полягає в тому, що оператори повернення повинні, коли це практично, розташовуватися перед першим кодом, який має будь-які побічні ефекти, або після останнього коду, який має будь-які побічні ефекти. Я б розглянув щось на зразок:

  if (! аргумент) // Перевірте, чи не є null
    повернути ERR_NULL_ARGUMENT;
  ... обробляти ненульовий аргумент
  якщо (добре)
    повернути 0;
  ще
    повернути ERR_NOT_OK;

чіткіше ніж:

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

Якщо певна умова повинна перешкоджати функції щось робити, я вважаю за краще достроково повернутися з функції в місці над точкою, де функція може щось робити. Однак, коли функція здійснила дії з побічними ефектами, я волію повернутися знизу, щоб чітко пояснити, що з усіма побічними ефектами потрібно боротися.


Ваш перший приклад, як керувати okзмінною, виглядає як підхід з єдиною віддачею для мене. Більше того, блок if-else можна просто переписати на:return ok ? 0 : ERR_NOT_OK;
Мелебій

2
Перший приклад має a returnна початку перед кодом, який робить все. Що стосується використання ?:оператора, виписування його окремими рядками полегшує багатьом середовищам середовища IDE приєднання точки зупинки налагодження до сценарію, що не відповідає нормам. До речі, справжній ключ до "єдиної точки виходу" полягає у розумінні того, що важливим є те, що для кожного конкретного виклику нормальної функції точкою виходу є точка безпосередньо після дзвінка . У наш час програмісти сприймають це як належне, але не завжди все було так. У деяких рідкісних випадках коду, можливо, доведеться обійтися без місця для стека, що веде до функцій ...
supercat

... які виходять через умовні або обчислені гото. Як правило, будь-що з достатньою кількістю ресурсів, яка може бути запрограмована на будь-яку іншу мову, окрім мови збірки, зможе підтримувати стек, але я написав код збірки, який повинен був працювати з деякими дуже жорсткими обмеженнями (до НУЛЬКОГО байта оперативної пам'яті в одному випадку), і наявність декількох точок виходу може бути корисним у таких випадках.
supercat

1
Так званий чіткіший приклад набагато менш зрозумілий і важкий для читання. Одну точку виходу завжди легше читати, легше обслуговувати, легше налагоджувати.
GuidoG

8
@GuidoG: Будь-який шаблон може бути більш читабельним, залежно від того, що відображається в пропущених розділах. Використання "return x;" дає зрозуміти, що якщо оператор досягнуто, повернене значення буде x. Використовуючи "result = x;" залишає відкритою можливість того, що щось інше може змінити результат до його повернення. Це може бути корисно, якщо насправді стане необхідним змінити результат, але програмістам, що вивчають код, доведеться перевірити його, щоб побачити, як може змінитися результат, навіть якщо відповідь "не може".
supercat

15

Точка входу та виходу була оригінальною концепцією структурованого програмування проти покрокового кодування спагетті. Існує думка, що для кількох функцій точки виходу потрібно більше коду, оскільки вам потрібно правильно очистити місця в пам'яті, виділені для змінних. Розглянемо сценарій, коли функція розподіляє змінні (ресурси), і вихід з функції рано та без належного очищення призведе до витоків ресурсів. Крім того, побудова очищення перед кожним виходом створить багато зайвого коду.


Це насправді не проблема з RAII
BigSandwich

14

У більшості випадків це зводиться до потреб результату. У "старі часи" код спагетті з кількома точками повернення запрошував витік пам'яті, оскільки кодери, які віддавали перевагу цьому методу, як правило, погано очищалися. Також виникали проблеми з тим, що деякі компілятори «втрачали» посилання на змінну return, оскільки стек з’являвся під час повернення, у випадку повернення з вкладеної області. Більш загальною проблемою була проблема з повторним входом коду, який намагається, щоб стан виклику функції був точно таким же, як і стан повернення. Мутатори oop порушили це, і концепція була відкладена.

Існують результати, зокрема ядра, яким потрібна швидкість, яку забезпечують кілька точок виходу. Ці середовища зазвичай мають власну пам’ять і управління процесами, тому ризик витоку зводиться до мінімуму.

Особисто мені подобається мати єдину точку виходу, оскільки я часто використовую її, щоб вставити точку зупинки в операторі return і виконати перевірку коду, як код визначав це рішення. Я міг просто підійти до входу і пройти, що я роблю з широко вкладеними та рекурсивними рішеннями. Як аналітик коду, багаторазове повернення функції вимагає набагато глибшого аналізу - тому, якщо ви робите це, щоб пришвидшити реалізацію, ви грабуєте Пітера, щоб врятувати Пола. Більше часу буде потрібно для перегляду коду, що скасовує презумпцію ефективної реалізації.

- 2 центи

Детальніше див. Цей документ: NISTIR 5459


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. Код з одним виходом посилається і повертається більш чисто.

Друга причина є тонкою і має певні переваги, особливо якщо функція повертає велику структуру даних. Однак я б надто не хвилювався про це, хіба що ...

Якщо ви студент, ви хочете отримати найкращі оцінки у своєму класі. Робіть те, що віддає перевагу викладач. Ймовірно, у нього є вагома причина з його точки зору; отже, принаймні, ви дізнаєтесь його перспективу. Це має цінність саме по собі.

Удачі.


4

Раніше я був прихильником стилю єдиного виходу. Мої міркування здебільшого спричинені болем ...

Одиночний вихід легше налагодити.

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

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

Методи одиночного виходу легше було пройти через налагоджувач і легше роздратувати, не порушуючи логіки.


0

Відповідь дуже залежить від контексту. Якщо ви створюєте графічний інтерфейс і маєте функцію, яка ініціалізує API та відкриває вікна на початку вашого основного, він буде повний дзвінків, які можуть спричинити помилки, кожна з яких призведе до закриття екземпляра програми. Якщо ви використовуєте вкладені оператори IF та відступи, ваш код може швидко перекоситися вправо. Повернення помилки на кожному етапі може бути кращим і фактичнішим для читання, настільки ж легко налагоджувати за допомогою декількох прапорців у коді.

Якщо ви протестуєте різні умови і повертаєте різні значення залежно від результатів у вашому методі, можливо, буде набагато кращою практикою мати одну точку виходу. Раніше я працював над сценаріями обробки зображень у MATLAB, які могли стати дуже великими. Кілька точок виходу можуть ускладнити дотримання коду. Оператори перемикання були набагато доречнішими.

Найкраще було б вчитися в ході. Якщо ви пишете код для чогось, спробуйте знайти код інших людей і подивитися, як вони його реалізують. Вирішіть, які біти вам подобаються, а які - ні.


-5

Якщо ви відчуваєте, що вам потрібні кілька точок виходу у функції, функція занадто велика і робить занадто багато.

Я б порекомендував прочитати розділ про функції книги Роберта К. Мартіна "Чистий код".

По суті, вам слід спробувати писати функції з 4 рядками коду або менше.

Деякі примітки з блогу Майка Лонга :

  • Перше правило функцій: вони повинні бути малими
  • Друге правило функцій: вони повинні бути меншими за це
  • Блоки в операторах if, в той час як оператори, для циклів тощо, повинні складати один рядок
  • ... і цей рядок коду, як правило, буде викликом функції
  • Має бути не більше одного або, може, двох рівнів відступу
  • Функції повинні робити одне
  • Оператори функцій повинні бути на одному рівні абстракції
  • Функція повинна мати не більше 3 аргументів
  • Вихідні аргументи - це запах коду
  • Передати логічний прапор у функцію - це справді жахливо. Ви за визначенням робите дві функції у функції.
  • Побічні ефекти - це брехня.

29
4 рядки? Що ви кодуєте, що дозволяє вам таку простоту? Я дуже сумніваюся, що ядро ​​Linux або git, наприклад, це роблять.
shinzou

7
"Передача логічного прапора у функцію справді жахлива. Ви за визначенням робите дві функції у функції." За визначенням? Ні ... це логічне значення потенційно може впливати лише на один з чотирьох рядків. Крім того, хоча я погоджуюсь з тим, щоб розмір функції був невеликим, чотири є занадто обмежувальними. Це слід сприймати як дуже вільний орієнтир.
Джессі

12
Додавання таких обмежень неминуче призведе до плутанини коду. Це більше стосується методів, які є чіткими лаконічними та дотримуються того, щоб робити лише те, що вони мають робити, без зайвих побічних ефектів.
Джессі

10
Одна з рідкісних відповідей на SO, за яку я хотів би, щоб я проголосував кілька разів.
Стівен Рендс,

8
На жаль, ця відповідь робить багато речей, і, мабуть, її потрібно було розділити на кілька інших - все менше чотирьох рядків.
Елі,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.