When I learned the C++ programming language in 1997, I assumed that exception handling was a completely optional language feature, since it was presented in the latter chapters of the C++ books, and up to that chapter none of the code examples would use exceptions. I was wrong, of course, but this mentality is what partly caused my biggest programming mistake of 2013.
Exception handling is a critical language feature in programming that you simply can't ignore, for at least two reasons.
First, in languages compiled for a run-time virtual machine, exceptions are part of the execution environment. In "system programming compiled languages" like C, C++ or D, many operations, like dereferencing NULL references or dividing an integer by 0, cause the operating system to throw a signal to the process that makes it crash. In VMs, they would cause an exception to be thrown at that point in the process. But then, in all VM-based languages I've seen, simply not attempting to catch any of these VM-level exception would cause the default exception handler to crash the process, which is equivalent to system languages.
Second, exceptions are "implicit", meaning they can be thrown from anywhere without warning. Sure, in Java "normal" exceptions are part of the function signatures, forcing exception handling to be handled properly for these at compilation time, but run-time exceptions can still happen. In C++, C# and most other modern programming languages, function signatures don't carry exception types as part of their interfaces.
This would be fine if all exceptions were "bad" to the extent that they would warrant crashing the entire process. But that's not what happened over the years. In Object-Oriented design, object constructors would often be used to perform both the object creation and its initialization to a fully working state. While languages guarantees object creation under all circumstances (actually, before the constructor function is called), initialization can fail. Since the only functional interface of the constructor is to return an object instance, the only other obvious way to report an initialization error is to throw an exception. (Of course, the object could remain in an "uninitialized" state and return an "uninitialized" error for every other function call, but most developers are too lazy to do that.)
For objects that wrap system resources, initialization can fail under normal execution. For example, an object that wraps a file could produce an error "file not found". Personally, I don't think the whole process should crash for something like that. But then, if opening a file is part of the object's constructor, then throwing an exception is the de-facto way of reporting that system error.
The lack of "finalize" statements in C++ kind of forces "Resource Acquisition Is Initialization" design for objects that wrap system resources, which in turns forces reference counting, and so on. It is typical that in most C++ software and libraries, exceptions are avoided as much as possible. Personally, I just avoided C++ altogether.
But for C# and .NET, the result is nearly catastrophic: Anything can and will throw exceptions, without any compilation warning, and not handling those exceptions will crash the entire process. And not just "exceptional exceptions" that ought to crash the process, but for simple things like attempting to open a file that doesn't exist.
Sure, you can add "try / catch" blocks everywhere, but things can get complicated if your code doesn't own all the execution threads. Combine COM objects, callbacks from foreign DLLs and using delegates with WPF, and now there is no simple "catch-all" solution to exception handling. Sure, there are last-chance exception handlers like Application.DispatcherUnhandledException and AppDomain.UnhandledException, but when was the last time you saw any book or sample code using these? So, of course, by ignorance I didn't set up those exception handlers in my code before it was too late.
My point is that exceptions should be avoided in programming interfaces as much as possible. Separating object creation and initialization would be a good step in that direction. Also, throwing exceptions should be reserved for "fatal" run-time errors that normally would be impossible to recover from and ought to crash the entire process.
As for the "other" part of my "biggest programming mistake", it's all about what various versions of the Windows kernel do when a process crashes. Coming from UNIX with its simple "fork / exec" and signal handling, what Windows does is just plain disgusting. But then, it's Windows, so even if Windows 7 is "less crap", I don't know what I expected from its programming environment when you look under its rug…
Published on December 29, 2013 at 14:30 EST
Older post: Cutting the Cord
Newer post: Back to Objective-C