Lately I’ve been working on a several projects, a couple of which are based around a modular framework I’m developing through them. In a move to correct one of the more serious issues with my previous work, I’ve been concentrating on a more defensive programming style and taking more time to write solid applications rather than rushing into getting pretty effects working.
In previous programs I’ve tended towards simply returning boolean variables from functions which would otherwise be void to indicate success or failure, created log files to keep track of problems and used asserts for checking critical components. Ignoring, for now, the fact that I just plain wasn’t using these methods often enough, there are several problems with this approach.
First, it employs three seperate methods for dealing with error handling and recording – I can use each method to relate to the others but this results in several lines of code just dealing with something which may be an error, and could lead to inconsistancy.
The second problem was that not enough information was being passed and recorded. Returning a boolean variable from a function only gives you two options – either the process succeeded or it didn’t – when in fact the situation is often more complicated than that. In particular for offering logging and debug output a more verbose output type becomes useful.
My solution was to create a simple result class encapsulating the information to be passed back from a function, and a seperate handler class to output or log these messages.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
class Result {
public:
Result(ReturnResult _result, std::string _location, std::string _message);
bool success(); //A boolean representation of the result value
ReturnResult value(); //The value/type of the result
std::string location(); //The location string from the message
std::string message(); //The error message
std::string valueString(); //The value as a string for logging/output
static ResultManager *ResultMgr;//set a result manager so results can be handled on creation
private:
ReturnResult m_result; //type of result being returned success/error severity
std::string m_location; //class/module/function being returned from
std::string m_message; //description of the error/success/test
};
//severity of any errors resulting
enum ReturnResult{
RESULT_SUCCESS, //success which happens frequently (eg every frame)
RESULT_SUCCESS_CRITICAL, //success in a critical component
RESULT_ERROR_TERMINAL, //errors required the program to quit
RESULT_ERROR, //errors which might be recovered from
RESULT_WARNING //minor errors, which may cause problems later
}; |
I think this is all pretty straight forward – the result class contains 3 pieces of data: the type of result generated (of type ReturnResult – the enumeration shown above), a string indicating where the result originated and a string with a message to give more information on the error or success. This keeps the class to a minimal size so that creating and passing results around doesn’t become an issue, but gives enough information for interpreting, logging and tracing errors.
Also a member of the class is a static pointer to a result manager which will handle these messages. When set, this means that the result messages can be handled (logging, outputing immediately or even exiting the application) as they are created rather than having to get hold of and processing each one individually. This allows for consistant decisions on how messages should be handled, and prevents error handling obscuring the actual functionality of the application.
Errors can be created, processed and returned in one statement like this:
1
| return HError::Result(HError::RESULT_ERROR, "Graphics Setup", "Pixel format could not be set."); |
If you’d like to check out the complete code for results and the result manager, I’ve uploaded it here: Source Code. Feel free to modify and use it under the terms of the MIT License, or get back to me with improvements or suggestions.