Найкраща практика щодо if / return


60

Я хочу знати, що вважається кращим способом повернення, коли у мене є ifзаява.

Приклад 1:

public bool MyFunction()
{
   // Get some string for this example
   string myString = GetString();

   if (myString == null)
   {
      return false;
   }
   else
   {
      myString = "Name " + myString;
      // Do something more here...
      return true;
   }
}

Приклад 2:

public bool MyFunction()
{
   // Get some string for this example
   string myString = GetString();

   if (myString == null)
   {
      return false;
   }

   myString = "Name " + myString;
   // Do something more here...
   return true;
}

Як ви бачите, в обох прикладах функція повернеться, true/falseале чи добре це ставити elseоператор, як у перший приклад, або краще не ставити його?


7
Якщо ви перевіряєте лише помилки в першому "якщо", вам краще не включати "else", оскільки помилки не слід вважати частиною фактичної логіки.
Мерт Аккаякая

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


14
На мій погляд , перша версія, як відкидаючи всі студенти в кімнаті , хто не закінчив свою домашню роботу, а потім , як тільки вони пішли, сказавши іншим студентам «Тепер , якщо ви навіть закінчити свою домашню роботу ...». Це має сенс, але зайве. Оскільки умовне вже насправді не є умовним, я, як правило, скидаю else.
Ніколь

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

Відповіді:


81

Приклад 2 відомий як блок захисту. Краще підходить повернення / викидання виключення рано, якщо щось пішло не так (неправильний параметр або недійсний стан). У нормальному логічному потоці краще використовувати Приклад 1


+1 - хороша відмінність, і що я збирався відповісти.
Теластин

3
@ pR0P має таку саму відповідь нижче і надає приклад коду щодо того, чому в кінцевому підсумку це більш чистий шлях, який слід слідувати. +1 за гарну відповідь.

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

2
+1. Неправильне уникнення блоків охорони, як правило, викликає код стрілки.
Брайан

Не обидва приклади показують блок охоронця?
ernie

44

Мій особистий стиль - використовувати сингл ifдля блоків захисту та if/ elseв фактичному коді обробки методу.

У цьому випадку ви використовуєте myString == nullумову захисту, тому я схильний використовувати єдиний ifшаблон.

Розглянемо код, який трохи складніше:

Приклад 1:

public bool MyFunction(myString: string){

    //guard block
    if (myString == null){
        return false;
    }
    else{
        //processing block
        myString = escapedString(myString);

        if (myString == "foo"){
            //some processing here
            return false;
        }
        else{
            myString = "Name " + myString;
            //other stuff
            return true;
        }
    }
}

Приклад 2:

public bool MyFunction(myString: string){

    //guard block
    if (myString == null){
        return false;
    }

    //processing block
    myString = escapedString(myString);

    if (myString == "foo"){
        //some processing here
        return false;
    }
    else{
        myString = "Name " + myString;
        //other stuff
        return true;
    }
}

У Прикладі 1 і захист, і решта способу знаходяться у if/ elseформі. Порівняйте це з Прикладом 2, де блок захисту знаходиться в одній ifформі, а решта методу використовує if/ elseform. Особисто мені здається, що приклад 2 простіший для розуміння, тоді як приклад 1 виглядає безладним і перерізаним.

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

Пристойний компілятор в будь-якому випадку повинен генерувати однаковий вихід для обох. Єдина причина використовувати той чи інший - особисті переваги або відповідати стилю існуючого коду.


18
Приклад 2 було б ще простіше прочитати, якби ти позбувся другого.
briddums

18

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


+1 за менший відступ Цей жест злісний, якщо у вас є кілька перевірок
d.raev

15

Моя особиста практика така:

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

Наприклад:

public bool myFunction()
{
   // First parameter loading
   String myString = getString();

   // Location of "quick exits", see the second example
   // ...

   // declaration of external resources that MUST be released whatever happens
   // ...

   // the return variable (should think about giving it a default value or not) 
   // if you have no default value, declare it final! You will get compiler 
   // error when you try to set it multiple times or leave uninitialized!
   bool didSomething = false;

   try {
     if (myString != null)
     {
       myString = "Name " + myString;
       // Do something more here...

       didSomething = true;
     } else {
       // get other parameters and data
       if ( other conditions apply ) {
         // do something else
         didSomething = true;
       }
     }

     // Edit: previously forgot the most important advantage of this version
     // *** HOUSEKEEPING!!! ***

   } finally {

     // this is the common place to release all resources, reset all state variables

     // Yes, if you use try-finally, you will get here from any internal returns too.
     // As I said, it is only my taste that I like to have one, straightforward path 
     // leading here, and this works even if you don't use the try-finally version.

   }

   return didSomething;
}
  • Єдиний виняток: «швидкий вихід» на початку (або в рідкісних випадках всередині процесу). Якщо реальна логіка обчислення не може впоратись із певною комбінацією вхідних параметрів та внутрішніх станів або має просте рішення без запуску алгоритму, це не допомагає, щоб весь код був інкапсульований у (іноді глибокому), якщо блоки. Це "винятковий стан", а не частина основної логіки, тому я повинен вийти з обчислення, як тільки я виявив. У цьому випадку іншої гілки немає, в нормальних умовах виконання просто триває. (Звичайно, "винятковий стан" краще виражається киданням винятку, але іноді це надмірність.)

Наприклад:

public bool myFunction()
{
   String myString = getString();

   if (null == myString)
   {
     // there is nothing to do if myString is null
     return false;
   } 

   myString = "Name " + myString;
   // Do something more here...

   // not using return value variable now, because the operation is straightforward.
   // if the operation is complex, use the variable as the previous example.

   return true;
}

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

Схоже, ваш випадок потрапляє до категорії "швидкий вихід перед реальною роботою", і я б написав це як ваш варіант 2-го прикладу.


9
+1 за "Мені не подобаються функції з кількома точками виходу"
Corv1nus

@LorandKedves - додав приклад - сподіваюся, що ви не заперечуєте.
Меттью Флінн

@MatthewFlynn добре, якщо ви не заперечуєте, що це протилежне тому, що я запропонував наприкінці своєї відповіді ;-) Я продовжую приклад, сподіваюся, нарешті це буде добре для нас обох :-)
Лоранд Кедвес

@MatthewFlynn (вибачте, я просто хитрий гад, і дякую, що допомогли мені уточнити свою думку. Сподіваюся, вам сподобалась поточна версія.)
Лоранд Кедвес,

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

9

Я вважаю за краще використовувати охоронні блоки де завгодно з двох причин:

  1. Вони дозволяють швидко вийти за умови певної умови.
  2. Усуньте необхідність складних і не потрібних, якщо висловлювання пізніше в коді.

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


5

Мені подобається підхід "Fall Through":

public bool MyFunction()
{
   string myString = GetString();

   if (myString != null)
   {
     myString = "Name " + myString;
     return true;
    }
    return false;
}

Дія має конкретну умову, все інше - це лише повернення за замовчуванням "провалитися".


1

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

Намалюй це. Припустимо, що тести складні, і ви дійсно не хочете зв'язати їх у єдиний стан, якщо-ORed, щоб уникнути складності:

//Style1
if (this1 != Right)
{ 
    return;
}
else if(this2 != right2)
{
    return;
}
else if(this3 != right2)
{
    return;
}
else
{
    //everything is right
    //do something
    return;
}

проти

//Style 2
if (this1 != Right)
{ 
   return;
}
if(this2 != right2)
{
    return;
}
if(this3 != right2)
{
    return;
}


//everything is right
//do something
return;

Тут є дві основні переваги

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

  2. Якщо вам доведеться додати / видалити одну умову, ви знижуєте шанси зіпсувати всю сходи if-elseif-else.

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


0

Це має бути питанням, яке звучить краще.

If condition
  do something
else
  do somethingelse

виражає себе тоді краще

if condition
  do something
do somethingelse

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


3
Якщо метод настільки довгий, що другу форму важко зрозуміти, він занадто довгий.
кевін клайн

7
Ваші два випадки є рівнозначними лише, якщо do somethingвключає повернення. Якщо ні, другий код буде виконуватися do somethingelseнезалежно від ifпредиката, який не є тим, як працює перший блок.
Аднан

Вони також пояснюють ОП у своїх прикладах. Тільки слідуючи рядку питання
Жозе Валенте

0

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

Однак, як правило, більше залучено, залежно від мети функції, наприклад, обробка помилок, ведення журналів і т. Д. Тому я з цим відповідь Лоранда Кедвеса, і, як правило, є одна точка виходу в кінці навіть за ціною додаткової змінної прапора. У більшості випадків це полегшує обслуговування та подальші розширення.


0

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


0

У вашому прикладі, elseочевидно, непотрібне, Але ...

Під час швидкого скріплення рядків коду, очі зазвичай дивляться на дужки та відступи, щоб отримати уявлення про потік коду; перш ніж вони насправді влаштуються на сам код. Таким чином, написання elseта накладення дужок та відступів навколо другого блоку коду насправді робить читача швидше зрозуміти, що це ситуація "А чи Б", де буде працювати один блок чи інший. Тобто читач бачить дужки та відступи перед тим, як вони бачать returnпісля початкового if.

На мою думку, це один з тих рідкісних випадків, коли додавання чогось зайвого до коду насправді робить його ЧИСЛО читабельним, не меншим.


0

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

public bool MyFunction()
{
    bool result = false;

    string myString = GetString();

    if (myString != nil) {
        myString = "Name " + myString;

        result = true;
    }

    return result;
}

З цим стилем я можу:

  1. Побачте відразу, що я щось повертаю.

  2. Ловіть результат кожного виклику функції лише однією точкою розриву.


Це схоже на те, як я підходив би до проблеми. Єдина відмінність полягає в тому, що я використовував би змінну результату, щоб захопити оцінку myString і використати її як повернене значення.
Чак Конвей

@ChuckConway Погодився, але я намагався дотримуватися прототипу ОП.
Калеб

0

Мій особистий стиль має тенденцію йти

function doQuery(string) {
    if (!query(string)) {
        query("ABORT");
        return false;
    } // else
    if(!anotherquery(string) {
        query("ABORT");
        return false;
    } // else
    return true;
}

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


1
Особисто я сподіваюся, що мені ніколи не доведеться працювати з вашим кодом.
CVn

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

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

0

Я б написав

public bool SetUserId(int id)
{
   // Get user name from id
   string userName = GetNameById(id);

   if (userName != null)
   {
       // update local ID and userName
       _id = id;
       _userNameLabelText = "Name: " + userName;
   }

   return userName != null;
}

Я віддаю перевагу цьому стилю, тому що найбільш очевидно, що я повернусь userName != null.


1
Питання в тому, чому ви віддаєте перевагу такому стилю?
Калеб

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

@Shadur Мені відомі додаткові булеві тести та невикористаний myStringрезультат. Я просто копіюю функцію з ОП. Я зміню це на щось, що виглядає більш реальним життям. Булевий тест є тривіальним і його не слід видавати.
тіа

1
@Shadur Ми тут не робимо оптимізації, правда? Моя суть полягає в тому, щоб зробити свій код легким для читання та обслуговування, і особисто я заплатив би це за вартість однієї контрольної нульової перевірки.
тіа

1
@tia Мені подобається дух вашого коду. Замість того, щоб двічі оцінювати userName, я б захопив першу оцінку в змінну і поверну її.
Чак Конвей

-1

Моє правило - або зробити if-return без іншого (в основному для помилок), або зробити повний ланцюжок if-else - якщо всі можливості.

Таким чином я уникаю спроб бути занадто фантазійним з моїм розгалуженням, оскільки всякий раз, коли я закінчую це, я кодую логіку програми в моєму ifs, роблячи їх важче підтримувати (Наприклад, якщо перерахунок OK або ERR і я записую всі свої ifs скориставшись тим, що! OK <-> ERR, це стає болем у дупі, щоб додати до третього варіанту перерахунок наступного разу.)

У вашому випадку, наприклад, я використовував би звичайну, якщо, оскільки "return if null", звичайно, є загальною схемою, і це не ймовірно, що вам потрібно буде турбуватися про третю можливість (крім null / not null) в майбутньому.

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


-1

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

Що робити, якщо пізніше код був ненавмисно введений у приклад 2, який істотно змінив вихід:

   if (myString == null)
   {
      return false;
   }

   //add some v1 update code here...
   myString = "And the winner is: ";
   //add some v2 update code here...
   //buried in v2 updates the following line was added
   myString = null;
   //add some v3 update code here...
   //Well technically this should not be hit because myString = null
   //but we already passed that logic
   myString = "Name " + myString;
   // Do something more here...
   return true;

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

Я дуже позичаю в цьому в деяких керівних принципах C # щодо Codeplex (посилання на це тут: http://csharpguidelines.codeplex.com/ ), де зазначено наступне:

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

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


Як не мати іншого в прикладі двох не враховує всіх справ? Задуманий результат ІС продовження виконання. Додавання іншого пункту в цьому випадку лише збільшує складність методу.
Чак Конвей

Я не погоджуюсь, базуючись на здатності не мати усі розглянуті логіки у стислій та детермінованій частині. Основна увага приділяється маніпулюванню myStringзначенням і кодом, що вступив до ifблоку, є результуюча логіка, якщо рядок! = Null. Тому, оскільки увага приділяється додатковій логіці, що маніпулює цим конкретним значенням, я думаю, що вона повинна бути інкапсульована в elseблок. Інакше це відкриває потенціал, як я показав ненавмисне розділення логіки та введення ненавмисних наслідків. Можливо, це не відбуватиметься постійно, але це справді було найкращим питанням щодо стилю думки.
atconway
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.