Чи слід повертатися з функції рано чи використовувати оператор if? [зачинено]


304

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

public void SomeFunction(bool someCondition)
{
    if (someCondition)
    {
        // Do Something
    }
}

або

public void SomeFunction(bool someCondition)
{
    if (!someCondition)
        return;

    // Do Something
}

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


9
Я трохи запізнююся на цю дискусію, тому не ставлю на це відповіді; Я також думав про це два роки тому: lecterror.com/articles/view/code-formatting-and-readability Я вважаю, що друге простіше читати, змінювати, підтримувати та налагоджувати. Але, можливо, це тільки я :)
доктор Ганнібал Лектер


4
тепер це питання є прекрасним прикладом запитання на основі думки
Рудольф Олах

2
то що робити, якщо немає абсолютного доказу в тому чи іншому напрямку? Коли достатньо аргументації подано в той і інший бік і відбувається голосування, якщо відповідь правильна - це робить її дуже корисною. Я вважаю, що подібні запитання є шкідливими для цінності цього веб-сайту.
gsf

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

Відповіді:


402

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


7
Smalltalk називає ці "охоронні положення". Принаймні, саме так називає їх Кент Бек у шаблонах кращих практик Smalltalk; Я не знаю, чи це звичайна мова.
Френк Ширар

154
Раннє вихідне дозволяє вискочити матеріали з обмеженого ментального стеку. :)
Джорен

50
Раніше я чув, як це називалося "Шаблон вистрибування" - позбудьтеся поганих справ, перш ніж вони потраплять у двері.
RevBingo

14
Я б навіть зайшов і сказав, що це точно ТІЛЬКИ правильна справа.
Олівер Вайлер

38
Крім того, ви не продовжуєте додавати відступи, якщо ви позбудетеся прикордонних справ на початку.
doppelgreener

170

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

public int SomeFunction(bool cond1, string name, int value, AuthInfo perms)
{
    int retval = SUCCESS;
    if (someCondition)
    {
        if (name != null && name != "")
        {
            if (value != 0)
            {
                if (perms.allow(name)
                {
                    // Do Something
                }
                else
                {
                    reval = PERM_DENY;
                }
            }
            else
            {
                retval = BAD_VALUE;
            }
        }
        else
        {
            retval = BAD_NAME;
        }
    }
    else
    {
        retval = BAD_COND;
    }
    return retval;
}

читабельніше, ніж

public int SomeFunction(bool cond1, string name, int value, AuthInfo perms)
{
    if (!someCondition)
        return BAD_COND;

    if (name == null || name == "")
        return BAD_NAME;

    if (value == 0)
        return BAD_VALUE;

    if (!perms.allow(name))
        return PERM_DENY;

    // Do something
    return SUCCESS;
}

Я повністю визнаю, що ніколи не розумів переваги одиничних точок виходу.


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

71
@JohnL великі функції - проблема, а не кілька точок виходу. Якщо ви не працюєте в контексті, коли додатковий виклик функції дуже сповільнить ваш код, звичайно ...
Dan Rosenstark

4
@Yar: Правда, але справа стоїть. Я не збираюся нікого намагатися перетворити, і я просто вказував на перевагу того, щоб їхати на якомога менше точок виходу (а приклад Джейсона в будь-якому випадку був людиною із соломи). Щойно наступного разу, коли ви витягуєте волосся, намагаєтеся з’ясувати, чому SomeFunction іноді повертає непарні значення, можливо, ви хочете, щоб ви могли додати дзвінок для реєстрації безпосередньо перед поверненням. Набагато простіше налагоджувати, якщо є лише один з багерів! :)
JohnL

76
@Jason Viers Ніби сингл return value;допомагає!?! Тоді вам належить полювати півдесятка value = ..., з тим недоліком, що ви ніколи не впевнені, що значення не буде змінено між цим завданням і остаточним поверненням. Принаймні негайне повернення зрозуміло, що результат більше нічого не змінить.
Sjoerd

5
@Sjoed: Я другий Sjoerd тут. Якщо ви хочете зареєструвати результат, ви можете увійти на сайт абонента. Якщо ви хочете дізнатися причину, вам потрібно увійти в кожну точку виходу / призначення, щоб обидва були ідентичними в цьому аспекті.
Матьє М.

32

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

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

public int myFunction(string parameterOne, string parameterTwo) {
  // Can't work without a value
  if (string.IsNullOrEmpty(parameterOne)) {
    throw new ArgumentNullException("parameterOne");
  } 
  if (string.IsNullOrEmpty(parameterTwo)) {
    throw new ArgumentNullException("parameterTwo");
  }

  // ...      
  // Do some work
  // ...

  return value;
}

9
І якщо кінцевий результат є ретельним, кого цікавить, який стиль було обрано?
Джефф Сівер

3
@ Джеф Сівер - Отже, чому це, як правило, питання "священної війни", в кінці дня воно зводиться до особистих уподобань і того, що каже внутрішній посібник зі стилю.
rjzii

1
Ключовим вилученням тут є те, що він кидає виняток, а не повертається рано. Повернене значення не повинно бути повторно призначене для перевірки валідності. Що робити, якщо у вас виникли різні умови і ви хотіли повідомити код, використовуючи метод, чому він не вдався? Раптом ви можете повернути фактичні дані про бізнес, нічого (порожній результат) або безліч різних рядків, кодів, чисел, ... з одного єдиного методу. Просто описати, чому це не вдалося. Ні, дякую.
DanMan

як щодо цикломатичної складності, як пропонує мій упорядник? хіба не краще не вкладати код, якщо хтось може допомогти?
l - '' '''--------- '' '' '' '' '' '

24

Я віддаю перевагу ранньому поверненню.

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

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


13

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

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

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

C # Напевно я не можу відповісти.

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

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


1
C ++ дозволяє щось, що називається оптимізацією зворотного значення, що дозволяє компілятору по суті опустити операцію копіювання, яка зазвичай відбувається при поверненні значення. Однак за різних умов, що важко зробити, і багаторазові значення повернення є одним із таких випадків. Іншими словами, використання декількох повернених значень у C ++ може насправді зробити ваш код повільнішим. Це, безумовно, стосується MSC, і враховуючи, що реалізувати RVO з декількома можливими значеннями повернення було б досить складно (якщо не неможливо), швидше за все, це проблема у всіх компіляторах.
Еймон Нербонна

1
У С просто використовуйте gotoі, можливо, повернення у дві крапки. Приклад (форматування коду в коментарях неможливе):foo() { init(); if (bad) goto err; bar(); if (bad) goto err; baz(); return 0; err: cleanup(); return 1; }
mirabilos

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

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

Зателефонував, що робить ваш код постійним. У реальному світі код пишеться для ділових цілей, а іноді розробнику потрібно пізніше його змінити. У той час, коли я написав цю відповідь, хоч я зафіксував CURL, щоб запровадити кешування та згадати бардак спагетті, що саме це справді потрібно було правильно переписати в сучасний C ++
CashCow

9

Раннє повернення за виграш. Вони можуть здаватися потворними, але набагато менш потворними, ніж великі ifобгортки, особливо якщо є кілька умов для перевірки.


9

Я використовую і те, і інше.

Якщо DoSomethingце 3-5 рядків коду, тоді код просто виглядає красиво за допомогою першого методу форматування.

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


2
Красиво ніяк! Занадто багато відступів!
JimmyKane

@JimmyKane Є лише стільки відступів, які можуть трапитися в 3-5 рядках, особливо якщо вам потрібно 2 (?) Рядки на рівні, решта отримує відступ: Один для структури управління та початку блоку, один для кінця блоку.
Дедуплікатор

8

Класична причина одноразового входу-одиночного виходу полягає в тому, що в іншому випадку формальна семантика стає невимовно потворною інакше (та сама причина, яку GOTO вважали шкідливою).

По-іншому, простіше міркувати про те, коли ваше програмне забезпечення вийде з розпорядку, якщо у вас є лише 1 повернення. Що також є аргументом проти винятків.

Зазвичай я мінімізую підхід щодо раннього повернення.


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

@Tim. Це залежить від того, яку кількість їжі ви хочете зайняти і що ви аналізуєте. Мені здається, що SESE є досить зрозумілим, якщо кодер розбирався в речах.
Пол Натан

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

@Tim: Досить справедливо. Хоча я і змушений зазначити, що статичні мови все ще мають багато гри.
Пол Натан

Причина, яка не стоїть перед винятками. Ось чому одиночний вихід чудовий у С, але в C ++ ви нічого не купуєте.
peterchen

7

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


6

Це залежить.

Дострокове повернення, якщо є явна умова тупикового зв'язку, щоб перевірити його негайно, що призведе до безглуздість роботи решти функції. *

Встановіть Retval + одиночне повернення, якщо функція є більш складною і може в іншому випадку мати декілька точок виходу (проблема читабельності).

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


6
Коли я пишу код, який може бути загальним, моя мантра - "Не припускайте нічого. Не довіряйте нікому". Ви завжди повинні перевірити свої входи та будь-який зовнішній стан, від якого ви залежите. Краще кинути виняток, ніж можливо щось зіпсувати, тому що хтось дав вам погані дані.
TMN

@TMN: Добре.
Столи Бобі

2
Ключовим моментом тут є те, що в ОО ви кидаєте виняток , а не повертаєтесь . Кілька повернень можуть бути поганими, кілька викидів винятків не обов'язково мають запах коду.
Майкл К

2
@MichaelK: Метод повинен бути винятком, якщо не можна виконати умову. У деяких випадках метод повинен вийти зранку, оскільки стан було досягнуто ще до початку функціонування. Наприклад, якщо за допомогою методу "Встановити мітку керування" змінити мітку керування на, мітка Fredвікна вже є Fred, а встановлення імені керування на його теперішній стан призведе до перемальовування (що, хоча потенційно корисне в деяких випадках, буде буде роздратованим в руці), було б цілком розумним, щоб метод set-name завчасно вийшов, якщо старі та нові імена збігаються.
supercat

3

Використовуйте If

У книзі Дон Кнут про «GOTO's» я прочитав його, і він наводив причину того, що він завжди мав найімовірніший стан у першому випадку. За припущенням, що це все-таки розумна ідея (і не з чистого врахування швидкості епохи). Я б сказав, що раннє повернення не є хорошою практикою програмування, особливо враховуючи той факт, що вони частіше за все не використовуються для обробки помилок, якщо тільки ваш код швидше виходить з ладу, ніж не виходить з ладу :-)

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

Специфічні для Delphi ...

Я маю на увазі, що це хороша практика програмування для програмістів Delphi, хоча я не маю доказів. Pre-D2009, ми не маємо атомний спосіб повернути значення, у нас є exit;і result := foo;чи ми могли б просто викинути виняток.

Якби вам довелося підставляти

if (true) {
 return foo;
} 

для

if true then 
begin
  result := foo; 
  exit; 
end;

ви можете просто захворіти, побачивши це у верхній частині кожної з своїх функцій і віддайте перевагу

if false then 
begin
  result := bar;

   ... 
end
else
   result := foo;

і просто уникати exitвзагалі.


2
З новим Delphis, це може бути скорочено до if true then Exit(foo);Я використовую часто метод для першої ініціалізації resultдо nilабо FALSEвідповідно, а потім перевірити всі умови помилки і тільки Exit;якщо один задовольняються. Тоді випадок успіху result(як правило) встановлюється десь у кінці методу.
JensG

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

І програмісти C #. Так, чесно кажучи, я вважаю це дійсно корисним, оскільки зменшує кількість рядків між декларацією та використанням (IIRC є навіть якась метрика для цього, забув ім'я).
JensG

2

Я згоден із наступним твердженням:

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

Взяте з цього питання в stackoverflow .


+1 За охоронні положення. Я також віддаю перевагу позитивно орієнтованому охоронцю, наприклад: якщо (умова = хибність) повернення на відміну від повернення, якщо (! Умова)
JustinC

1

Я використовую раннє повернення майже виключно в ці дні, до крайності. Я це пишу

self = [super init];

if (self != nil)
{
    // your code here
}

return self;

як

self = [super init];
if (!self)
    return;

// your code here

return self;

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


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

1

Я хотів би написати:

if(someCondition)
{
    SomeFunction();
}

2
eww. Це попередня перевірка? Або це у мехтоді, присвяченому валідації DoSomeFunctionIfSomeCondition?
STW

Як це роз’єднує ваші проблеми?
justkt

2
Ви порушуєте інкапсуляцію, зробивши реалізацію функції (її логіку залежності) зовнішньою.
TMN

1
Якщо це запускається у відкритому методі, а SomeFunction () є приватним методом у тому ж класі, це може бути нормально. Але якщо хтось інший телефонує SomeFunction (), вам доведеться дублювати чеки там. Я вважаю, що краще змусити кожен метод подбати про те, що йому потрібно робити, і ніхто інший не повинен цього знати.
Per Wiklander

2
Це, безумовно, стиль, який пропонує Роберт К. Мартін у «Чистому кодексі». Функція повинна виконувати лише одне. Однак у "Шаблонах впровадження", Кент Бек пропонує, з двох варіантів, запропонованих в ОП, другий є кращим.
Скотт Вітлок

1

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

Мені не подобається, як обробка помилок відсувається від чека.

if not error A
  if not error B
    if not error C
      // do something
    else handle error C
  else handle error B
else handle error A

Я вважаю за краще це:

if error A
  handle error A; return
if error B
  handle error B; return
if error C
  handle error C; return

// do something

0

Умови на вершині називаються "передумовами". Зазначаючи if(!precond) return;, ви візуально перераховуєте всі передумови.

Використання великого блоку "if-else" може збільшити накладні витрати (я забув цитату про 3-рівневі відступи).


2
Що? Ви не можете зробити раннє повернення на Java? C #, VB (.NET і 6) і, мабуть, Java (що я припускав, що це було, але довелося шукати, оскільки я не користувався мовою протягом 15 років), все це дозволяє достроково повертатися. тому не звинувачуйте "сильно набрані мови" як відсутність цієї функції. stackoverflow.com/questions/884429 / ...
ps2goat

-1

Я вважаю за краще тримати, якщо заяви невеликі.

Отже, вибираючи між:

if condition:
   line1
   line2
    ...
   line-n

і

if not condition: return

line1
line2
 ...
line-n

Я вибрав би те, що ви описали як "раннє повернення".

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

Вкладені, якщо і для, і поки страшні , уникайте їх будь-якою ціною.


-2

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


-2

Я практикую невдачу на рівні функцій. Він тримає код послідовним та чистим (як для мене, так і для тих, з ким я працював). Через це я завжди повертаюся рано.

Для деяких часто перевірених умов ви можете реалізувати аспекти цих перевірок, якщо ви використовуєте AOP.

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