LINUX.ORG.RU

Хороший тон в бросании исключений

 , ,


0

1

Сабж. Когда лучше бросать исключения самому (причем здесь два варианта: с использованием своего типа либо системного), а когда уповать на рантайм?

Первый простой пример: функция f(a, b, ...), где a не может быть равно нулю (на него происходит деление). Что лучше, ввести свое исключение и бросить его, бросить исключение деления на ноль или же оставить бросание исключения системе?

Второй пример: f(a, b, ...), где a принадлежит (0, 1) {допустим мы в теле делаем log(1-a) + log(a)}. Что делать в этом случае?

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

Считаем, что все функции — часть публичного API некой библиотеки.

★★★★★

Последнее исправление: beastie (всего исправлений: 6)

Наверное, лучше самому. Так как исключение dbz хуже, чем illegal argument бла-бла-бла. Потому-что первое отсылает к реализации func.

anonymous
()

Первый простой пример: функция f(a, b, ...), где a не может быть равно нулю (на него происходит деление). Что лучше, ввести свое исключение и бросить его, бросить исключение деления на ноль или же оставить бросание исключения системе?

зависит от глубины абстракции. если функция называется divide_by - бросай стандартное. если функция называется apply_parameter - бросай свое. ну или стандартное ValueError вместо ZeroDivisionError

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

python

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

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

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

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

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

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

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

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

На счёт лапши не уверен, если разбивать всё по феншую, то и не очень-то и запутанно будет, особенно, если запилить свою иерархию исключений.
Как обычно придётся выбрать срединный путь.
// Перехватывать в обёртке и транслировать дальше: oh no something went wrong ;_;

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

Ну, лапшу можно собрать в клубочек в одном месте.

А вообще, с таким отношением к обработке ошибок («мне лень проверять все инварианты самому») лучше глянуть на то, как эти ошибки собрались обрабатывать. Если там

catch (const std::runtime_error& e)
{
    Logging::log() << "shit happens" << e.what() << /* важные аргументы вызова */;
    throw AskUserToRecheckInputAndTryAgain();
}
или аналогичное, то пофиг же, что там за исключение.

ilammy ★★★
()

К этому думается важная вещь - совместимость API. Если ты допишешь в новой весии API к методу выброс checked exception, то если клиент юзает сорцы, то ему придется пересобирать код. А если бинарник - то не придется, но тогда этот экзепшен долетит до какого-нибудь пылесборника, и там обработается, и непонятно что при этом случится. То есть использование экзепшена может быть лишним головняком, и может быть стоит там вручную всё проверить, и вернуть ошибку/результат просто через return, используя Maybe или что-то такое.

stevejobs ★★★★☆
()

Бросай своё, аргументы проверяй в самом начале функции чтобы упасть как можно раньше.

допустим мы в теле делаем log(1-a) + log(a)}. Что делать в этом случае?

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

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

По поводу того что «юзеры идиоты». Чем меньше грабель и проще отлаживать тем лучше, вне зависимости от того какой у юзеров уровень.

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

если клиент ловит std::exception/std::runtime_error/std::logic_error, а ты все свои макароны наследуешь от него, то все будет ок и без пересборки

я за «с использованием своего типа»

Stil ★★★★★
()

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

Если ошибку не нужно обрабатывать, т.е. нужна проверка состояний которых не должно возникнуть в штатном режиме или после ошибки программа дальше работать не может, то нужно пользоваться assert'ами или abort'ами.

Попробую понять через libastral.so чего хочется:

Первый... Второй

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

Пример третий: читаем ... коллекцию данных

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

mashina ★★★★★
()
{error, _Reason}

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

Иногда имеет смысл что-то наподобие ORA-9999...

anonymous
()

Когда лучше бросать исключения

никогда.

Первый простой пример: функция f(a, b, ...), где a не может быть равно нулю (на него происходит деление). Что лучше, ввести свое исключение и бросить его, бросить исключение деления на ноль или же оставить бросание исключения системе?

менять алгоритм. Проблема вот в чём, если a _точно_ ноль, то всё не очень плохо, ты сам описал варианты, а если _не_ точно? Подумай, что у тебя случится? Если не понял, поставь эксперимент.

Ну а теперь реши, ОТКУДА у тебя взялся (почти)ноль?

Простой пример, вычисление углового коэффициента прямой в 2D. Математикам достаточно одной формулы, а нам нужно ДВЕ. Либо dx/dy, либо dy/dx. Тогда не будет не только деления на ноль, но и потерь точности при приближении к нулю.

Второй пример: f(a, b, ...), где a принадлежит (0, 1) {допустим мы в теле делаем log(1-a) + log(a)}. Что делать в этом случае?

см. выше.

emulek
()

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

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

ОТКУДА у тебя взялся (почти)ноль?

Какой-нибудь умник вызвал мою функцию с нулем. Поменять алгоритм я не могу в данном случае.

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

Зависит от ошибки. Если ошибка лучше описывается на твоём уровне(не выше и не ниже), то кидай своё. Если не можешь ничего более конкретного написать - оставляй системное. Как-то так.

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

Гайд для обезьян не является хорошим примером.

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

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

anonymous
()

На разных языках может быть принято по разному...Например, в python почти всегда бросаются исключения...В плюсах это довольно реже происходит... А вообще, выброс исключения всегда сопровождается конструированием объекта и раскруткой стека... Все зависит от того, какой объект ты конструируешь при исключении и каково тело исключения... Например, в коде:

try {
   int c = 0;
   b = a / c;
   std::cout << b << std::endl;
   ...
}
catch(Exception &e) {
   ...
}
Лучше исключения не проверять, а сделать проверку через if, так как конструирование объекта более тяжеловесная операция чем операция ветвления..., однако в случае следующего кода, можно задуматься и об исключении:
try {
   for(int i = 0; i < 1000; ++i) {
       int c = get_c_value();
       b = a / c;
       std::cout << b << std::endl;
       ...
   }
}
catch(Exception &e) {
   ...
}
Здесь, если вместо исключения воткунть проверку, то эта проверка выполнится 1000 раз, а в случае исключения, никаких проверок нету, по-этому код будет быстрее... И чем больше число таких итерации, тем правильнее будет выбор в пользу исключений... Вообще, исключения должны ловить ошибки, возникающие в крупных логических блоках, а на более низком уровне лучше искользовать оператор if

energyclab
()

оставить бросание исключения системе?

this

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

Какой-нибудь умник вызвал мою функцию с нулем

Он увидит в дебагере где упало и почему.

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

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

tailgunner ★★★★★
()

обвешивай весь код аннотациями из JSR-305, тогда никаких исключений в рантайме не будет.

maloi ★★★★★
()

Какой рукой, господа, посоветуете хлебать щи? С одной стороны, правой, ибо я правша. С другой стороны, использование левой руки придаст мне некоторую оригинальность поведения и известную долю интересности.

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

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

А ты лови стандартное исключение, а в обработке делай бросок своего. Сильно не обрастет.

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

Лапшу можно вывести в изолированную обертку, которая в остальном коде «условно не показана» :) Это ж С++

slackwarrior ★★★★★
()

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

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

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

Конечно годно. Оно не сильно раздувает код ифэлсами и все возможные ошибки аккуратно складываются в конце блока кода.

Конечно как выше сказал energyclab можно уделить внимание производительности - но это уже зависит от требований к ПО. Если производительность в этом участке кода критична - то можно это дело оптимизировать, сократив читаемость и аккуратность кода.

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

Лучше исключения не проверять, а сделать проверку через if

А что в таком случае должна вернуть функция?

int f(a, b)
{
  return a/b;
}

f(1, 0);

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

обвешивай весь код аннотациями из JSR-305, тогда никаких исключений в рантайме не будет.

Status: Dormant

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

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

slackwarrior ★★★★★
()

Это зависит многих факторов, даже в твоих примерах нельзя точно сказать, поэтому распишу только по первому примеру что да как.

Во-первых, от языка и как там принято делать - в Java лучше кинуть исключение illegal argument или положиться на системный эксепшн при делении на 0, в джавадоке обязательно прописать жирным шрифтом, что второй параметр не 0. В плюсах зависит от того, приняты ли в проекте исключения и их использование должно быть регламентировано, как правило. Если не приняты, то можно ассертом выкидывать из программы или проверять второй аргумент на 0 и возвращать -1 в результате, если функция позволяет, конечно. Про питон не знаю.

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

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

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

зависит

Дописал в ОП:

Считаем, что все функции — часть публичного API некой библиотеки.

Мысль понял

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

Пока сам склоняюсь к такому способу. Просто хочется узнать, как это принято делать в C++-way, Java-way, C#-way, Python-way, etc.

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

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

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

А что в таком случае должна вернуть функция?

Функция должна бросить SIGFPE и ничего не возвращать, очевидно

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

anonymous
()

Первый простой пример: функция f(a, b, ...), где a не может быть равно нулю (на него происходит деление). Что лучше, ввести свое исключение и бросить его, бросить исключение деления на ноль или же оставить бросание исключения системе?

Не нужно никаких кастомных исключений.

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

redixin ★★★★
()

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

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

Status: Dormant

это не мешает его поддерживать тому, кому надо.

maloi ★★★★★
()

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

для начала хватит 2 простых случаев:

1. чтобы не быдлокодить функции с такими сигнатурами: bool Calculate(const T &param, U &result);

2. если не можешь обработать ошибку в месте её возникновения или в вызывающей функции — кидай исключение чтобы не протаскивать её наверх руками.

осилишь это — твои программы сразу станут более мягкими и шелковистыми.

своя иерархия исключений vs наследование от стандартной — холивар из серии где-то рядом с пробелами и табуляциями.

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

Какой-нибудь умник вызвал мою функцию с нулем. Поменять алгоритм я не могу в данном случае.

проблемы умника. Лучше уж предусмотреть NaN, чем бросать исключения.

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

Лучше исключения не проверять, а сделать проверку через if, так как конструирование объекта более тяжеловесная операция чем операция ветвления...

кстати не всегда. Ветвления НЕ предсказываются, т.к. почти никогда НЕ выполняются. А объект можно собрать и на стеке/в регистре. Там же немного надо, одного слова хватит.

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