📜 ⬆️ ⬇️

No one can handle errors

From one book to another, from article to article roams the view that the expression

try { //do something } catch(Exception ex) { } 

is a bad practice. Returning codes is also a bad practice. But does it make it easier for us, programmers, to live with this knowledge and are they really indisputable? And the funniest question is - does anyone in the world know how to correctly handle errors that occur during the course of an application? (By this I mean processing only those errors that make sense to process and display error messages that correspond to what really happened, which do not lead the user to confusion, and ideally suggest a solution to the problem that has arisen).

The purpose of this article is not to “span” existing concepts, beyond the author’s ignorance of the best approach. The purpose of the article is to exacerbate the problem of exception handling and the recognition of the fact that it is not enough just to do the “do not catch(Exception) ” or “always throw exceptions to the top - perhaps, they will sort it out at the top”. It doesn't help anything. There is so little written about this topic in Russian (and who trusts Russian-speaking authors? It's a joke, but in every joke there is some joke). The article is also intended for developers who are not sophisticated by experience, so that the reader realizes that absolutely everyone suffers from error handling, including the “strongest of this world”, and also begins to understand the problem at a slightly deeper level.

First I would like to present my translation of two articles:
1. Problems with checked exceptions. Interview with Anders Hejlsberg. Part 2. August 18, 2003.
2. Life with unverifiable exceptions. March 3, 2014.
')
I would also like to mention that for the sake of brevity, I did not translate absolutely all of these articles. These are clippings that are most important in terms of a given topic. I will frame the text as follows: Spock speech text end of Spock speech

Problems with checked exceptions.


Bruce Eckel : C # has no mechanism for checked exceptions. How did you decide whether or not to include a mechanism for checked exceptions in C #?
Anders Hejlsberg : I see two problems with checked exceptions: scalability and versioning.
Bruce Eckel : I used to think that checked exceptions are cool.
Anders Hejlsberg : Exactly. Honestly, on the surface, they really look good and it seems that everything is fine with this idea. I absolutely agree that the exception mechanism is a great feature. Just a specific implementation can be problematic. If you implement this mechanism as it was implemented in Java, then I think you are simply exchanging one set of problems for another. As a result, it’s unclear to me whether life is getting any easier. We just make life different.
Bruce Eckel : Were there any differences in the C # development team regarding checked exceptions?
Anders Hejlsberg : No, I think that our team of language designers was more in agreement. C # is silent with respect to checked exceptions. If one day the best solution is found - and believe me, we continue to think about this problem - we will be able to go back and fasten what is needed. I am a staunch supporter of the fact that if you have nothing to say that will advance the art, then it is better to remain silent and stick to a neutral position, rather than trying to create your own framework.
Bruce Eckel : Developers who practice extreme programming say “do the simplest things to work.”
Anders Hejlsberg : Yes, Einstein said "do the most simple, but not easy". I believe that the checked exceptions are handcuffs for programmers. You can observe programmers who are trying to use some kind of new API, which declares many potentially thrown exceptions and you can see how clogged their code becomes. As a result, you realize that checked exceptions do nothing for them.
Bill Venners : You mentioned scalability and versioning with respect to checked exceptions. Could you clarify what you mean by two problems related to these concepts?
Anders Hejlsberg : Let's start with versioning, because problems here are easy to see. Let's say I create a method foo that declares forwarding exceptions A, B and C. In the second version of the method I want to add a couple of tokens and now the foo method can also throw out an exception D. Adding a new exception is an incompatible change, because the existing users of this method are almost 100 % will not catch this exception.
Adding a new exception breaks the client code. This is like adding a method to an interface.
Bill Venners : But do you break the client code anyway, even if there are no checked exceptions in the language?
Anders Hejlsberg : No, because in a large number of cases, people are “drummed”. They are not going to catch any of these exceptions. At the bottom level, around the message loop is an exception handler. This handler simply displays a window indicating that something went wrong. Programmers protect their code by covering it with try\finally constructs everywhere, so they simply shy away from handling the exception, and they were not going to handle the exception.
Bill Venners : In general, do you think that in the most common case, users prefer a handler at the top of the stack for explicit exception handling?
Anders Hejlsberg : It's funny that people think that the most important part of the relationship with exceptions is handling them. This is just not so important. In a well-written application, the ratio of try\finally to try\catch structures is about 10 to 1.
Bill Venners : So what's the result?
Anders Hejlsberg : As a result, you are protecting yourself from exceptions, rather than handling them. Exception handling you implement somewhere else. Naturally, in any type of event-oriented application, as is the case with any modern UI application, you implement exception handling around the message loop and just here you handle them. But in the course of the program you are protecting yourself by freeing the allocated resources that were captured and so on. You clean up behind you so that you are always in a consistent state. You do not want to write a program that, in a hundred different places, handles exceptions and throws out error windows.
Exception handling should be centralized and you should just protect yourself while exceptions are propagated before the handler.

Spock speech
The problem with scalability does not change the meaning of exception handling as suggested by Anders Hejlsberg, so it is not advisable to post a translation from the point of view of this topic (if you are interested, you can go and see, but in general it comes down to the fact that as the program grows exception is growing, and nobody will be able to handle them all, especially methods that can traverse a dozen or two different types of exceptions, your handlers will turn into hell footcloths try\catch and the code becomes impossible supported be).
Most recently, on March 3, 2014, Eric Lippert also raised the topic of exception handling in C # on his blog.
He divided the discussion into two parts: in the first part he asked a few questions, and in the second part he aggregated the answers and made a summary.
So, the questions that Eric asked the readers of his blog:
end of Spock speech

Life with unverifiable exceptions




Spock speech
The third question was also asked, but the question is not very interesting and the answers to it are also of no particular interest, therefore the relevant parts will be omitted.
end of Spock speech

The main conclusion from the comments of readers: exceptions introduce a little mess in C #. The semantics of the language and the organization (or lack of organization) of the hierarchy of exceptions make it difficult to know which exceptions to catch and which ones to skip. A lot of readers left a lot of great comments, but one of them made the strongest impression on me:
I think the whole concept of “handling” exceptions is a bit like a game for fools. I can probably count on the fingers of one hand the number of cases where I was really able to handle a specific type of exception and make something intelligent in the handler. In 99% of cases, you must catch all or nothing. When an exception of any type is thrown, restore the stable state and then either continue or interrupt the execution of the program.

It's rude, but, I think, fair.
This comment suggests Pokémon processing - catch them all! ( gotta catch 'em all ) - this is the solution. I was surprised that almost a third of the commentators expressed support for using catch(Exception) , because historically it was described as a bad practice by Microsoft. C #, as I usually say, is designed to be a “ pit of success” , where that which is the simplest is the most correct. This remarkable goal seems to have not been achieved in this case. If catch(Exception) is the least correct path and the simplest, then this is because the correct path is too heavy.
The overwhelming majority of commentators wrote that they were fixed by bugs, the cause of which was the lack of catch for specific types of exceptions, although such cases happened to different people with different frequency: “from 1 to 2 times”, “occasionally” to “often”.
A third said they used MSDN and other forms of documentation (including XML comments) to identify the types of exceptions that should be caught. MSDN and praised and criticized; some parts of MSDN are written perfectly, others are written so that nothing is clear. The third part of the documentation was comprehensively criticized, no one believes such documentation.
A quarter said they used something like a trial and error method — debugging, testing, reading logs, and getting crash dumps to figure out which exceptions should be caught.
Again, disappointment was summarized by the following comment:
Each try/catch block is an exercise in disappointment because you think that you are catching the type of exception you need until everything breaks down in operation.
Quite a lot of commentators have noticed that the exception handling system assumes the exception type itself is the most important information, but one type is not enough to make the correct exception handling.

Spock speech
Suddenly! Almost everything that is said either directly or indirectly contradicts The Best Practice!
I'll throw in another problem: what if you need to call the code in a loop, which always throws exceptions in case something is wrong? The old unproductive machine (and we have a great many in Russia) will “die”. There will come to the rescue (if you are the owner of the called method) return codes or instances of classes that contain the necessary information. Oh, it turns out the return codes are not dead, as expected.
And you can't go anywhere from catch(Exception) : you have to do catch(Exception) everywhere, and not just at the level of the message loop, unless you think that in your particular situation, let yourself get exceptions fly through the entire system (the further you go , the more they devour resources).
There is one catch with catch(Exception) : we can catch StackOverflow or OutOfMemory and not even lead with an eye, which will lead to sad consequences for which you can pay millions of rubles (or dollars) if you do not write Hello, World!
For the "solution" (deliberately quoted in quotes, since the existing solutions to the error handling problems are hardly fully satisfied, or at least close to complete satisfaction), filtering is appropriate for this problem. By the way, despite the fact that MSDN exclusion filtering is not recognized as the best practice, the part of the Enterprise Framework itself that is responsible for handling exceptions is based on filtering, which is configured through appropriate policies, a surprise!
Here is a simple static class that simplifies exception handling:
 public static class Exceptions { private static readonly List<Type> fatalExceptions = new List<Type> { typeof (OutOfMemoryException), typeof (StackOverflowException), //  ,        }; public static string FullMessage(this Exception ex) { var builder = new StringBuilder(); while (ex != null) { builder.AppendFormat("{0}{1}", ex, Environment.NewLine); ex = ex.InnerException; } return builder.ToString(); } public static void TryFilterCatch(Action tryAction, Func<Exception, bool> isRecoverPossible, Action handlerAction) { try { tryAction(); } catch (Exception ex) { if (!isRecoverPossible(ex)) throw; handlerAction(); } } public static void TryFilterCatch(Action tryAction, Func<Exception, bool> isRecoverPossible, Action<Exception> handlerAction) { try { tryAction(); } catch (Exception ex) { if (!isRecoverPossible(ex)) throw; handlerAction(ex); } } public static bool NotFatal(this Exception ex) { return fatalExceptions.All(curFatal => ex.GetType() != curFatal); } public static bool IsFatal(this Exception ex) { return !NotFatal(ex); } } 

Examples of using:
 Exceptions.TryFilterCatch(host.Close, Exceptions.NotFatal, ex => logger.Error("    .", ex)); private bool TryGetTpmProcess(out Process process) { process = null; try { process = Process.GetProcessById(App.TpmProcessId.GetValueOrDefault()); return true; } catch (Exception ex) { if (ex.IsFatal()) throw; return false; } } 


The TryFilterCatch method allows TryFilterCatch to TryFilterCatch into a short record. Extension methods are also convenient to use. The way with TryFilterCatch peeped here.

A brief summary of all my research on this issue is the following conclusion: we all use the lesser of evils, but we choose evil anyway, since no one knows how to get rid of evil or reduce it to a minimum. By necessary evil, I mean the concept of unchecked exceptions, which are the default way of notifying everyone and everything that something went wrong in C # (and in Java, too, from the moment everyone understood that checked exceptions give nothing ).

So, do not believe in "statements cast in granite", look in both ways and do not allow exceptions to kill your application.

end of Spock speech

Source: https://habr.com/ru/post/221723/


All Articles