Если функция имеет только один оператор return?

есть ли веские причины, почему лучше иметь только один оператор return в функции?

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

30 ответов


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

public void DoStuff(Foo foo)
{
    if (foo != null)
    {
        ...
    }
}

... можно сделать более читаемым (IMHO) следующим образом:

public void DoStuff(Foo foo)
{
    if (foo == null) return;

    ...
}

Так что да, я думаю, что это нормально иметь несколько "точек выхода" из функции/метода.


никто не упоминал и не цитировал Код Так что я сделаю это.

17.1 возврата

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

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


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

string fooBar(string s, int? i) {
  string ret = "";
  if(!string.IsNullOrEmpty(s) && i != null) {
    var res = someFunction(s, i);

    bool passed = true;
    foreach(var r in res) {
      if(!r.Passed) {
        passed = false;
        break;
      }
    }

    if(passed) {
      // Rest of code...
    }
  }

  return ret;
}

сравните это с кодом, где несколько точек выхода are разрешено:-

string fooBar(string s, int? i) {
  var ret = "";
  if(string.IsNullOrEmpty(s) || i == null) return null;

  var res = someFunction(s, i);

  foreach(var r in res) {
      if(!r.Passed) return null;
  }

  // Rest of code...

  return ret;
}

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


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

С теорией "одной точки выхода" вы неизбежно получаете код, который выглядит так:

function()
{
    HRESULT error = S_OK;

    if(SUCCEEDED(Operation1()))
    {
        if(SUCCEEDED(Operation2()))
        {
            if(SUCCEEDED(Operation3()))
            {
                if(SUCCEEDED(Operation4()))
                {
                }
                else
                {
                    error = OPERATION4FAILED;
                }
            }
            else
            {
                error = OPERATION3FAILED;
            }
        }
        else
        {
            error = OPERATION2FAILED;
        }
    }
    else
    {
        error = OPERATION1FAILED;
    }

    return error;
}

мало того, что это делает код очень трудно следовать, но теперь сказать позже на вас нужно вернуться и добавить операцию между 1 и 2. Вы должны отступить почти всю функцию freaking, и удачи, убедившись, что все ваши условия if/else и фигурные скобки подобраны правильно.

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


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


Как отмечает Кент Бек при обсуждении защитных предложений в Шаблоны Реализации создание процедуры имеет одну точку входа и выхода ...

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

Я нахожу функцию, написанную с защитными предложениями, намного проще следовать, чем одна длинная вложенная куча if then else заявления.


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

другими словами, когда это возможно, отдайте предпочтение этому стилю

return a > 0 ?
  positively(a):
  negatively(a);

этой

if (a > 0)
  return positively(a);
else
  return negatively(a);

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

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


Я видел это в стандартах кодирования для C++, которые были похмельем от C, как будто у вас нет RAII или другого автоматического управления памятью, тогда вам нужно очистить для каждого возврата, что означает либо вырезание и вставку очистки, либо goto (логически то же самое, что и "наконец" на управляемых языках), оба из которых считаются плохой формой. Если ваша практика заключается в использовании интеллектуальных указателей и коллекций в C++ или другой автоматической системе памяти, то для этого нет веской причины, и она становится все о читаемости, и больше о решении вызова.


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


есть ли веские причины, почему лучше иметь только один оператор return в функции?

да есть:

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

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

обновление: видимо MISRA руководящие принципы поощрения единого выхода тоже.

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


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


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


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

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


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

public void DoStuff(Foo foo)
{
    if (foo == null) return;
}

, увидев этот код, я бы сразу спросить:

  • является ли 'foo' когда-либо нулевым?
  • если да, то сколько клиентов "DoStuff" когда-либо вызывали функцию с нулевым "foo"?

в зависимости от ответов на эти вопросы может оказаться, что

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

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

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

объект не-POD C++ в области видимости при выходе из функции будет иметь свой деструктор. Если существует несколько операторов return, может оказаться, что в области действия есть разные объекты, и поэтому список деструкторов для вызова будет отличаться. Поэтому компилятор должен генерировать код для каждого вернуться заявление:

void foo (int i, int j) {
  A a;
  if (i > 0) {
     B b;
     return ;   // Call dtor for 'b' followed by 'a'
  }
  if (i == j) {
     C c;
     B b;
     return ;   // Call dtor for 'b', 'c' and then 'a'
  }
  return 'a'    // Call dtor for 'a'
}

Если размер кода-это вопрос, - то это может быть что-то стоит избегать.

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

A foo () {
  A a1;
  // do something
  return a1;
}

void bar () {
  A a2 ( foo() );
}

просто принимая код как есть, объект " a1 "строится в "foo", а затем его копия будет вызвана для построения "a2". Однако copy elision позволяет компилятор для построения "a1" в том же месте стека, что и "a2". Поэтому нет необходимости "копировать" объект, когда функция возвращается.

несколько точек выхода усложняет работу компилятора в попытке обнаружить это, и, по крайней мере, для относительно недавней версии VC++ оптимизация не состоялась, когда тело функции имело несколько возвращений. См.именованная оптимизация возвращаемого значения в Visual C++ 2005 для получения более подробной информации.


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


Я заставляю себя использовать только один return заявление, как это будет в некотором смысле генерировать код. Позвольте мне объяснить:

function isCorrect($param1, $param2, $param3) {
    $toret = false;
    if ($param1 != $param2) {
        if ($param1 == ($param3 * 2)) {
            if ($param2 == ($param3 / 3)) {
                $toret = true;
            } else {
                $error = 'Error 3';
            }
        } else {
            $error = 'Error 2';
        }
    } else {
        $error = 'Error 1';
    }
    return $toret;
}

(условия arbritary...)

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

  • несколько возвращает
  • рефакторинг в отдельные функции

Несколько Возвращает

function isCorrect($param1, $param2, $param3) {
    if ($param1 == $param2)       { $error = 'Error 1'; return false; }
    if ($param1 != ($param3 * 2)) { $error = 'Error 2'; return false; }
    if ($param2 != ($param3 / 3)) { $error = 'Error 3'; return false; }
    return true;
}

Отдельные Функции

function isEqual($param1, $param2) {
    return $param1 == $param2;
}

function isDouble($param1, $param2) {
    return $param1 == ($param2 * 2);
}

function isThird($param1, $param2) {
    return $param1 == ($param2 / 3);
}

function isCorrect($param1, $param2, $param3) {
    return !isEqual($param1, $param2)
        && isDouble($param1, $param3)
        && isThird($param2, $param3);
}

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

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

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

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

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


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

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

больше чем один стиль возврата может быть плохой привычкой в коде C, где ресурсы должны быть явно де-выделены, но такие языки, как Java, C#, Python или JavaScript, которые имеют такие конструкции, как автоматическая сборка мусора и try..finally блоки (и using blocks в C#), и этот аргумент не применяется - в этих языках очень редко требуется централизованное ручное освобождение ресурсов.

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

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

Я говорил о это более подробно в моем блоге.


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

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

и Спартанец Программирования статья "SSDSLPedia" и точка выхода одной функции статья "Wiki репозитория Portland Pattern" имеет некоторые проницательные аргументы вокруг этого. Кроме того, конечно, есть этот пост, чтобы рассмотреть.

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

int f(int y) {
    int value = -1;
    void *data = NULL;

    if (y < 0)
        goto clean;

    if ((data = malloc(123)) == NULL)
        goto clean;

    /* More code */

    value = 1;
clean:
   free(data);
   return value;
}

лично я, в общем, не нравится программирование стрелок больше, чем мне не нравится несколько точек выхода, хотя оба полезны при правильном применении. Лучшим, конечно, является структурой программы не требуется. Разбиение вашей функции на несколько кусков обычно помогает:)

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

int g(int y) {
  value = 0;

  if ((value = g0(y, value)) == -1)
    return -1;

  if ((value = g1(y, value)) == -1)
    return -1;

  return g2(y, value);
}

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

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

In коротко;

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

вы знаете пословицу -красота в глазах смотрящего.

некоторые люди клянутся NetBeans и IntelliJ IDEA, часть Python и PHP.

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

public void hello()
{
   if (....)
   {
      ....
   }
}

вопрос заключается в видимости и ремонтопригодности.

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

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

Я помню, 20 лет назад мой коллега был уволен за то, что использовал то, что сегодня называется agile-разработки стратегии. У него был тщательно продуманный план действий. Но его менеджер кричал на него: "вы не можете постепенно выпускать функции для пользователей! Вы должны придерживаться водопад."Его ответ менеджеру заключался в том, что инкрементное развитие было бы точнее к потребностям клиента. Он верил в разработку для потребностей клиентов, но менеджер верил в Кодирование по "требованию клиента".

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

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

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

все остальные правила отходят на второй план. И, конечно, это правило никогда не исчезает:

лень-это добродетель хорошего программист.


Я склоняюсь к использованию предложений guard для раннего возврата и в противном случае выхода в конце метода. Правило единого входа и выхода имеет историческое значение и было особенно полезно при работе с устаревшим кодом, который выполнялся на 10 страницах A4 для одного метода C++ с несколькими возвращениями (и многими дефектами). В последнее время принятая хорошая практика заключается в том, чтобы держать методы небольшими, что делает несколько выходов менее импедансом к пониманию. В следующем примере Kronoz, скопированном сверху, вопрос в том, что происходит в //остальной код...?:

void string fooBar(string s, int? i) {

  if(string.IsNullOrEmpty(s) || i == null) return null;

  var res = someFunction(s, i);

  foreach(var r in res) {
      if(!r.Passed) return null;
  }

  // Rest of code...

  return ret;
}

Я понимаю, что пример несколько надуман, но я бы соблазнился рефакторингом foreach цикл в оператор LINQ, который затем можно считать предложением guard. Опять же, в надуманном примере намерение кода не очевидно и someFunction() может есть какой-то другой побочный эффект или результат может быть использован в // остаток код....

if (string.IsNullOrEmpty(s) || i == null) return null;
if (someFunction(s, i).Any(r => !r.Passed)) return null;

дает следующие рефакторинг функции:

void string fooBar(string s, int? i) {

  if (string.IsNullOrEmpty(s) || i == null) return null;
  if (someFunction(s, i).Any(r => !r.Passed)) return null;

  // Rest of code...

  return ret;
}

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

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


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

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


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


одна точка выхода-все остальные вещи равны-делает код значительно более читаемым. Но есть подвох: популярная конструкция

resulttype res;
if if if...
return res;

является подделкой, " res= "не намного лучше, чем"return". Он имеет один оператор return, но несколько точек, где функция фактически заканчивается.

Если у вас есть функция с несколькими возвратами (или"res=" s), часто рекомендуется разбить ее на несколько меньших функций с одной точкой выхода.


моя обычная политика состоит в том, чтобы иметь только один оператор return в конце функции, если сложность кода значительно не уменьшается путем добавления больше. На самом деле, я скорее поклонник Eiffel, который применяет единственное правило возврата, не имея оператора return (есть только автоматически созданная переменная "результат", чтобы поместить ваш результат).

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


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

Perl 6: Плохой Пример

sub Int_to_String( Int i ){
  given( i ){
    when 0 { return "zero" }
    when 1 { return "one" }
    when 2 { return "two" }
    when 3 { return "three" }
    when 4 { return "four" }
    ...
    default { return undef }
  }
}

было бы лучше написать так

Perl 6: Хороший Пример

@Int_to_String = qw{
  zero
  one
  two
  three
  four
  ...
}
sub Int_to_String( Int i ){
  return undef if i < 0;
  return undef unless i < @Int_to_String.length;
  return @Int_to_String[i]
}

обратите внимание, что это был просто быстрый пример


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

void ProcessMyFile (char *szFileName)
{
   FILE *fp = NULL;
   char *pbyBuffer = NULL:

   do {

      fp = fopen (szFileName, "r");

      if (NULL == fp) {

         break;
      }

      pbyBuffer = malloc (__SOME__SIZE___);

      if (NULL == pbyBuffer) {

         break;
      }

      /*** Do some processing with file ***/

   } while (0);

   if (pbyBuffer) {

      free (pbyBuffer);
   }

   if (fp) {

      fclose (fp);
   }
}

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

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


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

но, нет, нет ничего плохого в одном / многих операторах возврата. В некоторых языках это лучшая практика (C++), чем в других (C).