C#: вложенные условия vs оператор continue

при использовании ReSharper в последнее время он предлагает уменьшить вложенность в определенных местах путем инвертирования if условия и использование continue заявления.

вложенные условные конструкции:

foreach(....)
{
    if(SomeCondition)
    {
        //do some things

        if(SomeOtherNestedCondition)
        {
            //do some further things
        }
    }
}

продолжить высказывания:

foreach(....)
{
    if(!SomeCondition) continue;

    //do some things

    if(!SomeOtherNestedCondition) continue;

    //do some further things
}

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

какой подход вы предпочитаете и почему? Вы используете continue над вложенными ifs в вашем повседневном коде?

10 ответов


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

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

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

if (valid)
{
    do stuff;
}

Он всегда должен начинаться

if (notValid)
{
    return;
}

не должно быть никакой значительной производительности, это все о читаемости. Лично я думаю, что последнее легче читать. Меньше гнезд, легче читать.


короткий ответ:

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

более длинный ответ:

Я обычно предпочитают отступы к предыдущим "нарушение" заявления (continue, break, return или throw).

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

здесь одно заметное исключение для меня, а именно проверка аргументов в начале метода. Мне формат этого кода следует:

if (thisArgument == null) throw new NullArgumentException("thisArgument");
if (thatArgument < 0) throw new ArgumentOutOfRangeException("thatArgument");
...

причина: поскольку я не ожидаю, что эти исключения будут фактически брошены (то есть я ожидаю, что метод будет вызван правильно; вызывающая функция отвечает за обнаружение недопустимого ввода, IMO), я не хочу отступать от всего остального кода для чего-то, что не должно произойти.


С continue код стиля; как бы вы обрабатывали третью операцию, которая не зависит от SomeOtherNestedCondition, это делает порядок кода важным, который IMO делает его менее ремонтопригодным.

например:

foreach(....) 
{ 
    if(!SomeCondition) continue; 

    //do some things 

    if(!SomeOtherNestedCondition) continue; 

    //do some further things 

    if(!SomeSecondNonNestedCondition) continue;

    // do more stuff
}

что происходит, когда SomeOtherNestedCondition вызывает continue; произойти, но SomeSecondNonNestedCondition все равно должен выполняться?

Я бы рефакторинг каждый бит "код" и использовать вложенные if()s для вызова каждого переработан способ, и я бы держать вложенную структуру.


простой ответ. Тот, который легче читать и понимать.


используйте комбинацию из двух. Я использую if(condition) continue; и if(condition) return; в верхней части цикла для проверки текущего состояния и вложенного if заявления дальше вниз, чтобы контролировать поток того, что я пытаюсь выполнить.


нет никакой разницы с точки зрения памяти или производительности. Базовый IL-это просто выбор инструкций ветви/прыжка, поэтому на самом деле не имеет значения, возвращаются ли они к верхней части цикла или к оператору "else".

вы должны выбрать то, что легче читать. Я лично предпочитаю избегать "продолжения", но если у вас много уровней вложенности, тогда ваш второй пример может быть легче следовать.


С помощью continues составляет основную часть кода на уровне обычного процессуального кодекса. В противном случае, если есть пять проверок, вы будете отступать "мясо" метода 10 или 20 символов, в зависимости от размера отступа, и это 10/20 символов, которые вам придется прокручивать, чтобы увидеть более длинные строки.


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

class Program
{
    static bool SomeCondition = false;
    static bool SomeOtherNestedCondition = false;
    static void Main(string[] args)
    {    
        for (int i = 0; i < 2; i++)
        {
            if (SomeCondition)
            {
                //do some things

                if (SomeOtherNestedCondition)
                {
                    //do some further things
                }
            }
            Console.WriteLine("This text appeared from the first loop.");
        }
        for (int i = 0; i < 2; i++)
        {
            if (!SomeCondition) continue;

            //do some things

            if (!SomeOtherNestedCondition) continue;

            //do some further things
            Console.WriteLine("This text appeared from the second loop.");
        }
        Console.ReadLine(); 
    }
}

и выход будет: enter image description here


однако из моего фона разработки предыдущий пример легче следовать при чтении кода.

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

Если вам больше нравится версия "до", чем" после", используйте ее.
Просто настройте ReSharper, чтобы он подсказывал, что вы очень хочу.

всегда держать в виду: ReSharper-довольно "тупой" инструмент рефакторинга - он не может заменить разработчика. Это может помочь ему, только выполнив некоторую в противном случае глупую работу копирования и вставки.
Некоторые рефакторинги, выполняемые ReSharper, приводят даже к ситуациям, в которых ReSharper предлагает противоположный рефакторинг.

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

BTW: вы должны руководствоваться соображениями производительности здесь - я был бы удивлен если есть действительно заметные различия в производительности.