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 erroneous 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 errors 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.
The 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...
Null pointer dereferencing . Java is remembered NullPointerException
, there is a NullPointerException
that can be handled. The Poco library also has a 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. Of course, 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 to continue further execution is simply dangerous: the data may diverge if not already. It happens when the availability of the 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 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 application logic, you can choose one or another error handling method. 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. We write application code, but it seems to us that it is application code. For other higher level code, our code will be library. Those. 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 can use component B , and component B can use component A (^ 2). Those. who determines what will happen is generally incomprehensible.
When you look at all this disgrace, then the question immediately arises: how to live with it? What to do? What guidelines for yourself 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 have once, and bought an expensive toy, because it is "fashionable" and "beautiful" (^ 3). Familiar? Do you think programmers don't do that? 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 a shit that is not worth discussing. I want to throw an exception - yes to health! Returned the error - well done! Want monadka? Congratulations, you created the illusion of advancement, but only in your own head (^ 4).
I also wanted to write here how to do it correctly, but I already wrote out. The wounds healed and stopped bleeding. In short, the tips are:
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 behind 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, i.e. monoid in the monoidal category of endofunctors! Applause and approving nods are heard. And somewhere far, far away, barely audible, someone orgasm.
Source: https://habr.com/ru/post/426971/