Hacker News new | ask | show | jobs
by bkv 3891 days ago
>Yes, you can write get() and risk an exception. So don't do that.

Yes, you can dereference a null pointer and risk an exception. So don't do that.

See where this is going?

The problem with Java's optional is that it is by all appearances (in name and high-level description), a general purpose optional, albeit with a ton of gotchas that will result in people saying "Just don't do that."

Java's optional is not a general purpose option type. In fact, its intended use-case is very specific, according to a rather obscure SO answer by Brian Goetz: http://stackoverflow.com/a/26328555/547365

This is, of course, just tribal knowledge, meaning people are going to be doing shit with optional that they shouldn't, and it's going to be messy.

1 comments

>>Yes, you can write get() and risk an exception. So don't do that.

>Yes, you can dereference a null pointer and risk an exception. So don't do that.

Except these aren't at all the same. With a reference, you have no good way of telling whether your reference IS nullable. With Optional, it's ALWAYS optional.

Therefore it is trivial to a) train developer habits to always check before fetch, and b) enforce an isPresent check with static checks.

Sure pattern decomposition is nicer, but that doesn't mean that Java's implementation "defeats the purpose entirely" like the author suggests. I'd love everything to be checked statically, but if that can't be done, I still like strong conventions which make the code clearer and safer.

>Except these aren't at all the same. With a reference, you have no good way of telling whether your reference IS nullable.

As the article mentions, we have annotations to do just that.

>Therefore it is trivial to a) train developer habits to always check before fetch, and b) enforce an isPresent check with static checks.

Again, if we're using static analysis to verify the correct use of Optional, why are we using optional at all? Why wouldn't we use static checks to verify null references can't be dereferenced, instead of hypothetical yet-to-be-implemented static checks that may not be implemented anyway? See https://github.com/findbugsproject/findbugs/issues/56

>>Except these aren't at all the same. With a reference, you have no good way of telling whether your reference IS nullable.

>As the article mentions, we have annotations to do just that.

Your mileage may vary, but my experience with nullable/nonnull annotations is that because they are not used across the board, you are either left with a ton of false positives or a ton of false negatives. The fact that there hasn't really been a great standardization in the core language is probably evidence of that (yes, JSR305 exists, but the JDK doesn't enforce its constraints by default).

With Optional, it's a clean slate. You can simply say "a reference to an Optional shall never be null" and you could probably enable such an enforcement for an existing project. Of course it's not going to be 100% while it's still a normal reference type, but catching 99% of the cases is a really big improvement!

Much better than using static analysis is to have it in the type system:

https://kotlinlang.org/docs/reference/null-safety.html

It's simple:

val x: Foo = maybeGetSomeFoo() // Error: method returns Foo?

val x: Foo? = maybeGetSomeFoo()

x.sayHello() // Error: x may be null

x!!.sayHello() // OK, the !! method throws an exception if it's null

if (x != null) x.sayHello() // OK, flow sensitive typing means the test narrows the type

val h = x?.sayHello() // OK, ?. yields null if left hand side is null, otherwise evaluates right

val h = x?.sayHello() ?: return // OK, ?: runs right hand side only if left hand side is null

"Much better than using static analysis is to have it in the type system"

I am not sure there is a distinction. Though it is true that some analyses may be run by a broader swath of users.

No, annotations do not accomplish the same thing in practice. Annotations function properly only when the code was written with annotations, and where the code and all callers are checked against the annotations. Unfortunately, annotations miss the mark in some practical cases:

1) A reference is @NonNull in my code, and I pass it to a component that accepts a regular reference as input. The annotation semantic is simply lost, and I may not even realize it as I'm writing that code. By comparison, if I need to pass an Optional<T> reference where a T reference is needed, then the type system reminds me to consider the present and absent cases. Optional does not allow the programmer to forget.

2) The fact that a component or library was written with annotations does not mean that all usage of the library will properly check the annotations. Sometimes people forget, or sometimes they interact with a component in a dynamic way like through Spring and the semantics get lost. An Optional makes this impossible to forget. You can confuse a @Nullable T with a T, or forget to check upon conversion, but there is no failure case that causes an Optional<T> to turn into a T without handling the present and absent cases.

More choice is better than less choice. You could design an Optional type to omit the get() method. That's less useful than what we have now. Like goto, it has rare but legitimate uses, and it's easy to avoid for the "right way" in the average case (don't use it, flag it during code review, and document the reason why when it's being used).

> With a reference, you have no good way of telling whether your reference IS nullable.

You do, a reference is ALWAYS nullable/optional. Just because people might expect a certain reference never to be null does not change that. You could equally as well as semantically expect an Optional never to be "optional" (it is just a matter of perspective). People who are too lazy to check for null might be too lazy to check isPresent() and both will give you, unsurprisingly, an exception.

java.util.Optional is really just yet another layer added to support laziness in software developers. To put it bluntly, write better manuals/JavaDocs if necessary and, most importantly, read them.

If you ever have actual success getting developers to obey documented input/output constraints without actually enforcing them with a static or runtime check, you could probably become a highly-paid coach. But I'm skeptical. I've never seen it happen, and most good libraries I've used (e.g. Guava) always follow up preconditions with a runtime check. Statically enforceable pre/postconditions shouldn't even have to be documented because the signature is the documentation.

Good libraries allow their developers to be lazy.