Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Your experience with languages with exceptions seem to come from people who misuse them. Randomly placing catch clauses around in the code is not good practice, even if perhaps a majority of all programmers in safe languages code that way. That causes latent bugs that are incredibly hard to debug.

The trick is to almost never ever catch exceptions. For example, in his post he describes a bug caused by accessing beyond allocated memory. That would in a safe language immediately have caused an ArrayIndexOutOfBoundsException (or equivalent) which the programmer would have fixed. In C, errors are often "silent" because you can't be bothered to check the return value of every call to printf. In a correctly coded program in a safe language errors are never ever silent.



"Your experience with languages with exceptions seem to come from people who misuse them."

Yes, it does, because people who misuse them seem to be a majority. I think that's an important point in language (or for that matter any kind of) design. You can't just look at how well things work for the experts - a mistake made by both Common Lisp and C++ in different ways). Nor can you just consign all non-experts to some straw-man-ish "blub" category. You have to look at what skill level is required to make the benefits outweigh the costs, which could be anywhere along the skill continuum, and compare that to the actual skill distribution of the programmers you have (or will have after hiring and/or training).

The sad fact seems to be that the tipping point for exceptions is at a point that leaves most programmers on the bad side. The same is almost certainly true for any kind of meta-programming. It might even be true for closures and continuations. "Primitive" languages lacking features like exceptions or GC surely do trip up the true beginners, but leave fewer traps further along the path.


Somewhat OT, but I find it curious that you would, in the same breath so to speak, put closures and continuations in the same category of "hard things". To me, continuations are still pretty mysterious, but closures are a pretty simple, usable idea.


Continuations are extremely counterintuitive and should only be used as low-level building blocks. Exceptions are actually a kind of continuation, and in Common Lisp the compiler uses continuations to implement exceptions (i.e. "conditions") and the restarts system. It is common in functional languages for the compiler to use continuation passing style as an intermediate representation, where instead of returning from a function you will invoke the "return continuation" that was passed to the function as an argument (i.e. it is the code that follows the function call -- where you return to).


> The trick is to almost never ever catch exceptions.

I strongly disagree. Not catching exceptions leaks abstraction layers. If I have a Prefs::save() method, I don't want it throwing a DiskFullException when the Prefs class is an abstraction of a preferences datastore. I don't care what is the final store, as long as it fits the abstraction. A well designed abstraction will catch and wrap the exception into something that makes sense at that level of abstraction, never leaking implementation details.


"A well designed abstraction will catch and wrap the exception into something that makes sense at that level of abstraction, never leaking implementation details."

This makes recovering from the error rather difficult. If the problem is that a disk is full, I need to do something about the disk being full (maybe ask the user to delete some files). If the problem is that the disk was disconnected, I need to do something else about it.

The real issue here is that you are thinking of exceptions as they exist in languages like C++ and Java, where you destroy your call stack in order to locate the exception handler. Such languages make the difficult problem of error recovery that much harder. Common Lisp does it better: the handler is allowed to invoke restarts (if they exist), which the function that signaled the error sets up. This encapsulates things very neatly. The disk was full? The error is signaled by write, which sets up a restart that tries to continuing writing to the disk. At the next level of abstraction, you might have a restart to remove the half-written record from your disk. In theory, you might only need one top-level exception handler, which interacts with the user as needed to recover from errors (or politely inform them that no recovery is possible).


I'm not familiar with the concept of restarts. I do concede, though, that wrapping extensions limits recovery. Either the library can recover on its own, or it can't fulfill its designed service.

On the other hand, the most revered architectures we have aren't leaky. You don't see network stack code trying to recover from Ethernet collisions at the IP level, or app logic trying to salvage an SQL transaction when a restriction has been tripped. The price for non leaky abstractions is not zero, but the gains are also definitely not zero.


I don't think you understand proper exception handling. Catching and wrapping DiskFullException is pretty pointless because what are you going to do about it? Nothing. It's nonsensical for a preferences class to deal with that situation. Instead let it bubble up and so that the caller has the option of handling it, for example by showing a dialog "Delete temporary files and try again?"

You'll never be able to catch all exception. In addition to your DiskFullException, you have PermissionDeniedException, NFSException, NullPointerException, InvalidFilenameException, PathToLongException ad infinitum. By trying to be "nice" by trying to wrap all those exception you are actually doing your api users a great disservice.


You state a lot of half truths ("you'll never be able to catch all exceptions"), don't justify the assumptions and didn't handle the core of my argument (abstraction leakage). In the hurry to insult me, did you actually read my argument?


Because the core of your argument was based on a stupid rule I don't agree with! You: "Throwing DiskFullException results in abstraction leakage" Me: "No it doesn't.."


My bad. I should have realized earlier I was discussing with a child.

Please accept my apologies.


To your second point: all of these errors should extend IOException ;)


Librairies that wrap exceptions into something else often do a disservice to their users. In this `Prefs:save()` example, what should the wrapping library throw? A "SaveFailedException"? That's more abstract, however now I would need to go check the source code of the library, find where the exception has been converted to something else, comment out the "try/catch" statement, and rerun the program. Then I can finally now what really happened and do something to fix it.


If done right, rethrowing exceptions does not affect the ability to debug the code. You don't throw a pristine new exception on the spot. You wrap the exception in a new one, effectively maintaining all the information. Coded recoverability suffers, but debugging ability does not. You have the same debugging information in the rethrown exception as you did in the bubbled up one.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: