Live data from Hacker News

You’re better off using Exceptions

eiriktsarpalis.wordpress.com

111–120 of 242 posts

Re: You’re better off using Exceptions

#111
post #80

Earlier quoted context omitted.

"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.

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.

Re: You’re better off using Exceptions

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

> It's not a normal thing for code that needs that record that wasn't found.

In many popular programming languages, the way to check whether some value has a certain property is with an “if” statement. It would be very odd to replace every code path inside an “if” statement with exception control flow simply because that code path “needs” some condition to be met for it it to execute.

Re: You’re better off using Exceptions

#113

Earlier quoted context omitted.

Absolutely. Whether a method throws or not signals the intent of the code. In .NET, there are plenty of throwing and non-throwing versions of various methods. For example, there are both Parse() and TryParse() and also First() and FirstOrDefault() in Linq. Which method you use signals to the reader the expected result. If you're using Parse() then you're expecting the parse to succeed; Perhaps the data comes from a d…

On the other hand, if you are writing library code or even a library-ish component of application code, having to write the same method a bunch of different times is tedious. Ideally, the language would be designed so that there is one sane way of propagating errors, which can be easily either handled or propogated. I am quite a fan of Rust's solution, with Result types and various macros/methods which can do the com…

In this case, it's the difference between what is an is not an error. Only one of those situations is an error.

I agree having to write the same method a bunch of times is tedious but that is usually not the case. Most situations have clear intentions. It's much more likely in framework APIs where there are a bunch of non-task-specific operations.

I find the whole manual propagation of errors, even with a lot of helpers, to just be tedious boilerplate. Most of the time I literally don't care about the error -- I want to crash my app, log the error, and alert me and the user. I don't need to propagate the potentially limitless number of errors possible in any non-trivial application.

Re: You’re better off using Exceptions

#114

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 check…

Nulls are also called the billion-dollar mistake (and that was decades ago; it's much more than that now). Both nulls and exceptions are ways of trying to make the main line of processing clear, while handling other lines in structured ways.

There's no one-size-fits-all solution. In a lot of ways, the best response to "record not found" is that you get the same result as finding one, except with zero answers. That means your main line can process the same way -- unless that doesn't produce the result you need. In which case maybe you an if(null) somewhere, or a special fake record, or an exception.

Re: You’re better off using Exceptions

#115
I completely disagree with this article, as it makes a lot of claims which are not true, uneducated or intentionally misrepresented to make a case for exceptions:

> Because runtime errors are difficult to anticipate, I claim that using result types as a holistic replacement for error handling in an application is a leaky abstraction.

Yes this is true, but it's not a leaky abstraction. There's always the posibillity that shit hit the fan and something happened which was unexpected and indicates an unhealthy system in which case it will result in an unanticipated exception such as a RuntimeException.

This is ok, that's why every application has somewhere a global error handler, which can capture that unusual exception, log it and potentially terminate the application or put it in an unhealty state so that a higher level scheduler can replace or re-start the app. Possibly even raise some emergency alerts with developers via PagerDuty, etc.

However, it most certainly doesn't leak anything or makes the Result type less useful, because most application errors are anticipated due to a combination of user inputs or other external factors such as API calls and these can be perfectly handled in a more predictable way by using a Result type.

> It’s by such misadventure that the working F# programmer soon realizes that any function could still potentially throw. This is an awkward realization, which has to be addressed by catching as soon as possible.

Not really. There's nothing awkward about a runtime exception. Shit sometimes happen. And it's not true that it has to be handled as early as possible. As said before, you only have to deal with exceptions in a global error handler. If the error must be caught as early as possible, then it means that there is a possible plan B and a possible plan B can only exist if the error is anticipated. If it is anticipated then it should be returned in a Result type. So fundamentally exceptions are not awkward. When they happen there's exactly only one place where they need to get handled and the rest of the application code just works with Result types where errors are possibly known.

> In the majority of codebases using result types that I’ve been reviewing, people typically just end up re-implementing exception semantics on an ad-hoc basis. This can result in extremely noisy code...

So he has just worked with badly written code. If all the code is doing is bubbling up an error from the Result type then whats the point of returning that error? Return errors which are meaningful and where the calling code can deal with it immediately, otherwise don't bother.

> An important property of exceptions -which cannot be stressed enough- is that they are entities managed and understood by the underlying runtime, endowed with metadata critical to diagnosing bugs in complex systems. They can also be tracked and highlighted by tooling such as debuggers and profilers, providing invaluable insight when probing a large system. By lifting all of our error handling to passing result values, we are essentially discarding all that functionality.

Well as said before, the Result type is not to be logged or thrown or something. It's there so calling code can deal with it - implement some sort of plan B. If all the author wants is to always log the entire exception including stack trace for every error and not really deal with it then fair enough, just use exceptions everywhere, but that application will suck big time.

> That said, I strongly believe that using result types as a general-purpose error handling mechanism for F# applications should be considered harmful.

Functions are Input -> Output. If you don't like that as a "general" mechanism, and you prefer Input -> Output, Exception, {whatever} then use OOP and not FP. It almost seems like that the author just doesn't like the functional appraoch in functional programming, which is a weird point to make.

> Exceptions should remain the dominant mechanism for error propagation when programming in the large.

Based on which logic? That's such a generalisation that it's just plain wrong. Exceptions are try-catch error handling and there is no proof that try-catch is the ultimate error handling solution. Lots of new languages make an effort to exactly not do that, so where's the evidence?

Re: You’re better off using Exceptions

#116
post #70

Earlier quoted context omitted.

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.

Yes, poorly designed API, but we still have to handle them and the world is filled with them. So you can have a structure that works for the real world, or you can have an ideological structure the stubbornly insists that you should know better. "bad programmer! you should know if the key exists or fail".

Re: You’re better off using Exceptions

#117
post #62

Earlier quoted context omitted.

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

In Java/C# land exceptions are EXPENSIVE. Like magnatudes more expensive. You have to build a full stack trace etc. Removing places in the code where it is "Throwing exceptions for non exceptional circumstances" has a dramatic performance increase benefit.

C++ too. I suspect it’s true in almost every language.

Maybe not Python? But Python is slow regardless.

Re: You’re better off using Exceptions

#118
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"
        | _ ->
            customers
            |> List.choose(fun c -> c.Credit) # Would remove the option type. Any nones would be filtered out the list
            |> List.average
            |> Ok
This is the best I could think off without a computer

Re: You’re better off using Exceptions

#119

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 usefulness of exceptions in the cases you present is to force the developer to handle the cases where things go wrong, such as no records are found.

There are more clever ways to do this nowadays with monadic Result types, but the core idea of forcing a developer to handle error conditions is useful in API design.

Re: You’re better off using Exceptions

#120
post #67

Earlier quoted context omitted.

> If you expected the record to be found, the failure of finding it is an exception. This is not always the case. In fact, for query APIs I do not expect it at all. Sometimes I'm doing something similar to a UPSERT operation with more nuanced behavior, then returning no existing entry on a preceding SELECT query could be the 99% use case.

>> If you expected the record to be found, the failure of finding it is an exception. > This is not always the case. In fact, for query APIs I do not expect it at all. And, in your case, "if you expected the record to be found" doesn't apply. If I'm iterating over a list of names/id provided by the APi, and then calling the API for more information each one, then I would expect the record to be found... because the A…

Only if you're the only user of that API or these entities are eternal once created. In any application with multiple users it's a normal thing for system state to change between two API calls.
Post reply on HN