LINUX.ORG.RU

Выбор подхода: исключения vs специальный возвращаемый тип


0

2

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

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

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

★★★★★

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

Исключения в таком случае могут быть сложены в 3-4 раза.

В смысле? Если у тебя любое исключение фатальное, то просто в мейне делаем try/catch, пишем что-то в лог и падаем, всё точно так же. Откуда возьмутся вложенные исключения?

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

Возвращаемое значение нужно тащить по всей цепочке вызовов и писать соответствующий код

В хаскеле это победили монадами и ADT. В частности оператор >>= передаст управление дальше только если левая часть не вернула ошибку (думаю, объяснил по-ламерски и неправильно, но суть понятна).

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

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

Исключения в таком случае могут быть сложены в 3-4 раза.

чё ты мелешь, грёбаный ты украинофоб? иди уроки делай, чувырло необразованное.

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

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

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

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

Является ли отсутствие тьюринг-полноты фатальным недостатком - открытый вопрос.

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

в яве некоторые вещи обязательно надо заварачивать в try-catch на месте, я поэтому и написал, про «что-то непотребное, что может заглючить» и таких штук может быть не одна, это где мы впиливаем исключения

а с возвратом кода, например на сях, все просто, return -1 и все

upd: про 4-5 вложенных исключений тоже все верно, ибо исходит из того, как правильно писать код на жабе, я именно ее имел в виду говоря об исключениях

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

В частности оператор >>= передаст управление дальше только если левая часть не вернула ошибку

ну а если левая часть, допустим, вернула ошибку, куда мы попадаем? сразу на топлевел, или также по всей цепочке вызовов будем тащиться? Тот факт что управление дальше не передается не отменяет цепочку вызовов. Насколько я помню, в хашкеле даже call/cc реализовано через жопу cps. Так что, непохоже, чтобы данная проблема там решалась так просто. И, причем тут монады, казалось бы?

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

Заявление уровня «списки в хаскеле сделаны через монады».

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

В хаскеле нет топлевела, это не питон. В целом, зависит от монадки, но для типичных (Maybe, Either) правая часть >>= не вычисляется совсем.

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

правая часть >>= не вычисляется совсем.

Да причем тут правая часть. В левой части как выход происходит? Минуя стек вызовов, или по стеку передается управление?

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

но для типичных (Maybe, Either) правая часть >>= не вычисляется совсем.

Оппа, а для типичных &&, внезапно тоже. Вот она где СИЛИЩА!

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

В хаскеле нет стека вызовов. Если слева обнаружился фейл, то больше ничего не вычисляется.

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

Если слева обнаружился фейл, то больше ничего не вычисляется.

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

хаскеле нет стека вызовов.

Это что то новенькое сейчас мы проверим, есть там или нет (LOL!) и как оно на самом деле происходит. Напиши на хаскеле аналог вот этого кода:

mul=function(x, y){console.log("foo"); return x*y}

casualFact=function(n){
  if(n<2) return 1 
  return mul(n, casualFact(n - 1))
}

casualFact(5)

//  foo
//  foo
//  foo
//  foo

////////////////////////////////////////////////////

fact=function(n){
  if(n<2) throw 1 
  return mul(n, fact(n - 1))
}

exitFact=function(n){
 try{
   fact(n)
 }catch(n){console.log(n)} 
}

exitFact(5) // --> 1

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

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

Новое для тебя, может быть. Всем остальным известно с 1987[0] года.

Я не думаю, что ты что-то поймешь, но аналогом твоего кода будет следующее:

module Main (main) where

import Debug.Trace (trace)

main :: IO ()
main = do
  print (casualFact 5)
  print (fact 1)

(×) :: Int -> Int -> Int
x × y = "foo" `trace` x * y

casualFact :: Int -> Int
casualFact n = if n < 2 then 1 else n × casualFact (n - 1)

fact :: Int -> Maybe Int
fact n = if n < 2 then Nothing else fmap (n ×) (fact (n - 1))
./Main        
foo
foo
foo
foo
120
Nothing

[0]: http://research.microsoft.com/en-us/um/people/simonpj/papers/slpj-book-1987/

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

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

Я тебе больше скажу, в Java, Python и прочих тоже нужно удалять объекты, т.к. как минимум нужно показать GC, что они теперь не нужны (декремент счетчика ссылок). Основная разница в производительности лишь в том, что в C++ объекты удаляются сразу, а в других удаление остается на совести GC. Вторая разница кроется в проблеме, как узнать, какие объекты нужно удалить. Тут есть разные подходы в реализации C++, обычно добавляется чуток дополнительного кода (всегда, вне зависимости от того, было исключение или нет).

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

декремент счетчика ссылок

Счётчики ссылок? В современных GC? Ты с ума сошёл?

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

Короче, не верю. Если бы все было так, ты бы не стал задом вилять, а переписал бы все как есть. Зачем ты fmap там используешь?

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

т.к. как минимум нужно показать GC, что они теперь не нужны

В нормальном GC(jvm, .net) - не нужно. В Питоне, может быть.

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

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

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

Да мне-то что, это анон утверждал обратное.

Deleted
()

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

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

В нормальном GC(jvm, .net) - не нужно. В Питоне, может быть.

Ты явно не понимаешь одной простой вещи. GC все равно нужно понимать, что можно удалить, а что нельзя.

galanthus
()

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

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

upd: про 4-5 вложенных исключений тоже все верно, ибо исходит из того, как правильно писать код на жабе, я именно ее имел в виду говоря об исключениях

Ясно, про джаву (почти) ничего не знаю, говорил про С++. Там вложенные исключения редкая штука.

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

Ммм, почитал про них по диагонали. Они сделаны через монады :).

Там есть Error и Exception. Первые, вроде, не через монады? По крайней мере, при использовании error в сигнатуре функции монады не появляются.

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

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

Можно возвращать объект Exception вместо его бросания!

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

Ты явно не понимаешь работу современных GC. Уменьшать счётчики ссылок при раскрутке стека - это дело как раз C++ и его умных указателей в духе shared_ptr.

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

GC все равно нужно понимать, что можно удалить, а что нельзя.

Для этого он строит граф связей для доступных объектов. Все остальные объекты(т.е. недоступные) можно удалять.

Кстати, упомянутый тобой подсчет ссылок зафейлится при наличии циклических зависимостей(для обхода этой проблемы есть отдельные костыли, но речь сейчас не о них), а нормальный GC - нет.

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

Для этого он строит граф связей для доступных объектов. Все остальные объекты(т.е. недоступные) можно удалять.

А связи сами собой появляются и разрываются, не подумал об этом?

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

А связи сами собой появляются и разрываются, не подумал об этом?

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

anonymous
()

Но таки что выбрать и почему?

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

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

Вообще лучше особо не валидировать, только фильтровать для безопасности (как например удаление html тегов из запроса и т.п.), но тут и ошибок быть не может. А если не валидно, пусть летит исключение, зачем разбираться в причине? Это дело внешнего кода - дать валидные данные, и если что-то сломалось - пусть внешний код и разбирается.

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

Отлично! Как мне кажется, этого уже достаточно, чтобы создать и заимплементить трейт Monad для того же Optional или Result.

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

Ты вообще не понимаешь GC. Почитай, что ли, про concurrent mark and sweep.

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

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

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

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

ozkriff
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.