Инвертировать оператор if, чтобы уменьшить вложенность


272

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

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

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

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

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


1
Я считаю, что проверка и возврат исключений в начале - это хорошо, но я бы изменил условие, чтобы вы проверяли исключение напрямую, а не что-то (то есть, если (какое-то условие) возвращает).
Bruceatk

36
Нет, это ничего не даст для производительности.
Сет Карнеги

3
Я бы соблазнился сгенерировать исключение ArgumentException, если бы вместо моего метода передавались неверные данные.
asawyer

1
@asawyer Да, здесь есть целая побочная дискуссия о функциях, которые слишком просты для бессмысленных входных данных - в отличие от использования ошибки утверждения. Написание Solid Code открыло мне глаза на это. В этом случае это будет что-то вроде ASSERT( exampleParam > 0 ).
Грег Хендершотт

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

Ответы:


296

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

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();
};   

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


13
Это действительно хороший пример! Реорганизованный код читается больше как инструкция case.
Другое

16
Вероятно, просто дело вкуса: я предлагаю изменить второе и третье «если» на «еще, если», чтобы еще больше повысить читабельность. Если пропустить оператор «return», все равно будет ясно, что следующий случай проверяется только в случае неудачи предыдущего, то есть важен порядок проверок.
Фораидт

2
В этом простом примере id согласен, что 2-й способ лучше, но только потому, что так очевидно, что происходит.
Эндрю Баллок

Теперь, как мы возьмем этот красивый код, написанный jop, и не дадим визуальной студии разбить его и разделить результаты на отдельные строки? Меня действительно раздражает, что он переформатирует код таким образом. Делает этот код очень читабельным.
Марк Т

@Mark T В Visual Studio есть настройки, которые не позволяют взломать этот код.
Аарон Смит

333

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

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

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

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


41
единые точки выхода? Кому это нужно?
sq33G

3
@ sq33G: этот вопрос о SESE (и, конечно, ответы) фантастический. Спасибо за ссылку!
Джон

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

1
@AndreasBonini: отсутствие доказательств не является доказательством отсутствия. :-)
Jon

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

102

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

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

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

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

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

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


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

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

3
Согласитесь с Джоном здесь ... Я не вижу проблемы в том, чтобы просто пройтись по всей функции.
Nailer

5
@Nailer Очень поздно, я особенно согласен, потому что, если функция слишком велика для перехода, она должна быть разделена на несколько функций!
Aidiakapi

1
Согласитесь с Джоном тоже. Может быть, это старая школа, которая ставит точку останова внизу, но если вам интересно, что возвращает функция, вы пройдете через эту функцию, чтобы понять, почему она возвращает то, что возвращает в любом случае. Если вы просто хотите увидеть, что он возвращает, поместите его в последнюю скобку.
user441521

70

Как уже упоминали другие, не должно быть падения производительности, но есть и другие соображения. Помимо этих действительных проблем, это также может открыть для вас при некоторых обстоятельствах. Предположим, вы имели дело с 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может не быть


4
Также следует отметить, что быстрое исправление с изменением резкости преобразует exampleParam> 0 в exampleParam <0, а не exampleParam <= 0, что застало меня врасплох.
nickd

1
И именно поэтому эта проверка должна быть заранее и возвращена, так как разработчик думает, что нан должен привести к спасению.
user441521

51

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

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

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


6
Вы только что выяснили, почему исключения являются UglyAndEvil [tm] ... ;-) Исключения - это готы в причудливой, дорогой маскировке.
EricSchaefer

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

Это ответ, который я действительно искал! Здесь есть отличные ответы, но большинство из них сосредоточены на примерах, а не на фактической причине, по которой эта рекомендация родилась.
Густаво Мори

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

If you've got deep nesting, maybe your function is trying to do too many things.=> Это не правильное следствие вашей предыдущей фразы. Потому что непосредственно перед тем, как вы скажете, что вы можете изменить поведение A с кодом C в поведение A с кодом D. код D более чистый, предоставленный, но «слишком много вещей» относится к поведению, которое НЕ изменилось. Поэтому вы не имеете никакого смысла с этим выводом.
v.oddou

30

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

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

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


2
Именно. Это скорее рефакторинг. Это помогает читать код легче, как вы упомянули.
rpattabi

«возврата» недостаточно и ужасно для охранника. Я бы сделал: если (ex <= 0) бросить WrongParamValueEx ("[MethodName] Неверное значение входного параметра 1 {0} ... и вам нужно будет перехватить исключение и записать в журнал приложения
JohnJohnGa

3
@John. Вы правы, если это на самом деле ошибка. Но часто это не так. И вместо проверки чего-либо в каждом месте, где вызывается метод, метод просто проверяет это и возвращает ничего не делая.
Петр Перак

3
Я не хотел бы читать метод, который состоит из двух экранов кода, точка, ifс или нет. LOL
Jpmc26

1
Лично я предпочитаю ранний возврат именно по этой причине (а также: избегая вложения). Однако это не связано с первоначальным вопросом о производительности и, вероятно, должно быть комментарием.
Патрик М

22

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

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


18

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

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

15

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

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


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

1
новость: на самом деле я нашел очень практическую причину, почему ранний перерыв и возврат плохи, и это является прямым следствием того, что я сказал, статического анализа. готово, руководство по использованию компилятора intel C ++: d3f8ykwhia686p.cloudfront.net/1live/intel/… . Ключевая фраза:a loop that is not vectorizable due to a second data-dependent exit
v.oddou

12

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

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

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

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

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


11

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

if (something) {
    // a lot of indented code
}

Вы отменяете условие и нарушаете его, если выполнено обратное условие

if (!something) return false; // or another value to show your other code the function did not execute

// all the code from before, save a lot of tabs

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

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

if (something) {
    do-something();
    if (something-else) {
        do-another-thing();
    } else {
        do-something-else();
    }
}

против:

if (!something) return;
do-something();

if (!something-else) return do-something-else();
do-another-thing();

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

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


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

@ Другая сторона: полностью согласен. Возвращения, написанные в последовательном порядке, заставляют ваш мозг сериализовать возможные пути. Когда if-дерево может быть отображено непосредственно в некоторую транзитивную логику, например, оно может быть скомпилировано в lisp. но мода на последовательный возврат потребовала бы компилятора гораздо сложнее. Суть здесь, очевидно, не имеет ничего общего с этим гипотетическим сценарием, она связана с анализом кода, возможностями оптимизации, доказательствами исправления, доказательствами завершения и обнаружением инвариантов.
v.oddou

1
Никому не легче читать код с большим количеством гнезд. Чем больше блоков, как что-то, тем проще читать с первого взгляда. Нет такого первого примера, который мне легче читать, и он уходит только на 2 гнезда, когда большая часть кода, подобного этому, в реальном мире идет глубже и с каждым гнездом требуется больше умственных способностей для подражания.
user441521

1
Если бы вложение было вдвое глубже, то сколько бы вам понадобилось времени, чтобы ответить на вопрос: «Когда вы« что-то делаете »?» Я думаю, что вам будет трудно ответить без ручки и бумаги. Но вы могли бы ответить на это довольно легко, если бы все было оговорено охранником.
Дэвид Сторфер

9

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


8

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

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


7

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

например.

 bool PerformDefaultOperation()
 {
      bool succeeded = false;

      DataStructure defaultParameters;
      if ((defaultParameters = this.GetApplicationDefaults()) != null)
      {
           succeeded = this.DoSomething(defaultParameters);
      }

      return succeeded;
 }

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


4
bool PerformDefaultOperation () {DataStructure defaultParameters = this.GetApplicationDefaults (); return (defaultParameters! = NULL && this.DoSomething (defaultParameters);} Там это исправлено для вас. :)
tchen

1
Этот пример очень простой и не соответствует смыслу этого паттерна. Проведите около 4 других проверок внутри этой функции, а затем скажите нам, что это более читабельно, потому что это не так.
user441521

5

Много веских причин о том, как выглядит код . Но как насчет результатов ?

Давайте посмотрим на код C # и его скомпилированную форму IL:


using System;

public class Test {
    public static void Main(string[] args) {
        if (args.Length == 0) return;
        if ((args.Length+2)/3 == 5) return;
        Console.WriteLine("hey!!!");
    }
}

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

Сгенерированный код IL делает следующее:

  1. Если первое условие ложно, происходит переход к коду, где находится второе.
  2. Если это правда, переход к последней инструкции. (Примечание: последняя инструкция является возвращением).
  3. Во втором условии то же самое происходит после вычисления результата. Сравните и: попал в Console.WriteLine, если false, или до конца, если это правда.
  4. Распечатайте сообщение и вернитесь.

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

using System;

public class Test {
    public static void Main(string[] args) {
        if (args.Length != 0 && (args.Length+2)/3 != 5) 
        {
            Console.WriteLine("hey!!!");
        }
    }
}

Результаты очень похожи в инструкциях по IL. Разница заключается в том, что раньше выполнялись переходы по условию: если ложь, переходите к следующему фрагменту кода, если истина, то переход к концу. И теперь IL-код работает лучше и имеет 3 перехода (компилятор немного его оптимизировал): 1. Первый переход: когда Length равен 0, до части, где код снова переходит (третий переход) до конца. 2. Второе: в середине второго условия избегать одной инструкции. 3. Третье: если второе условие ложно, прыгайте до конца.

В любом случае, счетчик программы всегда будет прыгать.


5
Это хорошая информация - но лично мне было все равно, будет ли IL «течь лучше», потому что люди, которые собираются управлять кодом, не увидят IL
JohnIdol

1
Вы действительно не можете предвидеть, как пройдет этап оптимизации в следующем обновлении компилятора C # по сравнению с тем, что вы видите сегодня. Лично я бы не потратил ЛЮБОЕ время на попытки подправить код C # для получения другого результата в IL, если только тестирование не покажет, что у вас есть СЕРЬЕЗНАЯ проблема производительности в каком-то узком цикле, и у вас закончились другие идеи , Даже тогда я бы подумал о других вариантах. В том же духе, я сомневаюсь, что у вас есть какие-либо идеи о том, какие оптимизации JIT-компилятор выполняет с IL, который подается на него для каждой платформы ...
Крейг,

5

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

Подробнее о прогнозе отрасли здесь .


4

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

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

Я не думаю, что вы получите окончательный ответ на этот вопрос.


Я не понимаю ваш аргумент производительности. Условия логические, поэтому независимо от результата всегда есть два результата ... Я не понимаю, почему изменение утверждения (или нет) изменило бы результат. (Если вы не говорите, что добавление «НЕ» к условию добавляет измеримое количество обработки ...
Оли

2
Оптимизация работает следующим образом: если вы гарантируете, что наиболее распространенный случай находится в блоке if, а не в блоке else, то в CPU обычно уже есть операторы из блока if, загруженные в его конвейер. Если условие возвращает false, CPU должен очистить свой конвейер и ...
Otherside

Мне также нравится делать то, что говорят другие, только для удобства чтения, я ненавижу иметь отрицательные случаи в блоке «then», а не «else». Имеет смысл сделать это, даже не зная и не задумываясь о том, что делает процессор
Эндрю Баллок

3

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

Я обнаружил, что мой код действительно не читается при написании хост-приложения ASIO с помощью ASIOSDK от Steinberg, поскольку я следовал парадигме вложенности. Он прошел глубиной в восемь уровней, и я не вижу там недостатка дизайна, как упомянул Эндрю Баллок выше. Конечно, я мог бы упаковать некоторый внутренний код в другую функцию, а затем вложить туда оставшиеся уровни, чтобы сделать его более читабельным, но мне это кажется довольно случайным.

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

Так что это может быть другая ситуация, когда инвертированные ifs могут внести свой вклад в более четкий код.


3

Избегание нескольких точек выхода может привести к повышению производительности. Я не уверен насчет C #, но в C ++ оптимизация именованных возвращаемых значений (Copy Elision, ISO C ++ '03 12.8 / 15) зависит от наличия единой точки выхода. Эта оптимизация позволяет избежать создания копии возвращаемого значения (в вашем конкретном примере это не имеет значения). Это может привести к значительному увеличению производительности в тесных циклах, так как вы сохраняете конструктор и деструктор каждый раз, когда вызывается функция.

Но в 99% случаев сохранение дополнительных вызовов конструктора и деструктора не стоит потери читаемости, которую представляют вложенные ifблоки (как указывали другие).


2
там. Мне потребовалось 3 длинных чтения десятков ответов на 3 вопроса с одинаковыми (2 из которых были заморожены), чтобы наконец найти кого-то, упоминающего NRVO. гы ... спасибо.
v.oddou

2

Это вопрос мнения.

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

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


2

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

Если вы на самом деле возвращаете returnValue, обычно лучше использовать вложение, потому что вы возвращаете returnValue только в одном месте (в конце концов), и это может сделать ваш код более понятным в большинстве случаев.


1

Я не уверен, но я думаю, что R # пытается избежать прыжков в длину. Когда у вас есть IF-ELSE, компилятор делает что-то вроде этого:

Условие false -> дальний переход к false_condition_label

true_condition_label: инструкция1 ... инструкция_н

false_condition_label: инструкция1 ... инструкция_н

конец блока

Если условие истинно, переход и кэш L1 не происходит, но переход к false_condition_label может быть очень большим, и процессор должен развернуть свой собственный кэш. Синхронизация кеша стоит дорого. R # пытается заменить прыжки в длину на короткие прыжки, и в этом случае существует большая вероятность того, что все инструкции уже находятся в кеше.


0

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


0

Моя идея заключается в том, что возвращение «в середине функции» не должно быть таким «субъективным». Причина довольно проста, возьмите этот код:

    function do_something (data) {

      if (! is_valid_data (data)) 
            вернуть ложь;


       do_something_that_take_an_hour (data);

       istance = new object_with_very_painful_constructor (data);

          if (istance недействителен) {
               сообщение об ошибке( );
                возвращение ;

          }
       connect_to_database ();
       get_some_other_data ();
       возвращение;
    }

Может быть, первое «возвращение» не ТАК интуитивно понятно, но это действительно экономит. Существует слишком много «идей» о чистых кодах, которым просто нужно больше практики, чтобы потерять свои «субъективные» плохие идеи.


За исключением языка, у вас нет этих проблем.
user441521

0

У такого рода кодирования есть несколько преимуществ, но для меня большой выигрыш в том, что если вы можете быстро вернуться, вы можете повысить скорость своего приложения. Т.е. я знаю, что из-за предусловия X я могу быстро вернуться с ошибкой. Это избавляет от ошибок в первую очередь и уменьшает сложность вашего кода. Во многих случаях, поскольку конвейер ЦП теперь может быть чище, он может остановить аварийное завершение конвейера или его переключение. Во-вторых, если вы зацикливаетесь, быстрое прерывание или возврат может сэкономить вам много ресурсов процессора. Некоторые программисты используют инварианты цикла для быстрого выхода из этого типа, но в этом случае вы можете разорвать конвейер процессора и даже создать проблему поиска памяти, что означает, что процессор должен загружаться из внешнего кэша. Но в основном я думаю, что вы должны делать то, что вы хотели, то есть конец цикла или функции не создает сложный путь к коду только для реализации некоторого абстрактного понятия правильного кода. Если единственный инструмент, который у вас есть, это молоток, то все выглядит как гвоздь.


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

1
Я не согласен - Первый Закон о правильном алгоритме. Но все мы - инженеры, которые решают правильную проблему с ресурсами, которые у нас есть, и делаем в доступном бюджете. Приложение, которое работает слишком медленно, не подходит для этой цели.
Дэвид Аллан Финч
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.