Hacker News new | ask | show | jobs
by slx26 1755 days ago
It's good to see so much focus on errors. They are essential when trying to build resilient systems. But our approaches are still very immature.

First, to make it clear, this article appropriately points out that exceptions are still necessary and relevant. I disagree with some of the use-cases given, but it's important to recognize that exceptions should still exist in programming languages. Joe Duffy's article about Midori's error model [0] is in my opinion the best reference to actually understand the difference between exceptions and errors and why it's so important to get right. It's a very good article, and it has been posted here before; if you are interested in error handling it's a must read.

Now, about errors as values. Treating errors as values is practical, and in modern programming languages, relatively ergonomic. That said, we already have some other comments in the thread pointing out how sticking to just "errors as values" is often not enough (btw, the article uses Rust, not Go, but anyway...). And it's also important to clarify this: errors are such an integral part of our programs, and have so much to do with flow control, that I don't believe thinking about errors just as values is enough. Sure, they might be "just values" under the hood, but in all programming languages we see either optional results or syntax sugar to be able to handle errors more gracefully. And in most cases, we still feel it's not enough (or it's enough to be practical, but not to be pleasant in many cases). So we should keep the door open, and not pretend we have already solved errors.

Finally, the topic of errors is extremely deep and complex, and when you start introducing other factors like how to report the errors publicly to a non-technical user, maybe in different languages, or whether to log it or send it who knows where, whether to trace or not, how, how to deal with duplicates or similar errors... then you start realizing that we are far from a satisfying and complete model for error handling. We haven't reached this part of the discussion yet.

For the moment, passing most errors as values is the relatively painless way that still allows us to customize errors to our needs. But there's still a long way to go.

[0] http://joeduffyblog.com/2016/02/07/the-error-model/

4 comments

> errors are such an integral part of our programs, and have so much to do with flow control, that I don't believe thinking about errors just as values is enough

Exactly. The control flow. Both errors and asynchronous programming share the quality that they don't go well with our call/return based programming model(s). You have to return something, but you either don't have anything (error) or don't have something yet (async).

A great solution to this is to use dataflow. This decouples the logic, which is encoded in the dataflow, from the control flow, which just serves to drive the dataflow, and thus negotiable.

For async, it is synchrony-agnostic, which is nice, because it solves, or rather sidesteps, the "function colouring" problem. For errors, it allows you to keep error handling out of the happy path without needing exceptions.

Interesting. Can you give some pointers/examples?
We can't forget that "error handling" is a civilization-level hard problem. Do you set up support structures to catch them when shit hits the fan, or do do you preventatively and excruciatingly suppress them?

It's as much a theoretical problem of what errors are as a practical problem in how to represent these intricate models ergonomically, and what will be sacrificed. In some sense it's an almost moral question!

And then throw PII concerns into the fray. How do you report meaningful information to your ops team but still not expose PII in your logs. There's ways of course but its not trivial.
> when you start introducing other factors like how to report the errors publicly to a non-technical user, maybe in different languages, or whether to log it or send it who knows where, whether to trace or not, how, how to deal with duplicates or similar errors...

I’ve tried searching for articles that talk about people deal with this in the context of web apps but have found it difficult to find content. It’s a tricky topic to google. Most of what I find are (usually content marketing) articles about how to log errors and/or how to send them to some service.

I’ll give you an example that is admittedly a little paranoid. Take a switch case statement where you branch of an enum-like value, meaning there’s a set of known values you expect. What do you do for the default case? In theory you don’t need a default case because you “know” the switch won’t hit it, but it’s weird to me to write code that has no logic to handle a possible scenario even if it’s highly unlikely. What if there’s a bug in the code or the enum-like value changes to contain a new value or some weird edge case? The point being in JavaScript there’s no way to be 100% sure that the switch condition will not contain an unexpected value.

Given how unlikely this scenario is though, how do you deal with it? (I realize some of it depends on where in the application’s code this is happening in). You don’t want to throw an exception and break the app. Or you can but you’d want to catch at some point. Do you log the event in the backend and create an alert so that you know a user ran into a weird edge case or bug? Do you write it out to the console in case you get a customer support call so that you can identify the issue? Is it a bad idea to write out errors like that to the console?

I’m sure these are questions that most mature apps have had to answer, but I haven’t been able to find what people consider best practices for these types of situations. If anyone knows of good resources I’d love to read them.

Throw an exception.

In PHP we would throw LogicException on these cases, it should never happen thus it something wrong with your code (logic).

https://www.php.net/manual/en/class.logicexception.php

Then in your outmost function , like the main function, you capture it and report it with a error reporting tool like Sentry (so you are aware of it and can fix it).

And for the end user you would show a modal or similar to describe the error in user friendly way.

I recommend again reading the article I linked to. The answer to the first part is: this should be an exception (or abandonment, as the Midori team called it). This is an error in the logic of the code, it's a programming error (even if it's due to later changes or whatever). It's an error that needs to be fixed in the code, not "recovered from".

Now, you can also catch exceptions, indeed. You could have your app catch all exceptions at the root level or wherever you think it's appropriate if your code is modular enough. Once you have caught the exception, you could silence it, as a lot of software does, and pray for the best... or be a bit more serious. If it was me, I would notify the user: "There has been an unexpected error". I would also append the technical info in a "technical details" section or something. And I would also provide a link to let the user report the issue easily. I'm kinda against hidden automatic reports for privacy-related reasons, as errors might sometimes contain sensitive data too, but it would really depend on the application.

There are many ways to make this more robust. Check for report dups on your side, or have some dynamic code to check the status of a specific error to provide the user with even more information, or even silence the error completely or whatever. But all this takes much more work, and it's really dependent on the application you are writing and how entreprisey you are willing to go. Crashes are not nice, data loss is not nice, but neither is corrupted data or subtle bugs due to errors silenced for the sake of the peace of mind of your users. You have to decide what's the right balance based on the type of program you are writing. Indie videogame? Crash as soon as possible, ask nicely for reports, get bugs fixed fast. Editor where a lot of data might be lost if you are lousy with exceptions? Definitely go out your way to auto-save separately if possible before crashing, and let the user know how to try to recover its data and how to get assistance. Non-critical webapp? Just let the user know something unexpected has happened, and allow to report and assure you will look into it soon. It always depends.

EDIT: I missed the most critical part, so I'll add it now... When communicating errors to users, the most important part is properly handling their frustration, not the logging method or the technical details included or anything else. If you have "few users", make sure they have a way to get in touch, and make sure they get a fix or a decent explanation of what's going on, let them know when it will be solved or what can they do meanwhile. Errors happen, but people are most often very understanding as long as you are there and don't leave them alone with their frustration. If you have too many users for that... good luck to you.

I appreciate the write up. I’ll also checkout that article. Thanks!
Author here. Thank you for the link to Midori's error model. It's on my reading list for this week.

> So we should keep the door open, and not pretend we have already solved errors.

Very much this. Even with errors as values, the approach languages like Go take makes composition difficult. I presented the Kleisli approach instead. That recovers compositionality. OCaml does something I find interesting. It makes exceptions performant by not capturing stack traces by default. But it's still too easy to forget handling the exception.

> btw, the article uses Rust, not Go, but anyway...

I actually used TypeScript. The Rust bit was meant to introduce the idea from Rust to TypeScript. It's not new in TypeScript, but it's not popular either. I have updated the article to clarify that.