Even in Ruby, handling the correct exception basically requires you to know that one is thrown, and then to know what kind of exception is thrown. So you sort of have to TDD your way through it. Or you just rescue every exception ever and hope for the best. You can still make it quite nice but I don’t think our modern brand of OOP has the intuitive approach here:
Haskell’s Either, Rust’s Result, Go’s facsimile of that with multiple return values, and Erlang’s approach to just letting things blow up are far easier for me to build a mental model around. There’s no guesswork: you don’t acknowledge the possibility of an error (let alone handle it), your build fails.
Even without them, you also have Maybe or Option to get rid of the accidental null values floating up the stack.