More nuanced ones can often be caught by using all args constructors and requiring the constructor values with Objects.requiresNonNull() or similar. Using spring? Don't use field or setter injection, always constructor so the above applies as well. Making the state of your objects largely immutable means their state is more consistent and what is null and when becomes a lot less surprising.
Lastly, write good tests. If you're exhausting the behavior of your system with tests, these things are much less likely to surprise you later.
NPEs are definitely a problem with Java, but they're a very avoidable one as well.
Edit: I don't understand the downvotes. The parent said they're a huge deal and I'm disagreeing because I think they're relatively simple to avoid?
Also there are plenty of domains where Java or C++ aren't even taken into consideration, in spite of decades of effort.
Just to cite Microsoft's recent security recommendation out of Blue Hat conference, regarding their own software, "use a mix of C#, Rust and constrained C++".
That said, "write good tests" is impossible. You can write as many tests as you want, you can make sure those loops loop or don't, and those ifs if or don't, but you cannot write "good" tests. Tests will always miss some corner case, some input, some unexpected locale variable.
Most issues in software fall into this category, though. The issue with Java’s null is that it’s not type safe. The language is generally a strongly typed language, except in the case of null. And until Value Types land, there is no way to express nullability to the compiler.
The arguments you make aren’t all that different than the undefined behavior arguments with C. “Just follow these simple rules and you’ll avoid all issues”, a compiler should do that for you IMO.
I can’t wait for Value Types in Java, at that point we can have real Option types.
No downvote, but GP post conflates "how code SHOULD be written" with "how existing code was written" in my opinion. Same with one of the earlier comments about "idiomatic C++ does bounds checking." I've seen plenty of NPEs in real, working Java.
If Mozilla isn't managing to write idiomatic C++, I don't hold out much hope for myself either.
> “Just follow these simple rules and you’ll avoid all issues”, a compiler should do that for you IMO.
This was one thing I really liked about Kotlin vs Java. It's nice that my IDE can do the work of generating getters/setters on a POJO for me, but it's not nice that the language requires me to fill my source code with that noise. To a first approximation, any task rote enough the computer could do it for me, it should not ask me to do.
See also: static languages that don't offer type inference.
For params and return values you still can’t know for sure, and in all these cases it -could- be null. But yes, making all fields and variables that are Optional would help as a coding practice, except of course where you need to reassign the value in the Optional, as there is no “set” on the type.
https://www.infoq.com/presentations/Null-References-The-Bill...
Rust goals in life towards bugs are basically
- Isolate memory bugs to unsafe code which you rarely write.
- Make as many classes as bugs reasonably possible rarer by encoding as much as is reasonable in the type system and encouraging the programmer to think about all possible cases.
The first gets all the attention because it is the one you can make guarantees about, but the second is really just as important.
/pedantic
One might expect that set to contain things like crashing (panic! and friends) and leaking memory (forget). It does not, and is not intended to.
The big difference is that, in Java, you typically get your NPE because you didn't know/didn't check that your pointer could be `null` – in other words, the default behavior of the language is to NPE. In Rust, the default behavior of the language is to inform the developer that they need to check. They may decide to explicitly assert that the pointer is not null, causing a crash if it is, but that's a conscious choice.
For instance, in my Rust code, pretty much every occurrence of `unwrap()` or `expect()` contains a comment explaining which invariant guarantees that the call will succeed. I don't think I have ever seen any comments associated to a member access in Java.
Making bugs grep'able is huge.
Rust’s idea of safety is similar: it tries to reduce the impact of common risks that happen during programming. It won’t save you from logical errors — just like a seatbelt won’t save you when you decide to maneuver your vehicle into a flaming pit of lava. The only thing that would save you in that situation is not maneuvering into flaming lava pits.
That beeing said rust has a very straightforward implementation for Unit Tests and Integration tests which could help you dealing with remaining risks.
Of course you can always live on the edge, ignore seatbelts and do things like calling unwrap() on results that might not retun Ok(value) at all times. But that will bite you sooner than later.
What Rust is really good at IMO, is to make risks clear at any givrn moment in time. The routes your program can take are very well represented, especially because of the ownership model. Once you understood it you can make very strong assumptions about which part of the software is manipulating which data. These strong assumptions also help mitigating risk. It is harder in Rust to do things just wrong than in most other languages and it is still fast
In Rust runtime panics are certainly possible (array out of bounds, out of memory, etc), but the ones analogous to Java's NullPointerException have to be explicit opted into (unwrapping optionals) and do not happen implicitly.
Rust lets you handle optionals in nicer ways which lets you be sure you covered the "null" cases at compile time with no runtime panics possible, if you like.
Issues arose at the interface between java and kotlin. Unless there are @Nullable @NonNull annotations (and they need to be truthful), the kotlin compiler cannot know the nullability of something coming from a java method.
It can be pretty pernicious : if you use some java written libraries like Moshi (json parsing), it can also lead to crashes : IIRC if you declare a moshi generated property to be non null but it is absent from the json, it will generate an object with null, creating a crash.
Still, null is now an anecdotal issue in Kotlin. The Android framework team is working on annotating all their APIs with the corresponding nullability annotations and more and more JVM libs are also working on handling it gracefully.
It was never a huge issue to begin with even in java, just pretty cumbersome to have to add annotations everywhere and have to add some policies like 'no null collections, only empty' in a codebase to have sane handling.