Live data from Hacker News

You’re better off using Exceptions

eiriktsarpalis.wordpress.com

151–160 of 242 posts

Re: You’re better off using Exceptions

#151
post #19

Earlier quoted context omitted.

I don't know Rust at all but what you're talking about sounds equivalent to (much maligned) Java's checked exceptions system? I never understood the hatred checked exceptions received especially from the younger crowd. I still write Java at work and I still use checked exceptions whenever they indicate an error condition that must not be ignored by the client code. Many new to the project developers hate me for it in…

Checked exceptions deserve all the hate they get. It's not a good solution. Here's a random rant online that ends up pitching the more common solution of unchecked exceptions + exception rewrapping: https://phauer.com/2015/checked-exceptions-are-evil/ . And of course, in Rust, the rewrapping is basically forced upon you making for a really nice error-handling ecosystem.

Unchecked exceptions are a mistake. If the method you call is modified to throw a new recoverable exception how will you be alerted to the issue? You won't. Your app will crash. With checked exceptions the compiler lets you know. That's much better.

Re: You’re better off using Exceptions

#152
post #19
post #4

When I moved to Rust the constant error wrapping or converting annoyed me. But after using it for a couple of years it turned out to be a huge blessing. Quite often I need to know exactly which error messages will be thrown so that I can do things like internationalization. While exceptions are quite convenient for prototyping for production I’m now firmly in the typed errors camp.

I don't know Rust at all but what you're talking about sounds equivalent to (much maligned) Java's checked exceptions system? I never understood the hatred checked exceptions received especially from the younger crowd. I still write Java at work and I still use checked exceptions whenever they indicate an error condition that must not be ignored by the client code. Many new to the project developers hate me for it in…

> I never understood the hatred checked exceptions received especially from the younger crowd.

I'd say not "younger crowd" but rather developers who have never dealt with a workplace or situation where he/she is demanded to take error-handling (and recovery case) seriously. Different workplaces have different accountability when it comes to code-quality (including error handling and i18n).

Java is more prevalent at some time in the past and it has built an amazing libraries of good/best practices while Python/PHP/JavaScript/Ruby weren't exposed to that level of commercial engineering scale in the past so when the developers came from the latter group, they rebel a bit when it comes to exceptions. Keep in mind that Java was born to address commercial needs thus there is more accountability compare to OSS languages. Just different mindset, different goals hence leading to different design decision.

Re: You’re better off using Exceptions

#153

Exception != Error Old Ada programmer here. Example of reading bytes from a file... just keep reading bytes and don’t include logic for checking for EOF. Let the exception handler catch it where the file will be closed. Clean separation of code. In Ada, every bock can have exception handlers at the bottom. No need for “try” syntax. Very clean.

That is like C++ destructors, which are called deterministically when they go out of scope (again with no "try" syntax). They can unlock mutexes, close files, etc. But unlike what you describe, there is no need to put anything at the end of the function: instead, the fact that you have instantiated an object already guarantees that its destructor will be called later. This is called RAII, short for "resource acquisit…

Yes... RAII is handy, but it doesn’t save you from checking EOF logic in my example.

Re: You’re better off using Exceptions

#154
post #140
post #123

Earlier quoted context omitted.

> Well, in real code you are likely bubbling up an error in some way for your 404 Not Found and 500 Internal Error handlers to kick in. Which is essentially GOTO 404 A horrible idea that I see often in clever frameworks disobeying encapsulation and reasonable control flow in favour of magic

That is a wonderful idea. You don't need to deal with 404, 500 or other stuff, you just write for the "happy path". The framework deals with the rest.

It's both wonderful and terrible.

On the one hand, it's terribly convenient when you're writing the code.

On the other hand, as clon says, it breaks encapsulation. Not necessarily, but I've rarely seen these style of Web frameworks used in a way that doesn't result in network communication concerns wormholing their way through the entire codebase. And it sometimes does do weird things with control flow. Again, not necessarily but in practice. For example, JAX-RS frameworks do the exception mapping off in some magic location that's outside the part of the call stack that you directly control, meaning that customizing the logic requires hacking some fairly complex special-purpose mechanisms into the framework.

I suspect that there's a way to achieve convenience without relying on spooky action at a distance. But one could be forgiven for looking at the way popular frameworks on popular platforms work and concluding that cleanliness must be the price of convenience.

Re: You’re better off using Exceptions

#155
I theoretically like exceptions, and used to extol their virtues, but I've come to hate them in practice. They're an endless source of fustration in that the APIs you least expect will throw various unexpected exceptions for various undocumented edge cases, which invariably leads to heisenbugs crashes in our bug tracker that I need to go waste a few hours trying to repro and root cause because I, foolishly, was a good boy who didn't spam catch(Exception) everywhere (as that would also catch the null/index exceptions indicating real bugs). Theoretically, forcing you to use checked exceptions would solve this, but would defeat many of the supposed virtues of exceptions.

I also frequently get stuck refactoring a lot of code other people have written because an exception tried to unwind across an ABI boundary, and nobody thought about how to handle that or what would happen. That ABI could be across a C module, which has no exceptions. Or perhaps across a C++ module built without exceptions, or using an incompatible unwinding mechanism. Cleanup gets skipped, undefined behavior gets invoked... it's a mess.

So, invariably, I write some catch(Exception) equivalents, if only to manually write the code to explode loudly and in a way condusive to debugging, instead of much later when the stack trace has been mulched through rethrows, or access violations from the UB, or ...

Now compare and contrast that to error codes, which I theoretically dislike, but swear by in practice.

Error codes are C ABI safe, any language can return them, set them, or store them. They're part of the method signature, neglected documentation be damned. There won't be any exotic failure modes where C++ destructors get skipped due to a setjmp style unwind. Even the absolute worst APIs usually have some kind of incomplete list about failure modes - a list of constants somewhere - and I have a fighting chance of deciding upon a sane local fallback (on top of breakpoint/logging/reporting) in the event of an undocumented/unexpected error code.

My biggest concern was always accidentally ignoring an error code, but every language at least has compiler extensions these days - to mark a function result as needing to be used - and a way of turning that warning into an error. Does this sometimes lead to a little extra error handling boilerplate? Yes. Does that even enter into the top 10 issues I have with error handling code? No.

I do get the occasional bug where ignoring an error code contributed to it's occurance, but those are a small price to pay compared to avoiding all the bugs arising from exception (mis)use I avoid.

Re: You’re better off using Exceptions

#156

Earlier quoted context omitted.

That’s a very, very common source of subtle bugs (often exploitable as security bugs) that are hard to spot and hard to test for. It’s good practice to design APIs to make race conditions less likely, by explicitly not splitting operations across multiple calls. Separating “exists” and “get” into separate calls is a disaster.

I would compare this to a map API: an access could throw if a key wasn't present, but you can check to see if the key was in there first anyways. As long as the map isn't being used concurrently, it isn't that much of a problem, and if it is being used concurrently, then you need a different design. In the former case, separating "exists" and "get" would be considered over-engineering. I guess databases and file syst…

For most implementations of maps (hashtables and various tree-ish things), even in the single-threaded case, doing .exists and then .get is about twice as expensive as doing a .get that returns an Option (or nullable reference, default value, whatever)- you have to do the map lookup twice.

(Okay, probably not twice as slow, since the cache will be hot, but still.)

Re: You’re better off using Exceptions

#157

For the readAllText example, could it return Result ?

Yes, but you can still argue just throwing it might be better. This also affects the stack trace, because, at least in C# (so probably F#) `throw ex;` is different than `throw;`, which rethrows the exception as if it weren't caught.

Re: You’re better off using Exceptions

#158
post #92

In the article he posts a snippet of F# code: type Customer = { Id : string; Credit : decimal option } let average (customers : Customer list) = match customers with | [] -> Error "list was empty" | _ -> customers |> List.averageBy (fun c -> c.Credit.Value) |> Ok And argues that the F# programmer must conclude that all functions may throw because this function accesses the Option.Value without checking that it isn't…

Thats not how f# works normally. Normally you chain option operations with either binds, or you do a match(Which is exhaustive). Directly accessing the value is normally frowned upon. This guy either needed to make the option type go away, or filter out the None and pass that into average. List.choose could be used for this let average (customers : Customer list) = match customers with | [] -> Error "list was empty"…

In support of the article, your code would still throw an exception if all the customers in the list have missing credit.

https://github.com/dotnet/fsharp/blob/master/src/fsharp/FSha...

You could instead write it as the following to handle that.

  let average (customers : Customer list) =
      customers
      |> List.choose (fun c -> c.Credit)
      |> function 
         | [] -> Error "no customers with credit"
         | xs -> Ok (List.average xs)

Re: You’re better off using Exceptions

#159
post #62

My problem with exceptions isn't so much exceptions themselves but the way they're used. The way I see it, exceptions should only be used for things that are irreconcilable, which most of the time is interpreter errors(e.g. undefined is not a function). In other words, I don't think it's that common that custom exceptions are needed outside of assertions to prevent the developer from doing something stupid. If you ar…

> A record not being found is a normal thing! It's not a normal thing for code that needs that record that wasn't found. > They literally tell you nothing and there's no way to solve them without catching/rescuing them. A null value, a plain "error" object, or an error argument in a callback would have been sufficient. If I need an exception to be raised for this kind of thing, I'll do it myself. They tell you lots:…

SQLAlchemy does this well:

obj = session.query(MyObject).one_or_none() # no exception if missing

obj = session.query(MyObject).one() # exception if missing

Re: You’re better off using Exceptions

#160

I love using exceptions as control-flow, Python makes it very easy, and it's really helpful. edit: grammer

For example, in a web service I maintain, we maintain an internal exception hierarchy like:

  class OurExceptions:
      http_status_code = 500

  class DatabaseRecordNotFoundError:
      http_status_code = 404
Database queries are all written like:

  rows = db.select(...)
  if not rows:
      raise DatabaseRecordNotFoundError
and the top-level Flask error handler has code like:

  try:
      return call_view()
  except OurExceptions as exc:
      return response(status_code=exc.http_status_code)
This is grossly oversimplified, but you get the idea. So, this means that we can write views like:

  def some_view(object_id):
      obj = fetch_obj_from_db(object_id)
      return {"found": obj.name}
If the object isn't found, the caller gets a 404 response without the person writing the view having to do a single thing. However, they can still handle the problem themselves if they really want to:

  def another_view(object_id):
      try:
          obj = fetch_obj_from_db(object_id)
      except DatabaseRecordNotFoundError:
          return {"error": "Not found. Try again later?"}, 404
      return {"found": obj.name}
I absolutely love this coding style because exceptions are still being handled everywhere, but don't have to be explicitly dealt with deep inside a nested call stack. That lets us write very uncluttered, testable view code like:

  def change_password(userid, oldpass, newpass):
      # This raises an exception if the user can't be found
      user = get_user_by_id(userid)

      # This raises an exception if the old password is wrong
      verify_password(user, oldpass)

      # This raises an exception if the DB couldn't be updated,
      # perhaps because of a race condition with another request
      update_password(user, newpass)

      # By the time we get to this line, everything above has to have
      # succeeded, with zero manual error checking inside this view
      return {"result": "Password successfully updated."}
Post reply on HN