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:
PREAMBLE
Distinguish between at least three types of errors:
- Logical errors, reflecting a violation in the invariant domain logic. In several languages 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, in a web application often take a number of licenses, for example, the database is there, the user accesses through a resource published by us and otherwise, 404, etc … This 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, you may not …. and it is not. 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, it 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 more candidate to be an error lógicoque exception.
- Implicit Return. Now because we use exceptions 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. We spent a lot more time reading code writing it. - Side-effects. Exceptions can be regarded as a side-effect. And change the flow can break atomic transactions (ie: with cleanup) within the logical domain. This is a problem in languages partially overcome with the concept of deterministic lifetime and C ++ (see RAII), but not in JavaScript, Java or the like. The cost therefore not catch exceptions when due, can be high.
Motto: State is evil. - 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. If 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 been implicitly (if to search I have a user who previously see if there is, it is possible that in the interval of time that has passed from one call to the other, the state has changed).
- Capture requires discipline. When we launched an exception we are basically saying that the error handling “and we will.” This invited to either:
no exceptions should capture are caught, ignore the error handling.
Error handling various sections are introduced in different parts of the code, with the loss of context that implies.
all exceptions are caught in the same section of code, thus avoiding the above, but with one important restriction: reuse a function that throws exceptions involves violating this discipline, therefore, or fall into 6.b, or fall to 5. 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.
CONCLUSION
- When talking of using exceptions, we talk of throwing, not to handle them locally.
- This means many big throw and one outer try block, under the “translation layer” that use the client (eg. HTTP exceptions transform codes).
- The rational behind this pattern are:
dead programs do not lie
fail fast
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 functional.
- We 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.
The problem of verbosity exceptions in Java and all other languages derived from misunderstanding of always dealing with exceptions localmente. El decided that doing so is because, as eg Javascript language that does not give us the monadic composition has comprehensions not have the expressiveness of the guys in the “algebraic abstractions”.
In short, it’s a lot closer to the imperative language expressiveness than declarative.
So the side-effect or harmful and undesirable effect on the exceptions is treated locally the exception, not throw