Exception Crashes

Last week, I, along with several of my colleagues, participated in a loud speech 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 pretty controversial topic because people are used to avoiding errors with exceptions, and Go returns an improved version of the well-known model previously adopted by several languages ​​- including C - in which errors are passed through the 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 goes into the direction of the fact that in languages ​​with exceptions, every mistake without any additional actions carries complete information about what happened and where, and this can be useful in some cases.

However, all these amenities have a cost that is easy to formulate:
Exceptions teach developers not to worry about bugs.

The sad consequence is that this is true even if you are a brilliant developer, as you are influenced by the world around you, which is lenient to mistakes. The problem will appear 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 writing a model with returning an error code, since anything can fail and you should be prepared for this. In a model with an error code return, the moment when you should check for errors is obvious: as soon as you receive an error code. In a model with exceptions, you just need to know that errors can occur anywhere.

In other words, in a model with an error code return, when someone skips error processing, this happens explicitly: they do not check the error code. At the same time, in the model with throwing exceptions when considering code in which someone processes the error, everything is not so clear, since the error is not indicated explicitly.
(...)
When you write code, do you think about the consequences of each exception that may occur on every line of code? You must do this if you intend to write the correct code.


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

This is not the first time I have written about this, and given the controversy surrounding this statement, so I found a couple of examples that confirm the problem. The best example I can find today is in the pty module of the Python 3.3 standard 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)
    (...)


Each time someone calls this code with the wrong executable file name in argv, an unused, garbage-free and unknown Python application will be generated because execlp will fail and the forked process will be ignored. And whether the client of this module will catch an exception or not does not matter. The local obligation has not been fulfilled. Of course, the error can be trivially corrected 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 feature 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 a pretty serious crash due to the lack of locale data in the system application, which, ironically, should tell users which packages to install if the command is missing. Notice that on top of the grid there is a link to crash_guard . This function is designed to catch all exceptions at the edge of the stack and display detailed system information and a traceback 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 the application. Instead of real protectionapplications, it becomes just a convenient way to crash. In this case, it would be more correct to display a warning, if necessary, and let the program work as usual. This could be done by simply wrapping this line:

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 this right away. In fact, this is more than natural: it really seems better not to consider the wrong path. In this case, the code will shorten, it will be more straightforward, and as a result, only the one that leads to the desired result remains.

As a result of this, unfortunately, we plunge into the world of fragile software and pink elephants. Although a more expressive style of returning errors builds right thinking: will a function or method return an error as a result? How will it be processed? Does the function interacting with the system really not return an error? How is a problem that is likely to arise?

An amazing amount of crash and simply unpredictable behavior is the result of such involuntary negligence.

Original

Also popular now: