⬆️ ⬇️

Crashes caused by exceptions

Last week, along with several of my colleagues, I participated in a loud talk about the fact that Go handles errors in expected scenarios by returning an error code instead of using exceptions or another similar mechanism. This is a rather controversial topic, because people are used to avoiding error with exceptions, and Go returns an improved version of a well-known model that was previously adopted in several languages ​​— including C — in which errors are transmitted via return values. This means that errors loom before the eyes of the programmer and force them to deal with them all the time . In addition, the dispute turns into the direction of the fact that, in languages ​​with exceptions, each error, without any additional actions, carries with it complete information about what happened where, and this may be useful in some cases.



However, all these amenities have a cost that is easy to articulate:

Exceptions teach developers not to care about errors.



The sad consequence is that this is relevant, even if you are a brilliant developer, because you are influenced by the world around us, which is condescending to mistakes. The problem will manifest itself in the libraries that you import, in the applications installed on your computer, as well as on the servers that store your data .

')

Raymond Chen described this problem in 2004 :



Writing the correct code in a model with throwing exceptions is in some sense more difficult than in a model with returning an error code, since anything can fail and you should be prepared for this. In the model with the return of the error code, the moment when you have to check for errors is obvious: as soon as you received the error code. In the model with exceptions, you just need to know that errors can occur anywhere.



In other words, in the model with the return of the error code, when someone skips error handling this happens explicitly: they do not check the error code. At the same time, in the model with throwing exceptions when considering the code in which someone handles the error, everything is not so clear, since the error is not explicitly indicated.

(...)

When you write code, do you think about what the consequences of each exception may be to each line of code? You should do this if you are going to write the correct code.




This is absolutely true. Each line that can cause an exception carries a hidden “else” branch for an erroneous script that is very easy to forget. Even if the introduction of code for error handling seems meaningless repetition, writing it makes developers remember the alternative scenario, and quite often this code is not empty.



This is not the first time I've been writing about this and, given the controversy surrounding this statement, so I found a couple of examples that confirm the problem. The best example I could find today is in the pty module of the standard Python 3.3 library:



def spawn(argv, master_read=_read, stdin_read=_read): """Create a spawned process.""" if type(argv) == type(''): argv = (argv,) pid, master_fd = fork() if pid == CHILD: os.execlp(argv[0], *argv) (...) 




Every time someone calls this code with the wrong executable file name in argv, an unused process that is not subject to garbage collection and is unknown to the Python application is spawned because execlp fails and the forked process is ignored. And whether the client of this module will catch an exception or not does not matter. Local commitment was not met. Of course, the error can be corrected trivially by adding try / except inside the spawn function itself. However, the problem is that this logic seemed normal to everyone who has ever seen this function since 1994 , when Guido van Rossum first committed it .



Here is another interesting example:



 $ make clean Sorry, command-not-found has crashed! Please file a bug report at: https://bugs.launchpad.net/command-not-found/+filebug Please include the following information with the report: command-not-found version: 0.3 Python version: 3.2.3 final 0 Distributor ID: Ubuntu Description: Ubuntu 13.04 Release: 13.04 Codename: raring Exception information: unsupported locale setting Traceback (most recent call last): File "/.../CommandNotFound/util.py", line 24, in crash_guard callback() File "/usr/lib/command-not-found", line 69, in main enable_i18n() File "/usr/lib/command-not-found", line 40, in enable_i18n locale.setlocale(locale.LC_ALL, '') File "/usr/lib/python3.2/locale.py", line 541, in setlocale return _setlocale(category, locale) locale.Error: unsupported locale setting 




This is quite a serious crash due to the lack of data about the locale in the system application, which, ironically, should inform users which packages need to be installed if the command is missing. Notice that on top of the grid is a link to crash_guard . This feature is designed to intercept all exceptions at the edge of the stack and display detailed system information and tracebacks to help solve the problem.



Such “parachute interception” is quite common in exception-oriented programming, and this approach, as a rule, gives developers a false sense of good error handling in an application. Instead of real protection of the application, it becomes just a convenient way to crash. In this case, it would be more correct to display a warning, if necessary, and to allow the program to work as usual. This could be done by simply wrapping this line around:



 try: locale.setlocale(locale.LC_ALL, '') except Exception as e: print("Cannot change locale:", e) 




Obviously, this is easy to do. But, again, the problem is that it was natural not to do it right away. In fact, this is more than natural: indeed, it seems better not to consider the wrong way. In this case, the code will be reduced, it will be more straightforward, and as a result only the one that leads to the desired result will remain.



As a result, unfortunately, we plunge into the world of fragile software and pink elephants. Although a more expressive style of returning errors builds the right mindset: will the function or method return an error in the result? How will it be processed? Does the function interacting with the system not return an error? How to solve the problem, which probably can occur?



An amazing amount of cracks and just unpredictable behavior is the result of such involuntary negligence.



Original

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



All Articles