Exception handlers can take up quite a lot of space. This tends to obscure the regular path of a function and makes the source code difficult to read.The reason why I used nested exceptions handlers above is because Java has a unique feature to help check whether exceptions handlers are missing. Other languages don’t have such feature, so we don’t have to confine our exception handling to within a single function. Below is the same example, but in C++:
static void throwConvertedExceptionFromActuallySaveToFile(const UnrecoveredException &i_exception, const fs::path i_temporaryFile) { try { throw i_exception; } catch (const UnrecoveredException_IO &c_exception) { if (fs::exists(i_temporaryFile)) { throw c_exception; } else { throw RecoveredException_IO(c_exception, i_temporaryFile); } } } static void throwConvertedExceptionFromMove(const RecoveredException &i_exception, const fs::path &i_temporaryFile) { try { throw i_exception; } catch (const RecoveredException_NoSuchFile &c_exception) { throw RecoveredException_IllegalStorageManipulation(c_exception, i_temporaryFile); } catch (const RecoveredException_SourceNotFile &c_exception) { throw RecoveredException_IllegalStorageManipulation(c_exception, i_temporaryFile); } catch (const RecoveredException_DirectoryNotEmpty &c_exception) { throw RecoveredException_IllegalStorageManipulation(c_exception, i_temporaryFile); } } static void throwConvertedExceptionFromMove(const UnrecoveredException &i_exception, bool i_destinationFileExisted, const fs::path &i_destinationFile) { try { throw i_exception; } catch (const UnrecoveredException_IO &c_exception) { if (i_destinationFileExisted) { throw c_exception; } else { fs::remove(i_destinationFile); if (fs::exists(i_destinationFile)) { throw c_exception; } else { throw RecoveredException_IO(c_exception, c_exception.getFilesCausingException()); } } } } /** * Save to file. * * @throws RecoveredException_CannotWriteToDirectory Cannot specify directory for i_filename. * @throws RecoveredException_IO An I/O exception occurred during file manipulation. * @throws RecoveredException_CannotWriteToFile Cannot write to i_filename for some reason. * @throws RecoveredException_IllegalStorageManipulation Files that should not have been touched were manipulated by an external entity during processing. * @throws UnrecoveredException_IO An I/O exception occurred, and storage may not have been recovered. */ void MakeFile::saveToFile(const String &i_filename) const { m_logger.arguments("MakeFile", __func__, i_filename); fs::path destinationFile(i_filename); bool destinationFileExisted = fs::exists(destinationFile); if (fs::is_directory(destinationFile)) { throw RecoveredException_CannotWriteToDirectory(nullptr, destinationFile); } fs::path temporaryFile = FileManipulation::createTempFile("MakeFile", ".tmp"); // throws RecoveredException_IO try { actuallySaveToFile(temporaryFile); } catch (const UnrecoveredException &c_exception) { remove(temporaryFile); throwConvertedExceptionFromActuallySaveToFile(c_exception, temporaryFile); } try { FileManipulation::move(temporaryFile, destinationFile); } catch (const UnrecoveredException &c_exception) { fs::remove(temporaryFile); throwConvertedExceptionFromMove(c_exception, destinationFileExisted, destinationFile); } catch (const RecoveredException &c_exception) { throwConvertedExceptionFromMove(c_exception, destinationFile); } } See that the exception conversion part of the exception handlers has been extracted to functions outside of MakeFile::saveToFile() [i]. In effect, this takes out the clutter from the source code, so that the reader can focus on the regular path first. [i] If you don’t like using catch blocks this way, you can, of course, use if statements instead.
|
Contents |