Гость
Целевая тема:
Создать новую тему:
Автор:
Форумы / C++ [игнор отключен] [закрыт для гостей] / C++ exceptions. Best practices. / 25 сообщений из 82, страница 1 из 4
28.01.2015, 17:59
    #38865800
mayton
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
C++ exceptions. Best practices.
Коллеги!

Давайте обсудим C++ exceptions.

1) Best practices.
2) Антипаттерны.

В рамках продолжения Идеология С++ и warnings
...
Рейтинг: 0 / 0
28.01.2015, 18:20
    #38865831
Basil A. Sidorov
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
C++ exceptions. Best practices.
Исключения, как уже отмечали, появились не в плюсах и появились они для того, чтобы отделить "рабочий поток исполнения" и "обработку ошибок".
Можно имитировать через setjump/longjump, но возникают проблемы с деструкторами объектов: всё, что компилятор трудолюбиво заготовил на стеке - идёт лесом.
Можно имитировать "циклом с прерыванием" - будет минимальное "замусоривание" кода тривиальными вставками вида:
Код: sql
1.
if (error) break;

Насколько я понимаю, в обоих вариантах будут проблемы с многопоточным кодом.
"Как-то так".
...
Рейтинг: 0 / 0
28.01.2015, 18:40
    #38865848
Basil A. Sidorov
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
C++ exceptions. Best practices.
...
Рейтинг: 0 / 0
28.01.2015, 18:46
    #38865858
mayton
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
C++ exceptions. Best practices.
Добавлю еще пункт.

3) Неперехваченный exception в конструкторе. Будет-ли создан объект?
...
Рейтинг: 0 / 0
28.01.2015, 18:49
    #38865863
Basil A. Sidorov
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
C++ exceptions. Best practices.
"Выполнение кода остановится в точке генерации исключения. Деструкторы не вызываются, если конструктор не отработал до конца" - не дословная цитата Скотта Мейерса.
...
Рейтинг: 0 / 0
28.01.2015, 19:55
    #38865927
White Owl
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
C++ exceptions. Best practices.
За что я не люблю исключения: 99.(9)% людей не умеют их использовать.

В большинстве учебников исключения объяснены на примерах типа такого:
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
try {
   File f;
   f.open(somepath);
   string contents = f.readAll();
   f.close();
} catch (Exception e) {
   message("не смогли прочитать файл");
   if(f.isOpen()) f.close();
}


Все мило и замечательно, четко, понятно и вообще красиво.
Но после этого, в реальном коде люди постоянно пишут текст типа:
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
try {
   File f;
   f.open(somepath);
   string contents = f.readAll();
   f.rewind();
   ///  делаем что-то с contents
   f.writeFile(contents);
   f.close();
} catch (Exception e) {
   message("не смогли обновить файл");
   if(f.isOpen()) f.close();
}

И потом начинают кручиниться: "А как узнать почему именно упали? Мы не смогли найти файл? Не смогли прочитать его? Не смогли записать? Или произошло что-то еще не связанное с файлом?"
Вот вам конкретный пример такого человеческого поведения: 17179694 . Дмитрий застрял именно на том, что у него есть несколько однотипных операций, все они способны выкинуть одно и то-же исключение. Но ему надо понять какая конкретно операция упала, а он не может этого сделать. Почему не может, хотя метод решения лежит на поверхности? Потому что он привык что в try{}catch блоке находится несколько операторов. И обработка ошибок ВСЕГДА идет по окончанию блока. Ну вот привык человек и все.
Как дополнительный пример этой же самой проблемы, можно ткнуть пальцем в статью Завалишина опубликованную аж в 97-ом, и до сих пор еще читаемую: 17185154 . В статье много эмоций, но... Берем самое начало статьи "Как это делают в С++". Завалишин предлагает два примера (номера 4 и 5) в качестве иллюстрации как удобно с исключениями и как не удобно без них. И на первый взгляд он прав - код с исключениями короче... но.... если чуть-чуть приглядеться, то видно что примеры разные и решают разные задачи по разным алгоритмам. Причем работа надо ошибками в пятом примере более качественная чем в четвертом примере.


Вторая гигантская проблема исключений это то что их можно перекидывать на уровень выше:
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
foo1() { ... }
foo2() {
   foo1();
}
foo3() {
   foo1();
   foo2();
}
foo4() {
  try {
     foo3();
  } catch(ExceptionFromFoo1 e) {
     message("Died in foo1()");
  }
}

А теперь попробуйте сказать, какой из вызовов foo1() упал. Тот который был вызван напрямую из foo3() или тот который вызывался через посредничество foo2()?
Примеров подобных вопросов на форуме я сейчас с ходу не нашел, но все кто занимается поддержкой существующего софта встречались с этой проблемой.


Вы можете спросить: ну так а что мешает разрезать пример с обновлением файла на три try{}catch блока? И что мешает написать foo2() во втором примере так чтобы он сам ловил исключение в foo1() и кидал в свою очередь более информативное исключение? Ответ: ничего не мешает кроме привычек.
Человек привыкший к работе с кодами возврата всегда думает в каком логическом блоке он находится. И с самого начала обработает ошибку по месту ее возникновения. При работе с кодами возврата мы вынуждены думать где мы сейчас находимся и что надо делать в случае потенциальной проблемы.
А у человека привыкшего к исключениям такого навыка просто нет. В учебнике сказано что обработку ошибок можно делать в самом конце? Значит мы всегда будем ее делать в самом конце и не важно правильно ли это.



Да, используя исключения можно написать хорошую и качественную программу. Теоретически это возможно. Но к сожалению, в реальности это практически не встречается.
Для правильной работы с исключениями надо понимать что они работают не так как думает человек. Не просто знать, а понимать, чувствовать, принять как дыхание. Мы, люди, когда пишем в программе:
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
baz() {
  try {
   call1();
   call2();
   call3();
  }catch(Exception e) {...}
}

Мысленно проговариваем про себя: "вызвали call1(), если упали идем в блок catch, потом вызвали call2(), если упали идем в блок catch, и наконец вызвали call3(), если упали идем в блок catch." И мы думаем что это все именно так и происходит.
Но к сожалению, это вовсе не так. На самом деле этот код означает "Если мы попали в catch, значит какой-то кусок, какой-то из функций не отработал. Причем мы не знаем конкретно какая из функций call1, call2 или call3 не доработала до конца и в какой степени эта функция не отработала до конца".
Если вы сумеете перестроить свое сознание на правильное понимание что такое исключение - вы сможете писать хорошие программы с ними. Практика показывает что таких людей на свете единицы.
...
Рейтинг: 0 / 0
28.01.2015, 20:34
    #38865946
mayton
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
C++ exceptions. Best practices.
White Owl, в данном примере хотелось-бы чтобы компиллятор сделал

Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
baz() {
  try {
   line:=__LINE__;
   call1();   
   line:=__LINE__;
   call2();
   line:=__LINE__;
   call3();
  }catch(Exception e) {
     message("Died after "+line);
  }
}


Или что-то в этом роде.
...
Рейтинг: 0 / 0
28.01.2015, 20:59
    #38865961
Anatoly Moskovsky
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
C++ exceptions. Best practices.
White OwlЗа что я не люблю исключения: 99.(9)% людей не умеют их использовать.
Ггггггг
Гигантская проблема исключений то что эти 99.9% считают что уж они то умеют. И потом по форумам рассказывают небылицы.
Например про то, что то, ради чего исключения и были придуманы, и без чего они вообще никому не нужны, оказывается - это гиганская проблема:
White OwlВторая гигантская проблема исключений это то что их можно перекидывать на уровень выше
Еще раз повторю то что писал уже в том топике другими словами.
Чем дальше уровень вложенности где ловятся исключения от уровня где они бросаются, тем правильнее исключения используются.
И верхним уровням никогда не требуется знать где конкретно произошло исключение нижнего уровня.
Если вам требуется - то у вас дизайн программы сделан через одно место.
...
Рейтинг: 0 / 0
28.01.2015, 21:06
    #38865968
Anatoly Moskovsky
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
C++ exceptions. Best practices.
mayton
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
baz() {
  try {
   line:=__LINE__;
   call1();   
   line:=__LINE__;
   call2();
   line:=__LINE__;
   call3();
  }catch(Exception e) {
     message("Died after "+line);
  }
}


Вот это пример антипаттерна. Когда ловля исключений используется только для отладки.
Для этого вообще то логирование существует.
Данный код должен выглядеть так:
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
baz() {
   logger.trace("call1");
   call1();   
   logger.trace("call2");
   call2();
   logger.trace("call3");
   call3();
}


Никаких try catch тут не надо вообще.
...
Рейтинг: 0 / 0
28.01.2015, 21:09
    #38865972
Dima T
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
C++ exceptions. Best practices.
В исключениях есть минусы:
1. нижний уроверь имеет больше конкретики о причине исключения.
2. освобождение ресурсов ОС. Хотя если обернуть все в классы, то решаемо.
...
Рейтинг: 0 / 0
28.01.2015, 21:36
    #38865979
Anatoly Moskovsky
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
C++ exceptions. Best practices.
Dima T2. освобождение ресурсов ОС. Хотя если обернуть все в классы, то решаемо.
Если используются исключения всегда должно применяться RAII.
Иначе смысла в них нет.
...
Рейтинг: 0 / 0
28.01.2015, 22:39
    #38866009
mayton
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
C++ exceptions. Best practices.
Anatoly MoskovskyВот это пример антипаттерна. Когда ловля исключений используется только для отладки.
Для этого вообще то логирование существует.
Данный код должен выглядеть так:
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
baz() {
   logger.trace("call1");
   call1();   
   logger.trace("call2");
   call2();
   logger.trace("call3");
   call3();
}


Никаких try catch тут не надо вообще.
Ну вроде как по пункту (2) отметились. Повестка двигается.

А теперь вопрос. Что это за объект такой logger? И зачем он мне нужен?
Я не хочу никаких логгеров. Мне выводить - некуда. Нет у меня файловой системы.
Вот такие вот условия.

Может быть такое быть?
...
Рейтинг: 0 / 0
28.01.2015, 23:05
    #38866029
mayton
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
C++ exceptions. Best practices.
2 Белый Сова.

Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
try {
   File f;
   f.open(somepath);
   string contents = f.readAll();
   f.rewind();
   ///  делаем что-то с contents
   f.writeFile(contents);
   f.close();
} catch (Exception e) {
   message("не смогли обновить файл");
   if(f.isOpen()) f.close();
}



Ну... если-б каждый метод подсистемы IO бросал свой уникальный expeption.
Код: plaintext
1.
2.
3.
4.
5.
class openexception : exception{...};
class readallexception : exception{...};
class rewindexception : ....
class writeexception :
class closeexception :



То мы могли-бы ловить в коде некий общий тип исключений и позиционироваться более конкретно. Но
опять-же... если в блоке try мы работаем с двумя файлами.... :(
...
Рейтинг: 0 / 0
28.01.2015, 23:15
    #38866041
White Owl
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
C++ exceptions. Best practices.
maytonWhite Owl, в данном примере хотелось-бы чтобы компиллятор сделал

Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
baz() {
  try {
   line:=__LINE__;
   call1();   
   line:=__LINE__;
   call2();
   line:=__LINE__;
   call3();
  }catch(Exception e) {
     message("Died after "+line);
  }
}


Или что-то в этом роде.Нет. Этого не достаточно.
Падение произошло внутри одной из функций. Мало знать какая функция упала, надо еще знать почему она упала.
Это более-менее хорошо сделано в java, где исключение автоматоматически собирает полный стек вызовов при передаче на верхние уровни. Там уже действительно можно пройти по списку и действительно найти истинный источник ошибки. В С++ это можно сделать и вручную конечно, но это надо делать.
В жизни с кодами возврата конечно все намного труднее потому что там вообще все надо делать вручную. Но в этом и плюс - ты всегда знаешь что если надо ошибку передать наверх, ты будешь ее передавать наверх. Сам. Вручную. Перед этим обдумав. А не просто махнув рукой: "Здесь мы работаем с фигулькой которая может глюкнуть. Но мне лениво сейчас разбираться как это обработать. Выкину исключение, пусть мой коллега который будет работать с этой функцией разбирается". Тот перекидывает исключение выше, а в итоге оно если и обрабатывается, то уже никому не известно откуда оно родилось.

Повторюсь еще раз. С исключениями можно работать, но надо думать что делаешь. Учебники (и форумные гуру) представляют исключение как очень простую и удобную штуку. Как результат: люди используя исключения не пытаются видеть полную картину своей программы.
...
Рейтинг: 0 / 0
28.01.2015, 23:17
    #38866042
White Owl
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
C++ exceptions. Best practices.
Anatoly MoskovskyWhite OwlЗа что я не люблю исключения: 99.(9)% людей не умеют их использовать.
Ггггггг Анатолий, прочитайте мой пост еще раз. И на этот раз не по диагонали, и не ограничиваясь примерами.
...
Рейтинг: 0 / 0
28.01.2015, 23:32
    #38866050
White Owl
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
C++ exceptions. Best practices.
mayton2 Белый Сова.

Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
try {
   File f;
   f.open(somepath);
   string contents = f.readAll();
   f.rewind();
   ///  делаем что-то с contents
   f.writeFile(contents);
   f.close();
} catch (Exception e) {
   message("не смогли обновить файл");
   if(f.isOpen()) f.close();
}



Ну... если-б каждый метод подсистемы IO бросал свой уникальный expeption.
Код: plaintext
1.
2.
3.
4.
5.
class openexception : exception{...};
class readallexception : exception{...};
class rewindexception : ....
class writeexception :
class closeexception :



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

Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
try {
  File f;
  try {
    f.open(somepath);
    string contents = f.readAll();
  } catch (Exception e) {
    message("не смогли прочитать файл");
    throw new ExceptionFailedToUpdate();
  }

  try {
     ///  делаем что-то с contents
  } catch (Exception e) {
    message("не смогли обработать данные");
    throw new ExceptionFailedToUpdate();
  }

  try {
    f.rewind();
    f.writeFile(contents);
    f.close();
  } catch (Exception e) {
    message("не смогли записать файл");
    throw new ExceptionFailedToUpdate();
  }
} catch (ExceptionFailedToUpdate e) {
   message("не смогли обновить файл");
   if(f.isOpen()) f.close();
}


Но... Вот наглядный пример, ты сам. С++ знаешь, с исключениями работать умеешь, но пошел по пути создания множества дополнительных исключений. Почему? Что тебе помешало разбить алгоритм на блоки и сделать для каждого свой обработчик?
Я со своей привычкой к кодам возврата обошелся одним дополнительным, ты сделал пять дополнительных и сам испугался. Почему?
Потому что привык что все исключения обрабатываются в конце. Так в учебниках пишут...
...
Рейтинг: 0 / 0
28.01.2015, 23:41
    #38866057
mayton
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
C++ exceptions. Best practices.
Тогда делаем макрос

Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
#ifdef DETAIL_IO_EX
#define READALL(f,somepath,contents) \
try { \
    f.open(somepath);\
    string contents = f.readAll();\
  } catch (Exception e) {\
    message("не смогли прочитать файл");\
    throw new ExceptionFailedToUpdate();\
  }
#elsif
#define READALL(f,somepath,contents) \
f.open(somepath);\
string contents = f.readAll();
#endif



Хотя возможно до меня этот макрос (класс или шаблон или функцию) уже где-то делали.
...
Рейтинг: 0 / 0
29.01.2015, 00:48
    #38866101
White Owl
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
C++ exceptions. Best practices.
maytonТогда делаем макросНафиг-нафиг. Наша цель же упростить программирование и не потерять гибкости. Макрос, особенно такой, это усложнение. Так что нафиг-нафиг.
...
Рейтинг: 0 / 0
29.01.2015, 07:35
    #38866174
Anatoly Moskovsky
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
C++ exceptions. Best practices.
maytonА теперь вопрос. Что это за объект такой logger? И зачем он мне нужен?
Я не хочу никаких логгеров. Мне выводить - некуда. Нет у меня файловой системы.
Вот такие вот условия.
Ну тогда вам сначала надо применить это условие по отношению к вашей функции message()
...
Рейтинг: 0 / 0
29.01.2015, 07:53
    #38866182
Anatoly Moskovsky
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
C++ exceptions. Best practices.
White OwlАнатолий, прочитайте мой пост еще раз. И на этот раз не по диагонали, и не ограничиваясь примерами.
Ну вот читаю еще раз.
White OwlНо ему надо понять какая конкретно операция упала, а он не может этого сделать.
Это нужно программисту расследующему баги. И это решается логированием, как я выше привел.
А вот зачем самой функции в процессе выполнения это знать?
...
Рейтинг: 0 / 0
29.01.2015, 09:43
    #38866258
egorych
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
C++ exceptions. Best practices.
White OwlЗа что я не люблю исключения: 99.(9)% людей не умеют их использовать.
...
Практика показывает что таких людей на свете единицы.прекрасный спич, но практика показывает, что 99.(9)% вообще не обрабатывают ошибки времени выполнения. Коды возврата технично игнорируются и программа падает в непредсказуемом месте из-за деления на 0 или по защите памяти, к примеру. В большой степени это происходит из-за того, что довольно нудно обрабатывать после каждого вызова этот самый код ошибки. catch в конце блока хотя бы гарантирует, что сломалось где-то здесь, и дальше этого места ошибка не распространится.
...
Рейтинг: 0 / 0
29.01.2015, 09:44
    #38866259
egorych
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
C++ exceptions. Best practices.
Anatoly MoskovskyWhite OwlНо ему надо понять какая конкретно операция упала, а он не может этого сделать.
Это нужно программисту расследующему баги. И это решается логированием, как я выше привел.
А вот зачем самой функции в процессе выполнения это знать?+1. Золотые слова ))
...
Рейтинг: 0 / 0
29.01.2015, 10:24
    #38866306
locked
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
C++ exceptions. Best practices.
Исключения в C++ состоитят из двух частей

1.механизм
2.имплементация в стандартной библиотеке.

Первое сделано хорошо, второе нет.
Можно добавить логирование backtrace() и __LINE__ при генерировании своих исключений.
...
Рейтинг: 0 / 0
29.01.2015, 10:26
    #38866312
OoCc
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
C++ exceptions. Best practices.
egorychAnatoly Moskovskyпропущено...

Это нужно программисту расследующему баги. И это решается логированием, как я выше привел.
А вот зачем самой функции в процессе выполнения это знать?+1. Золотые слова ))

выдать одну ошибку кастомеру "Ваша программа неработает" ?
...
Рейтинг: 0 / 0
29.01.2015, 10:31
    #38866320
mayton
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
C++ exceptions. Best practices.
OoCcegorychпропущено...
+1. Золотые слова ))

выдать одну ошибку кастомеру "Ваша программа неработает" ?
Уже говорили выше. Я-бы добавил что стоит различать просто факт
возникновения ошибки (например SocketException). После этого, метод
выболняющий к примеру softwareupdate молча уходит в ожидание. Мало-ли
что там с сетью. WiFi отвалился.

И второй вариант - это как сказано выше для анализа ошибок разработчиком.
В этом варианте я не просто хотел-бы получить __LINE__ где "стрельнуло"
но и "посмертный снимок" стека.
...
Рейтинг: 0 / 0
Форумы / C++ [игнор отключен] [закрыт для гостей] / C++ exceptions. Best practices. / 25 сообщений из 82, страница 1 из 4
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]