поведайте плиз как же грамотнее всего работать с исключениями, чтобы собирать наиболее полную информацию об ошибке
желательно, примеры приводить в C++, но в принципе, проблема актуальна для всех языков с исключениями (возможность вставлять в ошибку стек-трейс не рассматриваю, т.к. считаю, что для юзера это крайне вредная информация. никакой инфы о коде в ошибке не должно быть, только полезные читаемые данные, чтобы юзер не обращался в саппорт)
представим ситуацию:
remote console -> {network} -> RemoteFacade::GetUser(id) -> UserStorage->TakeUser(id) -> OpenDatabase(filename) -> OpenFile(filename) {Exception: File not found)
итак покажите, как составить для юзера (remote console) подробное описание ошибки
варианты решения
1
IDatabase OpenDatabase(filename)
{
throw FormatError("Cannot open database due to win32 error %d", GetLastError());
}
//нигде нет catch кроме RemoteFacade
RemoteFacade::GetUser(id)
{
try
{
user = UserStorage->TakeUser(id);
Connection->SendReply(MakeReplyForUser(user)); //тоже может швырнуть, но это не очень важно в моем вопросе
}
catch (const std::exception& e)
{
Connection->SendReplyError(e);
}
}
//в итоге remote console получит скудную ошибку: "Cannot open database due to win32 error 17, file not found"
2
RemoteFacade::GetUser(id)
{
try
{
user = UserStorage->TakeUser(id);
Connection->SendReply(MakeReplyForUser(user));
}
catch (const std::exception& e)
{
Connection->SendReplyError(FormatError("Problem has occured during remote request processing. Details: %s", e.what());
}
}
IUserStorage::TakeUser(id)
{
try
{
db = OpenDatabase(filename);
}
catch (const std::exception& e)
{
throw FormatError("Failed to take user. Details: %s", e.what());
}
}
IDatabase OpenDatabase(filename)
{
throw FormatError("Cannot open database due to win32 error %d", GetLastError());
}
//В этом случае клиент получит уже более подробную ошибку:
Problem has occured during remote request processing. Details: Failed to take user. Details: Cannot open database due to win32 error 17, file not found
3
class MyError : public std::exception
{
MyError& SetChild(MyError child)
{
//deep copy of child
.....
return *this;
}
....
};
MyError MakeError(.....);
RemoteFacade::GetUser(id)
{
try
{
user = UserStorage->TakeUser(id);
Connection->SendReply(MakeReplyForUser(user));
}
catch (const MyError& e)
{
Connection->SendReplyError(MakeError("Problem has occured during remote request processing.").SetChild(e));
}
}
IUserStorage::TakeUser(id)
{
try
{
db = OpenDatabase(filename);
}
catch (const MyError& e)
{
throw MakeError("Failed to take user.").SetChild(e);
}
}
IDatabase OpenDatabase(filename)
{
throw MakeError("Cannot open database due to win32 error %d.", GetLastError());
}
В этом случае клиенут можно промаршaлить всю MyError (цепочка ошибок) и он с разной степенью подробности может показать это (доверить ему форматирование).
буду рад советам и ссылкам. уверен, что подобные вопросы уже обсуждались.
прошу не придираться к коду, основную идею вроде отразил
спасибо
впрочем если мы добавляем трассу к любому исключению, то в catch() надо писать еще и std::exception (вдруг откуда-то прилетит) что и оно не осталось без трассы
или более хитрый вариант
class traced_exception;
struct with_tracing
{
const char* lastAction; // const char* чтобы не создавать оверхед,
// при этом можно присваивать только строковые литералы, что в общем-то не всегда хорошоtemplate<typename F>
void wrap(const char* ctx, F f)
{
try
{
lastAction = ctx;
f();
lastAction = nullptr;
}
catch(traced_exception& e)
{
if(lastAction)
e.add_trace(lastAction);
throw;
}
catch(std::exception& e)
{
throw traced_exception(e, lastAction );
}
}
};
class Foo : ..., with_tracing
{
public:
void foo()
{
wrap("doing foo", [this]{
...
lastAction = "do another";
...
});
}
};
Сдается мне, что вопрос все-таки не об исключениях, а об обработке ошибок как таковой.
Исключения — просто удобное средство.
В момент обработки исключения у тебя есть вся необходимая информация (во всяком случае — теоретически). Полезна ли она юзеру... и полезна ли она вообще — потом разберемся.
Вариант, когда ты сам же можешь обработать ошибку, например, подключившись к резервной базе — не рассматривается.
Итка, мы имеем:
1. Юзер может исправить ошибку сам. — Какая информация ему для этого нужна? Уж что-что, а код ошибки ему даром не нужен . Да и вобще, по условиям игры, юзер и не подозревает о наличии абсолютного большинства компонентов твоей программы.
Возможно, достаточно просто объяснить, что проблема где-то там — далеко-далеко.
2. Юзер может обратиься к сисадмину/хелпдеск/саппорт разработчика (нужное подчеркнуть). Опять таки, какая информация нужна им? Детальное описание ошибки должно подойти. Здесь — логи наше все.
3. Юзер уже обращался и к админу, и в саппорт, и даже к другу — гуру программирования на html. Похоже, что последняяя инстанция — ты. Что тебе нужно что бы решить проблему? Э-э-э... и кстати, а где ты это хранил???
Надеюсь, что навел на нужные мысли
Удачи!
Здравствуйте, DGurin, Вы писали:
DG>1. Юзер может исправить ошибку сам. — Какая информация ему для этого нужна? Уж что-что, а код ошибки ему даром не нужен .
как раз наоборот.
юзеру нужен код ошибки (а лучше текстовое описание),
а вот список вложенных вызовов — ему не нужен
т.е. ошибки "файл не найден", "файл занят", "нет прав для доступа к файлу" — юзер может и сам поправить, если будет знать код (описание) ошибки
Здравствуйте, Abyx, Вы писали:
A>т.е. ошибки "файл не найден", "файл занят", "нет прав для доступа к файлу" — юзер может и сам поправить, если будет знать код (описание) ошибки
Вот с этого места поподробнее пожалуйста
Как юзер будет исправлять эти ошибки? И если мне не изменяет память — на удаленной машине.
Разбираться почему "нет прав" — прийдется все-таки тому, кто эти самые права раздает.
А если "файл занят" — то вопрос к программисту, а чем это критичный для работы юзера файл так занят и уже 3-ий час подряд...
А вобще конечно да — в разных программах эти ошибки будут адресоваться на разные уровни.
Да и в одной для разных файлов — скорее всего тоже.
Здравствуйте, DGurin, Вы писали:
DG>В момент обработки исключения у тебя есть вся необходимая информация (во всяком случае — теоретически). Полезна ли она юзеру... и полезна ли она вообще — потом разберемся.
вопрос как именно собрать достаточно подробную информацию об ошибке, чтобы юзер понимал что происходит и предпринял ряд подходящих шагов, направленных на решение
по стоимости возможные решения можно распределить так:
1) сам решит проблему
2) обратиться к админу с ошибкой
3) обратиться к админу, предоставив ему еще и логи
4) обратиться к саппорт компании, которая продала продукт (самый дорогой и долгий метод) (возможно, обращение все же админ будет делать, а не юзер)
я предложил два решения с разной подробностю ошибок
1) "Cannot find file"
2) "Failed to open database. Details: cannot find file"
в плане имплементации первый вариант делается легко : пишем только throw, а catch пишем лишь на самом верху
во втором случае catch надо писать уже много где, чтобы цеплять информацию о контексте выполнении. неясно как дешево это сделать (чтобы и времени много не уходило и код не перемешивался с ценной логикой)
Здравствуйте, uzhas, Вы писали:
U>я предложил два решения с разной подробностю ошибок U>1) "Cannot find file" U>2) "Failed to open database. Details: cannot find file"
Мое ИМХО — в данном случае, юзеру "что в лоб, что по лбу" .
Он не может сделать то, что хотел. Он хотел залогиниться на удаленный хост?
Он не может этого сделать по вине того самого хоста.
Никакие файлы базы ему и в страшном сне не присняться.
То, что программа не смогла открыть файл базы — это будет интересно админу.
Ну, может продвинутым юзерам это тоже будет интересно, но легче от этого знания ни им, ни вобщем-то и админу не станет. Почему? Да вобщем-то потому, что программа не может найти свой собственный файл. Только ты в момент написания (ну или по факту баг-репорта ) можешь решить как это обрабатывать.
U>в плане имплементации первый вариант делается легко : пишем только throw, а catch пишем лишь на самом верху U>во втором случае catch надо писать уже много где, чтобы цеплять информацию о контексте выполнении. неясно как дешево это сделать (чтобы и времени много не уходило и код не перемешивался с ценной логикой)
Лично я склоняюсь ко второму варианту, где все исключения внутри компонента обрабатываются на его границе (пока мы знаем максимум о контексте). Что нельзя обработать — добываем контекст и пускаем дальше новое исключение — дабы не плодить развесистые catch-блоки.
U> try
U> {
U> user = UserStorage->TakeUser(id);
U> Connection->SendReply(MakeReplyForUser(user)); //тоже может швырнуть, но это не очень важно в моем вопросе
U> }
catch(Использовать конкретно объект который был отправлен, напреимер oledbexception){
В нём будет максимально подробные данные об исключении
}
U> catch (const std::exception& e)
U> {
U> Connection->SendReplyError(e);
U> }
U>}
Здравствуйте, uzhas, Вы писали:
U>возможность вставлять в ошибку стек-трейс не рассматриваю, т.к. считаю, что для юзера это крайне вредная информация. никакой инфы о коде в ошибке не должно быть, только полезные читаемые данные, чтобы юзер не обращался в саппорт
Тред не читал.
Всю полезную для исправления ошибки инфу я бы кинул в какой-нибудь error.log а пользователю вывел красивое окошко с понятным текстом ошибки и кнопочкой с помощью которой можно анонимно без регистрации отправить информацию об окружении пользователя и содержание error.log разработчикам, при наличии соединения с интернетом.
Для нас [Thompson, Rob Pike, Robert Griesemer] это было просто исследование. Мы собрались вместе и решили, что ненавидим C++ [смех].