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)
Ответ на: комментарий от no-such-file

С return у тебя всегда один выход, независимо от их количества. В современных языках это скорее про try/catch.

Чем raise + catch результата отличается от return + сопоставление по результату?

monk ★★★★★
() автор топика

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

Плохо лишь в одном случае - если логика алгоритма плохая.
Бывают конечно алгоритмы, когда возращаемое значение формируется в
зависимости от некоего анализа.
В этом случае продолжать выполнените алгоритмв нет смысла.
Можно конечно сделать, чтобы return был один, а можно много их использовать.
Это чисто субъективно и никак не влияет на качество алгоритма.

Как-то так.

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

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

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

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

а начинается с фундаментальной(и редко ваще кем указанной) дилеммы: организовавать алго

как некоторое дерево решений на каждом  листье которого  (сложносоставное)действие в очевидном(по пути дерева) контексте  
           либо 
как некоторый конвейер  из набора последовательных тест->действие

вот эти два варианта различные (фунда)ментально

первое это собрать достаточно(полный набор) сведений и осуществить реакцию

второе осуществлять последовательность действий на основе «интеракции со средой»

:)

цикломатическая сложность вроде как одинаковая :)

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

Для примера предложенного monk вполне подходит способ предложенный ранее.

Можно для каждого Res-ы в массиве место отвести и указать индекс элемента содержащего результат.
Ведь тем самым лапши if будет заметно меньше и жффективность кода от предложенного способа не пострадает.
Вообще-то не совсем понял. что monk хочет выяснить …

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

все вон lisp-а бои а по факту всем plex (by Douglass T. Ross) https://en.wikipedia.org/wiki/Douglas_T._Ross#:~:text=His later work focused ...

ибо массив это универсальная память на которой можно построить граф любой сложности - и возвращаемся к "https://en.wikipedia.org/wiki/Decision_table"

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

Редактирование комментариев с ответами запрещено

все вон lisp-а бои а по факту всем plex (by Douglass T. Ross) https://en.wikipedia.org/wiki/Douglas_T._Ross#:~:text=His later work focused ...

ибо массив это универсальная память на которой можно построить граф любой сложности - и возвращаемся к "https://en.wikipedia.org/wiki/Decision_table"

зы. возможность спискоты и не важно S-expr'ы Маккарти али IPL-IV Нобелевского https://en.wikipedia.org/wiki/Herbert_A._Simon али разыменования указателей сяшки (https://wiki.c2.com/?ThreeStarProgrammer И https://esolangs.org/wiki/Three_Star_Programmer ) али неведомая зверушка plex не_широко_известного ныне Росса

вон в Ross, Douglas T. (August 1991). «From Scientific Practice to Epistemological Discovery». In Floyd, Christiane; Zulligho, Heinz; Budde, Reinhard; Keil-Slawik, Reinhard (eds.). Software Development and Reality Construction

красота необходимости и достаточности косвенности трёх разыменнований для полного НЕИМЕНОВАННОГО(имяславием) :

The surprising (perhaps even amazing) consequence of all this is that :

Although        meaning                may be Nothing
and        meaning of meaning          may be Nothing
the     meaning of meaning of meaning  cannot be Nothing!

так что хорошо что Кодд реляционную алгебру на множествах зафорсил в индустрию - иначе бы plex в изводе Бахмана(овского) Программиста-Навигатора по мульти-гипер-графу структур хранения данных всем бы был до сих пор

ну и то что HLL есть то же позволяет ломать копья над важными вопросами ромба-наследования и прочих верлюдов в игольном ушке is-a vs has-a

и сколько return кошерно

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

plex (который что те же S-exp в этоху статической(аля Фортран без рекурсии )памяти ) излишне универсален

поэтому в массах редко когда реально применяют абстрактное мЫшленье

чаще чисто лингвистические преобразования

и поэтому шок(али видимость ) такой от льльмымок

qulinxao3 ★☆
()
Последнее исправление: qulinxao3 (всего исправлений: 2)
Ответ на: комментарий от anonymous

Очевидно если произвольный граф сводим к дереву а то к дереву с узлами арности не больше двух

ага

ващет граф с вершинами арности не более 3 необходим - это откуда то из NP полноты

так то текст это линия - но необходимы семантические связи для полного феншуя

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

однако дерево (а обычное бинарное если отслеживать не только исходящие но и от родителя входящию - это 3-арность) не достаточно

достаточно DAG

но опять же DAG будет как то эмулировать граф с циклом

а вообще ограничивать арность есть 2 причины - гомогенизация узлов чисто из за практики - али теоритические причины для уменьшения пространства анализа

qulinxao3 ★☆
()
Ответ на: комментарий от no-such-file

Вообще-то, нет. Именно про return’ы. Правило появилось как реакция на доструктурное программирование, когда нормой было прыгнуть на метку в середине функции (обратите внимание на один вход) и выпрыгнуть оттуда с любой строки. А исключений тогда и вовсе не было.

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

Вообще-то, нет. Именно про return’ы

Вообще нет

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

ЛОЛ. В доструктурном программировании не было функций. Были подпрограммы. И было принято прыгать не на метку внутри подпрограммы, а на метку ВНЕ подпрограммы. Именно это и называется несколько точек выхода. Когда у тебя подпрограмма обрабатывающая Ignore/Retry/Abort при каждом выборе делает goto в три разных места. А в структурном программировании это запрещено на уровне языка, return возвращает всегда в одну и то же место – туда где был вызов процедуры/функции.

no-such-file ★★★★★
()
Ответ на: комментарий от monk

Чем raise + catch результата отличается от return

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

no-such-file ★★★★★
()
Ответ на: комментарий от monk

ублажить линтер

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

Такова работа с литерами. Причём большая часть из них отключена из коробки. По крайней мере в Гошке так.

А Вы зря сишников спрашиваете. Они же апологеты дрисни. Шутка.

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

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

Это проще делается так:

def f(x):
  r = inner_f(x)
  # вот точка обязательного прохождения
  return r

def inner_f(x):
  ...
  return res1
  ...
  return res2
  ...
  return resN
  ...
monk ★★★★★
() автор топика
Ответ на: комментарий от no-such-file

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

raise может быть только туда, где была вызвана функция. Единственное отличие от return только в том, что если в точке возврата нет catch, на этот результат будет автоматически вызван raise. Можно не вводить особые правила для try/catch, а использовать обычное значение возврата как в Zig.

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

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

если в точке возврата нет catch

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

Можно не вводить особые правила для try/catch, а использовать обычное значение возврата как в Zig.

В языке не должно быть объектов. Потому что иначе непонятно как проверять дополнительное значение при инициализации. Кроме этого в языке не должно быть перегрузки операторов. Потому что a+b может внутри зафейлиться, а оператор как-бы не предполагает множественных значений (да и как ты будешь их проверять внутри выражения).

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

Потому что иначе непонятно как проверять дополнительное значение при инициализации.

Которое описывает ошибку? Как объект-наследник класса Error.

Кроме этого в языке не должно быть перегрузки операторов.

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

В Zig перегрузки операторов нет, но ничто не мешало бы её сделать. И работало бы также:

r = try a + b; // если a + b вернуло ошибку, возвращаем ошибку выше

или

if (a + b) |number| {
    doSomethingWithNumber(number);
  } else |err| switch (err) {
    error.Overflow => {
      // handle overflow...
    },
    ...
  }

Да, написать a + b + c не получится, но когда в С++ оператор вызывает исключение, то его вообще практически невозможно поймать в части выражения. А в контексте того, что

auto r = a + b + c;

и

auto r1 = a + b;
auto r = r1 + c;

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

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

Которое описывает ошибку? Как объект-наследник класса Error.

И каким раком MyObj some(42) тип MyObj примет объект типа Error?

написать a + b + c не получится

Именно, т.е. ты по факту запрещаешь выражения вообще. Даже a+f(b). И даже plus(a, f(b)). Это говно, а не язык получается.

no-such-file ★★★★★
()

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

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

С точки зрения программного кода он выполняется здесь и сейчас и незакрытое условие не создаст логике какого-то диссонанса. Конечно кроме случаев когда выделена память и её надо освободить.

Существует же Break или Continue в цикле.

AZJIO
()
Ответ на: комментарий от no-such-file

И каким раком MyObj some(42) тип MyObj примет объект типа Error?

А он и не примет. Если написать

some = get_some(); 

где get_some может вернуть ошибку, то не скомпилируется.

Надо писать

if (get_some()) |good_result| {
    some = good_result;
  } else |err| switch (err) {
    error.Bad_Some => {
      // обрабатываем ошибку
    },
    ...
  }

или

some = try get_some();

если как обрабатывать ошибку пока неизвестно.

Даже a+f(b). И даже plus(a, f(b))

Почему? Это как раз можно, если f(b) не вызывает ошибок. Если может вызывать, то a + try f(b), plus(a, try f(b)).

Если бы в Zig были операторы, возвращающие ошибку, то (try a+b) + c.

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

В С++

some = get_some();

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

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

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

если f(b) не вызывает ошибок

Гарантий нет же. Банально /0.

Надо писать

Я хз как там в этом зиге, но выглядит как мегакостыль. Что если надо

get_some() || get_another() || get_off()

Как в анекдоте: не делайте так?

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

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

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

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

Гарантий нет же. Банально /0.

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

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

try get_some() || try get_another() || try get_off()
monk ★★★★★
() автор топика
Ответ на: комментарий от monk

Это понятно. Как это перехватывать – не понятно. Ну как бы и хер с ним. По факту этот тот же try/catch вид сбоку. Только зачем-то вместо одного try нужно пихать его перед каждым вызовом функции. Все те же проблемы, ни одного плюса.

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

И не просто возврат, а возврат хз куда – ищи дальше по коду.

Вот поэтому исключения — зло. В Java пытались добавить throws в описание типа, но как-то не пошло. При этом просто код ошибки в сишном стиле ещё хуже: тогда необработанная ошибка гарантированно приводит к непредсказуемым последствиям. В Zig попытались поток выполнения сделать наглядным и при этом нормальную обработку ошибок (получилось лучше, чем в Си++, проигнорировать обработку компилятор не позволяет).

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

По факту этот тот же try/catch вид сбоку.

Не совсем.

Если написано

get_some() || get_another() || try get_off()

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

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

При этом просто код ошибки в сишном стиле ещё хуже: тогда необработанная ошибка гарантированно приводит к непредсказуемым последствиям

В голанге как-то живут.

Вот поэтому исключения — зло

Ну так без них классическое ООП не работает (во всяком случае при статических типах). В голанге вон вывернулись на тему ООП, и обошлись без исключений.

получилось лучше, чем в Си++, проигнорировать обработку компилятор не позволяет

Очень зря. Абсолютно все языки с выкручиванием рук программисту получались лютым говном.

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

В голанге как-то живут.

Превозмогают дисциплиной. Как и в Си.

Ну так без них классическое ООП не работает (во всяком случае при статических типах).

???

А что не так-то? Или ты про ситуацию, когда был класс A с методом без ошибок, а мы от него хотим наследовать класс B, который бросает исключения? Так поведение меняется, делай новый метод.

Абсолютно все языки с выкручиванием рук программисту получались лютым говном.

Здесь не выкручивание. Здесь просто наглядный код с минимумом магии. Или классический Си, по-твоему, тоже руки выкручивает?

monk ★★★★★
() автор топика