• 12
• 12
• 9
• 10
• 13

# Error Management

This topic is 2055 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

## Recommended Posts

(c++)

Making code robust in the face of errors such as missing textures, files and models.

For textures, if I cannot find a texture I simply return a debug texture.
This code will always return a valid texture, preventing any possibility of crashes since it doesn't return NULL:
 ITexture* texture = MaterialAPI()->GetTexture( "chicken" ); 

However, how can I check to see if an error has occurred in the process of loading a texture?
Something like this might work:
 ITexture* texture; bool succeeded = MaterialAPI()->GetTexture( "chicken", texture ); 
But how would you do it?

Similarly, some of my code just returns NULL on failure, such as when a file isn't found.
 IStreamFile* file = FileAPI()->GetStreamFile( "config.txt" ); 
Here a failure to check for NULL will result in a crash. Maybe I can return a 'debug' empty file to prevent this in case of programmer error?

It seems to me returning NULL when you don't have to is asking for crashes as soon as something isn't just so, but I'm having some trouble deciding the best way to prevent this; At least as far as the engine interface APIs are concerned. Not only that, but returning NULL in some cases, and debug versions in others is simply not consistent.

Bonus Question:
Also, I surround my entry point with a 'try' statement, and during a fatal error I 'throw' with a specific type. It also allows me to catch any other potentially uncaught 'throw' statements and shutdown reasonably gracefully, but how would you guys handle this type of thing?

Thanks as usual!

##### Share on other sites

However, how can I check to see if an error has occurred in the process of loading a texture?

You don't. The system returns a debug texture. If it can't load *that* texture, then it's time to die. Personally I favor returning null and letting the inevitable uncaught exception to take down the app. No need to write and test code that you've done everything you reasonably can to prevent and cannot really do anything about anyways.

##### Share on other sites
Interesting philosophy and it makes sense.

I was trying too hard to prevent it from ever crashing I suppose and should take a more moderate approach.

##### Share on other sites
What I want to say is, never try to patch for your mistake (or to say, bugs).

I had worked on a software, when there were any null pointer crash, the original developers said "hey, let's check when it's null we just don't do that function".
What's that? They were hiding the problems rather than solving them. The result is, the software became harder and harder to maintain and we had to rewrite quite a bit of parts. If we continued that kind of "hiding problem", the software would have died before our expectation.

Now when I find a bug, I would think why it happens, rather than try to get a workaround for it.

Just my personal experience.

##### Share on other sites
You definitely want to catch and handle your errors in some way. Allowing the application to just work with a NULL pointer and crash when it tries to dereference it is going to tell you that you have a bug, but it's not going to help you find it. The same is true of letting exceptions bubble up and crash the application.

This is what I like to do. Every function that has any potential at all to cause an exception, I will enclose it in a try-catch block, add some context information to the exception for function/file/line number, then rethrow the exception so the next level function in the call stack can handle it and it's information. Then the main function, or the main thread function, will handle the exception by throwing a dialog box, writing to a log file, or both. This way you can get a lot of information about where and how the error is occurring, even in a release build.

Writing those try/catch blocks can be pretty tedious, so I use some macros to make life easier. i.e.:

 #define TRYCATCH_OPEN \ try { #define TRYCATCH_CLOSE \ } \ catch( Exception& e ) \ { \ std::ostringstream dump; \ dump << e.message() << " " << __FUNCTION__ << "[" << __FILE__ << "(" << __LINE__ << ")]" << std::endl; \ if (e.nested() == nullptr) \ throw Exception( dump.str(), e); \ else \ throw Exception( dump.str(), *e.nested() ); \ } \ catch( std::exception& e ) \ { \ std::ostringstream dump; \ dump << e.what() << " " << __FUNCTION__ << "[" << __FILE__ << "(" << __LINE__ << ")]" << std::endl; \ throw Exception( dump.str() ); \ } 

Then when I throw my exceptions, I also use a macro so I can pin down exactly where the exception is being thrown. i.e.:

 #define jp_throw(ExceptionType, ...) \ throw ExceptionType( what_(__FUNCTION__, __FILE__, __LINE__, __VA_ARGS__ ) ); 

Then every function/method has something like this:

 void Foo::Bar() { TRYCATCH_OPEN Some logic... if (somecondition != expected) jp_throw(SomeException, "somecondition is: ", somecondition, " expected: ", expected ); TRYCATCH_CLOSE } 

Still a little bit tedious, but much easier and cleaner than writing out all of those try/catch blocks, and it pays off.

I use custom exception classes that can contain nested exceptions, so I can preserve the original exception object while bubbling it up the chain.

This works out very well for me, and makes it much easier to track down bugs in beta and production builds without having to include debug information in the executable. The information isn't as precise as a true call stack, but it's much better than nothing.

As far as things like not being able to find a texture, I do it the way you do it, by returning a default debug texture. The only thing I do different is that I have a command-line option that can indicate that I want to throw an assertion first, so that I have the option of having the application complain loudly and definitively if there are missing resources, but also have the option of gracefully handling the situation so that development doesn't grind to a halt just because somebody broke the build with a missing resource. Edited by krippy2k8

##### Share on other sites

it is going to tell you that you have a bug, but it's not going to help you find it.

I should've maybe clarified that I use a vaguely modern programming language that includes stack traces with its exceptions.

##### Share on other sites

I should've maybe clarified that I use a vaguely modern programming language that includes stack traces with its exceptions.

A stack trace from a NULL pointer exception will only tell you where it is dereferenced, not where it originated.

And by "vaguely modern programming language" I guess you either A) are not talking about C++, which the question is about, B) think that errors are nice and friendly and only happen in debug mode, or C) bloat your release code with debug symbols and disable compiler optimizations. Edited by krippy2k8

##### Share on other sites
The thing about the unforgiving crash, is that it can hinder progress on large teams, you use function A developed by programmer 1 which processes data introduced by scriptwriter or game designer X who uploaded a last minute change who then went to his dentist appointment and forgot his cellphone... you just lost a whole afternoon in the task you were doing because of someone else's mistake and there is nothing to do about it.

That is, after it happened, you can do things to minimize the impact of this situations, which in a 10+ team, tend to happen often, even in AAA development.
What you can do is having builds with different error tolerance:
The development build would be most permissive, trying to keep on going as much as possible and returning placeholder data or content whenever the real thing cannot be delivered.
The debug build can be the one that provides the most data, and is the least forgiving of all, crashing as soon as an error is detected and performing extra sanity checks (conditioned by pre processor code not to be part of the release).
Then the release build would lean towards speed (if we are speaking of videogames this is crucial) and resort to try catch code with error reporting support.

This way, if an error is introduced you can still keep on working without wasting your time, perhaps your progress would not be in a deliverable state due to the broken data on debug and release, but at least you will be able to make progress.