Оператор перемикача з поверненнями - правильність коду


90

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

switch (something)
{
    case 0:
      return "blah";
      break;

    case 1:
    case 4:
      return "foo";
      break;

    case 2:
    case 3:
      return "bar";
      break;

    default:
      return "foobar";
      break;
}

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

Що ти думаєш? Чи добре їх видалити? Або ви б зберегли їх для підвищення "правильності"?

Відповіді:


136

Видаліть breakтвердження. Вони не потрібні, і, можливо, деякі компілятори видадуть попередження "Недосяжний код" .


2
Те саме стосується інших безумовних тверджень про перехід , наприклад continueабо goto- ідіоматично використовувати їх замість break.
кафе

31

Я хотів би взяти інший варіант. Не ПОВЕРТАЙТЕ в середині методу / функції. Натомість просто помістіть повернене значення в локальну змінну та надішліть його наприкінці.

Особисто я вважаю наступне більш читабельним:

String result = "";

switch (something) {
case 0:
  result = "blah";
  break;
case 1:
  result = "foo";
  break;
}

return result;

23
Філософія "One Exit" має сенс у C, де вам потрібно забезпечити, щоб речі були прибрані правильно. Враховуючи, що в C ++ вас може виключити з функції будь-який момент виняток, одна філософія виходу насправді не корисна в C ++.
Billy ONeal,

29
Теоретично це чудова ідея, але часто потрібні додаткові блоки управління, які можуть заважати читабельності.
mikerobi

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

7
Ну, це, і блоки повернення в середині коду просто нагадують мені про зловживання GOTO.
NotMe

5
@Chris: У більш довгих функціях я повністю згоден - але це, як правило, говорить про більші проблеми, тому переробляйте це. У багатьох випадках це тривіальна функція, яка повертається на комутаторі, і цілком зрозуміло, що має відбутися, тому не витрачайте сили мозку на відстеження додаткової змінної.
Стівен,

8

Особисто я б знімав повернення і зберігав перерви. Я б використав оператор switch, щоб призначити значення змінній. Потім поверніть цю змінну після оператора switch.

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

Один виняток: Повернення достроково - це нормально, якщо поганий параметр виявлено на початку функції - до того, як буде придбано будь-які ресурси.


Може бути добре для цього конкретного switch, але не обов’язково найкращого взагалі. Що ж, припустимо, оператор switch усередині функції передує ще 500 рядкам коду, який є дійсним лише тоді, коли є певні випадки true. Який сенс виконувати весь той додатковий код для випадків, коли його не потрібно виконувати; хіба не краще просто returnвсередині switchдля тих case?
Пт0zenFyr

6

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

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


5

Видаліть їх. Ідіоматично повертатися із caseвисловлювань, інакше це "недосяжний код".


5

Я б їх видалив. У моїй книзі такий мертвий код слід розглядати як помилку, оскільки він змушує вас робити подвійні думки і запитувати себе: "Як би я коли-небудь виконав цей рядок?"


4

Зазвичай я писав би код без них. ІМО, мертвий код, як правило, вказує на неохайність та / або відсутність розуміння.

Звичайно, я б також розглянув щось на зразок:

char const *rets[] = {"blah", "foo", "bar"};

return rets[something];

Редагувати: навіть із відредагованим дописом ця загальна ідея може добре працювати:

char const *rets[] = { "blah", "foo", "bar", "bar", "foo"};

if ((unsigned)something < 5)
    return rets[something]
return "foobar";

У певний момент, особливо якщо вхідні значення є розрідженими (наприклад, 1, 100, 1000 та 10000), замість цього вам потрібен розріджений масив. Ви можете реалізувати це як дерево або карту досить добре (хоча, звичайно, перемикач все ще працює і в цьому випадку).


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

@your edit: Так, але це зайняло б більше пам'яті і т. д. IMHO перемикач - це найпростіший спосіб, саме цього я хочу в такій невеликій функції (він робить лише перемикач).
houbysoft

3
@houbysoft: уважно подивіться, перш ніж зробити висновок, що це займе додаткову пам’ять. З типовим компілятором, використання "foo" двічі (наприклад) використовуватиме два вказівники на один блок даних, а не копіюватиме дані. Ви також можете очікувати коротший код. Якщо у вас немає багато повторюваних значень, це часто заощадить пам’ять загалом.
Джеррі Коффін,

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

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

2

Я б сказав, видаліть їх і визначте за замовчуванням: branch.


Якщо розумного за замовчуванням немає, тоді ви не повинні визначати за замовчуванням.
Billy ONeal

є один, і я його визначив, я просто не ставив його у питанні, редагував зараз.
houbysoft

2

Чи не краще було б мати масив з

arr[0] = "blah"
arr[1] = "foo"
arr[2] = "bar"

і робити return arr[something];?

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


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

2

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


2

Що ти думаєш? Чи добре їх видалити? Або ви б зберегли їх для підвищення "правильності"?

Їх добре видалити. Використання return - це саме той сценарій, де breakне слід використовувати.


1

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

Я знаю, що це не так, коли returnзамість a є замість a break, але саме так мої очі «читають» корпуси блоків у комутаторі, тому я особисто вважаю за краще, щоб кожен з них caseбув у парі з break. Але багато компіляторів скаржаться на те, що breakпісля того, як returnє зайвим / недосяжним, і, мабуть, я все одно в меншості.

Тож позбудьтесь breakнаступногоreturn .

NB: все це ігнорує, чи є порушення єдиного правила входу / виходу доброю ідеєю чи ні. Що стосується цього, я маю думку, яка, на жаль, змінюється залежно від обставин ...


0

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

Також я завжди вважав за краще не поєднувати перерви та повернення в операторі switch, а скоріше дотримуватися одного з них.


0

Я особисто прагну втратити breaks. Можливо, одним із джерел цієї звички є процедури програмування вікон для програм Windows:

LRESULT WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_SIZE:
            return sizeHandler (...);
        case WM_DESTROY:
            return destroyHandler (...);
        ...
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

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


0

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

Bleah, ідея дуже проста:


Switch ( Algorithm )
{

 case 1:
 {
   Call_911;
   Jump;
 }**break**;
 case 2:
 {
   Call Samantha_28;
   Forget;
 }**break**;
 case 3:
 {
   Call it_a_day;
 }**break**;

Return thinkAboutIt?1:return 0;

void Samantha_28(int oBed)
{
   LONG way_from_right;
   SHORT Forget_is_my_job;
   LONG JMP_is_for_assembly;
   LONG assembly_I_work_for_cops;

   BOOL allOfTheAbove;

   int Elligence_says_anyways_thinkAboutIt_**break**_if_we_code_like_this_we_d_be_monkeys;

}
// Sometimes Programming is supposed to convey the meaning and the essence of the task at hand. It is // there to serve a purpose and to keep it alive. While you are not looking, your program is doing  // its thing. Do you trust it?
// This is how you can...
// ----------
// **Break**; Please, take a **Break**;

/ * Однак лише незначне запитання. Скільки кави ви випили, читаючи вищезазначене? ІТ іноді ламає систему * /


0

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


0

Якщо у вас є код "підстановки", ви можете упакувати пропозицію switch-case в метод самостійно.

У мене є декілька з них у системі "хобі", яку я розробляю для розваги:

private int basePerCapitaIncomeRaw(int tl) {
    switch (tl) {
        case 0:     return 7500;
        case 1:     return 7800;
        case 2:     return 8100;
        case 3:     return 8400;
        case 4:     return 9600;
        case 5:     return 13000;
        case 6:     return 19000;
        case 7:     return 25000;
        case 8:     return 31000;
        case 9:     return 43000;
        case 10:    return 67000;
        case 11:    return 97000;
        default:    return 130000;
    }
}

(Так. Це простір GURPS ...)

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

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

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

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