finally, anyone?
open_the_gate()
try
wait_for_our_men_to_come_in()
finally
close_the_gate()
end try
Or defers or on(exit) you can call them (see D language); so:
open_the_gate()
on(exit): close_the_gate()
wait_for_our_men_to_come_in()
Also found this blogpost and it's comments a good read about
error-based vs exception-based programming:
http://blogs.msdn.com/b/oldnewthing/archive/2005/01/14/352949.aspx
"Exceptions mean that you must imagine what happens if an exception
is thrown anywhere in the flow."
You mostly summed up in your own words why error codes are more
preferred than exceptions. How can you have reliable systems when at any
point during the programs well defined execution may or may not throw a
exception.
Granted this depend heavely on the language. Though I still consider
it a red herring because the whole exception vs error codes debates just
simply comes down to returning more than one value from (memeber)
functions/functions/methods.
Interesting article.
I'm always swinging back-and-forth on the issue. Mostly I'm coming
away with the realization that exceptions cause terrible code-bloat
(lots and lots of specialized exception classes) when one takes things
like internationalization into consideration.
You can't just throw a formatted string (e.g. with the file name in
it), because you can't show that to the user.
It almost feels like exceptions with an error code might be a way out
;-)
Making it more declarative places problem into a somewhat orthogonal
plane.
control_gates() {
if(are_our_man_approaching()) open_the_gate();
else close_the_gate();
}
Well, I allways thought that the whole "exception handling"-thing was
invented by CS professors because they were unable to teach their
students how to write real-world error handling. Fact is: Exceptions are
the worst thing that can happen in a (productive) application, for the
simple reason that the resulting intermediate states are harder to
handle then the proper passing of error codes from lower (file-io) to
the upper layer (GUI) of the software.
Working on backend systems, I like exceptions. The happy path is
straightforward, and the sad path is always the same: log a stack trace
and terminate the request, process, job, etc. Error codes would just be
a more complicated way of accomplishing the same thing for 95% of my
code, where a thrown exception means programmer error. Now that I think
about it, though, exceptions do have weaknesses when dealing with
non-fatal error conditions. What I've been (unconsciously) doing is
dividing work into setup->execute phases and paying very close
attention to what can be thrown during a setup phase. Exceptions there
don't necessarily mean a bug and my code ends up with catch blocks
instead of return code checks.
@Alex: the possible catastrophe in that code is that the gate could
close on one of our men who was half way through when an exception
occurred.
I am also not decided on the issue, but you should note that there is
third, often forgotten, option – error handlers. They are non-local just
like exceptions, but they won't unwind the stack. So you may decide
error strategy in the error handler, but handle the error at the place
it occurred (for example by retrying).
What is better in my opinion:
exceptions > error codes (non-local error handling)
error handlers > exceptions (won't unwind stack)
error codes > error handlers (speed & simplicity)
That's the issue here.
@Alex, @Onne: of course the bug is trivial to fix, you just need to
spot it or to not make it in the first place. The bug in the error codes
example is equally trivial to fix – you just need to spot it or to not
make it in the first place. The question isn't which option allows to
write correct code – both do – but which option has worse consequences
assuming human errors; my examples try to show that the worst case given
human errors is the same.
@hacksoncode: that's a nice point! I didn't think of that... (I
wonder if it proves my point that exceptions make critical code harder
to write – or just that it's hard for me...)
@JS: error handlers are nice but there are many cases where they
aren't an alternative to either exceptions or error codes but can at
most complement them. That's because upon some of the errors, you need
different control flow – as in, take a different exit path from a
function – and an error handler can tell you to do that but
then you must actually do that and for that you need
exceptions, or execution conditioned on error codes, or something else –
but something more than just the handler deciding the policy, something
actually implementing that policy.
I don't understand your justification for #3. I agree that error
codes has a *relative* advantage in this case, but not an absolute one,
i.e., if you review your code carefully, exceptions lose a little bit of
their advantage.
The two biggest problems I see with the reasoning here are that (1)
it's rarely possible to isolate "critical" code completely (e.g., what
if your close_the_gates() calls some string-handling function you wrote,
which somebody goes and changes later to fix an unrelated bug? do you
re-review every mission-critical section of code in the system after
every code change?), and (2) virtually no code review ever catches every
bug: you're only improving quality, not guaranteeing it.
Exceptions can respond to a problem by simply aborting, too. I can't
recall a case of an exception leaving my system in an unstable
intermediate state. Without an example of some code that would cause
this, it seems like this is another scarecrow like "performance": a
canard people cook up as an excuse to avoid exceptions.
@Entity: no, it comes down to what's done with the one of
the (multiple) return values that indicates errors. The case for
exceptions (at least for me) is that sometimes nothing is done, so you
lose the error context, and sometimes what's done is to convert it to
another error value but without preserving enough context information,
so you lose some of the error context. In both cases, whoever debugs
loses.
@Kat: first, exceptions, specifically, do have a cost – at
the very least in space – and some types of storage are costly enough to
make this cost prohibitive – and sometimes exceptions have serious
runtime performance cost even if not thrown (you can blame the compiler
but you can't blame the pragmatic, experienced developer for avoiding a
feature because compilers repeatedly failed to implement it well.)
As to your point: I think exceptions are vastly harder to reason
about than error codes. hacksoncode's example with closing the gate when
one of our men tries to enter because of an exception thrown by
wait_for_our_men and closing the gate in the finally statement is a nice
illustration. I think that code reviews are thus vastly more likely to
find error code-related problems than exception-related problems. I
think of this as an example of an "explicit is better than implicit"
motto – a Python's motto, ironically, Python being the exception-centric
language that I use the most these days...
As to states exceptions leave my systems in... I wouldn't know I
guess, because the programs that I do use exceptions in don't manage
important state. If my compiler barfs and leaves a partially written out
object file, it's a "logical disaster" because now the build system will
think that the object file is available and barf later (say, at the
linkage stage; an example how "failing early" eventually becomes
"failing late"...) But it's not a "practical disaster" because there's a
user seeing the error, and he asks for support or he just figures out
that he should delete the object file because of the ICE and life goes
on.
So this means that "unusable state" is a blurry thing; and to me this
also means that you actually can separate critical and not-so-critical
code rather well much of the time. My compiler is not safety-critical
because you can always recompile and you can work around its bugs
because there's a testing infrastructure looking for said bugs. The
testing infrastructure also isn't critical in the sense that if it
barfs, people will notice – what's important is that it doesn't silently
report buggy code as correct. But code running in an aircraft or a
vehicle is critical – in my case, code compiled with said compiler that
runs in a vehicle is critical. Ergo, no exceptions. (Which has the big
disadvantage of failing later, but a big advantage of being easier to
review).
@Steve: I think the question is what's the worst thing your backend
system could ever do, and whether it's possible to isolate the scary
stuff (like dealing with someone's life or money).
I commented on HN with regard to exception handling in D programming
language http://news.ycombinator.com/item?id=4565169 . With
the scope guard statement you can avoid all of the problems and write
exception safe code.
@Uli: why exception classes – why can't you use a format string table
or some such to handle i18n, and how does the problem change with error
codes? (With error codes formatting occurs further down the road, but
either way, it does occur in some place, and in one place for every kind
of error; so it looks like the amount of bloat would be about the
same).
@Ivan Tikhonov: I think I understood your comment, even though I
didn't quite understand from your pseudocode exactly when the gate is
closed (I guess our men are "approaching" as long as they haven't
"entirely entered"). So you say it'd work better if instead of a serial
flow, we'd use something event-driven or a dataflow language or such? I
think humans aren't very good at that – Verilog and make being two
widespread systems of the kind and both being notoriously hard to debug;
I think the problem is that this is inherently massively concurrent and
there's a lot of ways a bug could bite you – you either get it right or
you get it wrong, and debugging – hunting for bugs as opposed to just
knowing what you did wrong – is really hard.
But that's another can of worms...
How do you feel about D's exceptions? It's pretty easy to have a rule
(applied at code review time) that all resource freeing must happen in a
scope statement.
@Oliver: I think your example is, exceptions caught by a GUI event
loop (and logged but without exiting the app or some such). That's
really rather awful – especially the old Windows/MFC way of doing it
where you catch (with __except or some such) things like memory access
violations and just blithely keep running. The question is if it's
equally awful if you do quit – compared to the case of just ignoring an
error code and not quitting.
@Eldar, Nathan: is it that different from try/finally or RAII or with
or using or... – in the sense that it's fine as long as you don't forget
to use it? It's not just "resource acquisition" that matters – you could
remove an item from a list and then put it on another list, and if an
exception gets thrown midway, then your item isn't in any of the lists,
forever. What should I do – have a scope guard putting the item back to
the original list? That's wrong if I did manage to put the item into the
second list. It's possible to get this right, I'm just saying that it's
not easy and you have to pay attention to the option of exception
getting thrown at every single spot and it's not just about end-of-scope
resource cleanup, it's about having consistent state.
@Yossi: Well, error handlers (and I really mean here something akin
to Lisp restarts, which are unfortunately unavailable in most languages)
simplify the error handling at the point, because you only need to
handle the policy, and not the particular error types. Furthemore, they
can implicitly unwind stack too if desired.
Interestingly, in low level programming, error handlers are much more
commonly used.
@JS: restarts?! Now I see what you mean... But if they end up
unwinding the stack – the ability where their extra power is compared to
error callbacks – then I think "restart safety" is a lot like exception
safety, that is, about equally hard to get right, not?
@Yossi: I am not sure. If you change code that is being called, it
can start throwing different exceptions, which means you need to change
way you handle it in the caller. With restarts, you only have to modify
the error handler itself (because the code responds to policy, not the
error code).
@JS: I'm not sure I fully understood you; anyway, what I was
referring to was the difficulty of dealing with the fact that the stack
could be unwound anywhere in your code, thinking that it's about the
same with stack-unwinding restarts.
When the languages are turing complete, everything is technically
equivalent. Exceptions and error codes are no exception.
Another problem with error codes is they can give a false sense of
confidence. I don't know a language which doesn't have some form of
crisis management for runtime errors which circumvent error return
values. Corrupt memory, broken hardware, etc. Exceptions are the only
fit for crisis management of **unexpected/undocumented** error
types.
E.g. in Go
open_the_gate()
wait_for_our_men_to_come_in()
close_the_gate()
You may expect the gate to always close while in fact a panic() can
be thrown in extreme cases of wait_for_our_men_to_come_in, and bam, your
gates are left open.
@Yuval: I didn't mean "equivalent in expressive power", but
"equivalent in potential to create disaster through human error in the
worst case"; I think it's somewhat orthogonal to
expressiveness/Turning-completeness/etc.
As to a false sense of confidence – it's true that some errors blow
up, but it's a closed set of errors a reviewer can look for throughout
the code. Exceptions are an open set. I can say, "I've reviewed this
code and nowhere does it corrupt memory so that's not a problem" (of
course nobody has to believe me...). I cannot say a similar thing about
user-triggered exceptions, because obviously there are some in
correct code; so now it's a question of who calls who.
So that's my counter-argument about things like bounds errors for
thoroughly reviewed code; I don't have a similar counter-argument for
panic(), except that panics are a bit like asserts (that Go shuns...) in
the sense that a panic says, "hey, this program is just busted – if I
move forward I'd just do more damage". Of course it's not true that the
busted program can judge the extent of damage due to proceeding vs not
proceeding... Here you can have atexit handlers or some such, attempting
to close gates or something...
Let's say that I vastly prefer to code with exceptions and I think I
made it rather clear...
(OT: it looks like maybe we might meet in person soon, if I get to
visit UW or NYR who AFAIK work with you these days.)
Eiffel's exceptions can exit in one of two ways: you retry the
function that failed from the start, or you pass the exception up to the
caller. There's no way to use them for normal processing, and no way to
catch one and keep going. So you use them only for programmer errors,
which you can't fix by using error codes anyway. In Eiffel, programmer
errors include not checking for error codes, so you're good there.
Failing early due to not retrying was not the cause of the Ariane
crash. They put code into the vehicle from an earlier vehicle that would
not work with work with the new vehicle. It didn't matter whether it was
exception, error code checking, or none of the above. If it threw an
exception, it would have shut down the hardware. If it didn't throw an
exception, it would calculate values out of range of the storage they
were in, which would just as surely crash the vehicle. The range
controller blew up the space craft because it had a fatal error in the
code, and that was the last-ditch exception/error handler. All they
needed to do to fix it was to have tried actually using the old code in
the new environment before they actually set it on top of thousands of
tons of explosives.
If you use exceptions for programmer errors and error codes for
things you expect will "fail" even in a correctly-written program (such
as trying to open a file), you get easy to write and easy to read
code.
@Yossi, yes, I believe we might meet indeed.
Good points made, great examples! Best understood when you have faced
some disasters yourself ;-) My point: someone without experience will
probably not comprehend the truth of this article. Ed Smits
Exceptions and error codes should be two different animals. Error
codes are best as responses from lower level code that you have failed a
constraint (e.g. that the file you are opening actually exists) and that
you can cater for as normal execution. Exceptions should be reserved for
complete failures that are unrelated to normal constraints. e.g.
err = f.openfile( filename )
if err == OK then
// Normal processing
elseif err == filenotexist then
// ...
elseif err == accessnotallowed then
// ...
else // Something we do not cater for
throw exception
I'd say, we'd better take the word "exception" literally and reserve
exceptions for exceptional situations that are not part of the normal
program flow. For example, IMHO, a general file_open function should not
throw an exception, if the file doesn't exist. It may not even be an
error in a given situation. At least, it's quite a common condition. One
thing to avoid is "flow by exception", because it easily leads to code
that's hard to follow and exceptions do have a performance penalty.
Exceptions tempt people to handle errors, as if they're detecting mines
in a mine field, by simply setting their foot and see if anything
explodes. I've seen many times that exceptions are thrown an caught 20
steps down the call stack and of course that leads to almost
unpredictable software.
I haven't checked if this is mentioned in the comments, but there is
one distinct advantage of exceptions, that no amount of error code
programming can emulate: Your application will never silently die on
you!
Simple example: If you fail to check a pointer (in C/C++) that is
result of a function and dereference it without testing, your
application might crash and will be gone, without any chance of
reporting a problem, or saving the current state. With exceptions it's
trivial to catch all errors at the topmost level and at least dump the
current state for later recovery.
More importantly, if you constantly write all the user actions to a
log file (quite useful for emulating certain error conditions), catching
an exception at the top level allows you to flush the most recent output
and close the file properly. But if the application silently dies, your
buffer might swallow the most recent events, which are usually the most
informative pieces of information.
My feeling is that exceptions should only be used for handling
programming errors and not flow of the application. If user input is
causing your code to fail, your code obviously has flaws, regardless of
the user error. We use an application where I work that won't even let
you type in the file type for a report's output file. Unless you use the
drop down menu to trigger the onselectedindexchange or similar event,
you get an error message stating it can't find/determine the file type.
It's very frustrating useabilty wise, but I'm sure it saves time to lock
down the interface and not deal with having to check a user's manual
input. On the one hand, I think it's the coders being lazy, but on the
other hand the application is huge and doesn't need bloat.
I think there are individuals who have posted here that don't fully
understand exception based vs non exception based languages. For
example, C# is an exception based language. it uses exceptions because
returning error codes would make the calling code dependent on the
called method (object). That creates a object to object dependency,
which breaks the rule of encapsulation. While working in a non exception
based language like C, error codes are perfectly acceptable. If you find
that while using exceptions your code gets run down with them and
bloated by exception based classes you, once again, don't fully
understand exceptions and how to use them. Refer to these
articles.
1. http://codebetter.com/karlseguin/2006/04/05/understanding-and-using-exceptions/
2. http://www.artima.com/intv/handcuffs.html
3. http://russellallen.info/post/2011/03/11/C-net-Exception-Handling-Best-Practice-As-Easy-as-1-2-3.aspx
@Darren: I read a claim by Kahan somewhere that if no exception were
raised and the program just pushed forward, it'd be OK, perhaps, because
it was the dumping of a core snapshot that clogged a communication
channel or such; if I'm wildly off track here I should find that bit
again and re-read it.
As to "exceptions for programmer errors, error codes for legitimate
runtime errors" – first, I think the line is a bit blurred (say,
sometimes you open a file that your own code wrote and if the file isn't
there, it's actually a programmer error that this bit of code got called
at all; of course you can check the error code and raise an
exception...); and second, do you think this separation makes it easier
to spot human errors related to handling exceptions/error codes
incorrectly?
@j5c, @Peter Laman: I think your claim is a bit similar to Darren's;
again – why, in that scenario, are programmers likely to properly handle
all error codes, including the propagation of the error up a potentially
large call chain, and the collection of context information needed for
debugging?
@Stefan: actually, in C or C++, at least on some systems or after
some work on your behalf to make your system support it, you can dump
core and send it as the error report, and it's way more detailed than an
exception call stack; I wish all languages and systems had that. In fact
one disadvantage of C++ exceptions is that they unwind the call stack –
I'd much prefer an uncaught C++ exception to dump core without unwinding
the call stack. (It's possible to use C++ exception classes that record
a stack trace before being thrown using some platform-specific hack, but
I don't know of a way to get them to figure out that they're not going
to be caught by any handler and dump core on the spot without destroying
the local variables of said call stack).
@Lawrence Knowlton: my question to you as well as Darren and a few
others is, why do you think programmers are likely to properly handle
error codes – a rather hard job that usually isn't needed to get a first
version of anything out the door? The reason I like exceptions is that
such a poorly written first version tells more than nothing about its
problems when it encounters problems in the wild.
@diddle: I think someone opposing exceptions won't be comforted by
knowing that he got to use an "exception-based language", nor will
someone who dislikes error codes be happy with C or Go just because
they're "not exception-based"...
all wrong
That gate-opening code is not much different to
connection-opening-code. I wonder, how many C#-developers are out there
not not wrapping the Connection into a using-clause enforcing the call
to IDisposable.Dispose!?
As for throwing exception on non-existing files. It's a programming
error on the coders behalf and qualifies for the 'most code' definition
here. A proper program would either check existance of the file or would
open a file with the assumption, that it should be created if it doesn't
exist.
An example on how to not use exceptions is the firing of an
EndOfStreamException at the end of a stream which you are forced to
catch if using when network-streams (as length throws a
NotSupportedException). It would be much better to iterate Streams
through IEnumerable. Btw, the NotSupportedException is clearly an
example, too. If the Length-property would be delivered through a
perceived ILength-contract instead of being on the Stream-class, it
would be much cleaner and would not enable developers to invoke methods
not implemented.
In C++, I'll just throw out with no intent to defend it that
exceptions are ALWAYS potentially a BIG problem.
In Java they are fine in their implementation but it just doesn't
work in a low-level language.
But the ultimate solution has to be testing. Any code that throws
errors is easy to test, just provide the cases to cause them and see if
it handles them properly.
I don't dislike exceptions themselves as it's a quick, easy way to
catch dumb errors in development but they do nothing at all to help with
program correctness and error codes don't do much either.
Testing error handling is very hard though – in particular, if you
haven't thought of some of the possible error conditions, you're
unlikely to cover it in your tests.
> An exception can leave you in this intermediate state.
How can an error code avoid that?
I've seen many C codes that leave FILE open (or leaking allocated
memory, etc) on one or more error paths. I think intermediate states
comes from existence of errors. The way to handle errors doesn't matter.
There are different review points for each way.
Exceptions have a rather new review points for intermediate states
some people just don't know; check use of RAII, "with" statement,
etc.
For me, checking use of RAII seems easier than checking every
explicit code paths for intermediate states.
@nanasisan: with error codes you see the error paths, with exceptions
you don't (how do you tell where an exception can/cannot be thrown
through the code by looking at that code and without checking the
definitions of everything it uses)?
The problem with RAII, with statements, etc. is that you need to wrap
things like moving something from one list to another. It's easy to see
if file objects are wrapped but it's less easy to spot all the one-time
logical operations that actually would need to be undone upon stack
unwinding, and it's not necessarily convenient to wrap them all.
@Yossi With exceptions, you don't have to see every error paths if
the intermediate state is properly guarded with an appropriate tool.
Actual paths are not review points.
And an analogy again; how do you tell that an absence of explicit
branch after a function call means that no error code cannot be
returned, or that the programmer forgot the check, by looking at that
code and without checking the definitions of the function? I know there
are some modern techniques, like multiple return values, can be a
solution. But most existing code I've seen doesn't take such solution.
Thus, I have to know or guess about errors on both ways to handle
errors, not only on exceptions.
For "one-time logical operations", a try block with catch-all or
finally (or scope statement in D, at best) seems appropriate to guard
such a state, and seems not worse than explicit branches.
@Darren
>exceptions for programmer errors only
@Peter Loman
>avoid "flow by exception"
@j5c
>Exceptions should be reserved for complete failures
Are all on target. A fourth and quite common way of saying it is
"failures are no program flow cotrol structures". Subroutine call, if
and loop are the only control structures (in imperative langiages).
"EXCEPTIONS are no program flow cotrol structures".
I failed to understand why the text is not gender neutral ?
"Then he fixes the bug and all is well again" ....
Nothing is well – you sexist
@Uri W: I am very willing to have you as a guest author on a
technical topic of your choice so that you can expose us all to a higher
standard of thought and expression.
@Onne
> Or defers or on(exit) you can call them (see D language); so:
> open_the_gate()
> on(exit): close_the_gate()
> wait_for_our_men_to_come_in()
Or macros (Common Lisp):
(with-open-gate ()
(wait-for-our-men-to-come-in))
I know this is an old post, but I'm curious about comment #37 by
diddle:
"For example, C# is an exception based language. it uses exceptions
because returning error codes would make the calling code dependent on
the called method (object). That creates a object to object dependency,
which breaks the rule of encapsulation. While working in a non exception
based language like C, error codes are perfectly acceptable."
I would like to see an example to see the dependency which using error
codes create, but using exceptions does not.
The gate is a strawman; anytime you see "open" and "close" you can
bet this is a code smell of old non-RAII C++. It should be written as
such:
{
auto gate = Gate{} // open_the_gate()
wait_for_our_men_to_come_in()
}
// exiting the block or on exception causes Gate's destructor to be
invoked which closes teh gate // close_the_gate()
Post a comment