Чи варто використовувати "else" у ситуаціях, коли контрольний потік робить його зайвим?


18

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

function doSomething(value) {
  if (check1(value)) {
    return -1;
  }
  else if (check2(value)) {
    return value;
  }
  else {
    return false;
  }
}

Як ви можете бачити, if, else ifі elseзатвердження використовуються в поєднанні з returnзаявою. Для випадкового спостерігача це здається досить інтуїтивно зрозумілим, але я думаю, що було б більш елегантно (з точки зору розробника програмного забезпечення) скинути else-s та спростити код так:

function doSomething(value) {
  if (check1(value)) {
    return -1;
  }
  if (check2(value)) {
    return value;
  }
  return false;
}

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

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

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


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

3
E_CLEARLY_NOTADUPE
якийro

3
@DavidPacker: принаймні два; могло бути три. -1є числом, falseбулевим, і valueтут не вказано, тому це може бути будь-який тип об'єкта.
Yay295

1
@ Yay295 Я сподівався, що valueце насправді ціле число. Якщо це може бути що-небудь, то це ще гірше.
Енді

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

Відповіді:


23

Мені подобається той без elseі ось чому:

function doSomething(value) {
  //if (check1(value)) {
  //  return -1;
  //}
  if (check2(value)) {
    return value;
  }
  return false;
}

Тому що це не зламало нічого, чого не слід було.

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


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

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

27

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

if (x < 0) {
    return false;
} else if (x > 0) {
    return true;
} else {
    throw new IllegalArgumentException();
}

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

Якщо ваші випадки не залежать безпосередньо один від одного, опустіть це:

if (x < 0) {
    return false;
}
if (y < 0) {
    return true;
}
return false;

Це важко показати на простому прикладі без контексту, але сподіваюся, ви зрозумієте мою думку.


6

Я віддаю перевагу другому варіанту (окремо ifsбез else ifта ранній return), але це є до тих пір , як кодові блоки короткі .

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

Наприклад:

function doSomething(value) {
  if (check1(value)) {
    // ...
    // imagine 200 lines of code
    // ...
    return -1;
  }
  if (check2(value)) {
    // ...
    // imagine 150 lines of code
    // ...
    return value;
  }
  return false;
}

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

function doSomething(value) {
  retVal=0;
  if (check1(value)) {
    // ...
    // imagine 200 lines of code
    // ...
    retVal=-1;
  } else if (check2(value)) {
    // ...
    // imagne 150 lines of code
    retVal=value;
  }
  return retVal;
}

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


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

1
Цікавий аргумент. returnзнаходиться в межах 3 рядків, ifтак що це не так вже й інше, ніж else ifнавіть після 200 рядків коду. Один стиль повернення ви приведете тут традиція з і на інших мовах , що не повідомляють про помилки з винятками. Сенс полягав у створенні єдиного місця для очищення ресурсів. Мови виключень використовують finallyдля цього блок. Я гадаю, що це також створює місце для встановлення точки перерви, якщо ваш налагоджувач не дозволить вам поставити його на функції, що закривають фігурну дужку.
candied_orange

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

2
На мою думку, це ще гірше для довгих блоків. Я намагаюся вийти з функцій якомога раніше, незалежно від того, який зовнішній контекст.
Енді

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

4

Я використовую обидва в різних обставинах. Під час перевірки перевірки я опускаю інше. У контрольному потоці я використовую інше.

bool doWork(string name) {
  if(name.empty()) {
    return false;
  }

  //...do work
  return true;
}

проти

bool doWork(int value) {
  if(value % 2 == 0) {
    doWorkWithEvenNumber(value);
  }
  else {
    doWorkWithOddNumber(value);
  }
}

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


0

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

List<?> toList() {
    if (left != null && right != null) {
        Arrays.asList(merge(left, right));
    }
    if (left != null) {
        return Arrays.asList(left);
    }
    if (right != null) {
        return Arrays.asList(right);
    }
    return Arrays.asList();
}

Тут явно щось не так. Проблема полягає в тому, що не ясно, чи returnслід її додавати чи Arrays.asListвидаляти. Це неможливо виправити без глибшої перевірки відповідних методів. Ви можете уникнути цієї неоднозначності, завжди використовуючи повністю вичерпні блоки if-else. Це:

List<?> toList() {
    if (left != null && right != null) {
        Arrays.asList(merge(left, right));
    } else if (left != null) {
        return Arrays.asList(left);
    } else if (right != null) {
        return Arrays.asList(right);
    } else {
        return Arrays.asList();
    }
}

не буде компілюватися (статично перевіреними мовами), якщо ви не додасте явне повернення спочатку if .

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

List<?> toList() {
    if (left == null || right == null) {
        throw new IllegalStateException()
    }
    // ...
    // long method
    // ...
}

-2

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

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

var result;
if(check1(value)
{
    result = -1;
}
else if(check2(value))
{
    result = value;
}
else 
{
    result = 0;
}
return result;

Насправді відпустимо ще далі.

var result = 0;
var c1 = check1(value);
var c2 = check2(value);

if(c1)
{
    result = -1;
}
else if(c2)
{
    result = value;
}

return result;

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


3
Цей стиль походить з мов із чітким керуванням ресурсами. Для звільнення виділених ресурсів потрібен єдиний бал. У мовах з автоматичним управлінням ресурсами єдина точка повернення не так важлива. Ви повинні скоріше зосередитися на зменшенні кількості та обсягу змінних.
Бантар

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

1
Іноді вона зменшує складність, іноді збільшує її. Дивіться wiki.c2.com/?ArrowAntiPattern
Роберт Джонсон

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

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

-3

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

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


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