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

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

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

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

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

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

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

bruceatk 13.12.2008 01:24

Нет, это не повлияет на производительность.

Seth Carnegie 09.12.2011 00:34

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

zerkms 09.12.2011 00:35

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

asawyer 09.12.2011 00:36

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

Greg Hendershott 09.12.2011 01:29

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

Simon Richter 09.12.2011 01:48

Приятно спросить об этом, что, как я полагал, было серьезным предубеждением со стороны разработчиков resharper. Я не считаю их рефакторинг более чистым, чем мой вложенный if. Они также слишком сильно применяют DRY. например, поставьте var здесь и там или удалите лишние квалификаторы, когда мне нравится моя избыточная квалификация, как они ...

v.oddou 17.04.2014 11:21

возможный дубликат Повышает ли инвертирование «если» производительность?

Ryan Kohn 16.09.2014 19:31

"уменьшить вложенность" - вот объяснение, почему это предлагается :-)

kux 25.10.2020 01:45
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
286
9
130 872
25
Перейти к ответу Данный вопрос помечен как решенный

Ответы 25

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

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

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

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

Oli 06.11.2008 13:09

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

Otherside 06.11.2008 13:39

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

Andrew Bullock 06.11.2008 15:53

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

Мой нормальный подход - избегать однострочных ifs и возвратов в середине метода.

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

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

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

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

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

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

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

Кроме того, при использовании такого «инвертировать, если» у вас есть проверка ошибочного состояния и действия при ошибке, близкие друг к другу. Если бы вы поступили иначе, то было бы, если бы (хорошо) что-то сделали; остальное поправьте / сообщите. Таким образом, сразу видна ошибка - действие.

Marcin Gil 07.11.2008 11:04

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

EvilTeach 13.11.2008 01:21

Разве вы не можете поставить точку останова на закрывающей скобке метода?

g . 02.12.2008 17:42

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

LeopardSkinPillBoxHat 04.12.2008 02:18

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

John Dunagan 12.12.2008 16:24

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

Nailer 12.12.2008 16:58

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

Aidiakapi 24.01.2012 18:49

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

user441521 04.05.2017 20:46

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

John Stock 06.11.2018 19:12

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

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

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

Защитные предложения или предварительные условия (как вы, вероятно, видите) проверяют, выполняется ли определенное условие, а затем прерывают выполнение программы. Они отлично подходят для мест, где вас действительно интересует только один результат заявления 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();

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

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

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

Otherside 06.11.2008 13:32

@Otherside: полностью согласен. Последовательно записанные возвраты заставляют ваш мозг сериализовать возможные пути. Когда если-дерево может быть отображено непосредственно в какую-то временную логику, например, оно может быть скомпилировано для лиспа. но способ последовательного возврата потребовал бы гораздо более сложного компилятора. Дело здесь, очевидно, не имеет ничего общего с этим гипотетическим сценарием, оно связано с анализом кода, шансами на оптимизацию, корректирующими доказательствами, доказательствами завершения и обнаружением инвариантов.

v.oddou 17.04.2014 11:36

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

user441521 04.05.2017 20:59

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

David Storfer 29.08.2018 19:21

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

    function do_something( data ){

      if (!is_valid_data( data )) 
            return false;


       do_something_that_take_an_hour( data );

       istance = new object_with_very_painful_constructor( data );

          if ( istance is not valid ) {
               error_message( );
                return ;

          }
       connect_to_database ( );
       get_some_other_data( );
       return;
    }

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

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

user441521 04.05.2017 21:05
Ответ принят как подходящий

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

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

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

Это действительно хороший пример! Реорганизованный код больше похож на оператор case.

Otherside 06.11.2008 13:36

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

foraidt 06.11.2008 14:24

В этом простом примере я согласен, что второй способ лучше, но только потому, что он ТАК очевиден, что происходит.

Andrew Bullock 06.11.2008 16:00

@jop +1 за ссылку на каталог рефакторинга, это довольно мощный ресурс

Rob van Groenewoud 19.02.2010 14:51

Ваше последнее заявление следует выделить жирным шрифтом. Заменить вложенные условия на защитные предложения, потому что это правда. в то время как ваше первое утверждение неверно: A return in the middle of the method is not necessarily bad -> нет-нет! Мы можем только сказать, что охранник закрывается - единственное допустимое исключение.

v.oddou 17.04.2014 11:19

он также называется защитный подход

Alexey 07.09.2018 23:27

@jop what if 'second if Зависимость от первого if? ex: if (object.firstName.Value) {if (Regex.IsMatch (object.firstName, strNameRegex)) {// dosomething}}

AminM 22.06.2020 10:28

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

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

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

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

EricSchaefer 07.11.2008 18:58

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

Robert Paulson 01.12.2008 23:40

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

Gustavo Mori 15.03.2012 21:34

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

Buttle Butkus 31.10.2013 09:00

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

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

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

v.oddou 17.04.2014 11:31

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

v.oddou 30.06.2014 10:27

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

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

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

hangy 07.11.2008 18:44

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

David Allan Finch 02.12.2008 14:06

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

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

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

Давайте посмотрим на код 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. Если первое условие - false, переходит к коду, где находится второе.
  2. Если это true, то переходит к последней инструкции. (Примечание: последняя инструкция - возврат).
  3. Во втором условии то же самое происходит после вычисления результата. Сравните и: дошли до Console.WriteLine, если это false, или до конца, если это true.
  4. Распечатайте сообщение и вернитесь.

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

using System;

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

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

  1. Первый переход: когда длина равна 0, к части, где код снова перескакивает (Третий переход) до конца.
  2. Второе: в середине второго условия, чтобы избежать одной инструкции.
  3. Третье: если второе условие - false, переходите до конца.

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

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

JohnIdol 07.11.2008 18:36

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

Craig 12.09.2013 02:50

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

например.

 bool PerformDefaultOperation()
 {
      bool succeeded = false;

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

      return succeeded;
 }

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

bool PerformDefaultOperation () {DataStructure defaultParameters = this.GetApplicationDefaults (); return (defaultParameters! = NULL && this.DoSomething (defaultParameters);} Вот, исправил для вас. :)

tchen 07.11.2008 19:02

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

user441521 24.04.2016 08:06

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

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

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

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

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

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

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

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

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

sq33G 09.12.2011 02:10

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

Jon 09.12.2011 03:37

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

Thomas Bonini 09.12.2011 19:03

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

Jon 09.12.2011 19:13

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

Thomas Bonini 09.12.2011 19:30

@ sq33G Кого волнует / нужна единственная точка выхода? На это может ответить любой, кто является опытным разработчиком многопоточных приложений. Это не означает единого выхода в канон разработчика или что-то подобное, это просто ценная практика многих ветеранов-разработчиков. (и да, в наши дни это гораздо менее актуально).

Robert Altman 14.12.2011 21:07

@Robert Altman, забавно, я не припомню, чтобы что-нибудь встречал о многопоточности в этом исчерпывающем посте. Вы можете уточнить?

sq33G 14.12.2011 21:20

@ sq33G Просто пример того, почему некоторые люди настаивают на единой точке выхода. ReSharper, скорее всего, этим не занимается. Если в моем комментарии есть что-то достойное упоминания, так это то, что «стиль» необходимо рассматривать в контексте того, что оформляется. (Прочтите вашу ссылку после Я разместил свой комментарий, поэтому, возможно, я пропустил какую-либо умышленную иронию или объяснение с вашей стороны.)

Robert Altman 14.12.2011 21:35

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

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

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

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

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

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

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

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

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

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

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

nickd 02.12.2014 17:51

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

user441521 04.05.2017 20:49

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

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

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

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

rpattabi 14.12.2011 08:17

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

JohnJohnGa 14.12.2011 11:11

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

Piotr Perak 14.12.2011 11:24

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

jpmc26 15.07.2014 07:01

Я тоже это ненавижу! Вы не поверите, но я видел 11к строк кодовых методов на C# :)

Piotr Perak 15.07.2014 10:38

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

Patrick M 16.09.2014 23:57

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

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

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

v.oddou 21.11.2017 12:37

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

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

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

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

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

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

конец блока

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

Другие вопросы по теме