3 ms·
Functions should not be exposing their implementation and that's what makes something an error rather than just output from a function. One should not care if
by wvenable 2y ago
Functions should not be exposing their implementation and that's what makes something an error rather than just output from a function. One should not care if a function was implemented as a pure calculation, read from a file on disk, or use a network service. And that should be able to change at anytime without breaking the callers.
This means that checked exceptions and explicit error returns that are anything other than a very generic error type are incompatible with encapsulation and polymorphism.
I think the best generic solution to error handling is unchecked exceptions. Most code should not be littered with try/catch blocks and none of them should be gratuitous. Even in my largest projects the number of catch blocks can be counted on one or two hands. You don't need to know what might or might not throw -- everything can throw. If something does throw today, it could throw tomorrow. Don't worry about that. With unchecked exceptions, just concern yourself on what you can handle and where you can handle it. Most error handling is either to abort (the operation or application) or retry.
- layer8 2y agoIt is often important for the caller to know under what circumstances and for what reasons a function can fail, in order to react appropriately. In particular, callers want to distinguish between expected and unexpected errors, which can differ from use case to use case, and for expected errors they may want to distinguish between different expected cases, for example to decide whether it makes sense to retry the operation or not. This doesn’t mean that the function signature has to expose implementation details. If the function is file_open(), listing FileNotFound as one of the possible errors doesn’t expose an implementation detail. If the function is sql_insert(), distinguishing between ConnectionError and UniqueKeyConflict is useful and doesn’t expose implementation details. Different error modes like that exist on all levels of an application.
- wvenable 2y agoA function containing file_open() or sql_insert() will expose implementation details if it leaks those errors out. I don't disagree but most of my application is not in those leaves; it's a call stack 20 levels deep.
- layer8 2y ago> A function containing file_open() or sql_insert() will expose implementation details if it leaks those errors out. The correct thing to do in that case is to wrap/convert those errors to errors that are appropriate to that function’s interface contract. Exception types are an important part of a function signature, and should be designed accordingly, not be propagated blindly. With unchecked exceptions, you are leaking implementation details dynamically, and you don’t even know it/can’t control it, because they are not statically checked. If you care about leaking implementation details, unchecked exceptions are arguably worse. > I don't disagree but most of my application is not in those leaves; it's a call stack 20 levels deep. I regularly have such failure mode distinctions in higher-level program logic as well. With unchecked exceptions, you often aren’t even aware what failure modes you have.
- wvenable 2y ago> The correct thing to do in that case is to wrap/convert those errors to errors that are appropriate to that function’s interface contract. Those, however, tend to be useless. What you end up doing is placing the real error untyped into new the exception/error and that's where the real meat is. I can create an application that could retry an operation if it sees a particular kind of network error. Does it also work for the dozens of custom errors used by the interface contract of every single method? That's impossible to even know. How do I handle dozens of different SomeModuleError types? > you often aren’t even aware what failure modes you have. Again, that is the point of encapsulation and polymorphism. Maybe the implementation doesn't even exist at compile time in the case of a plugin. I don't want to have to re-write my entire application every time some implementation detail changes anywhere. If you don't want to do that, then you're have to transform your errors/exceptions into something useless. What I want to focus on what kinds of errors I can handle and where I can handle them. That has nothing at all to do with where the errors are generated.