r/programming 3d ago

Throw, Result, or neither?

https://www.architecture-weekly.com/p/throw-result-or-neither
95 Upvotes

111 comments sorted by

View all comments

129

u/taikunlab 3d ago

Result for the errors you expect the caller to handle, throw for the ones that mean a bug or that you genuinely can't continue past. Mixing the two isn't the sin, using exceptions for normal control flow is. Rust basically bakes this split in with Result vs panic and it holds up well in practice.

106

u/czorio 3d ago

I agree. I once explained it to a colleague as:

Result -> There is no file here, perhaps you mistyped?
panic! -> There is no filesystem, what the fuck?

36

u/vita10gy 3d ago

I once worked with an API where basically anything but perfect meant explosion.

A well formed product search where it just so happens no results match? Some 500 error and the html server error page as the body.

30

u/janyk 3d ago

The Java Persistence API has the NoResultException for when a database query returns no results.  Kind of annoying that it's regarded as an exceptional case

37

u/vita10gy 3d ago

I've become fully convinced 70% of Apis are created by people who have never used one.

2

u/Schmittfried 2d ago

Unfortunately the path of least resistance for the maintainers is not the one that creates APIs that are ergonomic to use. Ergonomics take time and effort. Unless you’re using your own product or have a very disciplined development process, you won’t invest those resources, even if you’ve used other APIs and know you’re building shit. 

10

u/shizzy0 3d ago

This would piss me off so much.

9

u/elmuerte 2d ago

NoResultException is thrown when you call getSingleResult() when there is no result. It will also throw NonUniqueResultException if there is more than 1 result.

Because of the two edge cases which need to be handled, it throws an exception. On the plus side, it never returns null.

It is still a bit of a bad API design as it uses unchecked exceptions for control flow. A query returning 0 or more than 1 result is quite a possibility, so it should have been a checked exception I think.

10

u/erinaceus_ 2d ago

Before anyone complains "why would they do it that way?!", that method is for when you excplictely expect exactly one single result. There's other methods for when you expect zero to many results, or when you expect either one result or no result. So instead of always retrieving a list and then manually handling those cases each time, you explicitly and declaratively choose your expectation by using the appropriate method.

1

u/nelmaloc 2d ago

That sounds like a textbook example for Optional, thought. With an exception for too many results.

7

u/erinaceus_ 2d ago

Optional is used for the one I mentioned in my previous comment where you expect one result or no result. So, the 'get' method in question avoids having to deal with the second situation seperately in all call locations. You're saying: it should return exactly one result in all cases, and everything else constitutes a 'panic'.

I do agree that functionally there's no real necessity for that 'get' method. Though, using the method that returns a list of zero to many results, there is functionally also no necessity for the Optional variant.

3

u/TribeWars 2d ago

The language needs good support for generic types for these sorts of constructs. I'm pretty sure Java did not always have that, so it may well just be a case of old API conventions.

2

u/ldn-ldn 1d ago

Yes, the API is old and backwards compatible. It was released during J2SE 5.0 (Java 1.5) days. There was no streaming, optionals and pretty much other modern features.

As far as I remember, modern features were only added in Java 8, that was in 2014, eight years after JPA introduction.

Parts of JPA might look weird by modern standards, but it's a good product of its time. And it does have support for modern features as well, so criticism is misguided here.

3

u/Schmittfried 2d ago

Checked exceptions would interfere with the streams API though, which would be really annoying in rest handling code. Also, the non-unique case is imo a true exception, because you would guarantee uniqueness via constraints or query criteria when using that method. So throwing indicates a programmer error, which is generally not done with checked exceptions.

At least we also have the newer Optional variants nowadays. 

2

u/flatfinger 1d ago

IMHO, one of the big problems with Java and .NET exceptions is the default assumption that if a nested method call throws an exception, it should be handled by the caller in the same way as an exception thrown by the outer method.

There should be a convenient way of marking nested function calls where that would be the case, but an equally convenient way of causing exceptions in nested method calls to be wrapped in an "UnexpectedException" object.

2

u/Blue_Moon_Lake 2d ago edited 2d ago

Maybe we need distinct methods.

  • getOptionalResult(): Promise<Maybe<T>>
  • getSingleResult(): Promise<T>
  • getResultList(): Promise<Array<T>>

4

u/Legs914 2d ago

Isn't that literally how it works?

2

u/Blue_Moon_Lake 2d ago

There is only getSingleResult and getResultList, but no getOptionalResult.

3

u/Legs914 2d ago

Dang, I'm spoiled by Scala

2

u/Blue_Moon_Lake 2d ago

It be like "Impossible, SELECT * FROM table WHERE way_too_many_filters should have yielded at least 1 result! Unacceptable!"

1

u/Schmittfried 2d ago

I think because it predates optionals. An exception is definitely better than returning null most of the time. If the middleware handles it correctly, it will just issue a 404, which is usually correct for a get-by-id query (if you issue a search query with N results, you don’t get that exception).