DEV Community

Cover image for Exceptions - A Bad Turn of Fate
VanPonasenkov
VanPonasenkov

Posted on

Exceptions - A Bad Turn of Fate

Introduction

You've all heard about exceptions. They are the basis of error handling in most modern OOP languages, examples being Java, JS, C++ . But are they as friendly as they are presented to us? let's find out in this article.

Origins of Exceptions

The Idea of exceptions originated back in the 1960/70's in early implementations of lisp, most notably in Lisp 1.5, where the ERRSET function was used for controlled execution of fallible code. Later lisp versions built on top of this idea, adding the TRY and CATCH primitives. But at that point it had already caught on and other programming languages were starting to implement it as well.

Exceptions in Modern Programming Languages

Exceptions in Java, C++, C#, etc., are represented using default or custom exception classes, which can be created and thrown by the programmer. This allows for a very easy and cheap (In terms of productivity) error handling pattern, but is it as good as people say it is?

Criticism of Exceptions

There are lots of criticism for this error handling pattern, but let's point out a few most significant ones.

  • NO-OP exception handlers. What i mean by this are exception handlers which are made solely for the purpose of mandatory exception checking. An Example of this behaviour would be:
try {
  code_that_might_fail();
}
catch(Exception e) { // no code here }
Enter fullscreen mode Exit fullscreen mode

Whilst they aren't too bad if you use them a couple of times here and there, once the codebase becomes bigger they make the program harder to read. It becomes more apparent after writing Java for some time, Because Java forces you to handle every possible exception point and it becomes a liability.

  • Developing from the previous point, it's hard to catch all exceptions without an IDE. If you aren't using a full-fledged IDE, or if you're using something simple like VIM or Emacs, your development time will increase as a result of compile or runtime crashes.

  • Binary size bloating. A very simple point with little-to-no need in explanation. Modern exception handling isn't native to our architectures, So as a result it causes binaries to become bloated. it is not a problem when for example, you're writing a web-server, but can become a real issue when writing something as low-level as an operating system, or a driver.

But... Is there a better way to do Errors?

The answer is... Most likely! One of the alternatives to exceptions are error values. They're implemented in a very crude way in C, usually by returning NULL from a function. Some modern programming languages expand on this idea. 2 Most prominent examples would be Go and Zig.

Go

Go is a statically typed, compiled programming language designed at Google. Among other design decisions, The Go team decided to use error values, instead of exceptions. Prior to this, Google banned the use of exceptions in their C++ Code. The way you check for errors in Go is something like the following:

val, err := function_that_might_fail()
if err != nil {
  print("Unfortunately our production servers are burning")
} // the error handling part, nil being the null value in Go
Enter fullscreen mode Exit fullscreen mode

As you can see error handling in Go is really straightforward, it fixes all of the above problems of exception handling. It is easy to represent error values internally (Fixes problem 3). It is easy to omit handling the error (Fixes problem 1, 2). A good example of this would be:

val, _ = function_that_might_fail_but_itsfine()
// no error check, Go compiler and the Runtime does not complain
// even if the function returns an error
Enter fullscreen mode Exit fullscreen mode

Go also introduces the defer keyword, which puts the execution of a statement at the end of a scope. It means if you open any resource and then you check for an error, you don't need to close the resource in both branches, thus, making the code clearer. example:

file, err := open_file_posix_is_cool(999)
if err != nil {
   print("Logging to our super secret database")
   return err
}
defer close_file_posix_is_cool(file)

file2, err := open_file_posix_is_cool(998)
if err != nil {
   print("Logging to our super secret database")
   return err
}
defer close_file_posix_is_cool(file2)

file3, err := open_file_posix_is_cool(2048)
if err != nil {
   print("Logging to our super secret database")
   return err
}
defer close_file_posix_is_cool(file3)

// resources are going to be properly cleaned up even if an error in one of the branches would occur
Enter fullscreen mode Exit fullscreen mode

Zig

Zig is an imperative, general-purpose, statically typed, compiled system programming language designed by Andrew Kelley. Zig error values are very similar to that of Go, But they differ in some slight ways. Zig also introduces exhaustive switch statement for error sets, and keywords like try and catch (Not to be confused with try/catch in C++, Java). the try keyword checks if the given expression evaluates to an error, and if it does, returns the error from the function. catch keyword is used for giving the code to be executed in case an error was evaluated from the given expression.

Summary

To wrap things up, let's answer the question given in the introduction - are exceptions as friendly as they are presented to us? from this article we've learnt that the answer is no, and there are good reasons for that. But we've also found an alternative error handling method and showed how it solves shortcomings of exceptions.

Links, References, and further Reading

Top comments (2)

Collapse
 
alxgrk profile image
Alexander Girke

I'd like to reference another recently published article about the different kinds of handling exceptional execution paths. Especially, the part about Go's approach pretty much nailed it in my opinion...
dev.to/nfrankel/error-handling-acr...

Collapse
 
ali_shakhzodov_b9e8887d83 profile image
Ali Shakhzodov

Very well written.