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

Если проверка входных данных завершена успешно - выходим;
иначе что-то делаем и если одна из предусмотренных ошибок - выходим с их описанием; если ошибок нет - ещё что-то делаем и проверяем результат на соответствие одному из двух условий.

Если ресурс откроется, то ни проверки ошибок, ни «что-то делаем с ресурсом» не случится. Если ресурс не откроется, но будет одна из известных ошибок - не случится «что-то делаем с ресурсом».

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

А как?

по месту надо смотреть, а так вот вроде нормальный вариант описан Чем плохо много return? (комментарий)

можно ещё исключения бросать :-) в псевдокоде tcl:

try {
  ## прямая последовательность действий
  process1
  process2
  process3
} on error err {
  ## какой-то шаг обломался
}
MKuznetsov ★★★★★
()

Если с использованием return получается более логичный и короткий код, буду писать именно так, а не по-другому. И это норма.

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

Если проверка входных данных завершена успешно - выходим;

cond1 возвращает истину при ошибке во входных данных.

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

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

а так вот вроде нормальный вариант описан

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

можно ещё исключения бросать

Можно. Будет вместо return везде raise. И одна лишняя вложенность. Это чем-то лучше?

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

Там совсем псевдокод.

Тут совсем не псевдокод: https://www.w3schools.com/python/python_try_except.asp

Будет вместо return везде raise. И одна лишняя вложенность.

Во-первых не везде. Во-вторых никакой лишней вложённости.

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

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

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

Morin ★★★★★
()
Последнее исправление: Morin (всего исправлений: 1)

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

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

проблема что мойша карузо плохо напел

у Дейктстры был цикл с оградами - ваще первоисточники хоть и бывают укуренными всёж обычно более информативны чем вторичные и третичные

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

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

def f(x):
  if cond1(x): return res1
  if cond2(y=process1(x)): return res2
  if cond3(y): return res3
  if cond4(x,y,z=process2(x,y)): return res4
  if cond5(x,y,z): return res5
  ...  
компактный лаконичный код вместо этих простыней во всех предлагаемых вариантах.

Но я не знаю можно ли так в питоне или тут синтаксические ошибки. На Си точно можно.

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

ну деточка - это ведь не скрижаль

оформить можно через педерачу аргуманта ля

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

Да название у проверки дурацкое, в этом проблема.

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

Мне что-то кажется, что в будущем, когда линтеры будут основаны на нейросетках (а к этому явно идёт), лучше не станет. Конкретно данная проблема — слишком «дубовые» проверки с соответствующими названиями — исправится, конечно. Но вместе с тем уйдёт и прозрачность с простотой.

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

компактный лаконичный код

вот за такое вот:

if cond2(y=process1(x))
if cond3(y): return res3

в жавке бьют ногами, иногда даже по лицу.

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

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

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

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

словит ли этот же линтер много break из однократного цикла!

qulinxao3 ★☆
()

второй вариант заведомо соломенное чучелко

qulinxao3 ★☆
()

реально с учётом elif и в текущих реалиях Python будет второй вариант(не фонтанный):

def f(x):
    res=object()
    if 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

можно в один return через if else вырвижение но это однострок-хойрам

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

Можно. Будет вместо return везде raise. И одна лишняя вложенность. Это чем-то лучше?

это освещено светочем UML и любимо менеджерами всех звеньев :-)

прямое следование их проектированию - пишется последовательность приводящая к желаемому(положительному,ожидаемому) результату, а всё побочное бросается как исключения

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

для отрытия рессурсов есть with и менежульё контекстов

with open_KAKOЙTO_PECUPC(cond1_check_input_data()) do:
.
.
.

cond2 и cond3 это ваще гиде? в открытии ресурса менежье али чек полученных датых?!

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

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

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

На нейросетках можно делать нечто вроде автоматического code review. Когда нейросетка выкатывает замечания, но их можно смело игнорировать. С этим подходом проблема в том, что программисты, как правило, смело игнорируют 100% того, что можно смело игнорировать, поэтому тут будет просто пустой нагрев воздуха. Но в целом этот подход перспективней.

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

Линтер на нейросетке не может работать.

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

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

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

На нейросетках можно делать нечто вроде автоматического code review. Когда нейросетка выкатывает замечания, но их можно смело игнорировать. С этим подходом проблема в том, что программисты, как правило, смело игнорируют 100% того, что можно смело игнорировать, поэтому тут будет просто пустой нагрев воздуха.

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

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

действия между условиями

это alwaysReturnFail(действие()) как условие и pass как респонс

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

На самом деле ТС мозги сношает.

Попытаться открыть ресурс, прочитать его и записать изменённое обратно (или в другое место). Если ошибки возникли на каком-то этапе - обработать это. try-catch для того и существует.

Но нет, мы будем гору return городить там где не надо. Или велосипеды изобретать.

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

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

Нет жеж.

if, for, while вообще структурные операторы. С ними сравнение некорректное.

А вот в отличие от break, continue , return возвращает значение.

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

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

т.е. проблема не в ограниченных готу (return,break,defer,continue etc)

а например goto в середину тела цикла и прочий содом - как например goto в тело невызванной функции <- можно жи нагрузить на такой случай копулятор автоcall функцией с null аргами :)

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

ну да - конечно - агась

так то, что в нашей бренной не детали :|

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

Первый вариант он как бы прост, но не прост. Идя ниже и ниже по коду я вижу, например, cond4, но при этом мне нужно держать в голове, что я до этого уже проверил cond1, cond2, cond3 и при каком-м нибудь рефакторинге мне нужно будет перенести не просто строчку с cond4 в другое место, но также убедится в том что и остальные предусловия не нарушены. Вариант с done он хоть и выглядит визуально грязно, но даёт намек на то, что всё сложнее чем просто if -> return

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

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

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

act if cond else allothers:


def f(x):
    return res1 if cond1(x)\
     else  res2 if cond2(y:=process1(x)\
     else  res3 if cond3(y)\
     else  res4 if cond4(x,y,z:=process2(x,y))\
     else  res5 if cond5(x,y,z)\
     else  float('inf')/0
😢

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

Я просто вспоминаю себя, который раньше писал код вида

int res;

res = func1();
if( !res ) res = func2();
if( !res ) res = func3();
....
if( res ){
	//<print error>
	return res;
}

...

А потом в какой-то момент я перешёл на


int res;

res = func1();
if( res ) return -1;

res = func2();
if( res ) return -1;

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


static const struct{
	int (*func)( void );
	const char* desc;
}initStages[] = {
	{ allocInitStage,      "Alloc init stage"     },
	{ notificationsInit,   "Notifications init"   },
	{ espPeripheralsInit,  "ESP peripherals init" },
	{ chipsInitStage,      "Chips init"           },
	{ subsystemsInitStage, "Subsystems init"      },
	{ servicesStartStage,  "Services start"       },
	{ guiStageInit,        "Gui stage init"       },
	{ NULL,                NULL                   }
};

int BicOS_init(){
	
	int res;
	int i;
	
	for( i = 0; initStages[i].func; i++ ){
		printf( "{B} %s...\n", initStages[i].desc );
		res = initStages[i].func();
		if( res ){
			printf( "{B} %s failed.\n", initStages[i].desc);
			return -1;
		}
		
		printf( "{B} %s success.\n", initStages[i].desc );
	}
	
	return 0;
}

На самом деле, я никогда не задумывался на тем, что количество return’ов имеет значение. Если уж на то пошло, то в первую очередь следует смотреть на количество операторов if.

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

Можно ещё так

if((res = func1()) && (print_error1(res),1) ||
   (res = func2()) && (print_error2(res),1) ||
   (res = func3()) && (print_error3(res),1)){
	return res;
}

Это если печать ошибок у каждой своя. Если общая можно вниз вынести.

Хотя я бы каждому свой return сделал но на той же строке что и вызов.

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

Никогда так не пишу и сильно ругаюсь, когда вижу такое.

Тебе вопросы на задуматься:

  1. У процессора появятся новые инструкции, если ты так запишешь?

  2. А как изменится читаемость кода?

  3. А какова вероятность запутаться в скобках оператора if при такой записи?

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

1. нет, разумеется

2. повысится, ради неё все и старания

3. меньше чем вероятность запутаться в мусорных переводах строк

Но, повторю, я бы сделал три if-а если им не нужны общие части кода кроме return -1. А если нужен общий print можно ещё

if(res=...) goto err;
if(res=...) goto err;
if(res=...) goto err;

...

err: print; return -1;

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

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

	int res;
	
	res = func1();
	if( res ){
		print_error1( res );
		return res;
	}
	
	res = func2();
	if( res ){
		print_error2( res );
		return res;
	}
	
	res = func3();
	if( res ){
		print_error3( res );
		return res;
	}
	
	return 0;

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

u5er ★★★
()

Линтер — не про качество или «читаемость», а про то, чтобы заставить всех строем ходить. Если тому, кто хочет людей построить, не нравится стиль с early returns, то он включает это правило, если нравится, то не включает (или включает обратное по смыслу правило, если такое есть).

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

Представь у тебя 40 таких блоков. И если будет по 1 строчке на блок - оно либо влезет в одну страницу и ты будешь видеть весь алгоритм сразу без лишних скроллов, либо, если у тебя маленький экран, максимум в 2 страницы и можно чуть-чуть полистать. Если же написать в твоём стиле - получится 5-10 страниц, которые замучаешься листать вверх-вниз, забывая что ты хотел посмотреть или что ты только что смотрел пока долистаешь до нужного места, ни о каком окидывании алгоритма взглядом речи не пойдёт.

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

if, for, while вообще структурные операторы. С ними сравнение некорректное.

Почему это некорректное? Это всё по сути набор условных и безусловных goto.

А вот в отличие от break, continue , return возвращает значение.

Т.е. если не возвращает - то всё хорошо что-ли? Какая разница - что он возвращает? break и continue вобщем-то тоже могут в некоторых ЯП передавать значения.

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

Не надо неуместного сарказма.

Он вполне уместен.

Мой вариант наилучше читается - по 1 строчке на логическое действие.

Так с какой-то точки зрения и функция это одно логическое действие. Да и вся программа это одно логическое действие. Что это такое «логическое действие»? Как хочешь - так определяй.

Упаковывать дальше - лишнее, разреживать код мусорными переносами строки и пробелами - тоже.

А я считаю, что и так упаковывать - лишнее. Условие на одну строчку (а то и не на одну, если оно нетривиальное), действие - на другую.

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

https://en.wikipedia.org/wiki/Guard_(computer_science)

и да и нет

и for и while и if это (могут быть) функции_охраны(аля префикса проверки предиката флагов на каком нить машкоде) к последующей одной команде - в частности call'у - который в отличии от goto помнит куда возвращаться ( и может быть) и вернётся - в отличии от goto - которое по факту «стирающее» присвоение указателю исполнения нового значения

т.е for/while/if это формы skip()_next_command

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

Я раньше тоже «топил» за «компактность» кода и основательно погорел на этом со временем. Поэтому теперь я пришёл к выводу, что код в первую очередь должен легко читаться и пониматься. Поэтому алгоритм пишем словами в документации, а в коде оставляем комментарии. Я могу над каждым ифом поставить развесистый комментарий, а ты?

P.S. А разбор 100500 вложенных скобок - это про лисп ;)

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

Напомнило чейнджлог поледней версии питона, где запретили return в finally, а до этого можно было делать так:

try:
  return 1/0
except:
  return 1
finally:
  return 0

Ну и разберись что тут вернется.

Вообще с ретерном в try/except издревле есть неоднозначность.

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