Boy, do I have some news for you... Like about 25 years old news. Did you ever try java?
Boy, do I have some news for you... Like about 25 years old news. Did you ever try java?
It just occurred to me that I could probably do the same with attributes and DynamicProxy for C#. And also implement additional checks like "IF X is thrown, its properties must satisfiy some constraints.". (I program both in Java and C# these days.)
Such "UnexpectedException" as I suggested would serve two purposes: 1) well, knowing that something unexpected happened and allowing you to handle it with "last chance handler", 2) helping the developers maintain the contract. If you change a method so that it can throw some new exceptions (compared to the previous version), you've broken its contract/compatibility. This would then show up during testing.
> You'll still have your "throws" statement but then the tool would wrap calls t
Yes. Fortunately, Java allows you to mention subclasses of RuntimeException in "throws" declaration.
Unless the method 1) throws an exception which it should not have according to its declarative contract (annotations in Java, attributes in C#), and 2) it gets (erroneously) handled by an intermediate handler.
> What does re-wrapping help with, exactly?
It makes it clear that the method broke its declarative contract, which is what exceptions are _for_. And given the restriction that only the runtime can throw such exceptions, you're sure that no other method can randomly throw such exceptions because... they like it so.
Another question is ergonomics. It's trivial (but tedious) to write code like this:
class Foo {
// ...
public void Frob(...) throws FooException {
try {
// ...
}
catch (Exception e) {
throw new FooException(e);
}
}
public void Blarg(...) throws FooException {
try {
// ...
}
catch (Exception e) {
throw new FooException(e);
}
}
}
which is actually a "best practice" already ("annotate inner exceptions with some high-level context and wrap them in high-level exceptions") — and adhering to it makes your proposition completely extraneous, because nothing can throw an UnknownException ever.And in before "don't catch and wrap Exception!", consider that Foo maybe parameterized by some dependency that may be implemented as a network service, or a disk file, or a DB: three different implementations will throw completely different exceptions: FileNotFound vs NetworkConnectionClosed vs OdbcInvalidManufaturer. Either dependency interface allows implementations to throw any of those (so that the user of Foo, who knows which implementation it specified, can handle those), or it makes them to wrap them all into DepException, but then, again, this means that Foo's methods will catch-wrap-rethrow DepExceptions instead of just Exceptions.
Depends. You can't know in a situation like
try {
DoA(); DoB();
}
catch (SomethingEx e) { ... }
If previously only DoB could throw SomethingEx and now DoA() can also throw it, the handler is almost certainly incorrect handling if this is some "generic" exception. C#/.NET base library is notorious for using InvalidOperationException for all kinds of unrelated crap.> It's trivial (but tedious) to write code like this
Eh. Methods without "throws" would be "unchecked" at runtime as well. So my (imagined) best practice would be that non-private methods declare their exceptions and leave it to the caller whether and how to wrap them.
> And in before "don't catch and wrap Exception!"
Oh, I do that all the time for precisely the reasons you mentioned. Exceptions from the lowest level are most precise and least useful as there's no information about the context. (Unless you go down the unmaintainable rabbit hole of parsing the call stack.)
You understand it correctly.
> if now some dependency of Foo would change drastically
The most frequent "drastic change" being writing new code and fixing bugs. It's extremely easy to widen the set of exceptions thrown by a method and zero tooling to help you with finding out what exceptions can be thrown from the code.
Say what you want about Java, but its division of throwables into errors and exceptions makes sense. Errors like stack overflow, VM faults, etc., should not be exceptions. Under this scheme, they would always propagate out of the method unwrapped. Again, .NETs predefined exception types are botched beyond repair.