[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)