Movatterモバイル変換


[0]ホーム

URL:


Improve your programming

Home »Articles »Exceptions vs error values

Exceptions vs error values

Exceptions vs error values has been a debate inerror handling for ages. Some people have firm stances on them. For example, in the bookClean Code, Uncle Bob recommends exceptions. In his post onExceptions, Joel mentions that he prefers error values.

Programming languages have also taken stances. Popular languages such as C# and Java traditionally use exceptions. Languages like Rust use error values.

In this article we’ll examine some of their similarities and differences. We’ll also provide suggestions about when to use which.

Basic examples of exceptions and error values

Just for a quick introduction, here are some examples of exceptions and error values.

If you’re already familiar with them, then please skip to the next section.

Here’s an example of throwing and catching an exception in C#:

public class Example{    public void Foo()    {        try        {            Bar();        }        catch (IndexOutOfRangeException ex)        {            // handle error        }    }    public void Bar()    {        if (true /* some condition to check if something went wrong */)        {            throw new IndexOutOfRangeException("Some error message");        }        else        {            // normal program execution        }    }}

In the code above,Bar throws an exception. The exception is caught and handled inFoo, in thecatch block.

Here’s the same thing in #"async">

Similarities between exceptions and error values

Exceptions and error values are fairly similar. In fact, some newer programming languages such as Rust and Swift eliminate most of the differences between them.

The most important thing about both of them is that they act as different return values from a function / method. The different return values should lead to different code execution paths.

They also share a big downside. It’s easy to mess up with both of them.

With an exception, you may:

  • forget to catch it
  • wrongly assume that some code higher in the call stack will catch it
  • accidentally catch it higher in the call stack in a place that’s not prepared to handle it properly

Also, you can completely avoid checking error values.

It’s very easy to forget or mess up. Even if you don’t, someone else might. So, you have to be very diligent.

Or, you can use a programming language that forces you to check all errors. (More on that later.)

Differences between exceptions and error values

Exceptions and error values have some differences:

Performance

Throwing and catching exceptions are commonly considered slow. Returning error values is fast.

However, exceptions are supposed to be "exceptional" (thrown very rarely). In practice, this means that the performance of your application shouldn’t be negatively affected by using them.

Crashing the program vs silent bugs

Uncaught exceptions crash the program. More rarely, exceptions can also result in silent bugs (if you catch them higher in the call stack without intending to).

Unchecked error values result in silent bugs.

Exceptions are better in this case. As explained inhow to respond to errors, crashing the program is a better default option than silent bugs.

Bubbling

Exceptions can "bubble" up the call stack. An exception that’s not caught in acatch block will be thrown in the caller (the previous code in the call stack). If it’s not caught there, the process will repeat. If it reaches the end of the call stack, the program will crash.

Bubbling is both good and bad.

The benefit is that it’s very convenient. You can have a single try / catch block in some parent function. The exception will propagate to it and will be caught there.

The downside is that the flow of execution is not explicit. You have to keep track of it yourself. You also have to remember which exceptions are caught where in the call stack.

This can put you into a bad situation. Sometimes you might not remember or know if an exception will be caught or not, or where it will be caught, or by what.

In comparison, error values are standard return values. If you want them to propagate, you have to propagate them manually. You have to manually return them across different functions / methods, all the way up the stack.

The benefit of this is that it’s very explicit. It’s very easy to track and reason about. The downside is that it’s very verbose. You need many return statements across many different function / method calls.

Note that you can technically manually propagate exceptions if you want to. However, that’s not common practice. For more details on this please see "checked exceptions" in a later section.

Suitability in functional programming

Generally, exceptions are less common in functional programming.

That’s because functional programming promotes immutability and pure functions.

With exceptions, sometimes you need to break immutability. For example, often, you need to declare variables outside of try / catch blocks and then mutate them in try / catch.

Here’s a code example:

let a;try {  a = new Something();  // do stuff with `a`} catch (error) {  // handle error} finally {  a.close();}

Also, thrown exceptions are not standard return values. This messes up the "pure function" point.

Exceptions and error values in some newer languages

Some newer languages, like Rust and Swift, change things up a bit.

Most importantly, they force you to check all error values and thrown exceptions. This means that you can never forget to check for errors or to handle exceptions.

In the case of Swift, it also makes exception bubbling more explicit. It still allows exceptions to propagate automatically. However, it requires intermediate functions (that an exception will propagate through), to be marked with the keyword "throws".

This additional explicitness makes exceptions easier to track throughout your code.

The downside is that it makes things more verbose.

(Rust uses error values, which you have to propagate explicitly anyway.)

Which should you use?

Overall, it seems like this is a question of robustness and amount of safety measures vs verbosity.

Enforcing error checking and having explicit error propagation have obvious benefits. It makes it much harder to forget to do your error handling. You’ll have to intentionally ignore it to avoid it.

However, verbosity has downsides too. It can can make code less readable. It can also make it harder to make large changes to code. This can be especially prominent if you’re propagating everything manually.

For example, imagine that you change a low-level function (or add a new one) to sometimes return an error value. That error may need to be handled at a higher-level function. This means that you’ll need to add code to every intermediary function to keep propagating the error.

That’s a large change. In comparison, if you added an exception that bubbled automatically, you would just add a try / catch block at the high-level function and you’d be done.

So it’s up to you to decide where you stand on the safety measures vs verbosity scale.

For maximum safety measures, you should probably use a language that forces you to check all errors and forces explicit propagation of them. The downside is that the error handling will be more verbose.

One level lower in safety is to use error values. I regard these as more robust than throwing exceptions. That’s because propagating error values is more explicit than bubbling exceptions. The downside is that there’s more verbosity. Also, note that you need to be very diligent with these. If you forget to check an error, you’ll get silent bugs. Unchecked error values are worse than uncaught exceptions.

Otherwise, go for throwing "normal" exceptions (such as the ones in Java, C# and JavaScript). They’re the least verbose. This doesn’t mean that you can’t create robust programs with them. It just means that it’s up to you to be diligent with errors and to track everything.

It’s probably also a good idea to consider the convention in your programming language. Some programming languages prefer exceptions. Some others prefer error values.

My personal preference is to lean towards higher safety for larger scoped and more critical projects. For smaller scoped projects, I lean towards less verbosity and more convenience (exceptions).

Final notes

So that’s it for this article. I hope that you found it useful.

As always, if any points were missed, or if you disagree with anything, or have any comments or feedback then please leave a comment below.

For the next steps, I recommend looking at the other articles in theerror handling series.

Alright, thanks and see you next time.

Credits

Images:

  • Duelling Legos – Photo by Stillness InMotion on Unsplash
  • Typewriter and laptop – Photo by Glenn Carstens-Peters on Unsplash
  • Post-it notes – Photo by Will H McMahan on Unsplash
Subscribe
Notify of
guest
3 Comments
Oldest
NewestMost Voted
Inline Feedbacks
View all comments
Pooya Parsa
3 years ago

Thanks for sharing this interesting article.

1
Reply
Spyros Argalias
Reply to  Pooya Parsa
3 years ago

My pleasure

1
Reply
Lukas
Lukas
2 years ago

Very well written. Thank you for the effort!

0
Reply
Manage Cookie Consent
We use cookies to optimize our website and our service.
FunctionalAlways active
The technical storage or access is strictly necessary for the legitimate purpose of enabling the use of a specific service explicitly requested by the subscriber or user, or for the sole purpose of carrying out the transmission of a communication over an electronic communications network.
Preferences
The technical storage or access is necessary for the legitimate purpose of storing preferences that are not requested by the subscriber or user.
Statistics
The technical storage or access that is used exclusively for statistical purposes.The technical storage or access that is used exclusively for anonymous statistical purposes. Without a subpoena, voluntary compliance on the part of your Internet Service Provider, or additional records from a third party, information stored or retrieved for this purpose alone cannot usually be used to identify you.
Marketing
The technical storage or access is required to create user profiles to send advertising, or to track the user on a website or across several websites for similar marketing purposes.
Manage optionsManage servicesManage vendorsRead more about these purposes
View preferences
{title}{title}{title}

[8]ページ先頭

©2009-2025 Movatter.jp