Використовуйте інше після винятку (або ні)


9

Розглянемо цей біт коду:

if (x == 1)
{
  throw "no good; aborting" ;
}

[... more code ...]

Тепер розглянемо цей код:

if (x == 1)
{
  throw "no good; aborting" ;
}
else
{
  [... more code ...]
}

Два випадки працюють абсолютно однаково. Перший випадок має ту перевагу, що вам не доведеться "обробляти" решту коду в else. Друга перевага полягає в тому, щоб дотримуватися практики явного використання elseдля кожного if.

Чи може хтось надати якісь тверді аргументи на користь одного над іншим?


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

Послідовність? Дозволити надійність при зміні коду? Читання?
Томас Едінг

1
Е-е-е, я не стільки прихильник ідеї, яка ifпотрібна кожному else. Останній програміст, який працював над нашою кодовою базою, суворо дотримувався цього ( ну, іноді ... це свого роду шизофренік ). Як результат, у нас багато безглуздих else { /* do nothing */ }блоків, що засмічують код ...
KChaloux

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

Це зайве ще. Якщо ви працюєте зі стеком .NET, встановіть ReSharper, і він буде нагадувати вам видалити всі зайві оператори.
CodeART

Відповіді:


16

Не слід додавати elseпісля ifгілок, які беззастережно порушують керування потоком , наприклад, ті, що містять a throwабо a return. Це дозволило б покращити читабельність вашої програми, видаливши зайвий рівень вкладення, запроваджений elseфілією.

Те, що виглядає більш-менш ОК із синглом, throwстає по-справжньому некрасивим, коли кілька кидків поспіль отримують пошкодження:

void myMethod(int arg1, int arg2, int arg3) {
    // This is demonstrably ugly - do not code like that!
    if (!isValid(arg1)) {
        throw new ArgumentException("arg1 is invalid");
    } else {
        if (!isValid(arg2)) {
            throw new ArgumentException("arg2 is invalid");
        } else {
            if (!isValid(arg3)) {
                throw new ArgumentException("arg3 is invalid");
            } else {
                // The useful code starts here
            }
        }
    }
}

Цей фрагмент робить те саме, але виглядає набагато краще:

void myMethod(int arg1, int arg2, int arg3) {
    if (!isValid(arg1)) {
        throw new ArgumentException("arg1 is invalid");
    }
    if (!isValid(arg2)) {
        throw new ArgumentException("arg2 is invalid");
    }
    if (!isValid(arg3)) {
        throw new ArgumentException("arg3 is invalid");
    }
    // The useful code starts here
}

+1 Правда Другий випадок "ОП" змушує вас уважно читати, а потім залишає вам WTF. Але ... завжди намагайтеся зробити методи короткими. Повернення в середині методу на 200 рядків також погано.
Tulains Córdova

1
Справедливості, якщо ви просто використовуєте повторення, якщо ви можете зробити else if.
Гуванте

2
@Guvante: Кожен ifтестує окрему умову і займається нею, якщо умова є істинною, і якщо тільки не існує іншої речі, яка повинна відбутися, якщо умова помилкова, else ifs не потрібні. У нас є термін навколо мого офісу для коду , як перший фрагмент dasblinkenlight в: « пачинко машини.»
Blrfl

@Blrfl pachinko machine hah, ідеальна аналогія +1
Джиммі Хоффа

@Blrfl: Я згадував, що повторні ifs є поганим прикладом занадто великої кількості гніздування. Ви все одно не повинні вкладатись повторно. Я погоджуюся, що загалом, якщо ви не говорите про незначну кількість коду, немає підстав для включення else.
Гуванте

5

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

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

Скажіть, наприклад, у вас є ця функція:

public void ConfigureOblogon(Oblogon oblogonToConfigure)
{
    if (_validColors.Contains(oblogonToConfigure.Color))
    {
        oblogonToConfigure.ColorIndex = _validColors.IndexOf(oblogonToConfigure.Color);
    }
    else
    {
        oblogonToConfigure.Color = _validColors[0];
        oblogonToConfigure.ColorIndex = 0;
    }
}

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

public void ConfigureOblogon(Oblogon oblogonToConfigure)
{
    if (!_validOblogons.Contains(oblogonToConfigure.Type))
    {
        oblogonToConfigure.Type = _validOblogons[0];
        oblogonToConfigure.TypeIndex = 0;
        if (_validColors.Contains(oblogonToConfigure.Color))
        {
            oblogonToConfigure.ColorIndex = _validColors.IndexOf(oblogonToConfigure.Color);
        }
        else
        {
            oblogonToConfigure.Color = _validColors[0];
            oblogonToConfigure.ColorIndex = 0;
        }
    }
    else
    {
        oblogonToConfigure.TypeIndex = _validOblogons.IndexOf(oblogonToConfigure.Type);
    }
}

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

public void ConfigureOblogon(Oblogon oblogonToConfigure)
{
    if (!_validColors.Contains(oblogonToConfigure.Color))
    {
        oblogonToConfigure.Color = _validColors[0];
    }

    oblogonToConfigure.ColorIndex = _validColors.IndexOf(oblogonToConfigure.Color);
}

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

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

SomeFunction()
{
    if (isvalid)
    {
        /* ENTIRE FUNCTION */
    }
    /* Nothing should go here but something does on accident, and an invalid scenario is created. */
}

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

SomeFunction()
{
    if (!isvalid)
    {
        /* Nothing should go here, and it's so small no one will likely accidentally put something here */
        return;
    }

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