LINUX.ORG.RU

Чем плохо много return?

 ,


0

3

В линтерах во многих языках программирования есть такая проверка: https://docs.astral.sh/ruff/rules/too-many-return-statements/

По ссылке приведён тривиальный пример, где достаточно сопоставления по ключу.

В общем случае код выглядит примерно как:

def f(x):
  if cond1(x):
    return res1
  y = process1(x);
  if cond2(y):
    return res2
  if cond3(y):
    return res3
  z = process2(x, y)
  if cond4(x,y,z):
    return res4
  if cond5(x,y,z):
    return res5
  ...  

после убирания return получается что-то вроде

def f(x):
  done = False 
  if cond1(x):
    res = res1
    done = True
  if not done:
    y = process1(x);
  if not done and cond2(y):
    res = res2
    done = True
  if not done and cond3(y):
    res = res3
    done = True
  if not done:
    z = process2(x, y)
  if not done and cond4(x,y,z):
    res = res4
    done = True
  if not done and cond5(x,y,z):
    res = res5
    done = True
  ...
  return res  

Второй вариант действительно легче читается или я неправильно преобразовал первый во второй?

UPD: С учётом наличия elif

def f(x):
  done = False 
  if cond1(x):
    res = res1
    done = True
  if not done:
    y = process1(x);
    if cond2(y):
      res = res2
      done = True
    elif cond3(y):
      res = res3
      done = True
  if not done:
    z = process2(x, y)
    if cond4(x,y,z):
      res = res4
      done = True
    elif cond5(x,y,z):
      res = res5
      done = True
  ...
  return res  

Вот это читается легче, чем первый вариант?

★★★★★

Последнее исправление: monk (всего исправлений: 1)
Ответ на: комментарий от monk
if this.cond1():
    return res1
  this.process1();
  if this.cond2():
    return res2

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

ya-betmen ★★★★★
()
Ответ на: комментарий от monk

походь


def f():
    res=[sentinel:=object()]
    Done=condact1(u1(..)) and ..... condactN(uN(....)) and res[0]
    return res[0] if Done else Done
qulinxao3 ★☆
()
Ответ на: комментарий от ya-betmen

Но из них же всё равно не собрать вменяемый монадный чейн через опшенал?

Собрать. Главное, чтобы тот, кто это сопровождать будет, не знал, где ты живёшь.

monk ★★★★★
() автор топика
Ответ на: комментарий от monk

Я что-то туплю наверное и не понимаю затруднения, вот же

    exit_condition, result = steps[1](result)

явно прописано какие данные откуда берутся и куда уходят. Т.е. тут вообще нету пространства для интерпретации.

ugoday ★★★★★
()
Ответ на: комментарий от monk

Т.е. буквально тот сценарий, что в описании по ссылке из ОП описан.

mogwai ★★★★★
()
Ответ на: комментарий от thunar

написанием лапши из глобальных переменных и безусловных прыжков

А в скандале с микроконтроллером ЕМНИП тойотовского движка разве не такая лапша фигурировала? Недодавил Дейкстра, получается.

legolegs ★★★★★
()
Ответ на: комментарий от ugoday

явно прописано какие данные откуда берутся и куда уходят

Прописано, что берут из структуры(?) result и возвращают новую структуру. А то, что первый step будет читать поле x и никуда не писать, второй читать поле x и писать y, это только внутри шагов смотреть. Вместо

  if cond1(x):
    return res1
  y = process1(x);

получаем

  ex, res = step1(res)
  if ex: return res
  ex, res = step2(res)
  if ex: return res
monk ★★★★★
() автор топика
Ответ на: комментарий от legolegs

Практики вымирают вместе с носителями.

thunar ★★★★★
()
Ответ на: комментарий от monk

это только внутри шагов смотреть.

Ну да. Это информация должна быть где-то записана. Любо внутри шага, либо в объемлющей функции. Тут никакой магии нет. Просто в одном случае отдельно описана логика «делай шаг n, если условие, верни результат, иначе делай шаг n+q» и отдельно шаги, а в другом это всё вперемешку.

ugoday ★★★★★
()
sub f {
    $_->[1]() and return $_->[0] for (
        [ 'res 1', sub { cond1($x) } ], 
        [ 'res 2', sub { $y = process1($x); cond2($y) } ], 
        [ 'res 3', sub { cond3($y) } ], 
        [ 'res 4', sub { $z = process2($x, $y); cond4($x,$y,$z) } ], 
        [ 'res 5', sub { cond5($x,$y,$z) } ], 
        [ 'wtf?!', sub { 1 } ],
    );
}
madcore ★★★★★
()
Ответ на: комментарий от monk

И все делния пополам лесом?

Тогда ясно почему не стоит это никому показывать.

ya-betmen ★★★★★
()
Ответ на: комментарий от monk

Вкусовщина, конечно, но мне ближе противопоставление map, filter, reduce vs for, if. Обоими способами можно выразить одну и ту же идею, но в первом случае получается яснее, потому как каждый этап преобразования данных логически обособлен, сразу понятно тут мы наделали данных, тут отобрали нужные, тут посчитали статистику. В for, if делается то же самое, но нужно приложить усилие, чтобы понять где-чего происходит.

ugoday ★★★★★
()

А так?

r = res1
if not cond1(x):
    r = res2
    y = process1(x)
    if not cond2(y)
        r = res3
        if not cond3(y):
            r = res4
            z = process2(y)
            if not cond4(z):
                r = res5
                if not cond5(z):
                    ...
return r
stabilitron
()
Ответ на: комментарий от stabilitron

Как это ни смешно, но «лесенка второкурсника» всё-таки лучше читается, чем код в ОП.

legolegs ★★★★★
()

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

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

a1ba ★★★
()

а мне нравится эта викторина
«1000 & 1 способ сделать код хуже, чтобы ублажить линтер»

madcore ★★★★★
()
Ответ на: комментарий от madcore

Ещё видел, как функции рубят, чтобы в ограничение длины влезть. Типа, была одна parse_file, стала parse_file, parse_file1, …, parse_file6. Зато линтер не ругается.

monk ★★★★★
() автор топика
Ответ на: комментарий от a1ba

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

Тоже не факт.

Вместо
def find(a, x):
  for s1 in a:
    for s2 in s1:
      for s3 in s2:
        if s3 == x: 
          return True
  return False

писать

def find(a, x):
  res = False
  for s1 in a:
    for s2 in s1:
      for s3 in s2:
        if s3 == x: 
          res = True
  return res

?

monk ★★★★★
() автор топика
Последнее исправление: monk (всего исправлений: 1)
Ответ на: комментарий от monk

Как вариант:

def find(a, x):
    return x in (s3 for s1 in a for s2 in s1 for s3 in s2)
CrX ★★★★★
()
Ответ на: комментарий от monk

Ну в целом мне приятнее с early returns, если у функции есть «ритм», не знаю как объяснить, субъективщина.

PolarFox ★★★★★
()

Можно разбить на отдельные функции. В питоне обычно всегда усложнённый код разбивают на функции.

def f(x):
  if cond1(x):
    return res1
  return f_process1(x)

def f_process1(x):
  y = process1(x)
  if cond2(y):
    return res2
  if cond3(y):
    return res3
  return f_process2(x, y)

def f_process2(x, y):
  z = process2(x, y)
  if cond4(x,y,z):
    return res4
  if cond5(x,y,z):
    return res5
neumond ★★
()

def f(x):
    res=object()
    for _ in 1,:#while((a:=1)if'a'not in locals()else locals().__delitem__('a')):
        if cond1(x                    ):res=res1;break
        if cond2(y:=process1(x)       ):res=res2;break
        if cond3(y                    ):res=res3;break
        if cond4(x,y,z:=process2(x, y)):res=res4;break
        if cond5(x,y,z                ):res=res5;break
    return res
#иль же дажь:
def f(x):
    res=object()
    for _ in 1,:
        if 0: ...
        elif cond1(x                    ):res=res1
        elif cond2(y:=process1(x)       ):res=res2
        elif cond3(y                    ):res=res3
        elif cond4(x,y,z:=process2(x, y)):res=res4
        elif cond5(x,y,z                ):res=res5
    return res
#но тадлы:
def f(x):
    res=object()
    if 0: ...
    elif cond1(x                    ):res=res1
    elif cond2(y:=process1(x)       ):res=res2
    elif cond3(y                    ):res=res3
    elif cond4(x,y,z:=process2(x, y)):res=res4
    elif cond5(x,y,z                ):res=res5
    return res
qulinxao3 ★☆
()
Последнее исправление: qulinxao3 (всего исправлений: 4)
Ответ на: комментарий от Morin

как я понял, речь о кол-ве строк, а не длине

madcore ★★★★★
()
Ответ на: комментарий от qulinxao3

Настоящий программист на фортране может написать программу на фортране на любом языке.

ugoday ★★★★★
()

В линтерах во многих языках программирования есть такая проверка: https://docs.astral.sh/ruff/rules/too-many-return-statements/

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

Rodegast ★★★★★
()
Ответ на: комментарий от monk

На Си «лесенку» можно не делать, а размещать каждый следующий if под else. Это не мешает понимать код, т.к. if-else-if-else-… по сути на одном логическом уровне. Меня так на работе научили делать. Еще и точки с запятой в конце блока if научили ставить там, где не предполагается наличие else. Теперь пишу в таком стиле.

void check_return_count_in_functions_new_token(struct source_file* s, struct token* toks, int tok_idx)
{
  int i = tok_idx;

  if (brace_lvl >= 1)
  {
    if (toks[i].toktyp == KW_RETURN)
       state += 1;
  }
  else
  if (brace_lvl == 0)
    state = 0;

  if (state > MAX_RETURNS_PR_FUNC)
  {
    /* one warning pr. violation is sufficient */
    if (state != state_last_warn)
    {
      state_last_warn = state;

      /* prettier names in the output: '3rd return statement in function' etc. */
      const char* endings[] = { "th", "st", "nd", "rd" };
      const char* ending = endings[0];

      if ((state < 10) || (state > 20))
      {
        if (state % 10 == 1) 
          ending = endings[1];
        else 
        if (state % 10 == 2)
          ending = endings[2];
        else
        if (state % 10 == 3) 
          ending = endings[3];
      };

      fprintf(stdout, "[%s:%d] (style) %d%s return statement in function.\n", s->file_path, toks[i - 1].lineno, state, ending);
    }; // if (state != state_last_warn)
  }; // if (state > MAX_RETURNS_PR_FUNC)

  if (toks[i].toktyp == OP_LBRACE) 
    brace_lvl += 1;
  else 
  if (toks[i].toktyp == OP_RBRACE)
    brace_lvl -= 1;

  if (brace_lvl < 0)
    brace_lvl = 0;
}
Vic
()
Последнее исправление: Vic (всего исправлений: 4)
Ответ на: комментарий от monk

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

Vic
()
Ответ на: комментарий от Vic

Вот поэтому и вынес вопрос на форум.

Получил возможную объективную причину и более симпатичные варианты обхода.

Всех благодарю!

monk ★★★★★
() автор топика
Ответ на: комментарий от perl5_guy

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

monk ★★★★★
() автор топика
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.