[HN Gopher] Retrofitting null-safety onto Java at Meta
___________________________________________________________________
Retrofitting null-safety onto Java at Meta
Author : mikece
Score : 151 points
Date : 2022-11-22 15:21 UTC (7 hours ago)
(HTM) web link (engineering.fb.com)
(TXT) w3m dump (engineering.fb.com)
| justin_oaks wrote:
| The null problem has been around a very long time. It's been a
| huge source of errors in programming. I'm glad to see it actively
| being worked on, even if it is awkwardly being retrofitted on an
| old language.
|
| I can only look on with disappointment when new (or newish)
| languages include null as part of the language instead of doing
| something more sensible like having an Option/Either type. Of
| course, I won't name names... _cough_ Go _cough_
| coin wrote:
| > cough Go cough
|
| Go is an old style language developed and released in modern
| times
| pjmlp wrote:
| Very old, given the time it has taken to catch up with CLU.
| travisd wrote:
| I also hate this about Go, but its (partial) saving grace here
| is the `x, err := NewX()` pattern which (at least for me) tends
| to prevent a decent number of these issues in practice since
| usually either `x` is non-nil _xor_ `err` is non-nil.
|
| Makes the // Java name =
| personService.getPerson(123).getName()
|
| problem less likely since you'd generally have to write:
| // Go person, err := personService.GetPerson(123)
| if err != nil { ... } name, err := person.Name()
| if err != nil { ... }
|
| Definitely more verbose than maybe //
| TypeScript personService.getPerson(123)?.getName()
|
| but I think that's part of Go's tradeoffs -- much more likely
| that errors will be annotated more correctly (i.e., in Go you'd
| be more likely return an error like "failed to load person:
| 123" if it was the GetPerson call that failed rather than a
| generic error that doesn't describe which step failed)
| brundolf wrote:
| It's okay for null to be a part of the language as long as the
| type system can reason about it (via nullable-types, sum types,
| etc). Dart, TypeScript, Kotlin are examples. But yeah, Go is
| the problem-child that doesn't have any way of statically
| reasoning about nulls
| justin_oaks wrote:
| Yes, it's OK to have null in the language in limited cases.
| I'm fine with null so long as the language enforces checking
| for null when it could cause a NullPointerException (or that
| language's equivalent).
|
| Java's biggest failure in null handling was allowing EVERY
| reference type to be null without a way of specifying that it
| could be non-null. This basically ensured that null could
| creep in almost anywhere.
|
| Allowing a type to be null should always be opt-in. It still
| bothers me that I have to put NOT NULL in almost every SQL
| column definition because the SQL spec has columns default to
| being nullable. Ugh. At least SQL does allow me to choose
| nullable vs non-nullable, unlike Java.
| Arbortheus wrote:
| I agree about Go. One quite frustrating scenario I have ran
| into multiple times is if you unmarshal (parse) some JSON
| into a struct, you can't tell if an integer field with value
| 0 was specified in the JSON input as 0, or missing from the
| input. AKA there's no way to differentiate the zero value
| from undefined for primitives.
|
| There are roundabout hacky ways like parsing the JSON into a
| map[string]any to check if it's there, but it's so ugly and
| requires so many lines of code.
| mbfg wrote:
| I always wondered why enums in java were allowed to be null.
| Seemed like something they could have enforced. Could have just
| had some sort of 'default' attribute on a value.
| masklinn wrote:
| > I always wondered why enums in java were allowed to be null.
|
| Because they're just the "type-safe enum" pattern, built in,
| with some compiler support (e.g. switches). Besides that
| they're normal reference types.
|
| > Could have just had some sort of 'default' attribute on a
| value.
|
| Universal defaults are, if anything, even worse than universal
| nulls: they generate garbage states you can't guard against at
| all.
| fedeb95 wrote:
| Beware of expected nulls and expected NullPointerException in
| your legacy code when exterminating nulls.
| didip wrote:
| Java has so many deficiencies in its design that so many
| frameworks are invented to cover its flaws.
|
| Just give a real Optional type at a language level. It's clearly
| possible in other JVM languages.
| rad_gruchalski wrote:
| Other jvm languages compile to the byte code and fix this by
| inlining the optional transparently.
|
| What you mean by "at a language level" would break the byte
| code for jars compiled with older jdks.
| didip wrote:
| Breaking backward compatibility is not the end of the world.
| bismuthcrystal wrote:
| It might not be, but is damn close to it.
| rad_gruchalski wrote:
| Maybe not for you. I have experienced the Python 2 to 3.
| When I use Java, I'm happy I can rely on the compatibility.
| pgwhalen wrote:
| Thank god the Java language designers don't share your
| point of view.
| mrkeen wrote:
| > Java has so many deficiencies in its design that so many
| frameworks are invented to cover its flaws.
|
| Java the language has its flaws, but the frameworks make the
| situation worse.
|
| > Just give a real Optional type at a language level. It's
| clearly possible in other JVM languages.
|
| It's not the presence of Optional that we need, it's the
| absence of nulls. And nulls are in all the libraries. And you
| also have to convince your fellow Java developers that null is
| bad.
| brabel wrote:
| We've been using `@Nullable` as Meta does in our code base for
| many years now (everything else is assumed to be non-null).
| Initially, we had CheckerFramework actually doing the checks at
| compile time... but eventually had to remove it because it's
| unstable, slow, has not kept up with Java evolution (we're on JDK
| 17, I think Checker still barely works on JDK 11) and also,
| because our IDE can be configured to flag errors on `probable
| nullability issue`! That was really a boon for us... it costs
| nothing at compile time and it's pretty easy to not mess up as
| the IDE keeps us in check whenever we make any change.
|
| At the same time, we have adopted Kotlin as well. Interestingly,
| most of our developers prefer to stay on Java... not because they
| love Java, but because of familiarity and that they seem to think
| it's "good enough".
|
| Anyway, we haven't had a large rate of NPEs for a very long time
| and when one actually happens in the wild we're extremely
| surprised!
|
| That said: looking forward to the specification[1] that's being
| cooked up as Java absolutely should have had this a long time ago
| (as mentioned in the post).
|
| [1] https://jspecify.dev/docs/start-here
| clumsysmurf wrote:
| > At the same time, we have adopted Kotlin as well.
| Interestingly, most of our developers prefer to stay on Java...
| not because they love Java, but because of familiarity and that
| they seem to think it's "good enough".
|
| I have been using Kotlin on Android for a few years now, and
| really enjoy the extra expressiveness and conciseness that the
| language offers ... but have come to the conclusion its much
| easier to write unreadable kotlin code than java code.
|
| One of the things I think makes Kotlin hard to read is scoping
| functions (apply/also/let/run/with). Based on their names
| alone, the newcomer can't tell what they do (I had to consult a
| table for weeks on whether they returned the original object,
| last expression, and whether they passed it, this, etc).
|
| I do love these when used at the right place, right time, and
| "just enough" but I keep coming into contact with code that
| nests these 4-5 levels deep: aaa?.run {
| bbb?.run run2@ { ccc?.run { ddd?.run {
| this@run2.foo() // whats in scope here urgh
| } } } }
|
| Other pathologies I see are making any method (foo), that
| operates on X, as an extension method X?.foo(). Then the
| namespace for X gets polluted with all these things.
|
| One of the great things about Kotlin's nullability in the type
| system is the operators like ?. and ?: ... but often see code
| like this: aaa?.bbb?.ccc?.ddd()
|
| Is it safe, yes, yes it is. It won't crash, but what happens if
| one of these is null, and _should not be_? There is nothing in
| Kotlin forcing people to write this way, but find Java 's
| explicit checking of null made people think more of the
| "negative case" (throw an exception? etc)
|
| Like anything else, I guess we could use linting rules, it
| comes to having _good taste_ ... which is not in abundance.
|
| What worries me the most though, is that in the beginning,
| Kotlin was mostly additive on Java. I read, in biological
| systems, adding is easier that changing or removing. It seemed
| like Effective Java + Typed Groovy. Now as Java has "last
| mover" advantage, and adopts the good parts of Kotlin, I am
| worried its not just additive, they need interop between
| different implementations (records vs data classes).
| Larrikin wrote:
| In your first example, thats poorly written code that should
| never make it past code review. I'm also having a hard time
| thinking of a case where run would even be useful in that
| situation that wouldn't also fix your second example.
|
| If you need all aaa through ddd to be non-null you return
| early or you check ahead of time aaa ?:
| return bbb ?: return ccc ?: return ddd ?:
| return
|
| or if (aaa != null && [the rest] != null)
| foo(aaa, bbb, ccc, ddd)
|
| Since if foo needs them all to be not null they should be
| passed in aaa?.bbb?.let{ bbb -> //definitely
| should not be null in this case bbb.ccc?.ddd()
| } ?: return // or throw an exception
| usrusr wrote:
| And val aaa = aaa ?: return val
| bbb = bbb ?: return val ccc = ccc ?: return
| val ddd = ddd ?: return
|
| if those three-letter friends happen to be "getter vals"
| that might change behind your back (the compiler is
| perfectly aware of the difference).
|
| That's the one gripe I have with kotlin, those "getter
| vals" that aren't even remotely related to immutability.
| Likely a consequence of building closely on top of java,
| there's simply no way an interface could promise to never
| return two different references from a call on the same
| object without that promise being a lie. And as usual,
| kotlin provides gentle damage mitigation on syntax level:
| "val x = x", as in the four lines above.
|
| I would have preferred something even more consise like
| "val=x" though, where the name overlay is promoted to
| explicit intention by not typing it twice, as in "call that
| getter and nail its return to the getter's name for the
| remainder of our scope". People tend to be reluctant with
| that name hiding, unsure of it's good style or bad, if
| there was an explicit shortcut it would be blessed as The
| Kotlin Way (and, as you said, it's so much better than
| getting pulled into that let/also nesting abyss that feels
| clever while writing but turns on you before you are even
| done)
| BoorishBears wrote:
| My workplace is trying to get onto the KMM train and I get
| sweaty palms trying to imagine people onboarding onto Kotlin
| from other languages.
|
| I mean I love Kotlin, but as you mention there is just _so
| much syntax_. The simplest things can be done 100 different
| ways and it 's hard to say what's idiomatic.
|
| I'm personally going to be ruthless about "syntax simplicity"
| in code reviews. Otherwise I'm imagining a situation where
| the dream of shared code really just becomes "Android devs
| throwing a black-box library over the wall at iOS devs"
| because only they spend enough time with Kotlin to understand
| it
| titzer wrote:
| Whoa, interesting. I didn't know Kotlin had all those
| constructs.
|
| In Virgil, a method on an object (or ADT) can declare its
| return type as "this". Then the method implicitly returns the
| receiver object. That trick is very useful to allow a chain
| of calls such as object.foo().bar().baz(). I find it readable
| and easy to explain:
|
| https://github.com/titzer/virgil/blob/master/doc/tutorial/Re.
| ..
| exabrial wrote:
| 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?"
| mrkeen wrote:
| > 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.
| BoorishBears wrote:
| It'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().setSomeFieldWithReal
| WorldImportance(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.
| java_expert_123 wrote:
| No 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 wrote:
| An 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.
| random314 wrote:
| Yes and Null safety forces you to ask that question at compile
| time instead of runtime
| commandlinefan wrote:
| > 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.
| [deleted]
| kaba0 wrote:
| You 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.
| colejohnson66 wrote:
| With 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` }
| bcrosby95 wrote:
| That 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.
| return_to_monke wrote:
| Surprised 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 wrote:
| That would be _way_ better, but I 've never seen it.
| jmartrican wrote:
| Thank 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.
| chrisseaton wrote:
| > 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 wrote:
| > 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.
| Thaxll wrote:
| There 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.
| _ZeD_ wrote:
| I really hoped that projects like lastnpe[0] will have more
| visibility.
|
| 0. http://www.lastnpe.org
| driggs wrote:
| It's so sad that Java 8 had the chance to really fix the null
| problem, but gave us only the half-assed `java.util.Optional<T>`.
| Rather than implementing optional values at the language level,
| it's just another class tossed into the JRE.
|
| This is perfectly legal code, where the optional wrapper _itself_
| is null: Optional<String> getMiddleName() {
| return null; }
| lazulicurio wrote:
| imo it seemed like there was this phase of "if we pretend null
| doesn't exist maybe it will go away" that resulted in a bunch
| of design issues. Beyond Optional, the other big one to me is
| Map.compute/Map.merge, which subtly breaks the previous
| interface contract for Map. As annoying as null is, I'd rather
| have null than have broken apis.
| nigerianbrince wrote:
| @NonNull Optional<@NonNull String> getMiddleName() {
| return null; // error }
| kaba0 wrote:
| Or even just setting the default to non-nullable with any
| static analysis checker.
| mrkeen wrote:
| It may have been hoped for, but it was an unrealistic hope.
|
| They couldn't have fixed it while retaining backward
| compatibility.
| masklinn wrote:
| > They couldn't have fixed it while retaining backward
| compatibility.
|
| Opt-ins exist. That's what .net ended up doing: by default
| the language uses "ubiquitous" nullability, but you can
| enable reference nullability support at the project or file
| level.
|
| If you do that, references become non-nullable, and have to
| be explicitly marked as nullable when applicable.
| [deleted]
| cheriot wrote:
| Scala had the same possibility, but the ecosystem of libraries
| used Option<T> appropriately so in practice I never thought
| about null. That might be harder in an older, larger ecosystem
| like Java...
| dheera wrote:
| I'm not a Java person but isn't this just very similar to
| std::optional in C++?
|
| https://en.cppreference.com/w/cpp/utility/optional
| masklinn wrote:
| No, because in C++ bare types are not nullable. In java,
| reference types are nullable, Optional being a reference
| type, Optional<T> can be null, Empty, or have a value,
| whereas T can be null or have a value.
|
| Also because std::optional does essentially the opposite.
| It's a more efficient _ptr rather than a safer one.
| Optional<T> is strictly less efficient as it implies an
| additional allocation.
| dheera wrote:
| > Optional<T> can be null, Empty, or have a value, whereas
| T can be null or have a value
|
| Thanks for the explanation. Wow, this sounds like a a bit
| of a mess, one because of the allocation, and also because
| it seems there are multiple ways something can be
| semantically null.
| krzyk wrote:
| OK, so when valhalla lands I assume Optional will become
| value type (or we will have another class for that, to
| avoid issues with backward compatibility)
| bagels wrote:
| I love this solution. Now you have two kinds of nulls.
| modeless wrote:
| Just like JavaScript!
| golergka wrote:
| Javascript also has Option with fp-ts, so it still leads on
| this front.
| britch wrote:
| I disagree. The problem with Null in my opinion is that it is
| the default and can easily be created accidentally.
|
| There's nothing inherently wrong with an "absence of value,
| value"
|
| Optional.empty is not null, it's no-value
| ender341341 wrote:
| But java has the worst of both worlds cause with an
| optional you now have 3 cases, Optional.empty,
| !Optional.empty, & null instead of just 2 cases.
|
| In our code base this means we have a the rule that
| Optional is only allowed for returns so we aren't adding
| 3rd cases all over the place.
| krzyk wrote:
| I have never came up to a code that would pass null in
| place of Optional, why?
| ender341341 wrote:
| Because you don't always control where the code is being
| called from.
|
| Other compile time typed languages (C, C++, C#,
| typescript, rust, kotlin) usually would let you enforce
| that as part of the type system, but the java compiler
| will silently accept null in place of optional and throw
| an NPE at runtime.
| mohaine wrote:
| Pretty much every JSON parser in java does this by
| default. Why? Because they were all written before
| Optional existed.
|
| I have to agree with the Optional hate. I loved it in
| Scala but after a few months on Kotlin I don't think I
| can go back.
| pron wrote:
| What do you mean by _had_ a chance? That chance isn 't gone.
| This area is under investigation, and when we have a solution
| we like, we'll implement it. We can't address all issues at
| once.
|
| Optional isn't half-assed because it was never envisioned as a
| general solution to the null problem. It's simply an interface
| that's useful in streams and other similar cases for method
| return values, and it does the job it was intended to do
| reasonably well. It's possible that a solution to nulls in the
| type system could have obviated the need for Optional, but I
| don't think that delaying lambdas and streams until such a
| solution was found would have been a better decision.
| jimbob45 wrote:
| _This area is under investigation, and when we have a
| solution we like, we 'll implement it_
|
| What's to investigate? Just copy what C# did and be done with
| it.
| 5e92cb50239222b wrote:
| Yeah, I've heard this kind of stuff in the past. "Just copy
| async/await from C#, what are you waiting for?". And now C#
| is stuck with that thing, while we'll soon be getting the
| proper solution.
|
| Thanks, I'd rather wait.
| jayd16 wrote:
| I'd rather have the decade of increased productivity.
| pron wrote:
| Increased productivity comes in various ways. A more
| popular language often has a better ecosystem that helps
| productivity, and adding lots of language features
| quickly is a hindrance to huge popularity. This might not
| be the case for many here, but most programmers prefer
| fewer features than more, and the most popular languages
| are also often those that can be taught as a first
| language, which also requires restraint. Also, languages
| that add features quickly are often those that add
| breaking changes more easily, which loses productivity.
|
| So even if some language feature helps with some problem,
| the assumption that adding it ASAP is the best way to
| maximise the integral of productivity over time is not
| necessarily true. Language features also cost some
| productivity, and the challenge is finding the right
| balance. Java chooses to follow a strategy that has so
| far worked really well.
| jayd16 wrote:
| What is this argument exactly? We're arguing against the
| concept of features? Why are we talking hypothetically?
|
| Async/await provides immediate user value. If people
| didn't like it they could just use threads in a Java-like
| style. While some dislike the features, and other push
| against the sour grapes, its a popular feature found in
| many languages used by many developers.
|
| Java is popular and so is C# and Javascript, so I can't
| see how we can draw any conclusions on async/await.
| pron wrote:
| First let me say that since it's established that
| different programmers have different preferences, the
| fact that some programmers might prefer a different
| evolution strategy is no reason to change it, because
| that will always be true. A reason to change strategy is
| if some other one has fared better, and none has. The
| only languages that have arguably fared better than Java
| are Python and JS, and they have fewer, not more
| features. So there's simply no good reason to pick a
| different strategy that has not shown to fare better.
|
| Now, when it comes to async/await, first let's take JS
| off the table, because JS has no other convenient
| concurrency feature, and it _couldn 't_ add threads
| because it would have broken most existing JS code (it
| could add isolates, but they're not sufficiently fine-
| grained, at least not in the JS world).
|
| If Java had got async/await ten years ago, it would have
| been burdened with it for decades. It would have provided
| some value in those ten years, and extracted value for
| the following forever (albeit gradually diminishing).
| "Just don't use it" works fine for library features, but
| not so much for foundational language features, because
| programmers usually don't start with a blank slate, and
| don't pick the features in the codebase they work on.
| Therefore, all language features carry a cost, and they
| carry a higher cost when it comes to beginners, where
| this matters most.
|
| It's hard to precisely describe what could have been, but
| I think most would agree that in those ten years Java
| didn't lose many developers to languages with async/await
| _because they had async /await_. It probably lost some
| developers to Python and JS for other reasons (say,
| Python is easier to get started with, and JS is easier
| for those who know it from the browser), and it didn't
| even lose that many people to Go (Python lost many more
| to Go than Java did). Considering all that, I think that
| Java's position in 2022 is better, now that it has
| virtual threads, than it would have been had it also had
| async/await (which would have likely also delayed virtual
| threads).
|
| If I could go back in time knowing what I know now, I
| would have advocated against adding async/await ten years
| ago with even higher certainty. Back then I just believed
| there's a better way; now not only do I know there's a
| better way, but I also know that not adding async/await
| didn't cost us must if at all.
|
| Going back to the original topic, Java's primary
| competitors -- Python and JS -- also don't have a great
| solution for nulls. So while I would very much like to
| address this problem, I see no reason to change our game
| plan in favour of scrambling for a quick solution. We'll
| tackle this issue like we try to tackle all others:
| methodically and diligently, and preferably after we've
| had time to study how well various solutions work
| elsewhere.
| Retric wrote:
| I know vastly more programmers that have abandoned Java
| than swapped to it.
|
| That's not to say it failed. Java is one of the most
| popular languages, but it does suggest real issues with
| the current approach.
| eternalban wrote:
| I think you are jumping to your desired conclusion here.
| Even if we take your vastly at face value, there are so
| many reasons people switch languages, and problematic
| language evolution is not the only possible reason.
| pron wrote:
| And you could probably say the same for JavaScript and
| Python, that, together with Java, make up the current
| topmost tier of super-popular programming languages,
| which is why I think your conclusion is wrong.
|
| These three languages are often first languages (or, at
| least, first professional languages), which is a
| necessary (though insufficient) condition for being
| super-popular. All of these languages more than make up
| for the loss of programmers who prefer richer languages
| with the programmers for whom it's a first (professional)
| language. Different programmers indeed prefer different
| languages, but that doesn't mean that the preferences are
| easily distributed. My rough personal estimate is a 90-10
| split, where the 10% prefer more feature-rich, faster-
| moving languages. That's a very big minority, but Java
| addresses it by offering the Java language for the 90%,
| and supporting other languages on the Java platform for
| the 10%.
|
| You can also see that while the market is becoming more
| fragmented, no language is currently threatening those
| top three (although TypeScript could threaten JS, I
| think), and no language is doing better than them. I.e.
| other strategies seem to be doing worse.
|
| So knowing that for every X programmers you win you lose
| Y no matter what you do means that we try to carefully
| balance the evolution. I can tell you that we are more
| worried about teachers telling us that Java is getting
| too many features too quickly, and that it's harder to
| teach than Python (hence "Paving the Onramp [1] and other
| planned features) than the programmers asking for more
| features quicker. The former represent a much larger
| group than the latter. Also, moving more toward the
| latter group is both easier and less reversible, so it
| has to be done with great care.
|
| [1]: https://openjdk.org/projects/amber/design-notes/on-
| ramp
| BoorishBears wrote:
| Can you point a couple of cases where Java got a better
| version?
|
| Generics? Value Types? Lambdas? Null Safety? Async/Await?
|
| Also do you just have to assign all the value the
| features provide in interim 0 value? Like Async/Await has
| provided a 10 years of value, and I'm guessing you mean
| Project Loom... so how much better than Async/Await does
| Loom have to be to justify an entire decade of just
| straight up missing the value add entirely, let alone
| having a better one
|
| -
|
| I can't reply to comments anymore (thanks dang) but
| yeah...
|
| Generics at the Java layer are worse, generics at the JVM
| layer are convenient if you're going to write a language
| targeting the JVM. That's just an artifact of the fact
| that the JSR for Generics defined backwards compatibility
| as its top goals, not because they were trying to enable
| a friendlier runtime for languages that didn't exist yet.
|
| I already addressed Project Loom: They're coming a decade
| after async/await are coming after a decade of a strictly
| worse concurrency story, so unless you're assigning 0
| value to a decade of having a better situation (which I'd
| say is disingenous) I don't think it's a great example.
|
| Also definitely not sure how Java Records are better than
| C#?
| pron wrote:
| I can! Generics, virtual threads, and records. Java is
| unburdened with the prevalent and obsolete features of
| async/await, properties, and its generics allow a
| flourishing ecosystem in exchange for a minor
| inconvenience.
| Nullabillity wrote:
| .NET's async is far from great, but Loom/vthreads is an
| impressive achievement in distilling all of the mistakes
| that .NET made, while learning from none of the good
| ideas that it (or future promise/async/await) had.
| islon wrote:
| I guess he's talking about project loom (fibers, go style
| concurrency) which IMHO is a much better solution than
| async/await, but yeah... took some 10 years to arrive.
| _old_dude_ wrote:
| During the last 10 years, the Java ecosystem has heavily
| invested in reactive APIs, so it's not like if Java devs
| have no option.
|
| And IMO, the semantics of any reactive APIs is better
| than just providing await.
|
| Await serializes the async calls instead of running them
| concurrently. And too few C# devs are aware of/using the
| Task API.
| krzyk wrote:
| And saved millions of developer minds not having to deal
| with mind bending async/await.
|
| Thanks, I prefer to wait.
| Nullabillity wrote:
| You could have had the Loom experience 20 years ago by
| just spawning OS threads. Of course, there's a reason
| that this was discouraged... threads quickly turn into a
| nightmare to manage safely, especially when they need to
| interact.
| za3faran wrote:
| Genuine question, how is managing cross interacting
| virtual threads any different or easier than managing
| interacting threads? I say this and I am greatly looking
| forward to using Loom in production. It's definitely the
| correct way to go as opposed to async/await.
| pharmakom wrote:
| Do-notation is much simpler than callback hell
| nlitened wrote:
| If you want all the C# features, just use C#. Java is a
| separate language with its own tradeoffs.
| [deleted]
| hn_throwaway_99 wrote:
| I have definitely come to the conclusion, especially after
| doing a lot of work in Typescript, that class Optional is a
| big mistake, whether from the JDK or other libraries that
| preceded it.
|
| First, because exactly the type of code that the parent
| commenter showed. I've actually seen this in production code
| (and shrieked). The fact is that without language-level
| support, you can end up getting the worst of all worlds.
|
| Second, like all things in Java along the lines of "why use 1
| character when 10 will do?", the Optional syntax is verbose
| and annoying to use.
|
| But, most importantly, the fundamental issue is that _all_
| classes are optional by default in Java (indeed, that 's the
| problem at hand). Adding an Optional class doesn't really
| mean you can make any non-nullability assumptions about
| _other_ classes, especially when using different libraries.
| The Optional class just added more complexity while
| simplifying very little.
| simplotek wrote:
| > But, most importantly, the fundamental issue is that all
| classes are optional by default in Java (indeed, that's the
| problem at hand). Adding an Optional class doesn't really
| mean you can make any non-nullability assumptions about
| other classes, especially when using different libraries.
|
| Perhaps it's just me, but I don't equate optional with
| nullable. An optional value is just the designer specifying
| that you may or many not specify that value, while nullable
| are objects which may or may not have been initialized yet.
|
| Even though nullables have been used and abused to
| represent optional values, I'm not sure it's a good idea to
| conflate both. It would be like in C++ equating shared
| pointers with optional types.
| StevePerkins wrote:
| I miss the pre-Rust days. When Haskell was HN's cool pet
| language, and you had to obtain at least some vague
| familiarity with the term "monad" to understand half the
| discussion threads here.
|
| I'm sorry, but I don't think you understand the purpose
| that "Optional" was intended to serve. And are unduly
| dismissive simply because it does not serve some larger
| purpose that was not intended.
| theLiminator wrote:
| I mean having a monadic api is nice if it's strictly
| enforced by both ecosystem/culture/typesystem. But with
| Optional being potentially a null itself, it barely
| improves the need to defensively program and might in
| fact be worse. For example, when I used a lot of Scala in
| a past job, Java libraries were scary to use unless you
| defensively wrap java functions with Try/Option/etc.
|
| Whereas with Haskell/Rust/OCaml/etc. you can largely
| trust type signatures to properly encode the existence of
| nullability or failure.
| cogman10 wrote:
| > But, most importantly, the fundamental issue is that all
| classes are optional by default in Java (indeed, that's the
| problem at hand). Adding an Optional class doesn't really
| mean you can make any non-nullability assumptions about
| other classes, especially when using different libraries.
| The Optional class just added more complexity while
| simplifying very little.
|
| When pron mentioned "this is actively being worked on", he
| meant exactly this problem. [1] [2] Java is currently
| working on changing the dynamics of the language such that
| classes can in fact be non-nullable. Last I checked in,
| this was the notion of `primitive` types being added in
| project Valhalla.
|
| Optional just so happens to be one of the types in the JDK
| that will converted over to being a `primitive` type.
|
| Now, what that means in terms of existing code I believe
| what's being discussed. How do you handle
| public Optional<Foo> bar() { return null; }
|
| ?
|
| We'll see, it might be something like changing the code to
| something like this public
| Optional<Foo>.val bar() { return Optional.empty(); }
|
| which would guarantee that the optional type returned is in
| fact never null.
|
| [1] https://github.com/openjdk/valhalla-
| docs/blob/main/site/desi...
|
| [2] https://github.com/openjdk/valhalla-
| docs/blob/main/site/earl...
| BoorishBears wrote:
| It's like when people say Java _had_ the chance to do
| generics right (like C#) and then didn 't
|
| Yeah technically tomorrow morning Java could fix it, but
| there have been kingdoms built on the current situation. C#
| took its lumps years back on breaking things and so there
| were fewer kingdoms to demolish. And if you fix it, it'll be
| years before it trickles down to a large portion of devs who
| don't get to work at the bleeding edge, or even near the
| edge.
| coding123 wrote:
| Every time this argument comes up I get a sudden urge to
| eat refried beans for some reason.
| pron wrote:
| First of all, Java's generics are already superior to C#'s.
| We've exchanged the minor inconvenience of not being able
| to have overloads that erase to the same type with the
| ability to support multiple variance strategies rather than
| one that's baked into the runtime. That's why Java has
| Clojure and Kotlin and Scala and Ruby running on top of it
| with such great interop. And, as it turns out, when
| specialisation is really important -- for Valhalla's value
| types -- then it's invariant and doesn't require
| demolishing kingdoms. We've also added virtual threads
| years after C# had async/await, and now they're the ones
| stuck with that inferior solution, which we'll remain in
| the ecosystem even if and when they add user-mode threads.
|
| But in this case, I don't see what additional kingdoms
| there would be to demolish. No Java code employs
| nullability types now just as it didn't in 2014. Optional
| does the limited job it needs to do rather well, and will
| become even better when we extend pattern matching, so
| there would be no need to demolish it even if and when we
| have some solution to nulls. As you can see here (https://d
| ocs.oracle.com/en/java/javase/19/docs/api/java.base...)
| it's used sparingly and in specific situations, definitely
| not as a general null replacement.
| BoorishBears wrote:
| > First of all, Java's generics are already superior to
| C#'s. We've exchanged the minor inconvenience of not
| being able to have overloads that erase to the same type
| with the ability to support multiple variance strategies
| rather than one that's baked into the runtime.
|
| Working on the JDK I'm sure you're aware just how
| revisionist that take is: we got the version of generics
| that landed because of backwards compatibility concerns.
|
| One of the key selling points going from Pizza to GJ was
| that the result ran on then current JVM targets without
| modification.
|
| https://jcp.org/en/jsr/detail?id=14
|
| > C1) Upward compatibility with existing code. Pre-
| existing code must work on the new system. This implies
| not only upward compatibility of the class file format,
| but also interoperability of old applications _with
| parameterized versions of pre-existing libraries_ , in
| particular those used in the platform library and in
| standard extensions.
|
| > C2) Upward source compatibility. It should be possible
| to compile essentially all existing _Java language_
| programs with the new system.
|
| Interop came at the expense of Java (not the JVM) having
| a worse generics story, since the incredibly onerous
| constraint of "works with parameterized versions of pre-
| existing libraries" forced other languages to then go and
| reinvent the exact same wheel in different ways.
|
| -
|
| > We've also added virtual threads years after C# had
| async/await, and now they're the ones stuck with that
| inferior solution, which we'll remain in the ecosystem
| even if and when they add user-mode threads.
|
| "Kingdoms to demolish" refers to changing generics, but
| there are "kingdoms" have been built that wouldn't be
| demolished: they still represent a lot of throwaway work
| from putting off attacking the problem. Case in point,
| the post we're commenting under.
|
| Nullability specifically is much more subject to the part
| you left out: "And if you fix it, it'll be years before
| it trickles down to a large portion of devs who don't get
| to work at the bleeding edge, or even near the edge."
|
| -
|
| At the end of the day it's always been a tradeoff between
| backwards compatibility, timeliness, and correctness, but
| when I switch between Java specifically and C#
| specifically (not the JVM and the CLR) I find C# did an
| amazing job providing value upfront rather than hand
| wringing, and is not nearly as worse off for it as this
| comment would imply.
|
| I mean you're looking down on async/await, but it came
| out a decade ago while Loom is just landing. I guess
| you're trying to paint that decade of development as
| "weighing down" whatever .NET comes out with because
| it'll still exist, but that seems like a stretch at best.
|
| Java had no async/await story and will get a very good
| alternative. C# had an async/await story and will get an
| improved alternative (it's already in the experimentation
| stage according to the .NET team). I'd just much rather
| have the latter?
| pron wrote:
| > we got the version of generics that landed because of
| backwards compatibility concerns.
|
| We got the version of generics because of the need for
| two languages with different variance strategies -- Java
| 1.4 and Java 5 -- to be compatible, and so precluded
| baking a particular variance strategy into the runtime.
| It is true that the goal at the time wasn't to support,
| say, Scala or Clojure or Ruby specifically, but why does
| it matter?
|
| > forced other languages to then go and reinvent the
| exact same wheel in different ways.
|
| I don't think so. Both untyped languages like Clojure and
| Ruby, as well as existing typed languages such as
| Haskell, have been ported to the Java platform. Erasure,
| BTW, is a pretty _standard_ strategy. Even Haskell does
| not distinguish at runtime between List Int and List
| String.
|
| > Nullability specifically is much more subject to the
| part you left out: "And if you fix it, it'll be years
| before it trickles down to a large portion of devs who
| don't get to work at the bleeding edge, or even near the
| edge."
|
| So what? Java is likely to be one of the world's most
| popular languages 20 years from now. Who would care if we
| wait to do things right, especially as it worked very
| well for Java so far. Who cares now that lambdas were in
| Java 8 and not Java 5?
|
| As for C# vs Java, there's no doubt some developers
| prefer one over the other -- for some it's Java, for
| others C# -- but I see absolutely no reason for Java to
| adopt C#'s strategy. Even if you don't think it's any
| worse, it's certainly hasn't proven to be better. Those
| who prefer C#'s approach already use either it or Kotlin,
| so we've got them covered on the Java platform, too.
| tehlike wrote:
| "Java generics is superior to C#" - That's a first for
| me. C# generics don't have to do boxing of types like
| java does, and overall have much better type safety
| between library boundaries, etc...
| [deleted]
| pjmlp wrote:
| The guys from Scala.NET did mention .NET generics as one
| of the reasons they gave up on porting Scala to .NET.
|
| That is also a reason why .NET needed the whole effort to
| create DLR and the dynamic keyword, while Java only
| needed to add one additional bytecode invokedynamic.
| pharmakom wrote:
| .NET has F# and Clojure so not convinced by this
| jimmaswell wrote:
| C# also declined to make the mistake of type erasure.
| johannes1234321 wrote:
| So then there is language-level Optional and library-level
| Optional?
| pron wrote:
| If that happens, there would be language level nullability
| types that may supersede Optional if they indeed end up
| solving a strictly more general problem. It's not unheard
| of to have more general solutions supersede older, more
| specific ones. For example, the newly added pattern-
| matching features largely obviate the need for visitors,
| which are a pretty common pattern in libraries; or virtual
| threads, which largely obviate the need for many
| asynchronous APIs (and there are opposite examples, such as
| j.u.c locks, which _followed_ the language 's locks).
| That's the nature of evolution: sometimes there are
| surviving vestiges of the past.
| joefkelley wrote:
| Do you feel that a language has to have something at the
| language level to prevent NPEs?
|
| In my experience, Scala does pretty well without it.
|
| I guess is your point that the language should make it
| impossible to write bad code, not just make it easy to write
| good code?
| kaba0 wrote:
| Just a heads up, Scala 3 actually has a compile flag that
| will make types exclude 'null' as a valid subtype, so every
| nullable variable will have to have type signatures like
| String | Null.
| pharmakom wrote:
| Optional is not just another class. The control flow in the
| compiler will check that optionals are checked.
| sanedigital wrote:
| Don't people use @NonNull everywhere now? It's been a few years
| since I've programmed in Java but even then I feel like that
| was common practice.
| melling wrote:
| "Unannotated types are considered not-nullable"
|
| Defaulting to not-nullable is a great idea. Much less
| boilerplate.
| krzyk wrote:
| Depends on code base, at the beginning you would prefer the
| opposite, (not annotated are nullable).
| tyingq wrote:
| Seems common, though it only helps so much with 3rd party
| libraries.
| mrkeen wrote:
| Yes, it's always been possible to check for nulls at runtime.
|
| Personally I use notNull(..) over @NonNull since it actually
| fires when you expect it to (as opposed to whether your
| framework dispatcher interceptor trigger decided to invoke
| it)
| lowbloodsugar wrote:
| I think he means using @NonNull for compile time checking,
| not instrumenting it for runtime checking (though that
| doesn't hurt either).
| BoorishBears wrote:
| Do the heuristics used for Kotlin-interop work with
| notNull(...)?
| csunbird wrote:
| isn't @NonNull just a syntactic sugar for adding
| checkNonNull() call as first statement to the function
| declaration in compile time, or am I mistaken? Just like
| lombok, it is supposed to generate code that checks null
| arguments, from what I know.
| mrkeen wrote:
| I tried: @Test public void
| foo() { bar(null); }
| void bar(@NonNull String param) {
| System.out.println(param); }
|
| All versions of that annotation let the null right
| through. I tried with: import
| lombok.NonNull; import
| javax.validation.constraints.NotNull; import
| io.micronaut.core.annotation.NonNull; import
| org.springframework.lang.NonNull; import
| reactor.util.annotation.NonNull;
| za3faran wrote:
| Did you try https://github.com/uber/NullAway?
| guelo wrote:
| @Nullable/@NotNull is great when the IDE shows the
| warnings, basically dev time checking. There are also
| tools to integrate it into your builds for compile time
| checking.
| mrkeen wrote:
| I get a little green tick in the top right of my IDE
| window with the following: @Test
| public void foo() { final Map<String,
| Integer> map = new HashMap<>();
| map.put("present", 1);
| bar(map.get("missing")); } void
| bar(@NotNull Integer param) {
| System.out.println(param); }
|
| "No problems found"
| guelo wrote:
| Right, that's because the system libraries don't have the
| annotations. That's the biggest issue with it. But it
| still helps a lot if you're religious about it in your
| own code.
| BoorishBears wrote:
| In my experience mostly people forced to use Java who are
| then consuming said code in Kotlin do that
| joshlemer wrote:
| Which @NonNull are you referring to, Lombok's? Or
| javax.annotation.NonNull, or something else?
| Aardwolf wrote:
| Isn't Object itself (and any class) already an optional at the
| language level?
|
| Either it's null, so has no value, or it's not null so has a
| value, and you can check for null-ness. What more is needed?
|
| Seems like the opposite, a guaranteed non-null value at core
| language level, would be novel instead...
| usrusr wrote:
| Right, if only there was a language on the JVM that did that
| /s
|
| It's really an eye-opener when you compare kotlin and scala,
| with all their superficial similarities: Where kotlin simply
| takes the @Nonnull annotation, promotes it to a core language
| feature and drowns it in convenient function-scope syntactic
| sugar until it's actually nice to use, scala opts for Option
| and stacks layer upon layer of architecture trying to make
| Option somehow disappear. I lost half a decade holding on to
| plain java snobbishly dismissing kotlin as a second class
| scala before I finally got converted.
| dehrmann wrote:
| I wonder if the plan is to add syntactic sugar for Optional<T>
| in a future version.
| layer8 wrote:
| It wouldn't be compatible with regular nullable references.
| krzyk wrote:
| One can abuse everything, but should one? (and that error
| should be catched by any IDE or some static analysis).
|
| Optional are quite nice and we use it where appropriate, they
| play nice with streams which is a nice bonus.
| revskill wrote:
| OK, then i realize GoF design patterns lack the single most
| important pattern: Option data structure.
| twawaaay wrote:
| Nobody ever claimed that GoF is a complete set of patterns. It
| is just a subset of patterns from infinite variety of possible
| solutions to problems.
| howinteresting wrote:
| A very good argument is that the GoF stuff should be looked
| at with suspicion because they didn't bother nailing down the
| fundamentals.
| twawaaay wrote:
| I actually have the fricking book on my bookshelf. The
| first paragraph on the first page of the book starts:
|
| "This book isn't an introduction to object-oriented
| technology or design. Many books already do a good job of
| that. This book assumes you are reasonably proficient in at
| least one object-oriented programming language, and you
| should have some experience in object-oriented design as
| well."
|
| At no point it claims to be any kind of software
| development handbook or complete set of patterns or
| teaching fundamentals of anything. It is just a collection
| of "hey guys, see, we figured out this might be useful for
| ya!".
| jerf wrote:
| Nobody _should_ have ever claimed the GoF is a complete set
| of patterns. The GoF themselves vigorously said it was not
| intended to be.
|
| But it has definitely been raised up to The Official Set Of
| Design Patterns by a lot of people. I've lost count of the
| number of languages I've seen someone write about "design
| patterns" in, and what they mean is they show a complete
| ported implementation of all the GoF design patterns,
| including all OO quirks, even in languages where they are
| _manifestly_ inappropriate, including dynamically typed
| languages (where many of them apply, but are optimally
| designed quite differently to account for the huge
| differences in the type system) and functional languages
| (when many of the GoF patterns just dissolve into the
| language, but a whole other set of patterns is necessary).
| twawaaay wrote:
| It is the same old story, later repeated with agile
| manifesto, REST, and so on.
|
| They became misunderstood and abused by enough people that
| the terms stopped meaning anything useful in public space.
| When I hear a company say "we're Agile, we are doing REST"
| etc. I just roll my eyes and think to myself: unlikely.
|
| Over the years I figured out the right way to use all of
| these things: as resources with solutions to frequent
| problems.
|
| So no, I will not preach GoF or DDD or CQRS or Agile or
| REST or anything else, but I will make sure I understood
| each one very well on many levels and apply the lessons to
| my projects.
| pjmlp wrote:
| One interesting point is how many think there is Java
| code on GoF.
| twawaaay wrote:
| Wow, just came up with fantastic interview question (just
| kidding).
|
| But, yeah, this is a good point. I interview senior
| engineers (mostly Java) a lot and I work with a lot of
| people like managers, tech leads, architects and senior
| engineers and I have the feeling that almost nobody has
| ever actually read any of the things that they are
| talking about.
| tinglymintyfrsh wrote:
| Null References: The Billion Dollar Mistake by Tony Hoare
|
| https://www.infoq.com/presentations/Null-References-The-Bill...
| BiteCode_dev wrote:
| For the same reason, this alone is why I use mypy: it will tell
| me when I risk of nulling myself with None in Python.
|
| I still wish for ?, ?[] and ?. to offer failsfe for None,
| IndexError/KeyError and AttributeError one day.
| Traubenfuchs wrote:
| I have been writing Java for money for more than ten years and
| never ever have I had non-trivial problems with null. Less than
| one percent of the bugs I fixed were caused by nullpointers, less
| than one percent of write-deploy-test loops were caused by it.
|
| I either have code that can't be null (e.g. getters of lists that
| create a list if the field is null, outright validation before
| usage), code where null has a desired meaning that must be
| handled (new JPA entity with no key yet) or code where the
| Nullpointer will lead to a client/user/implementor caused bad
| request style response.
|
| What the hell are you guys doing for you to spend significant
| amounts of time on NullpointerExceptions?
|
| On the same note, I didn't understand the inclusion of Optional
| in Java. Always felt like some annoying special flavor type of
| custom Java nuisace like vavr or jooq.
| mrkeen wrote:
| The slow-down doesn't come from NPEs occurring _as bugs_.
|
| The slow-down occurs from all the extra thinking that happens
| once [your teammate] allows nulls into the code "as a feature".
|
| If you _assume no nulls_ , then you get to lean on the type
| system. A String is _actually_ a String, not an instruction to
| GOTO the nearest enclosing RuntimeException handler.
|
| And honestly, I'd be _mostly happy_ to work on the no-null
| assumption, hit a few NPEs, and fix them as I go. But someone
| else will decide that nulls are OK as values, and now _every
| value is suspect_. Which means you can no longer just blindly
| assert against nulls.
|
| And that's the value-add of Optional. You can use it to
| _deliberately_ represent things that aren 't there, meaning you
| can go back to treating every null as a bug to squish, rather
| than a design choice.
| rightbyte wrote:
| Null pointer exceptions are like free unit tests. The
| programmer assumed something never signaled an invalid result
| and was wrong.
|
| I don't understand the fuss either. Logic problems from
| improper error handling is way worse than NPE.
|
| Edit: Pointers that can be null actually are a sum type but
| without the fancy pants hype.
| justin_oaks wrote:
| There are patterns that experienced developers will use to
| avoid issues with nulls. Here are a couple off the top of my
| head.
|
| 1) Reversing string comparisons:
| "literal".equals(variable)
|
| instead of variable.equals("literal")
|
| 2) Always initializing lists instead of leaving them to default
| to null public class SomeClass{
| private List someList=new ArrayList(); //or
| Collections.emptyList() ... }
|
| The problems with NullPointerExceptions can mostly be solved by
| teaching programmers to use these strategies and teaching them
| to look for boundaries where nulls can slip in.
|
| The real problem happens when things get scaled up. If you have
| thousands of developers then it makes sense to take steps to
| automate the problem away. Also, if the code is used by
| billions of users then even rare NullPointerExceptions will
| happen frequently. So it makes perfect sense for Facebook to be
| the one to work on this.
| ben7799 wrote:
| The funny thing is these techniques were introduced in books
| like "Effective Java" before many of the developers who
| complain were even born.
|
| Everyone should know these things by now. They are good
| things to ask about in interviews.
| ahtihn wrote:
| > On the same note, I didn't understand the inclusion of
| Optional in Java.
|
| It interacts nicely with stream pipelines.
| ben7799 wrote:
| Way too many developers are lazy and haven't actually _studied_
| the language they 're using to adapt their code. So instead you
| get discussions where the tools are blamed.
|
| Obviously there are a lot of lazy/bad developers and the larger
| the company/team the more likely you have some of them. The
| irony is when this comes up in discussions about places like
| Google and Facebook that have built up a cult of believing they
| are better than everyone else while seemingly having lots of
| trouble dealing with these kinds of issues.
|
| I don't hate Optional, but what I find is the same people who
| can't handle using null also fall into the Optional anti-
| patterns.
| Zak wrote:
| It amazes me that nullable references keep getting put into
| otherwise strongly statically typed languages. In 1965, it was
| completely understandable. By 1995, the harm should have been
| well understood.
| djinnandtonic wrote:
| Or use Kotlin, it's near perfectly interoperable with Java and
| the acclamation period is measured in weeks if you're a Java dev
| stjkalsfd wrote:
| And simply delete however many millions of lines of java code
| already exists :)
| pron wrote:
| But then you have to use Kotlin, which isn't just Java with
| nullability types, but a language with quite a different design
| philosophy, and a language that is increasingly at odds with
| the evolution of the JDK (partly but not solely because it also
| targets other platforms, such as Android and JS). It appeals to
| some but certainly not to all (interestingly, it hasn't
| significantly affected the portion of Java platform developers
| using alternative languages, which has remained pretty much
| constant at about 10% for the past 15 years).
| jillesvangurp wrote:
| The article actually addresses Kotlin. They'd love to switch to
| it but they just can't do it overnight because they have so
| much mission critical Java code. So, this is a stop gap
| solution for legacy code. They published another article some
| time ago how they are switching to Kotlin:
| https://engineering.fb.com/2022/10/24/android/android-java-k...
|
| Migrating millions of lines of code is a non trivial effort.
| They'll be stuck with bits of Java for quite some time. So,
| this helps make that less painful.
| iLoveOncall wrote:
| I can't think of any popular language that would take more than
| a few days to get acclimated to as an experienced developer, so
| that's not a very compelling argument.
| yamtaddle wrote:
| It's always the (usually quite bad) tooling, learning about
| platform/SDK shittiness and pitfalls, and figuring out which
| parts of the open-source library ecosystem you want to engage
| with, that takes like 90+% of the time getting decent with a
| new language, in my experience. Getting comfortable with the
| language _per se_ takes low tens of hours at most, as you
| wrote.
| adra wrote:
| Kotlin (the base language) is really not that different
| from java. I went from 0 to standing up new backend
| services with limited friction. Coroutines and maybe
| frontends are a different story. Java doesn't yet have a
| coroutines equiv so that was a larger hurdle for me.
|
| Most of the changes for me from 10/20+ hours to now we're
| more about identifying a style that works as effectively as
| I can. These types of behaviours are normal in all but the
| most idiomatic languages, so if anyone is doing java dev as
| their daily language, Kotlin felt very natural(though you
| really are limited to Intellij since the IDE does a ton of
| lifting to make your life easy).
| afandian wrote:
| I can't think of many other languages that will compile into
| a Java codebase, and be interoprable in both directions, as
| well as Kotlin. It's a lot quicker to pick up than e.g. Scala
| IMHO.
| rr888 wrote:
| Ooof, I think most devs can modify an existing code base in a
| few days. To learn all the idiomatic styles, tradeoffs of the
| major libraries and different build systems take months,
| maybe years IMHO.
| jlund-molfese wrote:
| Well, C and Scala are some counter examples that immediately
| come to mind.
|
| Kotlin is probably more similar to Java than any other
| mainstream language. There's almost no learning curve there,
| while going from Java to other "easy" languages like Python
| requires significantly more time to get used to.
| Milner08 wrote:
| Scala it depends how you want to use it. If you're going
| for full FP then sure it can take a little bit longer, but
| you can also just use it like Java+ if you really want...
| Kamq wrote:
| I think there's a difference here between getting
| acclimated to scala (for new code, presumably), which is
| reasonably easy, and getting acclimated to a scala
| codebase that was already written by someone else.
|
| You can do the first one basically the same way you'd do
| kotlin, the second one can get pretty hairy if someone
| decided to bring in a bunch of macro heavy DSLs and
| syntax extensions.
| TylerE wrote:
| C++? Java? Rust?
| howinteresting wrote:
| Rust can take a while if you don't know either C++ or
| Haskell.
| jgavris wrote:
| Migrating from Java to Kotlin looks nice and easy on the
| surface (optionals!), but the lack of checked exceptions will
| absolutely bite you sooner or later if you are consuming Java
| code. Better carefully read the docs and source of all your
| transitive Java dependencies.
| sedro wrote:
| Kotlin interops nicely with Java but it's not null-safe (all
| references from Java are assumed non-null).
| Larrikin wrote:
| That is wrong all, non annotated Java code gets a special
| type denoted with a ! . You can use ?. or assign it to a new
| val with ?: return.
|
| Or you throw all the null safety away and write unsafe java
| code but in Kotlin the same as the Java code you're working
| with.
| sedro wrote:
| You _can_ use ?. or assign to a nullable type. Or you can
| get a NPE at runtime. Which is to say, it 's not null-safe.
| Larrikin wrote:
| So you're in the exact same case as you were in Java,
| which was my third point. But the type is a special type
| to let you know what you're doing is unsafe.
| dehrmann wrote:
| I tried Kotlin, and while I liked it, iterop was still somewhat
| annoying, Java's lambdas are better, using the Java 8 stream
| API is ugly, and the code ends up being similar enough that I'd
| rather use Java and avoid tooling hassles.
| anyonecancode wrote:
| > the acclamation period is measured in weeks
|
| Ah, "acclimation" -- thought at first you were saying that Java
| devs love it for a few weeks and then hate it lol.
| pjc50 wrote:
| As they point out, they already have some Kotlin and use it
| interoperably, but "rewrite existing code in Kotlin" is not a
| realistic demand.
| lesuorac wrote:
| IIUC, there's already automated tooling that will do the
| conversion and not produce a giant mess like c/c++ to rust
| does so the cost is predominately CPU and not SWE.
| mdaniel wrote:
| My experience has been that "migrate on touch" is a
| reasonable strategy, so if you have to make a change to a
| file, use the "Code > Convert Java to Kotlin" (control-alt-
| shift-k)
| usrusr wrote:
| A reasonable strategy if you never ever merge two branches.
| If there's a non-homeopathic chance that a parallel change
| to the file in question might eventually pop up I'd limit
| "migrate on touch" to occasions when you do major rework
| and not just a minor touch. If it's possible to
| occasionally enforce a "branch singularity moment" I'd go
| with migrate on major rework until a branch singularity
| opportunity comes up and then do the bulk conversion to
| what I affectionately call "shit kotlin" (the endless
| procession of exclamation marks that faithfully recreate
| each and every opportunity where the java could, in theory,
| achieve an NPE) in one go. And leave only the cleanup of
| that mess to "on touch". If it later comes to parallel
| cleanup, that wont be half as annoying to merge, not even
| remotely.
|
| What I haven't tried is "migrate on touch" with a strict
| rule that there must be explicit commits just before and
| after the conversion (plus a third commit documenting the
| file rename separately, before or after). That could
| perhaps work out well - or not help much at all, I don't
| feel like I could even guess.
|
| But other than that, the intermediate state of partial
| conversion is surprisingly acceptable to work with, I'm not
| disagreeing!
| thedougd wrote:
| At a former workplace, someone had a great idea to collect and
| aggregate the logs of our software from across our test labs. At
| the time, the idea was a bit novel for a bunch of programmers and
| testers who shipped and didn't run their own software. The plan
| was to drive elimination of commonly logged errors through bug
| fixes or reducing the severity of the messages.
|
| I was a lowly programmer, invited to a meeting of distinguished
| engineers and senior staff to review some of the findings from
| the log collections. When NPEs came up, their comment was that
| 'there should be zero NPEs' and that they would handle them
| punitively through reports to managers. So, I asked, "What's your
| recommendation to the developers to prevent the NPEs?". Silence,
| nothing. Findbugs existed at the time, and I believe JSR303 was
| in draft. Happy to report they decided to treat it the same as
| any other error or bug.
| pulse7 wrote:
| @Oracle: Please add this solution to the Java language and make
| Optional deprecated!
| pron wrote:
| Why _this_ solution? Maybe there 's an even better solution?
| We're following the various experiments in the area, and when
| we have a solution we believe is the right one, we'll provide
| it, but not sooner than that.
| RedNifre wrote:
| Oh yeah, I wrote one of those, too, complete with safe navigation
| through type aligned sequences etc.
| https://github.com/cosee/null4j
|
| If I were still writing Java I would throw in some exception
| throw/catching based Haskell style do notation for binding
| nullables, i.e. var nullableResult = nullable(()
| -> { var a = bind(getNullableA()); var b =
| bind(getNullableB());
| doSomethingRequiringBothNotBeingNull(a,b); });
|
| In my experience, Java developers don't want null safety though
| (the ones that do switched to Kotlin anyway).
| holysantamaria wrote:
| Just use C#. :P
| pjmlp wrote:
| Not always an option, and even if that is possible, plenty of
| existing code doesn't work with non nullable references.
| cletus wrote:
| So after working at Google for 6 years (and doing Java and other
| things prior) I went to Facebook and programmed in Hack for 4
| years. Hack is actually a really interesting and convenient
| language in many ways. One way I really appreciated is how
| nullability is built into the type system. Having to deal with
| nullability in Java is actually way more of a chore than any of
| the verbosity that people normally complain about (eg boilerplate
| around anonymous classes).
|
| The story I like to tell about how people abuse Java is this:
| @nullable Optional<Boolean>
|
| I don't know if this is still the case but you could literally
| find many instances of that in Google3 (and not all of them were
| the product of an auto-generated tool). I mean it's a super-large
| code base. There was a cleanup at one point for the (many)
| instances of 1204 in the code base.
|
| The way I like to describe this is "for when 3 values for your
| boolean just aren't enough" (since you obviously now have 4
| states: 1) null 2) Not set 3) Set, true 4) Set, false).
|
| I have a few rules that have held up v ery well over the years
| including:
|
| 1. If you don't do arithmetic on it and it's not an ID of some
| kind, it's almost certainly not a number (eg people who store
| SSNs as INT fields); and
|
| 2. It's almost never a boolean. You're almost always better off
| replacing any boolean with an enum. Not only is this typesafe but
| if done right adding an enum value will cause compiler errors
| because you've been exhausitve with switches without a default
| case.
| pjc50 wrote:
| Related work: https://devblogs.microsoft.com/dotnet/nullable-
| reference-typ...
|
| C# has made it possible to gradually roll out stricter
| nullability checking as well. The static analysis gets integrated
| into the regular language analyzers. Incremental migration is the
| only way to go.
| AtNightWeCode wrote:
| C# have taking it further. New projects give errors if you
| don't declare vars as nullable when they may be null in some
| context. It will help for static analyzers to find these cases.
| I see a big risk in it. That people will just init nonsense
| objects to get around the warnings.
| Deukhoofd wrote:
| > That people will just init nonsense objects to get around
| the warnings.
|
| You technically can do "= null!", which means assign null by
| default, but assume it is not null. This is currently the
| recommended way to do deserialization, where you know the
| value is not null, but you have not explicitly filled it.
| rndgermandude wrote:
| It sure can be a problem when people initialize stuff with
| nonsense to avoid running into nullable warnings. However,
| accidentally running into null pointer exceptions is still
| worse than people deliberately footgunning themselves with
| bad workarounds.
|
| I feel C#'s nullable has helped me personally to avoid a lot
| of potential bugs and also changed the way I write code in a
| lot of places - like creating `bool Try...(..., out var)`
| style APIs instead of "old school" returns-null/throws style
| stuff, which I think make a lot of code cleaner and more easy
| to read.
|
| Sometimes nullable can get a little messy and annoying,
| especially when retrofitting old code to make use of it
| without breaking existing APIs, and all in all the way C#
| does it is a clear net win in my opinion.
| AtNightWeCode wrote:
| I think you have to separate nullable types from the global
| nullable directive. The global nullable directive will just
| make people return nonsense like the trend some years ago
| when people started to return empty lists instead of null.
| zasdffaa wrote:
| Some rare nice words from me about C# - the not-null facility
| is great. It's very basic propagation that should be better and
| I've fought the compiler too many times over it, but I _really_
| like the compile time guarantee it gives.
| pjmlp wrote:
| And we are still not using it, due to the pile of code out
| there.
|
| Lets not forget the whole drama with prefix ! planned for .NET
| 7.
| mhoad wrote:
| In a similar fashion I was extremely impressed with how Dart
| approaches this as well and was able to move the entire
| ecosystem along in about 18 months. https://dart.dev/null-
| safety
| munificent wrote:
| I work on Dart.
|
| It was a _lot_ of work to get there and we 're still not
| totally there yet. Dart still supports running legacy code
| that isn't null safe. But I'm really glad we did this and
| very grateful that Leaf and others on the team were able to
| design a null safety type system that:
|
| * Defaults to non-nullable.
|
| * Is fully sound.
|
| The latter in particular is very nice because it means the
| compiler can take advantage of null safety to generate more
| efficient code, which isn't true in languages like Java, C#,
| and TypeScript where their null safety type rules
| deliberately have holes.
| endtime wrote:
| Yeah, I did a bit of Dart null safety migration work at
| Google and I thought they did a good job. (The auto migration
| tool sometimes made things nullable unnecessarily, and so you
| had to carefully review its output, but you could always just
| not use the tool.)
| AdmiralAsshat wrote:
| Did Meta release Nullsafe out to the world? Or is it an internal
| tool at the moment?
___________________________________________________________________
(page generated 2022-11-22 23:01 UTC)