Fatalism in error handling
This article is a reaction to the article: What will happen to error handling in C ++ 2a . After each paragraph I had an itch, healed wounds opened and started to bleed. Maybe I take too much to heart what is written. But I just want to howl about the myopia and illiteracy that C ++ programmers show in the 21st century. And not even at the beginning.
Let's get started
Conventionally, all error situations in the program can be divided into 2 large groups:
- Fatal errors.
- Not fatal, or expected errors.
I will now find fault. But fatal mistakes are also in some sense expected. We expect that a trip through memory often leads to a fall, but it may not lead to it. And this is expected, isn't it? When a classification is introduced, it would always be to check its consistency.
But this is so often a subtle mistake.
Let's look at fatal errors.
Division by 0 . I wonder why this error is fatal? I would gladly throw an exception in this case and catch it for further processing. Why is she fatal? Why is the specific behavior of my own program being imposed on me, and I cannot influence it in any way? Isn't C ++ about flexibility and the fact that the language is turned to face the programmer? Although...
Dereferencing null pointer . Immediately recalls Java, there is
NullPointerException, which can be processed. The Poco library is there too
NullPointerException! So why the developers of the standard with the stubbornness of the deaf-and-dumb repeat the same mantra?
In general, why did I start this topic? It is very important and just reveals the developer’s understanding of error handling. Speech here does not go about error handling as such, it is a prelude to an important action. Speech is always about application reliability, fault tolerance, and sometimes, in the rarest and most endangered, I would say, endangered types of programs, transactional and consistent behavior.
In this aspect, all disputes about dividing by zero and dereferencing pointers look like bird fights for bread crumbs. Certainly an important process. But only in terms of birds.
Let us return to the division into fatalism and its absence ... I will begin with a simple question: if I received incorrect data over the network, is this a fatal error?
The simple and correct answer: depends on. It is clear that in most cases this is not a fatal error, and all data received over the network must be validated, and 4xx should be returned in case of data error. Are there any cases when you have to crash? And to crash with a wild howl to send SMS, for example. Yes, and not one.
There are. I can give an example from my subject area: a distributed algorithm of consensus. A node receives a response that contains a hash from chains of changes from another node. And this hash is different from the local one. This means that something went wrong, and continuing with further execution is simply dangerous: the data may diverge if not already. It happens when the availability of a service is less important than its consistency. In this case, we need to fall, and with a crash, so that everyone can hear around. Those. we got the data over the network, checked it in, and dropped it. For us, this mistake is nowhere more fatal. Is this error expected? Well, yes, we wrote the code with validation. It is foolish to say the opposite. Only we do not want to continue the program after this. Manual intervention is required, automation did not work.
The choice of fatalism
The most obscure thing about error handling is deciding what is fatal and what is not. But this question every programmer asks himself throughout the development. Therefore, somehow answers itself to this question. The correct answer comes for some reason from practice.
However, this is only the visible part of the iceberg. In the depths there is a much more monstrous question. To understand the depth of the depths, you need to set a simple task and try to answer it.
Task . Make a framework of something.
It's simple. We do a framework, for example, network interaction. Or parsing json. Or, at worst, XML. The question immediately arises: when an error occurs from a socket, is it a fatal error or not? I paraphrase: should I throw an exception, or return an error? Is this an exceptional situation or not? And can return
std::optional? Or monadka? (^ 1)
The paradoxical conclusion is that the framework itself cannot answer this question. Only the code using it knows. That is why in the excellent, in my opinion, boost.asio library uses both options. Depending on the personal preferences of the author of the code and the application logic, you can choose one or another method of error handling. At first, I was a little embarrassed by this approach, but over time I became imbued with the flexibility of this approach.
However, this is not all. The worst thing to come. Here we write application code, but it seems to us that it is applied. For other code, more high-level, our code will be library. Those. the division into application / library (framework, etc.) code is a pure convention, which depends on the level of component reuse. You can always screw something up and the application code will cease to be so. And this immediately means that the choice of what is valid and what is not, is already decided by the code using, and not used.
If we jump aside, it turns out that sometimes it is even impossible to understand who is using whom. Those. Component A may use the component B , and component B Component A (t2). Those. who determines what will happen is generally incomprehensible.
Unraveling the coil
When you look at all this disgrace, then the question immediately arises: how to live with it? What to do? What guidelines for themselves to choose, so as not to drown in diversity?
For this it is useful to look around and understand how such issues are resolved in other places. However, we must look wisely: we must distinguish between "collecting stamps" from full-fledged solutions.
What is "stamp collecting"? This is a collective term, which means that we exchanged a goal but something else. For example: we had a goal - to call and communicate with loved ones. And we once, and bought an expensive toy, because it is "fashionable" and "beautiful" (^ 3). Familiar? Do you think that does not happen with programmers? Do not flatter yourself.
Error handling is not a goal. Whenever we talk about error handling, we immediately come to a standstill. Because it is - a way to achieve the goal. And the initial goal is to make our software reliable, simple and understandable. Such goals should be set and always adhere to them. And error handling is bullshit, which is not worth discussing. I want to throw an exception - yes to health! Returned the error - well done! Would you like a monadka? Congratulations, you created the illusion of advancement, but only in your own head (^ 4).
Here I also wanted to write how to do it correctly, but I already wrote out. The wounds healed and stopped bleeding. In short, the tips are:
- Separate into components with clear boundaries.
- At the borders, describe what and how can fly. It is desirable to be uniform. But much more important that it was.
- Make it easy to handle errors in the code it will use.
- If something can be processed inside without load on the user code - do not push it out. The fewer errors a user must handle, the better.
- Respect your user, do not be assholes! Write understandable interfaces with expected behavior so that it does not need to read comments and use foul language.
The 5th Council is the most important, because he combines the first four.
PS As a child I was always curious to look at the anthill. Thousands of ants, each doing something, crawling about their business. The process is underway. Now I also watch with interest. Also for the anthill. Where thousands of individuals are engaged in their small business. I wish them good luck in their difficult task!
^ 1: People are susceptible to fancy stuff. When everyone had played enough, C + + programmers woke up, and then everything started to happen.
^ 2: This can happen when there are several abstractions in the component B that binds them. See Inversion Control .
^ 3: And the next day, bang, and the screen crashed.
^ 4: I don’t mind monads, I don’t want to breathe, like, look, here is a monad monoid in the monoidal category of endofunctors! Applause and approving nods are heard. And somewhere far away, barely audible, someone orgasm.