• Error handling discussion
    28 replies, posted
I see that WAYWO is getting derailed, so I decided to create a separate thread about error handling. My vision of error handling is a function which displays information useful both to the user (cause, possible solution) and to the programmer (stack trace, instruction pointer), which is called every time unintended behavior occurs. Similar to Doom and Quake, but more descriptive. With exceptions, if you don't catch them, you won't have any descriptive information about the error, but if you catch every exception, the code will be too branched and non-linear. What do you, FP programmers, think about error handling?
Exceptions should be thrown when there is an exceptional circumstance. You should only catch an exception if it's one you can handle, otherwise you should let it climb the stack until it reaches a higher level function that knows what to do with it. If this never happens, then your application crashes, as it should. All the information you need is there: The exception object should contain some sort of error message and information about the error itself, and the stack trace is automatically generated as your exception climbs the stack. If you catch [B]every[/B] exception, you're probably not using them correctly (or handling them in the right place). Your vision of error handling is some gimped version of this with one function call that handles it all. It's not as flexible as exception handling at all, and you accomplish only a subset of the features, so I don't see the point. It makes sense in Doom and Quake because it's C (even the C++ games after that were HEAVILY based on prior C code), but in C++ there's no reason to do it that way unless you want to avoid the exception facility.
...and gparent pretty much sums up my thoughts to the T. Everything you want can be achieved with exceptions if you use them correctly, SiPlus.
Continuing here [QUOTE=KmartSqrl;38357994]I'd rather be ready to catch it in the future than have to go through and change all the asserts to exceptions later.[/QUOTE] I'm not saying don't use exceptions, I just think that on a programmer error when all you want to do is crash, it is better to assert (or your own), than to throw an exception that shouldn't be caught manually but might be confused that you need to catch it and handle it yourself.
gparent pretty much nailed it with exceptions, I just think there are some cases that warrant manual error handling, when failure is going to be as common as success. For example, in a component-based entity system there's an Entity that contains multiple subclasses of Component. An Entity will only have a handful of the Component types it needs, never all of them. So when you want to retrieve a Component type, you're expecting most of the entities to fail. So it would make sense to use a "bool TryGetComponent<T>(out T component)" over a "T GetComponent<T>()" that throws an exception if type T isn't included. (C# syntax) A more common example would be user input. People not entering a number correctly is an error that is expected and will be handled immediately (no need to push it up the stack), so it makes sense to avoid exceptions in this case: [url]http://stackoverflow.com/questions/467613/parse-v-tryparse[/url] Again, exceptions work for most cases, there are still a few cases where they're overkill though.
Sockets and File IO also tends to cast exceptions regularly. A whole bunch of those can and should be handled immediately instead of crashing the whole program. Like the endpoint not accepting a TCP connection or the file being opened and locked by another program.
[QUOTE=Lord Fear;38358858]Sockets and File IO also tends to cast exceptions regularly. A whole bunch of those can and should be handled immediately instead of crashing the whole program. Like the endpoint not accepting a TCP connection or the file being opened and locked by another program.[/QUOTE] Synchronously, yes. It might be a bit harder to do with async IO/networking. Specifically for IO, it still makes a lot of sense to throw exceptions with constructors "File f = new File("blah.txt");", but with a static method like "File f = File.Open("blah.txt");", it would make sense to include a "bool TryOpen(string path)" overload as well.
I use assertions too, mostly to validate assumptions made when writing a function. So if my function assumes that you pass a valid pointer, I'll assert on that rather than throw an exception because any code that passes me a NULL pointer is wrong and needs to be fixed. If a null pointer is a common thing that is part of the code I'm writing, then I'll add an 'if' condition. If a null pointer is a result of some exceptional condition (network loss, etc), then I'll throw after the if check instead of doing something else.
[QUOTE=gparent;38360574]I use assertions too, mostly to validate assumptions made when writing a function. So if my function assumes that you pass a valid pointer, I'll assert on that rather than throw an exception because any code that passes me a NULL pointer is wrong and needs to be fixed. If a null pointer is a common thing that is part of the code I'm writing, then I'll add an 'if' condition. If a null pointer is a result of some exceptional condition (network loss, etc), then I'll throw after the if check instead of doing something else.[/QUOTE] Holy shit this is something that I've been meaning to ask for a while: Is it sensible to use assertions to validate function input according to documentation? Like, if I have an API function like so: [cpp]locInfo getLocInfo(std::locale const& loc) { locInfo info; // Empty by default. typedef locInfoFacet facet; if(std::has_facet<facet>(loc)) { info = std::use_facet<facet>(loc).getInfo(); } return info; }[/cpp] Should I assert "std::has_facet<facet>(loc)" since by documentation, it's required that the locale has it? My only gripe is that I can't backtrace assertions to figure out where I fucked up my code? [cpp]locInfo getLocInfo(std::locale const& loc) { typedef locInfoFacet facet; assert(std::has_facet<facet>(loc)); return std::use_facet<facet>(loc).getInfo(); }[/cpp] I can understand that things like user input should be validated without assertions, but this is leaning towards contractual programming I guess?
[QUOTE=Jookia;38363256]I can understand that things like user input should be validated without assertions, but this is leaning towards contractual programming I guess?[/QUOTE] That's how I see it was well. I tend to follow a 'validate once, assert everywhere' approach. So at the user input level, I perform validation with exceptions or error codes. Anything past that 'barrier' is assumed to be validated user input, so any parameter error in a function should necessarily be due to program logic errors. At that point, an assertion seems like the correct choice to validate that this remains true as you work more and more on the program, modifying different bits and pieces.
Ah. I wonder if it's worth writing my own assert code to throw a custom exception, that way I could get a stack trace? I'm fine with my entire program going down because of a bug: I'd rather have no behaviour than broken behaviour. By this I mean actually having the assertions on production. I kind of have a weird rule of using the same code in production/debug as I'm trying to test my code when I run it as well.
I agree with gparent et al. An example of when "handle all errors" is nice is user-issued commands. For example, when you run a console command made with Lua, you wouldn't want it to crash the game; hence, it will catch all Lua errors. However, for a critical part of the application (e.g: game loop in a game) any unhandled (read: unexpected) exception should probably result in an abort.
Anybody who wants to learn more about exceptions in C++ should definitely check out Effective C++, it's a great book, and goes pretty in depth about them.
Generally, if an error implies that the programmer did something wrong, crash early and hard, and print as much error information as possible, *for the programmer*. You can't expect the user to fix the programmer's screwing up, the best you can do is give him a message he can send back to you so you can fix the problem. On the other hand, if it's something that can arise naturally during the running of a program, but still implies something has gone wrong, handled exceptions are(IMO) a good way to keep the program running while doing something to rectify the error.
Here's a great post about exception handling: [url]http://variadic.me/posts/2012-10-30-you-should-let-it-crash.html[/url]
[QUOTE=FlashStock;38394966]Here's a great post about exception handling: [url]http://variadic.me/posts/2012-10-30-you-should-let-it-crash.html[/url][/QUOTE] Just because you use a catch all exceptions doesn't mean you can't tell the user something went wrong. In my opinion, I'd rather catch all exceptions and show a more graceful error display and then let the program die. And in the example of the word document, isn't it better you tell the user an error happened when attempting to save and let the program stay alive so he can copy what he's written and save it manually rather then just crashing? He will lose what he has written anyways if it crashes.
But you won't know if your program is still working if you catch all exceptions? What if it's HDD_GONE_EXCEPTION?
[QUOTE=Lord Fear;38395982]And in the example of the word document, isn't it better you tell the user an error happened when attempting to save and let the program stay alive so he can copy what he's written and save it manually rather then just crashing? He will lose what he has written anyways if it crashes.[/QUOTE] I don't think you have to change much of what we say to make your scenario compatible with our advice. In this case, *any* error is a catchable error, because you have behavior designed to run whenever an unexpected error occurs. This isn't the case for the vast majority of applications, but in the case of Word it just means that your "catch all" behavior is different. You still want Word to reach that stage as fast as possible if it encounters a programmer error, rather than prolonging the inevitable and perhaps inducing more memory corruption.. (this is why pressing "Continue" on those unexpected exceptions handler is often a really bad idea).
[QUOTE=gparent;38426067]I don't think you have to change much of what we say to make your scenario compatible with our advice. In this case, *any* error is a catchable error, because you have behavior designed to run whenever an unexpected error occurs. This isn't the case for the vast majority of applications, but in the case of Word it just means that your "catch all" behavior is different. You still want Word to reach that stage as fast as possible if it encounters a programmer error, rather than prolonging the inevitable and perhaps inducing more memory corruption.. (this is why pressing "Continue" on those unexpected exceptions handler is often a really bad idea).[/QUOTE] I didn't say it was supposed to continue run as a zombie state after the error. I was however saying it can be used to give the user a chance to recover what has been written. What many of you doesn't get is that just because you catch all exceptions doesn't mean you have to keep the program/game running afterwards. You could use it to write a crashlog/dump file and maybe even run an external error report sender to the developer.
I get that, the thing about pressing continue wasn't in relation to anything you said but an extension of what I was saying.
All functions are void. Every call goes in a try catch. Then just define your ReturnException and ActualException classes and you're all set.
[QUOTE=Larikang;38456041]All functions are void. Every call goes in a try catch. Then just define your ReturnException and ActualException classes and you're all set.[/QUOTE] W-What?
All functions take a callback to pass their result to. The return value is used for exceptions. [editline]15th November 2012[/editline] For example: [cpp] const char* wrapped_sqrt(void* state, double x, const char*(*cb)(void*,double)) { if(x < 0) { return "negative value passed to wrapped_sqrt()"; } else { return cb(state, sqrt(x)); } } [/cpp] [editline]15th November 2012[/editline] A nice side effect of this convention is that it means you can 'return' multiple values and you can implement continuations very easily
[QUOTE=swift and shift;38458508]All functions take a callback to pass their result to. The return value is used for exceptions. [editline]15th November 2012[/editline] For example: [cpp] const char* wrapped_sqrt(void* state, double x, const char*(*cb)(void*,double)) { if(x < 0) { return "negative value passed to wrapped_sqrt()"; } else { return cb(state, sqrt(x)); } } [/cpp] [editline]15th November 2012[/editline] A nice side effect of this convention is that it means you can 'return' multiple values and you can implement continuations very easily[/QUOTE] How hard is it to just use exceptions? Jesus people.
[QUOTE=gparent;38458593]How hard is it to just use exceptions? Jesus people.[/QUOTE] I'm pretty sure it is sarcastic. [SUB]I hope.[/SUB]
But isn't that on the verge of spaghetti code mixed with lasagna?
[QUOTE=PiXeN;38458608]I'm pretty sure it is sarcastic. [SUB]I hope.[/SUB][/QUOTE] One of my side projects is actually an interpreter for my own little lisp written in this style. It's very complicated and time consuming to code this way.
[QUOTE=swift and shift;38458792]One of my side projects is actually an interpreter for my own little lisp written in this style. It's very complicated and time consuming to code this way.[/QUOTE] Is this a new thing? Badly designed languages as turing tarpits?
[QUOTE=Jookia;38458873]Is this a new thing? Badly designed languages as turing tarpits?[/QUOTE] no i'm coding it this way so I can support first class continuations [url]http://en.wikipedia.org/wiki/Continuation[/url] [url]http://en.wikipedia.org/wiki/Continuation-passing_style[/url]
Sorry, you need to Log In to post a reply to this thread.