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

Background

Review of Exception Handling Basics

The Requirements

The Three-tiered Exception Handling Architecture

Summary