Java and C# have inner exceptions built into the base classes and it's not at all hard to implement inner exceptions in other languages like PHP.
And you're right, this is a good compromise.
When it comes to exceptions (and many other things) you realize some boundaries are more important than others. If you're building, say, a database access library that has a well-defined API, you are better off defining a good set of meaningful exceptions.
Inside a particular module, however, creating new exception classes tends to create more problems than it is worth. I can't tell you how many times I've seen basically the same exception defined three or four times by different programmers in different parts of the same system.
Even when you look at the "better" examples of exception hierarchies the results are frequently depressingly bad. For instance, many database access APIs just give a SQLException or the equivalent thereof. Some of them will add a huge range of Exceptions that are pretty superfluous such as InvalidTableNameException, InvalidColumnNameException, YouViolatedTheTypingRulesOfDatabaseXException (all of which should be DontProgramaticallyGenerateSQLException) but they never seem to have a DuplicateKeyException which is the one case where you actually might want to do something different from all of the others.
I haven't programmed Java for a while, so I missed that development. Back when I learned it this didn't exist yet, or at least was rarely used.
Yes agreed, database libraries seem to be notably bad in exception reporting. In Python, even though SQL is wrapped in a general layer, exceptions are still different per engine. MySQL has one exception with an error code which makes it very hard to distinguish between even a duplicate key and a network error.
Another mistake that many libraries make is that they make a generic 'FooLibraryException' and subclass all their exceptions from there, instead of from where it makes sense (IoError, KeyError, MemoryError ...)
But even with those issues, I hugely prefer exceptions to the 'check return value after every call'.
"Another mistake that many libraries make is that they make a generic 'FooLibraryException' and subclass all their exceptions from there, instead of from where it makes sense (IoError, KeyError, MemoryError ...)"
Actually, this isn't black and white. When I need to create more than one exception class for a library, I prefer to avoid subclassing standard exceptions to prevent that kind of confusing situation:
If my_library was returning a IOError, you would not know if the problem came from your code or the library code. Moreover, this would leak implementation detail.
Of course, you can always get around this by wrapping each library call within a try/catch block, but that does not scale well if you make many calls interspersed with your code.
Well, at least from a maintainability perspective it's more clear to do
try:
afile = open('myfile.txt')
content = afile.read()
except XXX:
(handle exception)
else:
(continue here using content)
If you want to capture the exceptions from a certain call. Otherwise, my_library.do_something might start raising a IOError somewhere in its bowels later on, and you have a confusing debug process.
When you find that you have a lot of similar or even repeated try/catch blocks, using 'with' context managers might be a good idea.
My point was that an IOError is an IOError, wether it's a MongoDBIOError or a MySQLIOError. Yes, you leak some implementation detail (namely, that I/O is used) but do do useful diagnostics you need some (abstract) implementation knowledge... network error: is your router plugged in? io error: is the disk full? keyerror: implementation bug? and so on.
Hm maybe this would be a case for multi-inheritance exceptions? MongoDBIOException is both and IOException as a MongoDBException. Don't even know if it's possible, or wise :)
And you're right, this is a good compromise.
When it comes to exceptions (and many other things) you realize some boundaries are more important than others. If you're building, say, a database access library that has a well-defined API, you are better off defining a good set of meaningful exceptions.
Inside a particular module, however, creating new exception classes tends to create more problems than it is worth. I can't tell you how many times I've seen basically the same exception defined three or four times by different programmers in different parts of the same system.
Even when you look at the "better" examples of exception hierarchies the results are frequently depressingly bad. For instance, many database access APIs just give a SQLException or the equivalent thereof. Some of them will add a huge range of Exceptions that are pretty superfluous such as InvalidTableNameException, InvalidColumnNameException, YouViolatedTheTypingRulesOfDatabaseXException (all of which should be DontProgramaticallyGenerateSQLException) but they never seem to have a DuplicateKeyException which is the one case where you actually might want to do something different from all of the others.