LINUX.ORG.RU

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

 


0

2

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

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

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

★★★★★

То, о чем ты говоришь уже реализовано в Rust.

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

Что тебе нравится, то и выбирай. Исключения или АТД - дело вкуса. У каждого из этих подходов свои плюсы и минусы.

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

Что тебе нравится, то и выбирай.

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

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

Исключения или АТД - дело вкуса

Ну я тут локальный министандартик прост пилю, хотелось бы к пунктам какой-нибудь обоснуй добавить, а не просто «нраица», «привыкли»

Debasher ★★★★★ ()

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

Чем проще сделаешь реализацию - тем проще потом переделать, а переделывать скорее всего придётся т.к. нет ясности на данном этапе...

И да - внедряй (когда это действительно необходимо и без этого не обойтись) абстракции, классы, методы

anonymous ()

ИМХО, для динамики - исключения. Просто потому, что компилятор не заставляет проверять возвращаемые значения.

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

Просто копируй отсюда

Бездумное копирование без понимания причин — это всегда идиотская мысль. Гугл — это компания со своей спецификой, историей и подходом к разработке. Причины, по которым они сделали тот или иной выбор, могут не иметь никакого отношения к тому, что делает ТС или кто-то иной. Более того, ТС явно написал, что вопрос про динамические языки, а ты ему гайд по Цпп суёшь

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

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

anonymous ()

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

А если нужно обработать исключение и продолжать работу дальше?

anonymous ()

По сути разницы особой нет

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

goingUp ★★★★★ ()

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

И как там сделать обработку, чтобы переменная a не была больше 100 и отлавливать ошибки при работе с файлами?

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

В ексепшен можно положить дополнительные данные.

В возвращаемое значение - тоже (никто же не заставляет возвращать int).

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

Если эксепшенов нет, то что ты будешь обрабатывать?

Код ошибки.
Ваш Капитан.

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

К стати, а что случится с памятью, выделеной в «отвалившемся» участке и неучтенной вне его? Например какой-нибудь временный буфер, который выделяется внутри обернутого блока, там же используется и должен уничтожаться, но исключение вылетело раньше. Хотя, наверное, это актуально только для С++.

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

Ну и вообще исключения не самая лучшая идея наверное

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

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

Я думаю тут еще разница в «физическом смысле». Все-таки исключение это когда «шеф, все пропало», а возвращаемое значение кодом ошибки это когда «у вас тут опечатка».

По факту - в Java все, что может «ошибаться» кидает исключения. Во многих библиотеках для С++ тоже.

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

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

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

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

К стати, а что случится с памятью, выделеной в «отвалившемся» участке и неучтенной вне его?

Утечет, очевидно. В С++ для этого есть RAII (конкретно - std::unique_ptr), больше языков с исключениями но без GC нет.

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

хотелось бы к пунктам какой-нибудь обоснуй добавить

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

unt1tled ★★★★ ()

Исключения имеют то преимущество, что их случайно не пропустишь.

Потому для динамических языков это фактически обязательная практика по обработки ошибок.

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

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

это актуально только для С++.

Для C++ не актуально, там RAII - одна из ключевых особенностей языка. Это было бы актуально для Си, но там нет исключений(в языке).

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

а вернуть код в миллион раз проще и быстрее, чем обработать стопицот трейсов и киданий ексепшона

Зависит от разницы уровней кидальщика и обработчика.

Кстати, никто не сравнивал производительность исключений в C++ и Vala? Vala, как я понимаю, генерирует сишный код с обычными возвратами и GError'ом. Интересно было бы сравнить, что быстрее.

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

Я имею в виду производительность конкретно подхода по раскрутке. Код она генерирует простой, ибо все уже есть в GObject/Glib/etc. Это просто обертка с более удобным синтаксическим сахарком.

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

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

а почему так? как оно устроено? ведь (как я щас представил) при бросании эксепшна можно просто всё из стека вынуть до try контекста, а при возвращении там может ещё в каждой функции по ифу на результат верхних.

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

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

Незнаю как в других, но в JS не всегда:


myforeach=function(f, data){if(data[0]){f(data[0]); myforeach(f, data.slice(1))} }

arr=[]; n=10000; while(n--) arr.push(n) 

console.time("first")
 myforeach(function(n){if(n===9000) return}, arr)
console.timeEnd("first")


console.time("second")
 try{
 myforeach(function(n){if(n===9000) throw ""}, arr)
 }catch(e){}
console.timeEnd("second")

//  first: 1051ms
//  second: 149ms
Но обратной стороной является то, что код внутри try/catch плохо оптимизируется. Тут уже по месту надо смотреть, что быстрей.

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

Во многих библиотеках для С++ тоже.

Да ладно? По моему, скорее наоборот. В стандартной библиотеке исключений почти нет. В бусте, как правило, предлагается выбор. В Qt «нет исключений».

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

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

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

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

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

А в расте при панике разве не будут деструкторы вызваны?

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

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

к примеру, я бы пованговал, что у тебя второй foreach не выполняется вовсе

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

Тут уже кто как пишет код. В простейшей ситуации с сишным кодом (коих 99%) я вызываю мелкие функции из одной главной и все они должны отработать и вернуть код о том, как отработали. Флоу все равно вернется в главную функцию, так что пофигу будет там возврат значения или нет.

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

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

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

Исключения хорошо: локаничный код вместо

match a() {
  Ok(a') ->
    match b() {
      Ok(b') -> return c(a,b)
      Err -> return Err
    }
  Err -> return Err
}
будет
try
  a' = a()
  b' = b()
  return c(a',b')
catch(e)
  return e

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

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

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

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

Это не сравнение исключений против кодов возврата

Почему же не сравнение? В любом рекурсивном вызове у тебя есть два варианта, пробросить возвращаемое значение по стеку или бросить исключение «наверх». Это как раз об этом.

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

Ты коды возврата и их обработку вообще видел когда-нибудь?

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

Понятия не имею. В Go при панике вызываются все defer-вызовы на выходе.

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

В Qt и кодов ошибок, насколько я опмню, тоже нет.

Они сами говорят вот так:

Qt itself will not throw exceptions. Instead, error codes are used. In addition, some classes have user visible error messages...

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