4 ms·
This is just covering up design problems. NPEs show you were you have design deficiencies. If you have getAccount().getContact().getPhoneNumber() and contact i
by exabrial 4y ago
This is just covering up design problems. NPEs show you were you have design deficiencies.
If you have getAccount().getContact().getPhoneNumber() and contact is null, you'll get an NPE. The question shouldn't be: "How do I shove the NPE under the rug for the next 1337 coder to deal with?", the question should be: "How did I initialize an Account without a Contact?"
- random314 4y agoYes and Null safety forces you to ask that question at compile time instead of runtime
- chrisseaton 4y ago> the question should be: "How did I initialize an Account without a Contact?" Well yeah that's exactly what static analysis can tell you - what code path led to that. You're saying this project should be what it already is.
- commandlinefan 4y ago> How did I initialize an Account without a Contact Actually I already know - he got it from a JSON feed from an external system and ran that JSON through an automated code generator.
- mrkeen 4y ago> This is just covering up design problems. Static analysis (detecting) is the opposite of "covering up". > "How did I initialize an Account without a Contact?" As long as your language lets you do it, your teammates will do it. And in many cases it won't be accidental either. Your teammate will want an Account with its Contact set to null.
- commandlinefan 4y ago> getAccount().getContact().getPhoneNumber() Every time I see people "deal" with this problem, it looks like this: if (getAccount() != null && getAccount().getContact() != null && getAccount().getContact.getPhoneNumber() != null) { // do something } // don't put an else condition in, just keep going and let the program // produce the wrong result in a confusing way when it happens in production I actually blame rampant code generation and an adamant refusal to even consider object-oriented design for this problem - this sort of case ought to be handled by something like: getAccount().callContact() (or whatever you were going to do with the phone number when you got it). Insistence on generating code from SQL schemas or XML DTDs and then writing procedural code makes that pretty much impossible, though.
- colejohnson66 4y agoWith that awful `if` statement, you're calling the functions multiple times. It's negligible with the simple getters Java users use everywhere, but if that `getAccount()` call involved, say, the database, now you're making multiple calls when you don't have to. C# at least has null propagation and pattern matching that can make that line: if (getAccount()?.getContact()?.getPhoneNumber() is string pn) { // do something with `pn` } The "idiomatic" C# way would also include properties: if (GetAccount()?.Contact?.PhoneNumber is string pn) { // do something with `pn` }
- return_to_monke 4y agoSurprised that guard clauses weren't mentioned here yet, you could also write ``` if (account == null) { throw NoAccountException } if (account.contact == null) { throw AccountWithoutContactException } if (account.contact.phoneNumber == null) { throw ContactWithoutPhoneNumberException } // do stuff here ```
- commandlinefan 4y agoThat would be way better, but I've never seen it.
- kaba0 4y agoYou mention code generation as a problem, but it is actually a solution as well for this particular problem, I have really grown on mapstruct, where you create an interface declaratively specifying what field maps to what, and it will generate at compile time fast, efficient, correct code you might write by hand.
- deleted 4y ago[deleted]
- bcrosby95 4y agoThat sort of design easily leads to a god object. Class based OOD is way harder than that. There's a reason codebases are full of these train wrecks (the unofficial official name of the pattern). And it's not because of auto generation and/or laziness. It's because, despite what's on the tin, passable - nevermind good - OOD doesn't look anything like how we actually think of objects as humans.
- Thaxll 4y agoThere are other problems such a null vs empty string in the database for example. Once you start dealing with API + conversion + db, if your language exposse null, it will be painful.
- java_expert_123 4y agoNo it doesn't, it just forces these designs to be apparent at compile type and fail there instead of blowing up at runtime. Do you not know why Kotlin exists or why dart added null safety or why C# added null safety? Or were they also wrong in their choices?
- godshatter 4y agoAn aside here, but wouldn't it be simpler to eschew the syntactic sugar and get the account first and then if it's not null then get the contact and if it's not null then get the phone number and return it, returning null if any of those conditions fail? Then there's no exception to handle and flow is not broken. A null phone number would indicate one wasn't found, which could mean the contact record didn't have one, the account didn't have a contact record, or there was no account. If this violates a business rule, then throw a MissingAccount or MissingContact or MissingPhoneNumber exception at the appropriate place that would be more understandable to the end user or could be handled more specifically than a generic null pointer exception. I'm a C programmer, though, so I'm used to taking the long way around to do things, usually because there's no other choice.
- BoorishBears 4y agoIt's sad because while I don't think the second concern is so big, the first half of your comment is deceptively important, but this is a conversation that really difficult to have. I've brought it up when using Swift and Kotlin and it takes so much energy for people to realize that safety operators, which feel really really good to use, accidentally sweep up major issues People assume you're missing something when you explain that they need to get more comfortable with force unwrapping (which is intentionally introducing a NPE) - But for example, if you have getAccount().getContract().getPhoneNumber() so you can show it in a settings page, you should not throw an NPE. You can easily correct the issue in a non-invariant breaking way by say defaulting to "<missing>" in the UI. But if you have getAccount().getContract().setSomeFieldWithRealWorldImportance(true), you should either be force unwrapping or returning a Result type, and ensuring someone is actually handling it with a hard stop on the user. The problem is reality is often much more subtle than "doSomethingImportant()". For example, I worked on an iOS app that was used for medical work, and one habit iOS devs had was wrapping access to weak references to objects with a null safety: So they were super comfortable writing things equivalent to viewController?.showSomeDialog() because in most situations crashing over lifecycle issues is a bad idea... but here we could be creating incorrect output in a highly sensitive situation. "someDialog" could be "Drug interaction between X and Y detected" for example, but the mental patterns wouldn't detect that a safety operator was hiding that. It was better for the app to crash than silently hide that information, since at least then it'd be known the app was in an invalid state. - Now the problem is here devs start to think "well I'm not making medical apps" or "well I wouldn't make that mistake"... But go through any moderately large codebase and you will find important user actions that silently fail where a crash would actually serve the user better. It's how to convince people that a hard crash can ever be good though.
- jmartrican 4y agoThank you for stating this. I been pulling my hair out trying to comprehend how more focus is not put on that fact. Whether your language is null-safe or not, you have to deal with this. Like for example, if I have a type that is serialized from JSON to Kotlin type and one of the fields is not-nullable but the JSON does not have that field, you still going to get an error, right? With Java, at least you'll be able to do something about it and not just fail the request at the these null-safe touchpoints.