LINUX.ORG.RU

История изменений

Исправление 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 сбойнувшая функция перезапускается целиком, а у нас выполнение продолжается с места сбоя.