This last week has been a very productive discussion on how to use an exception policy. In particular one of our best agile development team leads stated the following:
Exception policy in agile development
Distinguish between at least three types of errors:
- Logical errors, reflecting a violation in the invariant domain logic. In several languages, they are usually declared ASSERTs. What comes to be a bug, come on!
- The exceptions are logical errors introduced by systems on which we have delegated some responsibility for our application. For example, a web application often takes a number of licenses, let’s say that for the database, the user accesses through a resource published by us and otherwise, 404, etc … it makes sense to treat it as an exception.
- The results represent an outlier in the logic of domination, the typically recoverable error. For example, I’m going to find a user in the database, but I don’t. Well, this kind of results can be represented in various ways, and indeed, one of them would be by exceptions, but should keep an eye on alternatives such as algebraic types to prevent abuse of the exceptions.
Avoid exception abuse
- Exceptions introduced an exceptional syntax in the code. TryCatch blocks, a different way of testing, etc.
- Refactorability. Spend an error exception or vice versa, is usually not a simple incremental change, but it requires paying exceptional attention. Moving from an error of one type to another is a trivial change. Exceptions only represent an improvement in refactorability when we add an error code where previously there was no place for mistakes. Such errors are good candidates to be a logical error than exception.
- Implicit Return. Now because we use exceptions, we have to figure out whether each function we use has the side-effect of being able to throw an exception. When writing code, this is an additional cost that may well be alleviated with proper documentation. But when reading, you probably ignore the implicit flow derived exceptions, or else pay a cognitive cost that would not be present in a more explicit code. Motto: Explicit is better than implicit. Motto: Read-oriented programming.
- Composition. Code reuse is a good idea. Pulling exceptions reuse code is not such a good idea, because you must enter a unique syntax also in the new method, and especially because you are entering a section of additional code to capture exceptions. To avoid this, I have to introduce more versions of the method that does not throw exceptions, I’m probably sinning duplication, or something worse, temporal coupling interfaces, which is another way of introducing implicitly.
- Capture requires discipline. When we launch an exception we are basically saying that we will handle the error. This either means that no exceptions that should be captured are caught, ignoring the error handling. Error handling are introduced in different parts of the code, with the loss of context that it implies. All exceptions are caught in the same section of code, thus avoiding the above, but with one important restriction: reuse of a function that throws exceptions involves violating this discipline. It is also typical to have to turn a blind eye using third party libraries (ie: JSON.parse), which breaks the coherence of the discipline.
- When talking of using exceptions, we talk about throwing, not handling them locally.
- This means many big throw and one outer try block, under the “translation layer” that uses the client (eg. HTTP exceptions transform codes).
When in most cases we do not expect local treatment of exceptions, the consequences are:
- Exceptions default end flow, which is exactly the behavior of monadic composition in functional languages
- You can not use the catch blocks as conditional logic. No exceptions will try locally, except in cases of inter-domain translation, which would also classically be functional.
- Taxonomies exceptions. DomainException is different from RuntimeException or TechnicalError.
- Atomicity solves it as necessary to resolve, that is, by design, from the point of view of transactional design and orientation domain.