Должен ли я вернуться из функции раньше или использовать оператор if?[закрыто]

softwareengineering.stackexchange https://softwareengineering.stackexchange.com/questions/18454

Вопрос

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

public void SomeFunction(bool someCondition)
{
    if (someCondition)
    {
        // Do Something
    }
}

или

public void SomeFunction(bool someCondition)
{
    if (!someCondition)
        return;

    // Do Something
}

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

Это было полезно?

Решение

Я предпочитаю второй стиль.Сначала уберите недопустимые случаи, либо просто выйдя, либо создав исключения, поместите туда пустую строку, а затем добавьте «настоящее» тело метода.Мне легче читать.

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

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

public int SomeFunction(bool cond1, string name, int value, AuthInfo perms)
{
    int retval = SUCCESS;
    if (someCondition)
    {
        if (name != null && name != "")
        {
            if (value != 0)
            {
                if (perms.allow(name)
                {
                    // Do Something
                }
                else
                {
                    reval = PERM_DENY;
                }
            }
            else
            {
                retval = BAD_VALUE;
            }
        }
        else
        {
            retval = BAD_NAME;
        }
    }
    else
    {
        retval = BAD_COND;
    }
    return retval;
}

более читабелен, чем

public int SomeFunction(bool cond1, string name, int value, AuthInfo perms)
{
    if (!someCondition)
        return BAD_COND;

    if (name == null || name == "")
        return BAD_NAME;

    if (value == 0)
        return BAD_VALUE;

    if (!perms.allow(name))
        return PERM_DENY;

    // Do something
    return SUCCESS;
}

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

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

Тем не менее, если при вызове функции требуются некоторые данные, я обычно выдаю исключение (см. пример), а не просто возвращаюсь.

public int myFunction(string parameterOne, string parameterTwo) {
  // Can't work without a value
  if (string.IsNullOrEmpty(parameterOne)) {
    throw new ArgumentNullException("parameterOne");
  } 
  if (string.IsNullOrEmpty(parameterTwo)) {
    throw new ArgumentNullException("parameterTwo");
  }

  // ...      
  // Do some work
  // ...

  return value;
}

Я предпочитаю раннее возвращение.

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

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

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

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

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

C# я, конечно, не могу ответить.

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

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

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

Я использую оба.

Если DoSomething это 3-5 строк кода, тогда код выглядит просто красиво, используя первый метод форматирования.

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

Классическая причина принципа «один вход-один выход» заключается в том, что в противном случае формальная семантика станет невыразимо уродливой (по той же причине, по которой GOTO считался вредным).

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

Обычно я минимизирую подход раннего возврата.

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

Это зависит.

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

Установите Retval + одиночный возврат, если функция более сложна и в противном случае может иметь несколько точек выхода (проблема с читаемостью).

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

Используйте если

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

Если вы последуете приведенному выше совету, вам нужно будет поместить этот возврат в конец функции, и тогда вы можете даже не называть его там возвратом, просто установите код ошибки и верните его через две строки.Тем самым достигается идеал «1 вход — 1 выход».

Специально для Делфи...

Я считаю, что это хорошая практика программирования для программистов Delphi, хотя у меня нет никаких доказательств.До D2009 у нас не было атомарного способа вернуть значение, у нас был exit; и result := foo; или мы могли бы просто генерировать исключения.

Если бы вам пришлось заменить

if (true) {
 return foo;
} 

для

if true then 
begin
  result := foo; 
  exit; 
end;

вам может просто надоесть видеть это в верхней части каждой из ваших функций, и вы предпочитаете

if false then 
begin
  result := bar;

   ... 
end
else
   result := foo;

и просто избегай exit вообще.

Я согласен со следующим утверждением:

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

Взято из этот вопрос в stackoverflow.

В наши дни я использую ранний возврат почти исключительно, до крайности.я пишу это

self = [super init];

if (self != nil)
{
    // your code here
}

return self;

как

self = [super init];
if (!self)
    return;

// your code here

return self;

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

Я бы предпочел написать:

if(someCondition)
{
    SomeFunction();
}

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

Мне не нравится, как обработка ошибок вынесена из проверки.

if not error A
  if not error B
    if not error C
      // do something
    else handle error C
  else handle error B
else handle error A

Я предпочитаю это:

if error A
  handle error A; return
if error B
  handle error B; return
if error C
  handle error C; return

// do something

Условия вверху называются «предварительными условиями».Поставив if(!precond) return;, вы визуально перечисляете все предварительные условия.

Использование большого блока if-else может увеличить накладные расходы на отступы (я забыл цитату о трехуровневых отступах).

Я предпочитаю, чтобы операторы if были небольшими.

Итак, выбираем между:

if condition:
   line1
   line2
    ...
   line-n

и

if not condition: return

line1
line2
 ...
line-n

Я бы выбрал то, что вы назвали «ранним возвращением».

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

Вложенные операторы if, for и while ужасный, избегайте их любой ценой.

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

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

Для некоторых часто проверяемых условий вы можете реализовать аспекты этих проверок, если используете АОП.

Лицензировано под: CC-BY-SA с атрибуция
scroll top