История изменений
Исправление uuwaan, (текущая версия) :
А если тебе надо структурировать эту информацию в несколько полей? :-) Парсер писать изволишь, чтобы в catch им парсить?
Я не имел в виду только строку. Если строки от what() недостаточно, для своих исключений делаем базовый класс не std::exception, а myexception со всеми полями, необходимыми логгеру. А для чужих исключений, которые от myexception не наследуются, один фиг нужно писать код, который из их полей будет делать наши поля. Я бы сделал перегруженную функцию convert(their_exception) -> myexception.
Вот нужно знать точную причину, по которой не выполнилась транзакция в функции f(): сервер недоступен, переданы недопустимые данные, транзакция уже существует... :-) Каждая ситуация обрабатывается по-разному :-)
Значит f() у нас будет принимать интерфейс ISolverForF с 3мя методами: onServerError(), onDataError(), onTransactionError(). Для удобства можно сделать класс, который с этим интерфейсом умеет хавать лямбды по одной на каждый случай, чтобы код не уносить далеко от вызова f(). Это если нам нужна какая-то нетривиальная реакция на эти ошибки. А если просто залогировать, то см выше: myexception со всем готовым и осознание факта, что f() не смогла.
Теперь представь, что от этой функции зависит ещё одна функция g()... Теперь ты вызываешь g(), в которую ты должен будешь передать уже теперь 2 решателя для f() и для g()?
g() принимает только решатель для g(). Те проблемы f(), которые g() может решить самостоятельно, она в интерфейс ISolverForG не выставляет. А те, которые решить не может, дублирует в SolverForG, делегируя их решение выше.
Количество обрабатывающего кода в принципе не меняется. В случае с исключениями было бы так:
main()
bool didit;
try {
didit = g();
}
catch (g_exception1 from_g) {
}
catch (g_exception2 from_g) {
}
catch (f_exception1 not_handled_by_g) {
}
catch (f_exception2 not_handled_by_g) {
}
catch (// все остальные исключения, которые хотим логировать) {
log_exception(get_log_info(e));
didit = false;
}
}
g() {
try {
f()
}
catch (f_exception3 handled_by_g) {
}
catch (f_exception4 handled_by_g) {
}
}
f() {
...
call_some_lib_fun();
...
}
А с коллбэками:
main() {
SolverForG gs;
gs.on_g_error1 = [](){...};
gs.on_g_error2 = [](){...};
gs.on_f_error1 = [](){...};
gs.on_f_error2 = [](){...};
bool didit;
try {
didit = g(gs);
}
catch(myexception e) {
// "левые" исключения ловим по местам и конвертируем в myexception, throw myexception{get_log_info(e)};
log_exception(e);
didit = false;
}
}
g() {
SolverForF fs;
fs.on_f_error1 = gs.on_f_error1;
fs.on_f_error2 = gs.on_f_error2;
fs.on_f_error3 = [](){...};
fs.on_f_error4 = [](){...};
bool didit = f(fs);
}
f() {
...
try {
call_some_lib_fun();
} catch(// исключения от используемых не наших ф-ций, которые хотим логировать) {
throw myexception{get_log_info(e)};
} catch(// исключения от используемых не наших ф-ций, которые хотим обрабатывать) {
call_appropriate_solver_method();
}
}
Обрабатывающий и логирующий код прежний, только перераспределился по-другому. Стало видно из кода, обработку каких ситуаций каждая функция делегирует более высокому слою. Плюс наведён порядок: ситуации, которые мы намерены обрабатывать, мы обрабатываем не используя исключения. Исключения используем только чтобы залогировать нежданчик.
механизм исключений из языка Eiffel
Только там при указании retry сбойнувшая функция перезапускается целиком, а у нас выполнение продолжается с места сбоя.
Исходная версия uuwaan, :
А если тебе надо структурировать эту информацию в несколько полей? :-) Парсер писать изволишь, чтобы в catch им парсить?
Я не имел в виду только строку. Если строки от what() недостаточно, для своих исключений делаем базовый класс не std::exception, а myexception со всеми полями, необходимыми логгеру. А для чужих исключений, которые от myexception не наследуются, один фиг нужно писать код, который из их полей будет делать наши поля. Я бы сделал перегруженную функцию convert(their_exception) -> myexception.
Вот нужно знать точную причину, по которой не выполнилась транзакция в функции f(): сервер недоступен, переданы недопустимые данные, транзакция уже существует... :-) Каждая ситуация обрабатывается по-разному :-)
Значит f() у нас будет принимать интерфейс ISolverForF с 3мя методами: onServerError(), onDataError(), onTransactionError(). Для удобства можно сделать класс, который с этим интерфейсом умеет хавать лямбды по одной на каждый случай, чтобы код не уносить далеко от вызова f(). Это если нам нужна какая-то нетривиальная реакция на эти ошибки. А если просто залогировать, то см выше: myexception со всем готовым и осознание факта, что f() не смогла.
Теперь представь, что от этой функции зависит ещё одна функция g()... Теперь ты вызываешь g(), в которую ты должен будешь передать уже теперь 2 решателя для f() и для g()?
g() принимает только решатель для g(). Те проблемы f(), которые g() может решить самостоятельно, она в интерфейс ISolverForG не выставляет. А те, которые решить не может, дублирует в SolverForG, делегируя их решение выше.
Количество обрабатывающего кода в принципе не меняется. В случае с исключениями было бы так:
main()
bool didit;
try {
didit = g();
}
catch (g_exception1 from_g) {
}
catch (g_exception2 from_g) {
}
catch (f_exception1 not_handled_by_g) {
}
catch (f_exception2 not_handled_by_g) {
}
catch (// все остальные исключения, которые хотим логировать) {
log_exception(get_log_info(e));
didit = false;
}
}
g() {
try {
f()
}
catch (f_exception3 handled_by_g) {
}
catch (f_exception4 handled_by_g) {
}
}
f() {
...
call_some_lib_fun();
...
}
А с коллбэками:
main() {
SolverForG gs;
gs.on_g_error1 = [](){...};
gs.on_g_error2 = [](){...};
gs.on_f_error1 = [](){...};
gs.on_f_error2 = [](){...};
bool didit;
try {
didit = g(gs);
}
catch(myexception e) {
// "левые" исключения ловим по местам и конвертируем в myexception,
throw myexception{get_log_info(e)};
log_exception(e);
didit = false;
}
}
g() {
SolverForF fs;
fs.on_f_error1 = gs.on_f_error1;
fs.on_f_error2 = gs.on_f_error2;
fs.on_f_error3 = [](){...};
fs.on_f_error4 = [](){...};
bool didit = f(fs);
}
f() {
...
try {
call_some_lib_fun();
} catch(// исключения от используемых не наших ф-ций, которые хотим логировать) {
throw myexception{get_log_info(e)};
} catch(// исключения от используемых не наших ф-ций, которые хотим обрабатывать) {
call_appropriate_solver_method();
}
}
Обрабатывающий и логирующий код прежний, только перераспределился по-другому. Стало видно из кода, обработку каких ситуаций каждая функция делегирует более высокому слою. Плюс наведён порядок: ситуации, которые мы намерены обрабатывать, мы обрабатываем не используя исключения. Исключения используем только чтобы залогировать нежданчик.
механизм исключений из языка Eiffel
Только там при указании retry сбойнувшая функция перезапускается целиком, а у нас выполнение продолжается с места сбоя.