Инвертировать оператор if, чтобы уменьшить вложенность - PullRequest
246 голосов
/ 06 ноября 2008

Когда я запустил ReSharper для моего кода, например:

    if (some condition)
    {
        Some code...            
    }

ReSharper дал мне вышеупомянутое предупреждение (оператор Invert "if", чтобы уменьшить вложенность), и предложил следующее исправление:

   if (!some condition) return;
   Some code...

Я бы хотел понять, почему так лучше. Я всегда думал, что использование «возврата» в середине метода проблематично, что-то вроде «goto».

Ответы [ 25 ]

326 голосов
/ 09 декабря 2011

Это не только эстетично , но также снижает максимальный уровень вложенности внутри метода. Обычно это считается плюсом, поскольку упрощает понимание методов (и, действительно, многие статические анализ инструменты обеспечивают меру это как один из показателей качества кода).

С другой стороны, он также дает вашему методу несколько точек выхода, что, по мнению другой группы людей, является отрицательным.

Лично я согласен с ReSharper и первой группой (на языке, в котором есть исключения, я считаю глупым обсуждать «множественные точки выхода»; почти все могут бросить, поэтому во всех методах есть множество потенциальных точек выхода). 1017 *

Относительно производительности : обе версии должны быть эквивалентными (если не на уровне IL, то, конечно, после завершения джиттера с кодом) на каждом языке. Теоретически это зависит от компилятора, но практически любой широко используемый сегодня компилятор способен обрабатывать гораздо более сложные случаи оптимизации кода, чем этот.

268 голосов
/ 06 ноября 2008

Возврат в середине метода не обязательно плох. Возможно, лучше сразу вернуться, если это прояснит смысл кода. Например:

double getPayAmount() {
    double result;
    if (_isDead) result = deadAmount();
    else {
        if (_isSeparated) result = separatedAmount();
        else {
            if (_isRetired) result = retiredAmount();
            else result = normalPayAmount();
        };
    }
     return result;
};

В этом случае, если _isDead истинно, мы можем немедленно выйти из метода. Может быть, лучше структурировать это так:

double getPayAmount() {
    if (_isDead)      return deadAmount();
    if (_isSeparated) return separatedAmount();
    if (_isRetired)   return retiredAmount();

    return normalPayAmount();
};   

Я выбрал этот код из каталога рефакторинга . Этот специфический рефакторинг называется: Заменить вложенное условие условным выражением.

100 голосов
/ 06 ноября 2008

Это немного религиозный аргумент, но я согласен с ReSharper, что вы должны предпочитать меньше вложенности. Я полагаю, что это перевешивает недостатки наличия нескольких путей возврата из функции.

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

Предварительные условия являются отличным примером того, где можно вернуться рано в начале функции. Почему наличие проверки предусловия должно влиять на читабельность остальной части функции?

Что касается отрицательных результатов при многократном возврате из метода - отладчики сейчас довольно мощные, и очень легко точно определить, где и когда возвращается конкретная функция.

Наличие нескольких возвратов в функции не повлияет на работу программиста по обслуживанию.

Плохая читаемость кода будет.

68 голосов
/ 09 декабря 2011

Как уже упоминалось, не должно быть снижения производительности, но есть и другие соображения. Помимо этих действительных проблем, это также может открыть для вас при некоторых обстоятельствах. Предположим, вы имели дело с double вместо:

public void myfunction(double exampleParam){
    if(exampleParam > 0){
        //Body will *not* be executed if Double.IsNan(exampleParam)
    }
}

Сравните это с на первый взгляд эквивалентной инверсией:

public void myfunction(double exampleParam){
    if(exampleParam <= 0)
        return;
    //Body *will* be executed if Double.IsNan(exampleParam)
}

Так что при определенных обстоятельствах то, что представляется правильно перевернутым if, может не быть.

50 голосов
/ 06 ноября 2008

Идея возврата только в конце функции возникла еще в те дни, когда языки поддерживали исключения. Это позволило программам полагаться на возможность помещать код очистки в конец метода, а затем быть уверенным, что он будет вызван, и какой-то другой программист не будет скрывать возврат в метод, который вызвал пропуск кода очистки , Пропущенный код очистки может привести к утечке памяти или ресурсов.

Однако на языке, который поддерживает исключения, он не предоставляет таких гарантий. В языке, который поддерживает исключения, выполнение любого оператора или выражения может вызвать поток управления, который вызывает завершение метода. Это означает, что очистка должна выполняться с использованием ключевых слов finally или использования.

В любом случае, я говорю, что я думаю, что многие люди цитируют рекомендацию «только возврат в конце метода», не понимая, почему это когда-либо было полезно, и что сокращение вложенности для улучшения читаемости, вероятно, лучше прицелиться.

26 голосов
/ 13 декабря 2011

Я хотел бы добавить, что есть имя для тех, кто перевернут, если это - Guard Clause. Я использую его, когда могу.

Я ненавижу читать код там, где есть, если в начале, два экрана кода и больше ничего. Просто инвертируй если и верни. Таким образом, никто не будет тратить время на прокрутку.

http://c2.com/cgi/wiki?GuardClause

21 голосов
/ 09 декабря 2011

Это не только влияет на эстетику, но и предотвращает вложение кода.

На самом деле он может служить предварительным условием для обеспечения правильности ваших данных.

18 голосов
/ 06 ноября 2008

Это, конечно, субъективно, но я думаю, что это сильно улучшается по двум пунктам:

  • Теперь сразу очевидно, что вашей функции больше нечего делать, если выполняется condition.
  • Сохраняет уровень вложенности вниз. Вложенность вредит читабельности больше, чем вы думаете.
14 голосов
/ 06 ноября 2008

Несколько точек возврата были проблемой в C (и в меньшей степени в C ++), потому что они заставляли вас дублировать код очистки перед каждой из точек возврата. С сборкой мусора, try | finally конструкции и using блоки, на самом деле нет причин, почему вы должны их бояться.

В конечном итоге все сводится к тому, что вам и вашим коллегам легче читать.

12 голосов
/ 09 декабря 2011

С точки зрения производительности, между этими двумя подходами не будет заметной разницы.

Но кодирование - это больше, чем производительность. Ясность и ремонтопригодность также очень важны. И в таких случаях, когда это не влияет на производительность, это единственное, что имеет значение.

Существуют противоречивые мнения о том, какой подход предпочтительнее.

Одно представление - это то, о котором упоминали другие: второй подход снижает уровень вложенности, что улучшает ясность кода. Это естественно в императивном стиле: когда вам нечего делать, вы также можете вернуться рано.

Другое мнение, с точки зрения более функционального стиля, состоит в том, что метод должен иметь только одну точку выхода. Все на функциональном языке является выражением. Так что если в утверждениях всегда должны быть предложения else. В противном случае выражение if не всегда будет иметь значение. Так что в функциональном стиле первый подход более естественный.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...