I have yet to encounter an OO-first language for which Optional provides any real difference from nil checks. Often, it actually makes it all worse. Layering optional types on top of a system that allows any value to be nil tends to just produce a situation where there are more conditions you have to consider if you want to code defensively. As many as four:
- nil
- None
- Some(nil)
- Some(non-nil)
As far as how to do good OO design, ideally you try to avoid explicit branching whenever possible, and instead use dynamic dispatch to decide what to do. In principle, if you've architected things well, you should generally be able to avoid conditional branching.
An ironically useful example of how this works is Smalltalk's implementation of Booleans and conditionals. Smalltalk doesn't actually have an if statement. Instead, it has a Boolean class that defines three methods: ifTrue:, ifFalse:, and ifTrue:ifFalse:. Each takes one or two blocks, which are effectively anonymous functions.
And then the implementation is that the True subclass as an ifTrue: that executes the block, an ifFalse that doesn't, and an ifTrue:ifFalse: that executes its first argument. And the False implementation does the opposite.
This isn't meant to be an example of "look, OOP doesn't need conditional branching, just use {library implementation of conditional branching}," so much as a small, self-contained example of the kinds of ways that you can achieve conditional-style logic without explicit branch statements. A more real-world example might be something like having separate NotLoggedInUser and LoggedInUser classes that behave differently in relevant situations, rather than having an isLoggedIn field to have to keep checking at every use site.
The big thing working against us on this is that most the popular OO languages - C++, Java, Python, C#, etc - come from the same approach of trying to layer object-oriented features on top of a procedural core. In my not-so-humble opinion, this has been about as successful as more recent efforts to support functional programming by pulling a mess of functional features into existing OO languages. Technically it gets you somewhere, but the resulting language is not an ergonomic pit of success.
Probably a better example is #at:ifAbsent:, which is a place I've seen all kinds of faffing about with default retutn valures in languages without closures/blocks.
So what is good OO design? NPEs? Nil checks?