When use what Error-Handling

Started by
23 comments, last by ApochPiQ 12 years, 9 months ago
I have few basic rules. Fail fast, fix fast.

If the error value is from user input, throw exception and tell the user how and where it is invalid. It may be input from keyboard, XML or any configuration file, graphics file, basically any content feed into the program.
Also if the error value is from composition of classes, more precisely dynamically composing the classes, assert and after that throw exception. It's still in developement.

Assert invariants in constructors. Throw aswell if too paranoid. It's still in developement, put enought checks to catch all errors.

When released, asserts are omitted. There shouldnt be any exceptions coming from data or logic errors. At this point, depending if it's required, you can disable exceptions.

I dont understand this exception bashing, from STL anyway. I've never released a product where I'm masking any exception, nor there should be many in released product. It's just so much faster to develop with them on.

Advertisement
I'm curious to your reasoning about never though, Hodgman.
There may have been a sprinkle of facetiousness there, but perhaps a simple flow-chart will do:

[font="arial, verdana, tahoma, sans-serif"]*Does your whole team completely grok the different levels of exception safety[/font][font="arial, verdana, tahoma, sans-serif"]?[/font]
[font="arial, verdana, tahoma, sans-serif"]*At a glance, can you look at any function in your code base and determine which particular level of guarantee it provides?[/font]
[font="arial, verdana, tahoma, sans-serif"]*Is it possible to look at any function in your code base and determine which, if any exceptions it's allowed to throw?[/font]
[font="arial, verdana, tahoma, sans-serif"]*Are you using RAII?[/font]
[font="arial, verdana, tahoma, sans-serif"]*Are you not[/font][font="arial, verdana, tahoma, sans-serif"] targeting a games console?[/font]
[font="arial, verdana, tahoma, sans-serif"]*Do you not [/font][font="arial, verdana, tahoma, sans-serif"]care about generated code size and efficiency?[/font]
[font="arial, verdana, tahoma, sans-serif"] [/font]
[font="arial, verdana, tahoma, sans-serif"] [/font][font="arial, verdana, tahoma, sans-serif"]If you answered 'yes' to all of the above, then C++ exceptions might be ok for you. If you answered 'no' to any of them, then it's a red flag.[/font]

[font="arial, verdana, tahoma, sans-serif"]
You're already using them (unless you disable them, or never allocate memory using new). They are the only way to write invariant-enforcing constructors that can fail.[/quote]...or use [font="Courier New"]new(nothrow) T[/font] wink.gif
[/font]
IMHO though, code that relies on new/malloc is almost as bad as code that relies on global variables, but that's a different discussion...
[font="arial, verdana, tahoma, sans-serif"][font="arial, verdana, tahoma, sans-serif"]

[/font][/font][font="arial, verdana, tahoma, sans-serif"][font="arial, verdana, tahoma, sans-serif"]I'm in the same camp. If allocation fails, abort the process -- you should replace new (and malloc) with versions that abort the process when things go wrong.
I think a lot of people have taken this the wrong way. It does sound like a very extreme thing to say, yes...[/font][/font]
[font="arial, verdana, tahoma, sans-serif"][font="arial, verdana, tahoma, sans-serif"] [/font][/font]
[font="arial, verdana, tahoma, sans-serif"][font="arial, verdana, tahoma, sans-serif"]However, a failed (system-level) allocation is generally a bug -- when you choose C++, you're stating that you're ok with manually managing resources. This means you have to know your memory requirements, and work within them. In 'normal' software, you might be able to compartmentalise things, so that just some particular feature is disabled when there's not enough memory to support it, but in games software, you generally have a fairly fixed memory requirement, and any overflow of that requirement is a developer error.[/font][/font]
[font="arial, verdana, tahoma, sans-serif"][font="arial, verdana, tahoma, sans-serif"] [/font][/font]
[font="arial, verdana, tahoma, sans-serif"][font="arial, verdana, tahoma, sans-serif"]If there's a bug in your shipping build, you can either try to write fault-tolerant code that will limp on (using exceptions or otherwise), or you can crash and submit a crash report.[/font][font="arial, verdana, tahoma, sans-serif"] [/font][/font][font="arial, verdana, tahoma, sans-serif"][font="arial, verdana, tahoma, sans-serif"]Whether you use exceptions or not doesn't change the fact that you've got a bug, and doesn't change your options with regards to whether you limp or crash![/font][/font]
I find this statement funny. So basically instead of having to wrap a function call in a try/catch block, you want to wrap it in a if/else or switch block? Possible performance issues aside (to be determined by profiling), you are basically just trading one test for another (hopefully, since I frequently see examples where no test is performed).
The main difference is that returned errors are visible in the function signature. In C++, a function could be throwing 6 different types of exceptions, and the signature is allowed to not tell you. That means that when you call those functions, they 'pollute' your new function with the same uncertainties. Java and C# on the other hand, don't suffer from this problem. On smaller or more disciplined teams, this deficiency might not be a problem for you, but it's still a deficiency that C++ forces you to deal with.

The main difference is that returned errors are visible in the function signature. In C++, a function could be throwing 6 different types of exceptions, and the signature is allowed to not tell you. That means that when you call those functions, they 'pollute' your new function with the same uncertainties. Java and C# on the other hand, don't suffer from this problem. On smaller or more disciplined teams, this deficiency might not be a problem for you, but it's still a deficiency that C++ forces you to deal with.


I'm not sure I see why Java or C# differ in this respect? How can you tell from looking at the function which exceptions the function could potentially throw? Or am I missing something - I'm certainly no expert in C# or Java.

Does your whole team completely grok the different levels of exception safety?
*At a glance, can you look at any function in your code base and determine which particular level of guarantee it provides?
*Is it possible to look at any function in your code base and determine which, if any exceptions it's allowed to throw?
[/quote]
I believe these points are equally valid when applied to return codes. There is something in the idea that exceptions are "hidden", but this is somewhat counterbalanced by the "ignorability" of return codes.

Neither is perfect.

*Are you using RAII?
[/quote]
Yes.


*Are you not targeting a games console?
[/quote]
No.


*Do you not care about generated code size and efficiency?
[/quote]
For the kind of code that I use exceptions for, nope =]

Overall I think there are places where return codes are preferred, and other places where I'd only use exceptions.
I think the real problem with exceptions is that there are a lot of misconceptions about them, many people use them wrong, and exception antagonists keep repeating their mantra over and over again, some of them not really understanding why. Funnily, you usually "STL is evil" and "exceptions are evil" in the same session (such as on e.g. GDC 2004).
  • On no-crap implementations, exceptions do not have a serious overhead or enlarge your code in a really significant manner if you do not very seriously abuse them.
  • Yes, yes, I know... it is one of those mantras that people keep repeating, but it's nevertheless wrong.
  • On some consoles both exception handling and the STL implementation suck. As does the C runtime and the allocator. Right. Get over it.
  • Exceptions on a compiler that doesn't suck are nowadays on the order of a dozen clock cycles each when entering and exiting the try block. As long as no exception is thrown, you will not be able to measure the difference.
  • If you can measure a difference, use a compiler that doesn't suck.
  • Exception handling adds maybe a hundred kilobytes to your executable size (30-40kB stub, and then some for each try/catch block). If that is prohibitive, then a lot of other things are equally prohibitive.
  • Exceptions are exceptional, not expectional. If there is a very reasonable chance (say, 30%) that a function will fail, consider returning an error code instead.
  • If you enter/leave a try block within an inner loop and experience performance issues, don't be surprised. That's not something particular to exceptions, however. 12 clock cycles a billion times is 12 billion cycles, no surprise really. Just don't do that kind of thing. You don't want to call a virtual function from within an inner loop either.
  • if you have ten thousand try/catch blocks in your sources, you are doing something wrong. Don't complain about having to write so many of them, write fewer.
  • Throwing exceptions can take an unpredictable long time (in theory, minutes, hours, or days) depending on the call stack. Usually, it's something around 200-10000 clock cycles (so, on a desktop, in the lower microsecond range).
  • If, on the average, you throw more than 2-3 exceptions per minute, that's already unusual. If you throw more than 10-20 exceptions per second, you're doing something seriously wrong.
  • A single-threaded, non trivial OpenGL app with initially ca. 40% CPU load, vsynced to 60fps on a desktop computer which is not the fastest machine in the universe, just about tilts the frame time when throwing and catching 5,000 exceptions per frame. Who really needs thousands of exceptions per frame?
  • If your expectation to exceptions is "print a message and exit, because what can you do anyway", you should not use them. There is exit() for killing the process. Though inevitable in many situations, the intended use of exception handling is not to kill the process, but to retreat to a state from where you can resume normal operation. And, sometimes, this is possible in a very elegant way using exceptions.
So what am I supposed to do if I'm trying to write a game for a console that has shitty compiler support for exceptions? I can't change platforms, I can't change compilers, and I sure as hell can't use exceptions because they will be a performance liability.


Sometimes, exceptions really are evil.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

This topic is closed to new replies.

Advertisement