Live data from Hacker News

You’re better off using Exceptions

eiriktsarpalis.wordpress.com

91–100 of 242 posts

Re: You’re better off using Exceptions

#91

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 acquisition is initialisation".

Re: You’re better off using Exceptions

#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 None first and there's nothing in the type signature of the function to indicate that it may throw.

Is that really how F# works? Similar code wouldn't compile in Rust. You have to handle both cases for Option every time you want the value (or, yes, you can get the value by force, but if you do that, it's definitely NOT an accident and you're basically acknowledging that you want the whole program to crash if the value is None).

Anyway, I'm still in the Either/Result/Try camp. I even used Result/Try types in Java, Kotlin, and Swift (before they officially showed up in Swift and Kotlin).

I think the extra boiler plate in the middle of a function chain is absolutely worth it. I hate the idea that I need to actually read the source code of every function I call from library X just to see if it will throw an exception on me.

Swift has a pretty good compromise, IMO. A function's signature must indicate that it throws, but it doesn't have any type indicated. At least I know when I need to investigate a function further!

Re: You’re better off using Exceptions

#93

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…

The biggest issue I've seen with exceptions is that people bring up the issue when they are abused. I SORT of agree with you that an 'Record not found' exception is an abuse. This should probably be a null-object like an Optional ALA Java or Scala.

They are trying to avoid returning null in this scenario.

Unfortunately, if the API is designed poorly you're stuck with it.

I can sort of understand why people hate checked exceptions but working without exceptions is just terrible.

Re: You’re better off using Exceptions

#94
Interesting article, also very F# specific.

A similar approach with type aliases (to be moved to value objects or structs with behaviour aka rich models) in Rust.

```rust type Amount = u128; type MoneySign = Option; type Precision = u8; type CurrencyIsoCode = &'static str; type CurrencyName = &'static str; type Currency = (CurrencyIsoCode, CurrencyName); type Money = (Amount, MoneySign, Precision, Currency); type Balance = (Money); type ExpirationDate = &'static str; type MoneyWithdrawalError = &'static str;

enum MoneyWithdrawalResult { Success(Money), InsufficientFunds(Balance(Money)), CardExpired(ExpirationDate), UndisclosedFailure(MoneyWithdrawalError), } ```

Re: You’re better off using Exceptions

#95
post #80

Earlier quoted context omitted.

If you expected the record to be found, the failure of finding it is an exception. If it was an open question, then it isn’t. This is why API generally have checks that allow developers to avoid that failure if the failure is expected (eg Record.exists). What you are suggesting is simply a reverse order (check after vs check before). I think check before leads to a much cleaner API than one that dumps a null or failu…

"Check before" is a race condition nightmare - for anything where you care about concurrency, you must do the thing and then see whether it succeeded or not, assuming that the underlying layer is basically sound in this regard. The world is full of bugs of the form "does this file exist? no? OK, open it for writing", which is exploitable by dropping a symlink in there between the check and the open. The classic primi…

That case is even much more exceptional: where the record existed when you checked but didn't in the millisecond afterwards when you actually opened it. But if that is common, then I would guess a concurrent check for existence and open if it is might be necessary.

Re: You’re better off using Exceptions

#96

Earlier quoted context omitted.

If you expected the record to be found, the failure of finding it is an exception. If it was an open question, then it isn’t. This is why API generally have checks that allow developers to avoid that failure if the failure is expected (eg Record.exists). What you are suggesting is simply a reverse order (check after vs check before). I think check before leads to a much cleaner API than one that dumps a null or failu…

I don't agree. There is nothing inherently irreconcilable about a record not existing. Anyone who expects a database to always have records and that the absence of a record is an "exception" has a very strange way of thinking. It's like if a car was programmed not to start and to turn on an obnoxious alarm bell because the windshield wiper fluid is empty, and the technicians built in a jumper wire to short that circu…

> There is nothing inherently irreconcilable about a record not existing.

IMHO, that's being a bit too ideological. It's better to think about it in terms of how you'd want to respond to the condition, and pick your tool appropriately.

For instance, if I'm asking for a record by ID, then a record not found exception is a good fit. I was probably going to do something with the record, but now I can't, and the exception idiom tool the condition to be dealt with where it happened even if I forget.

If I'm asking for a set of records matching a criteria, and nothing matches, then a record not found exception is a poor fit. Code that can deal with a populated list usually can deal with an empty list just fine, and using an exception just adds friction.

If I'm asking for a set of records matching a criteria, and the database is is down, then an empty list is a poor fit. I have information about the connection error I need to communicate, so it's better to use an exception.

Re: You’re better off using Exceptions

#97

Earlier quoted context omitted.

If you expected the record to be found, the failure of finding it is an exception. If it was an open question, then it isn’t. This is why API generally have checks that allow developers to avoid that failure if the failure is expected (eg Record.exists). What you are suggesting is simply a reverse order (check after vs check before). I think check before leads to a much cleaner API than one that dumps a null or failu…

I don't agree. There is nothing inherently irreconcilable about a record not existing. Anyone who expects a database to always have records and that the absence of a record is an "exception" has a very strange way of thinking. It's like if a car was programmed not to start and to turn on an obnoxious alarm bell because the windshield wiper fluid is empty, and the technicians built in a jumper wire to short that circu…

If you're wrapping every call in `try/catch` or equivalent, then something's probably wrong in your invariants. If customer.phoneNumber is a non-null foreign-key, your application should safely be able to assume that Phone.findByNumber(customer.phoneNumber) will succeed. If you don't get to assume that, then you have business-logic errors elsewhere and your best bet is to crash.

Re: You’re better off using Exceptions

#98
post #70

Earlier quoted context omitted.

If you expected the record to be found, the failure of finding it is an exception. If it was an open question, then it isn’t. This is why API generally have checks that allow developers to avoid that failure if the failure is expected (eg Record.exists). What you are suggesting is simply a reverse order (check after vs check before). I think check before leads to a much cleaner API than one that dumps a null or failu…

The problem is that these systems are built in a way in which the data access latter is throwing the exception even though it can’t reasonably know if the record is expected to exist or not. It can’t or shouldn’t know that this is an invalid program state. Specifically, if exceptions are being used properly, you should almost never need to use try/catch.

Those are poorly designed APIs then. That is like having a dictionary that throws when a key isn't found but doesn't have an API for determining if a key exists or not.

Re: You’re better off using Exceptions

#99
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:…

> Exceptions are a hell of a lot better than littering your code with null checks or error code checks, especially when you forget one and get a null pointer error or your code wanders away from the root cause and fails later.

That's basically limitation of the language. Null checks and result checking can basically be abstracted away with non-nullable types, option types, and result types and bind.

You only need to do match or check right at the edge, so they're not everywhere.

Re: You’re better off using Exceptions

#100

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…

We tried to use a result type in C# for cases like this. For example, we want to save an object, returning the updated object (on success), or an error on failure. Unfortunately in static languages this leads to an unholy amount of boilerplate: public Result PerformUpdate(int goodThingId, UpdateForm form) { ... return new Result (error); ... return new Result (obj); } The type annotations were hell. Sometimes there w…

It's been awhile since I've been in C#, but in other static languages like Rust and Kotlin with type inference, it would look more like:

public Result PerformUpdate(int goodThingId, UpdateForm form) { ... return Err(error); ... return Ok(obj); }

which is considerably less boilerplate-y. When there are three cases you want to consider, that's no longer a sum type consisting of just success or failure - that's a different sum type. Maybe even represented as Result.

Post reply on HN