Hacker News new | ask | show | jobs
by zvrba 1747 days ago
> For example, Java's parseInt [1] throws a NumberFormatException if the string can't be parsed. IMHO this is terrible design.

It's unergonomical design, but it's the _correct_ design: the method is declared to return an int, and it can't fulfill its promise: throwing an exception is the right thing to do.

4 comments

It's the correct design only if we assume that the design space didn't allow for a different return type. Kotlin for example offers toIntOrNull (https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.text/to-...) as an alternative.
A null doesn't contain any information about what went wrong and unless you religiously check your objects for null values at every turn you just turned a clear stack trace into a search for waldo at the international waldo impersonators meetup.
Note that in Kotlin the return type is `Int?`, not `Int`. You can't forget to check for null because the compiler enforces it.

To your first point: Another example in the design space would be Rust which works very similarly to Kotlin but returns more information in the failure case.

> Note that in Kotlin the return type is `Int?`, not `Int`.

How does that fare with unnecessary boxing of primitives?

If the function isn't total (as in: for every string there is an int) then the boxing is necessary, no?
Not if you have user-defined value types (soon coming to Java).
But the mechanism is just wrong; Exceptions are heavyweight and should only trigger with unexpected issues, bugs that a developer wants to see a stacktrace for.

I mean in this case you could consider it developer error; a developer tried to parse an integer without first validating the input and checking if it COULD be parsed. But it's normalized to just "let it crash", instead of writing additional pre-check code.

With errors as values - like Go has normalized - instead of a big, expensive, potentially panicky exception, you just get a lightweight error option back. With the more functional approach of Either, you are basically forced to deal with the "but what if I can't parse it" code branch.

> a developer tried to parse an integer without first validating the input and checking if it COULD be parsed

Is there a meaningful difference between validating that input can be parsed and parsing it?

Any validator that doesn't actually parse the data - according to the same rules the parser uses - runs a risk of incorrectly passing/failing certain cases, no?

So here's a pattern neophytes often end up implementing:

   if (isValidInput()) {
     parseInput();
   }
It seems like a good idea but it's not, for several reasons:

1. It requires an explicit second step. Worse, the behaviour of the second step may be undefined if the first step didn't take place. Either way it's just error-prone;

2. While this may be correct for a static string with static rules, what if this isn't stateless? You've now introduced a race condition;

3. If each step requires context (eg options) you need to correctly pass them to both. This is another potential source of error; and

4. You've created what's likely an artificial differentiation between valid and invalid input. What happens when that changes and you need to distinguish between invalid (not a number) and invalid (number out of range)?

(2) is particularly common in security contexts. You'll see this pattern as:

    if (userCanEditPhoto()) {
      editPhoto();
    }
Usually you can't do this, as in the primitives won't be there. This is for good reason. Engineers should instead change their mindset to implementing these things as an atomic action that gives reason for failure. Exceptions are one version of this. They're just bad for other reasons.
Java needs a TryParseInt (sorta like C# has) so can you use either one as appropriate.

There are actually two main use cases for integer parsing: one where the value is expected to be an integer (you're parsing a file format) and the other where it's just likely not to be an integer (getting input from the user).

To me this is not "ecxeptional" at all, as it is easy to call that function with a non number and it should return "normally" that the input was not an integer. I pretty much prefer the rust Result or C++'s expected<>.
It has nothing to do with "exceptionality". It has to do with the method not being able to fulfill its contract. Rust's returning `Result<>` is a _different contract_.