Pull to refresh

Comments 167

Хм, насколько я помню, МакКоннелл, затрагивая эту тему, сказал, что в принципе ничего сильно плохого в этом нет, но и злоупотреблять не стоит. По большому счёт, почти все строки такого рода есть ни что иное, как реализация защитного программирования. И логически на самом деле проще «проверить и забыть», чем проверку делать в начале функции, а генерацию исключения — в конце.
я специально написал, что это только пример
вызов return часто можно наблюдать в середине кода, иногда бывают множественные вызовы return по всему коду
не думаю, что МакКоннелл такое бы одобрил
В том-то и дело, что как раз подход, описаный в примере вполне допустим, и я думаю, даже приветствуется. Собственно я и сам так делаю в ядре — сначала проверяю условия, если что-то не так — эксепшн и вернуть null.

А вот насчёт return'ов в середине метода, это да, часто выгоднее, удобнее и логичнее определить в начале $return, а потом присваивать ей значения.

А вот как быть, например, в случае поиска элемента внутри массива? Скажем, нашли мы элемент в середине массива. Почему бы сразу не вернуть его? Юзать break? Эта штука очень часто лишь запутывает, да и по сути — тот же goto. Так что иногда, но не всегда, даже return в середине метода применимы, имхо.
break значит запутывает, а return нет? :)
Конечно. Слёту не поймёшь куда break 3; в коде с сильной вложенностью вернётся. А return $var он и в середине кода return $var.
это наверное php? я просто не знаком
в C# на котором пишу я break всегда прерывает текущий цикл и с ним все понятно
msdn.microsoft.com/en-us/library/adbctzc4(VS.80).aspx
Это еще хуже — если циклов несколько (вложенных) то нужно делать break в каждом из них, плюс проверять какое-то условие по которому этот break делается. Вернуть результат в такой ситуации будет гораздо лучше и с точки зрения читабельности кода, и с точки зрения его оптимизации.
тьфу мать перемать…
test = false;
for(i=0; i<someVar && test==false; i++) {
for(b=0; b<someOtherVar && test==false; i++) {
if(i==100 && b==200) {
test = true;
}
}
}

только те же проблемы, что и с множественными return'ом здесь :(
ну а я о чём? такая же х… ня, как и множественные return'ы, только менее прозрачная :)
Кстати вы вкурсе что у вас тут бесконечный цикл. А множественные return не такое уж и зло иногда удобно поставить return и забыть про эту ветку условия а не держат это все в уме. Да и при чтение исходников сразу видно в этом месте происходит выход с определенным значением а не думат изменится ли переменная result дальше или нет. Я вот часто делаю переменную result масивом и туда складываю значение для последующего возврата (javascript для ясности).
Это только если мы избавляемся от return в самом конце цикла. А если в середине? Что-то типа этого (вместо funcX, естественно, может стоять и просто код):

for (...) {
for (...) {
func1();
if ( func2() ) return…
func3();
}
}

Будем добавлять еще несколько вложенных if-ов с break-ами?
Вынужден не согласиться, break действительно запутывает.
Не так давно у нас в движке я нашёл глупую ошибку. Дело было как раз с поиском в массиве.
Вот как примерно это было сделано:

public Category Get(ArrayList array, int id)
{
Category result;
foreach(object o in array)
{
result = array as Category;
if(result.ID == id) break;
}
return result;
}

И ведь даже не позаботились подумать, что будет, если ничего не найдётся. А будет то, что вернётся последний элемент.
Намного понятнее смотрится.

public Category Get(ArrayList array, int id)
{
foreach(object o in array)
{
if((array as Category).ID == id) return result;
}
return null;
}

Просто нельзя так сразу говорить, что это вот такое правило и всё тут, следовать нужно этому правилу.
Главное знать что делать.
исправляюсь:
Было:
public Category Get(ArrayList array, int id)
{
Category result;
foreach(object o in array)
{
result = o as Category;
if(result.ID == id) break;
}
return result;
}

Стало:

public Category Get(ArrayList array, int id)
{
foreach(object o in array)
{
Category result = o as Category;
if(result.ID == id) return result;
}
return null;
}
Можно переписать с единственной точкой выхода так:
public Category Get(ArrayList array, int id) {
  Category result = null;
  foreach(object o in array)
  {
     Category current = o as Category;
     if(current.ID == id) {
        result = current;
        break;
     }
  }
  return result;
}
На мой взгляд, зря вы в этом случае используете приведение через as (через ass, хе-хе :). Здесь лучше делать явное:
public Category Get(ArrayList array, int id)
{
     foreach(object o in array)
     {
          Category result = (Category)o;
          if(result.ID == id) 
               return result;
     }
     return null;
} 

Почему? Как минимум, потому что метод публичный, а в ArrayList можно положить что угодно. Если вам однажды передадут в него ArrayList из, например, строк, оператор o as Category вернет null. И вместо более или менее понятного InvalidCastException (здесь он может появиться только по упомянутой причине) вы получите NullReferenceException, который в данном случае может произойти аж в трех местах…
  • — если as вернет null
  • — если сам array == null
  • — если в ArrayList в каком-то элементе лежал null (это допускается)

… а на конкретизацию ошибки вы еще потратите время.

Кстати, если вы можете использовать .Net 2.0, лучше заменить ArrayList на типизированный Generic-список (List<Category>) — тогда и работать быстрее будет, и проблемы с приведением типов исчезнут… А если нет — не поленитесь, напишите какой-нибудь СategoryList: IList :)
Зря вы не заключили код в теги pre
Выравнивание по левому краю ломает глаза гораздо сильнее, чем поиск break или return…
UFO just landed and posted this here
Надо уметь задавать такое условие продолжения выполнения цикла, чтоб можно было обойтись безо всяких брейков. Нас этому на первом курсе ещё учили.)
пример приведите, пожалуйста, прямо с первого курса можно…
Ну да, давайте флаг заведём, засунем в условие цикла и при выполнении условия будем менять его значение, делая прерывание цикла ещё менее очевидным
Вместо циклов лучше использовать рекурсивные функций, тогда не возникнет проблем с break, continue и return. Рекурсия, естественно, должна быть хвостовой.
Это уже относится к функциональному стилю программирования :)
Рекурсия, естественно, должна быть хвостовой

Далеко не все компиляторы умеют хвостовую рекурсию разворачивать в цикл, так что есть шанс получить Stack Overflow. Или пользоваться Trampoline'ами.
какой break??
цикл while с правильным условием выхода делаете и все.

Мне зачастую проще return поставить. Моей религии это не противоречит:) Я же не отказываюсь от других схем, но и от этой, которую некоторые считают моветоном, я тоже не отказываюсь.
тогда это будет черти что, но не структурное программирование :)
В настоящее время, когда повсеместно используются Exception'ы правило «один вход — один выход» перестает работать!!! Из любой строчки кода может вылететь исключение и на этом выполнение функции прервется. В таких сутуациях, имхо, ставить return нужно руководствуясь здравым смыслом.
А если Вы пишите к примеру на чистом C, то такой подход будет разумным.
Exception, мне кажется, это не нормальный выход из функции, такой как return. Поэтому, все в силе.
А в С да, там без возврата кода ошибок и т.д. тяжело.
Все верно. Exception это не нормальный выход, но тем не менее это точка выхода из метода. И с этим не по споришь )). Возникают те же самые проблемы с читаемостью и отладкой, что и при множестве return'ов.
Хм, МакКоннел, как мне кажется имелл в виду — используйте, но перед использованием подумайте чем вам это грозит. Если использование множественных точек выхода сильно усложняет понимание кода, тогда от этого лучше отказаться. Если же наоборот упрощает — тогда применять.
По МакКоннелу самый главный принцип — управление сложностью кода, а следовательно надо пользоваться теми методами, которые приведут к его упрощению.
> сложность отладки такого кода, не всегда очевидно когда произошёл возврат из функции, к примеру мы имеем несколько return и никогда точно не можем сказать какой из них был вызван;

Чтобы не было сложностей метод не должен быть более 10 строчек… И тогда неважно сколько return-ов в нем. Я бы не стал сейчас бросаться и изменять все методы вставляя в начало Result result; а в конец return result; лишние две строчки => отлаживать сложнее =))))
«Представленных двух причин, лично для __ достаточно, чтобы отказаться»
«меня» не забыли? )
Замечу, однако, что множественные возвраты порой уменьшают глубину вложенности. А единственная точка выхода — напротив, увеличивает.
И с некоторого момента уже неоднозначно, что будет хуже читаться.
Насколько помню, с 4-го уровня вложенности.
наверное, стоит избегать излишне глубокого кода?
«return», часто и позволяет его избежать :)
Вы сами пришли к немаловажному ответу. Полиморфизм говорит нам делить свойства класса на методы так чтобы не было повоторений код, еще здесь обязательна краткость. Т.е. ошибка уже в том если вы допускаете «особенно, если он большой» то в таком случае лучше разбейте этот код на много маленьких методов и получите код где видно будет и два и три возврата без лишних проблем.
Полиморфизм говорит нам делить свойства класса на методы

«Полиморфизм» тут вообще ни в дугу.
какой еще полиморфизм в структурном программировании? %)
Такие ошибки Хабр не любит, хоть все понимают что речь была о объектном или структурном программировании.

Тоже не понравилось
> особенно, если он большой
Собственно от циклов можно вообще отказаться в пользу использования array_map, array_filter, array_reduce. На с# с появлением LINQ я вообще отказался от циклов. Конечно на php немного напрягает задавать кучу побочных функций для реализации логики фильтрации и преобразования массивов, но всё изменится с версией php 5.3, когда появятся c# подобные анонимные функции.
А затем закрыть! По мне так совсем неудобная вещь…
Удобнее вырожденные случаи отсекать в начале работы функции, в этом случае несколько return в начале не затруднят чтение.

«# сложность отладки такого кода, не всегда очевидно когда произошёл возврат из функции, к примеру мы имеем несколько return и никогда точно не можем сказать какой из них был вызван;»
Для этого часто используют например return -1, return -2,… и т.д. и всегда известно условие выхода.

И, кстати, Вирт был против отладки ;)
И подтвердил это, выступая у нас в городе.
Всмысле словами подтвердил
про -1 и -2:
без изучения кода все равно не ясно, что он вернул, в вашем случае, похоже, изучать код придется подольше, чтобы понять почему он возвращает различные отрицательные значения…

PS:
хотел бы обратится ко всем, я не пытаюсь учить вас программированию, пишите как хотите,
цель это заметки — поделится мнением, мыслями, не согласны — ради бога.
Когда есть документация функции, изучение кода остается на усмотрение того, кто собрался ее использовать.
Выводы у вас хорошие — писать по одним принципам и писать правильно.

А так, я тоже, собственно, делюсь мнением и мыслями :)
задокументировать можно и goto :)

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

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

А вот насчет мелких функций и методов, помоему это не всегда оправдано.
Просто нет серебрянной пули, а значит не стоит говорить «не используйте return».

Мне больше нравится первый вариант

>>> def getValue(data): 
...     if isWrongValue(data):
...             return 0
...     return parseValue(data)
... 
>>> def isWrongValue(data):
...     return data == 'wrong'
... 
>>> def parseValue(data):
...     return data.count('h')
... 
>>> getValue('wrong')
0
>>> getValue('hello')
1


А вот без отсечения результата:
>>> def anotherGetValue(data):
...     value = 0
...     if isGoodValue(data):
...             value = parseValue(data)
...     return value
... 
>>> def isGoodValue(data):
...     return data != 'wrong'
... 
>>> anotherGetValue('wrong')
0
>>> anotherGetValue('hello')
1


Может не использовать временную переменную? Лучше но зависит от ситуации:
>>> def yetAnotherGetValue(data):
...     return isGoodValue(data) and parseValue(data) or 0
... 
>>> yetAnotherGetValue('wrong')
0
>>> yetAnotherGetValue('hello')
1


PS: тоже делюсь мнением
PSS: есть платформа Symbian, использование return там практически невозможно в связи с оригинальной системой обработки ошибок и это очень неудобно
Самому не нравится такой подход, но ReSharper в подобном вашему правильному примеру предлагает инвертировать условие и делать возврат.

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

Вообще сам иногда использую return не в конце функции, но там все более логично и понятнее. Например если удалось получить какой-то элемент, то внутри условия делаем его возврат. Если нет, то ниже по условию пишу return null; Хотя в последних кодах все равно так не делаю. Сначала объявляю переменную и инициализирую её null, потом если получается, то заполняю данными и в конце возвращаю переменную.

О как меня понесло что-то… Наверное, это как раз показывает, что в большинстве случаев можно обойтись только одной точкой выхода.
>>>
Сначала объявляю переменную и инициализирую её null, потом если получается, то заполняю данными и в конце возвращаю переменную.
>>>

на мой взгляд, самый правильный вариант, всегда придерживаюсь именно его
Только вот какой ценой:) Это такая же религия, как и вокруг goto.
В решарпере все его хинты, в том числе и инвертирование условия и var можно настроить как вам больше нравится.
Согласен. Просто те люди которые его писали, и которые решали какими делать умолчания достаточно авторитетные. Вот любой новичек, который поставит студию и решарпер по наводке друзей, уже будет писать 2 вещи, которые мне совсем не нравятся — это var везде и инвертировать условие для возврата.
> Какая-то вроде бы и супер вещь этот ReSharper, а вот местами я с ним нахожу разногласия в том, как надо писать код. Второй случай был с использованием повсеместно var.

Не пойму почему везде использовать var это плохо?
Потому что
var x = MyFuncReturnX();
гораздо менее читабельнее чем
MyClassX x = MyFuncReturnX();
Понятно я думал речь о javascript а не о C# но как я понимаю в С# var появился с появление linq и использовать стоит только там. А вот в javascript нужно использовать всегда а не как некоторые просто ленятся три буквы написать и обьект window превращается в помойку :(.
У вас в проектах у функций и переменных именно такие имена? Или, быть может, нечто более осмысленное?

IDashboardExtension dashboardExtension = ApplicationServer.GetExtension<IDashboardExtension>(); ReportDefinitionSpecificationDocument specificationDocument = ApplicationServer.GetBusinessObject<ReportDefinitionSpecificationDocument>(e.DocumentMetadata.ID);

vs

var dashboardExtension = ApplicationServer.GetExtension<IDashboardExtension>(); var specificationDocument = ApplicationServer.GetBusinessObject<ReportDefinitionSpecificationDocument>(e.DocumentMetadata.ID);
Мое твердое убеждение — не писать var, если в строке не присутсвует явное описание типа. Здесь есть параметры дженериков, которые точно указывают тип. А вот если функции возвращают другие, отличные от указанных, типы, то их просто необходимо указать.
А вот если функции возвращают другие, отличные от указанных, типы, то их просто необходимо указать.

Указанных где?
Гм. Видя вот эту строчку, я предполагаю что у переменной тип будет IDashboardExtension
var dashboardExtension = ApplicationServer.GetExtension();

И очень сильно удивлюсь, если это не так.
Черт, уголки пропали
var dashboardExtension = ApplicationServer.GetExtension<IDashboardExtension>();
var плох тем, что снижает контроль типов на этапе компиляции. вы можете ожидать, что тип будет один, а оказывается, что в результате «вычисления типа выражения» получается несколько другой.
Насколько я понимаю, необходимость в var возникла в связи с введением LINQ и анонимных типов.
На скоко я знаю с помощью var в C# не льзя динамические типы объявлять те в переменной четко известно еще при этапе компиляции какой там будет тип переменной. Да и появился этот оператор иза того что нужно ведь както объявлять анонимные обьекты :)
Да, выше имел ввиду конечно же C#, а не Javascript.
И с var путанины в C# не возникнет, все что написали неправильно всплывет ещё на этапе компиляции.
Ну и сам считаю так же как и вы написали — var в 99% случаев нужно использовать только там, для чего его придумали, т.е. при работе с анонимными типами.

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

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

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

какая связь между языком на котором вы пишите и размером логических блоков? или вы хотите сказать что на php это простительно? вы видели компании в которых по стандарту принято группировать логические блоки на 50 и более строк?
извиняюсь. теги копипастил а / у закрывающих забыл.
Вот она вложенность! :)))
Нечитабельно :( Вложенность мешает.
На мой взгляд не очень хорошая практика. Я имею ввиду одну точку выхода. Потому что для сложных функций, которая возвращает огромный набор значений, подобное перерастает в сложный, отягощенный операторами условий код. Куда полезнее, и логически понятнее бывает «отметать» возвраты функции по мере её выполнения:

  exp1 = doSomthing1();

  if (exp1) return 0;

  exp2 = doSomthing2();
  
  return exp2 ? exp2 : 0;


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

  exp1 = doSomthing1();

  if (exp1) retval = 0;
  else
  {
      exp2 = doSomthing2();
      retval = exp2 ? exp2 : 0;
   }
   
   return retval;


Первый кусок кода читается от попроще, не согласны?
это пример того как на мой взгляд писать не стоит, что мешает написать так?

var result = 0;

exp1 = doSomthing1();

if (exp1) result= 0;

exp2 = doSomthing2();
result = exp2? exp2: 0;

return result;
var result = 0;

exp1 = doSomthing1();

if (exp1) result= 0;
else
{
exp2 = doSomthing2();
result = exp2? exp2: 0;
}
return result;
обшибся в первый раз
мой последний вариант читается, на мой персональный взгляд, гораздо лучше
Простите, но… это чем-то отличается от моего второго варианта? :-)))
7 строчек, протиив 4-х, и это не счится { }.

И «это пример того как на мой взгляд писать не стоит» — это вы про что говорили? :-))))
да вы правильно написали, точно то что я и хотел сказать
зря я запостил такой же вариант

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

примерно так
еще хотел сказать, что считать качество кода в строчках как бы не очень правильно :)
В вашем варианте смотрим на метод и думаем «где изменяется result». Посмотрите сами на код — поток управления в вашем варианте просто теряется.

Количество кода может отражать качество — чем больше кода тем дольше в него вникать.
Краткость — сестра таланта :-)
Tут выполнится doSomthing2, хотя в первом примере этого не произойдет
да я поправил
ваш пример наглядно на мой взгляд показывает то, что поддерживать такой код труднее
смотрим в середину кода и точно не знаем, будет ли этот код выполнен
в моем случае мы видим что код находится в условии, значит он определенно может не выполнятся
ой это не ваш код
адресую коммент enartemy
Возможно, но его получилось меньше, и понимается он проще… Я ошибаюсь?
меньше не показатель качества
выполнение середины вашего кода не очевидно
в этом вся трудность
хорошо когда строчек 5, а когда их 155?
Кстати, а ваш код будет еще к тому же и медленне, за счет объявления дополнительной переменной. Вы уверены, что всё это стоит того, что бы понимать с первого взгляда что-то в середине не будет выполнено?

А когда 155 строчек, то на 12-ой вложенности IF {} конечно будет очевидно, что этот код МОЖЕТ БЫТЬ не выполнится, но… помойму много чего другого будет уже совсем не очевидно.
Не волнуйтесь, оба ваших кода пережуются компилятором (оптимизатором) в одинаковый байт-код :)

Поднятая проблема больше в читаемости кода
По вашему if () видно лучше чем return? :)

А если код модернизировать, то все равно надо будет ознакомится с ним с самого начала, а если просто использовать метод, хватит и описания.
Во всяком случае мне пока не пришлось заглядывать в исходные коды например встроенных функций PHP :)
Дополнительные return (не считая последнего), обычно всегда идут с if () :)))

А пример я бы переписал так:
// condition one
if (doSomthing1()) {
    return 0;
}
return doSomthing2();
Согласен, что 6 подсвеченых символов лучше заметны чем 2 :)
Но if определяет блок кода. Тут имхо намного легче перейти к началу этого блока, понять где он завершается и т.д. returns же приходится взглядом вылавливать, или же перечитывать все от начала до конца.

PS. И ещё для меня неудобоваримо однострочное представление конструкции if. Хотя это больше адресоавано к enartemy.
ИМХО, ваша собственная грубая ошибка (первый ваирант кода) наглядно демонстрирует, насколько «легче» писать и поддерживать такой код. В исходном варианте return гарантирует, что дальнейший код функции выполняться не будет.
Ваш код ужасен. зачем выполнять doSomthing2 если exp1 тру? А если doSomthing — запрос в базу данных или просто длительно выполняемая функция? Вы извините, но вы явно не подумали это писав.
это все холивары, но как вы можете заметить — большинство как бы не согласны с вашим мнением, а значит наверное вариант с ретурном после первой проверки лучше. И да — если придерживаться хороших правил оформления (не больше 80 символов длина строки), то уже один уровень вложенности отнимает 4 символа (ну у кого как), и соответственно ничего нормального при большой вложенности в строку уже не входит. Так что лично я за вариант с ретурном — и буду его использовать. Но не буду с вами делиться мнением, что оно лучше…
Это приемлимо когда условие выхода одно. Если их больше 5, код становится уже менее читаемым. Я использую вариант с несколькими return, и return false в конце функции.
Не надо путать несколько точек выхода и предусловия.
Мне удобнее видеть, что если что-то не так с параметрами, то функция тут же или выходит, или кидает исключение, или ещё что, а не куча вложенных if'ов, а в конце, что немаловажно — уже далеко от самих условий, эти самые возвраты и бросания исключений.
Не могу согласиться с тем, что код с одним return'ом проще отлаживать. Если их несколько, на каждый можно поставить точку останова и сразу увидеть, где функция вышла.
Если же есть один return в конце, нужно следить за значением переменной-результата и ловить момент когда она принимает «ложное» значение. Экономия нервов в первом случае налицо.
А вообще, как говорят в Гугле, Debugging sucks, testing rocks.
Насчет того, что такой код трудно сопровождать. Дописывать какой-то код в конец функции не разобравшись в ее логике, как по мне, недопустимо. Именно такая практика приводит к созданию новых багов при починке старых.
Ну, и насчет goto. Как без него предлагаете выходить из дважды вложенного цикла? (Это, кстати, единственное известное мне обоснование применения goto).

Резюмирую. Структурное программирование — это хорошо, но это лишь рекомендации. Их нужно учитывать, но нужно знать, когда можно от них отступить. В этом истинный путь ;)
О да, testing rocks — и с этим турдо не согласиться. Думаю, многим просто надо взять эти практики на вооружение и забыть про долгое пребывание в дебаггере.
Вовсе нет, ведь второй (отрефакторенный) код читабельнее, разве нет?
Было бы странно будь это не так. Рефакторинг, тысяча чертей :).
абсолютно согласен, Фаулер четко дал понять, что в совремменом кодировании главное чтобы точка входа была одна, а выходов может быть сколько угодно.
автор учит прямо противоположно Фаулеру, незачет
Следует однако отметить что Фаулер писал про ООП, а не про структурное программирование. И предлагал делать методы как можно компактными — в том числе и наверное для того чтобы множественные точки выхода не запутывали код.
А что мешает написать такой код:
double getPayAmount() {
  double result;
  if (_isDead)
    result = deadAmount();
  else if (_isSeparated)
    result = separatedAmount();
  else if (_isRetired)
    result = retiredAmount();
  else
    result = normalPayAmount();
  return result;
}

* This source code was highlighted with Source Code Highlighter.
Чуть изменил форматирование, но не суть.

PS. Ещё особо не проснулся, так что, если я написал что-то не то, сильно не пинайте :)
Объективно — ничего. Субъективно же я вижу присваивание и запоминаю, что значение необходимо в дальнейших вычислениях, помню об этом какое-то время и представьте мое разочарование когда я вижу, что все вычисления свелись к возврату. В этом месте я бы «надел волшебный плащ и волшебную шляпу» и сделал бы «Inline Temp».
UFO just landed and posted this here
А Хм… это, конечно, понятно, но тогда получается «много лишних скобочек» и вложенных условий, когда понять что к чему ничуть не легче. Разумеется, надо писать как можно более минималистичные функции, а не пытаться все объединить в одной, но все же, на практике встречается разное.

Мы, например, не гнушаемся использовать такие «преждевременные» return, поскольку легко их отслеживаем (на каждую такую «точку выхода» есть свой набор unit-тестов, которые держат поведение метода в согласованном состоянии и предотвращают неопределенное поведение метода).
Можно также использовать
do
{
if(/***********/) break;
/*************/
}while (false)
Пишите небольшие функции, и код будет ясен как божий день.
Дано утверждение «return это плохо». Если выбросить всю воду — останется всего два довода — 1. сложнее в отладке, 2. ухудшается читаемость кода. Оба довода даны совершенно неубедительно. В структуре кода с множеством вложенных скобок — гораздо легче запутаться. При отладке вообще без разницы return там или куча скобок. Пример неубедительный.

И так понятно, что не стоит злоупотреблять return-ами, но и бояться их как огня (как goto?) тоже не стоит.
О, господи. Ещё Макконнел писал, что вместо кучи вложенных if в подобных методах как раз лучше сделать отдельные проверки с последующими возвратами, поскольку это уменьшит сложность кода, устранив большую вложенность. А Вы предлагаете с точностью до наоборот.
Кстати, нынче модно использовать исключения, но возврат всё равно при этом имеет место быть.

> Я не буду отговаривать, но хотел бы напомнить, что принципы на то и принципы, чтобы их придерживаться всегда, а не только по требованию.
Думать своей головой надо, а не придерживаться всяких догматических высказываний.
Я пишу так:
Базовые проверки правильности полученных данных и выход сразу если что-то не так. И еще одна точка выхода в конце тела функции. Это помогает избежать кучи фигурных скобок.
Например:
   void* func(char* a)
   {
       if (a==NULL)
            return NULL;

        void* result = NULL;
        /* function body */
       return result;
   }

хм… а я пишу что то вроде
public void func(ClassA a)
{
    if(a == null)
        throw new NullPointerException(...)
    ....
}

и никак иначе, потому что возвращать null — вселенское зло :)
Я привел пример на С. Поэтому исключений нет.
Кстати, получается что exceptions — тоже множественные точки выхода, и поэтому, в соответствии со статьёй, ими нельзя пользоваться. Какая-то ерунда… :)
исключения — это исключения
часть операционной системы, можно сказать, что это механизм «вне программы», поэтому вызов исключения не является точкой выхода
не не не исключение это не точка выхода. К тому же я считаю, что множественные точки выхода пользовать можно, только надо сначала подумать нужно ли их использовать :)
> принципы на то и принципы, чтобы их придерживаться всегда, а не только по требованию

Из приведённых выше коментариев можно сделать выводы:
1. иногда полезно делать выход «по-скорее»: при проверках вначале функции, или если функция маленькая и/или простая.
2. иногда полезно нагородить условий, но оставить один выход: если функция большая и/или алгоритм сложный и выходы могут затеряться в глубине.

Спасибо за тему.
Успехов.
Блок выполняет добавление комментария к посту, который находится в группе, которая находится в сообществе.
1. Проверить, авторизован ли пользователь.
2. Проверить, пуст ли комментарий.
3. Проверить, существует ли топик.
4. Проверить, не закрыт ли топик.
5. Проверить, существует ли группа, в топик которой пользователь собирается добавить комментарий (вполне могла бы быть потеря данных и группу уничтожили, забыв уничтожить топик и какой-нибудь злодей решил в этот топик надобавлять левых данных, чтобы базу загадить).
6. Проверить, является ли пользователь участником группы, в топик которой он добавляет комментарий (он может и не быть участником, тогда не может добавлять комменты).
7. Проверить, не заблокирована ли группа.
8. Проверить, существует ли сообщество, которому принадлежит группа (зачем, см. пункт 5, аналогично группе).
9. Проверить, является ли пользователь участником сообщества, которому принадлежит группа.
10. Все ОК, сохраняем комментарий.
11. Проверить, получилось ли сохранить комментарий или произошла SQL-ошибка в запросе или БД была недоступна.
12. Теперь все точно ОК. Точка выхода?
В общем, я про то, что в маленьких конструкциях да, лучше одним return обойтись. А вот в таких, как я описал — код потом просто невозможно читать и разбираться, какой из } закрывает какую проверку (да, комментарии тоже можно писать после каждого } ).
Пункты 3, 5, 8 можно с чистой совестью исключить из списка, если иметь базовые понятия о реляционных СУБД.
Бесспорно, можно навешать ключей и будет всё само. Ну будет не 11 проверок, а 8. Намного легче стало?
Попробуйте использовать структурный или объектно-ориентированный подход.
(Я не о return, код действительно нужно поправить)

Итак есть метод
addComment

1. Думаю авторизация проверяется не только в этой операции — выносится из метода
2.
comment->Validate()
— там же проверки ввода мейл и чего еще пожелаете.
3.
post = new Post(:id)
— если поста нет — эксепшн и в лог!
4.
post->isClosed()
— ок
5, 6, 7, 8, 9.
user->canPost()
— права доступа это совсем другая история
10, 11.
comment->save()
— сохранение, обработка сохранилось/нет — тоже другая история

12. Имхо, стало проще. Ну а точки выхода это мелочи — ставьте где угодно :-)
Для таких штук есть монада Maybe. Очень, очень удобна для связывания такого рода вычислений.
«модуль(в данном случае функция) должен иметь только одну точку входа и только одну точку выхода»

Приведите пожалуйста пример нескольких точек входа в функцию.
Все зависит от того что пишете. На асме я вам легко организую несколько точек входа в функцию.
это легко делается на ассемблере, бейсике
в любом языке где нет ярко выраженных функций, возможно даже что и в паскале можно через goto прыгнуть в код другой функции, но не уверен, забыл, давно таким извращением не баловался
утверждение это было дано в эру становления структурного программирования, поэтому не ищите такой возможности в современных языка, которые слава богу ограничивают пользователя хотя бы от нескольких входов…
Спасибо. Может Дейкстра именно на goto и намекал. По-моему ситуация такая, что программировать структурно подразумевает разбиение на модули имеющие одну точку входа и одну точку выхода если смотреть на них из ВНЕШНЕГО модуля.
Т.е. это означает, что хотя в функциональный элемент может входить несколько функций, остальная часть программы видит его как неделимый элемент и в него есть вход и есть выход, о котором внешняя часть программы знает.
функция по своему определению имеет только одну точку входа. В изначальной фразе подразумевается обобщённое понятие подпрограммы.

Если хочется представить себе множественный вход на современных языка — методы объекта формально можно назвать множественным входом в модуль/подпрограмму.
Точка входа в данном случае всетаки остается одна. Несколько может быть если во внутрь функции перейти с помощью goto.
Какая, интересно, а двух разных методов объекта одна общая точка входа?

Безусловный переход в этом случае просто нарушает структуру. О множественности входа в подпрограмму здесь говорить нельзя, т.к. управление не вернётся, значит это не вызов подпрограммы.
Господа, программисты вы тут по-моему обсуждаете сферического коня в вакууме.
Есть задачи, где использовать return, как в примере даже нужно.
Например если программу нужно максимально оптимизировать по времени выполнения. Если у вас уже есть нужный результат, то зачем тратить время на выполнение ненужного кода?
Соответственно, есть задачи где читабельность важнее, и там все эти рассуждения уже правомерны.
это принцип, в чем-то любой принцип — это идеально черный сферический конь в вакууме
Особенно удобно получается, когда в конце метода приходится разребать тонну закрывающихся скобок :(
Нет, все таки возврат из середины или начала — иногда куда понятнее. Да и при правильном стиле программирования, методы не должны быть не больше 50 строк.
Так он практически влазит в один в два экрана, так что понят что за возврат и откуда — понять не трудно. а вот со скобками бывает проблема.
А вот я имею привычку ставить коментарии после }, особенно, если вложенность большая. Удобно.
UFO just landed and posted this here
делайте свои защитные условия на исключениях, зачем return использовать?
перед тем как вы спросите: да, исключения я не считаю точкой выхода

PS 2all:
спасибо всем кто сделал мою карму короче, видимо топик совершенно плохой
писать подобные статьи больше не стоит, хабру не нравится
Просто в программирование почти нигде нет _одного правильного решения_

Все очень зависит от контекста. А этот топик выражает одно безальтернативное мнение.
UFO just landed and posted this here
почему чудовищный? не делайте чудовищный кто вам мешает разделять ваш чудовищный код на менее чудовищный? только не говорите мне что невозможно никаким образов избежать 12 уровней вложенности…
Кармы нет — не поучаствовал :(

Не нужно учить плохому — кто-то ведь может послушать.
классическое применение этого случая- есть класс предложение, он состоит из массива слов.
для проверки предложений на равенство можно банально идти по массивам слов до тех пор, пока не будет разницы и делать return false; а в конце return true; — читабельно и удобно на мой взгляд.
Гораздо удобнее чем вводить какой то флаг булевский и возвращать его. Плюс ускорение на очень длинных предложениях возможное :))(в случае неравенства).
Присоединяюсь.

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

>>Плюс ускорение на очень длинных предложениях возможное :))(в случае неравенства).
Так break никто и не отменял
да, мое упущение, каюсь :)
Выскажу своё мнение.

Если все методы делать понятными и короткими, не допуская разрастания кода и чрезмерных обязанностей, то и проблемы не возникает. Т.е. методы должны быть такого размера, чтобы можно было понять их работу с одного взгляда. Может так случиться, что выйдет исключение, но почти всё, что приходится решать, вполне подчиняется этому принципу. И тут уже всё равно, сколько точек выхода у метода, главное, чтобы все они были очевидны и осмысленны.
Кент Бек, один из разработчиков Eclipse, в своём эссе (книга «Шаблоны реализации корпоративных приложений») пишет: «Сторожевой пункт используется для описания простых нестандартных ситуаций.
void initialize() {
if (! isInitialized()) {

}
}
и
void initialize() {
if (isInitialized())
return;

}
Во время чтения первой версии я отмечаю, что нужно будет взглянуть на пункт else. Я мысленно помещаю условие в стек. Это отвлекает внимание, пока я читаю пункт then. Первые две строчки второй версии сразу же ставят на заметку простой факт: получатель не был проинициализирован.
<...>
Возвращаясь в „средневековье“ программирования, вспомним старую заповедь: процедура должна иметь одну точку входа и одну точку выхода. Так делалось, чтобы предотвратить путаницу, возникающую при множественных переходах внутри процедуры. Этот принцип применим к языкам, подобным FORTRAN и Assembler, с большим количеством глобальных переменных, когда даже понимание структуры программы является тяжёлой работой. В Java, с её маленькими методами и преимущественно локальными данными, это чрезмерно консервативно. Данная часть программного фольклора, бездумно применяемая, мешает использованию сторожевых пунктов.
<...>
Вложенные ветви условий приводят к дефектам [понимания кода]. Та же самая версия кода, использующая сторожевые пункты, даёт предпосылки для обработки запросов без сложной управляющей структуры.»
спасибо за цитату, было интересно почитать
наводит на размышления, термин и концепция «сторожевой пункт» понравилась, хоть и использовал ранее с исключениями, но наименование первый раз слышу
«принципы на то и принципы, чтобы их придерживаться всегда». Ужас какой, Вы правда так считаете? Давайте без нездорового фанатизма. И потом, если предположить, что две указанные Вами проблемы действительно существуют, то ваш пример «рефакторинга» их не решает.
И что теперь делать, если где-то в коде делитель обратится в ноль? Или вы предлагаете весь код обёртывать в try { if ( арифметическая операция ) { код } } catch a{} catch b{} catch c{}

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

Вот что пишет Ален Голуб «Иногда, когда подпрограммы короткие, не стоит стараться обеспечить единственную точку выхода. (По моему мнению, правило „избегай запутанности“ перекрывает любое другое правило, с которыми оно входит в конфликт). В этой ситуации всегда старайтесь убедиться, что из подпрограммы нет таких путей, которые не проходят через оператор return.»

К сожалению сайт с книгой сейчас в дауне, так что через webarchve (кодировку только 1251 выставьте)
web.archive.org/web/20080212170721/http://www.cyberguru.ru/programming/cpp/cpp-programming-rules-page61.html
И эттта: «Парсер — лох» :-)
Побольше ретурнов, хороший и разных. Из двух вариантов — первый воспринимается проще, особенно когда когда внутри цикла будет страничка кода с еще несколькими условиями. Если нужна обязательно общая точка — лучше сделать ее в отдельной функции, хотя сама её необходимость не совсем понятна.
Второй вариант читается хуже, особенно когда уровней вложенности много. Я за первый.
Оченно верная статья) У меня на работе один ученик делает программу-переводчик с Basic (в QB-диалекте) на Pascal (BP), с чисто учебным назначением (дабы не было между юными программистами холиваров)… Так вот — замечательный эффект, писать этот переводчик для обработки структурного кода — гораздо легче, чем неструктурного)
UFO just landed and posted this here
UFO just landed and posted this here
да пост старый, с тех пор я несколько изменил взгляд и во многом обсуждению в этом треде
как этот пост воскрес — не понимаю :-)
UFO just landed and posted this here
Цветом фона и шрифтом) Чем чернее фон и белее шрифт — тем старее пост)

Статья данная актуальности не потеряет ещё долго)
Более всего структурность пригождается как раз в конструкциях вложенностью более чем 2 уровня, постоянно модифицируемых)… Без структурности, в общем-то, остаётся только переписать всё заново
Как ни странно, но предлагаемый подход есть антипаттерн, основанный на толковании не самых лучших формулировок Дийкстры.
Причина — структурный переход («вперед-вверх») является вполне законной частью структурного программирования, отнюдь не нарушая принцип «один вход — один выход»: после выхода из подпрограммы управление переходит не куда попало по метке, а строго к следующему оператору (или блоку обработки исключений для raise) в вышестоящей процедуре.
В рассматриваемом примере после рекомендованной модификации добавляется лишний уровень вложенности кода (что резко ухудшает читаемость) без всякой компенсации, кроме моральной. Хорошо еще, что эрзац-флагов не прибывает.
Sign up to leave a comment.

Articles