14 ms·
Retrofitting null-safety onto Java at Meta
- BiteCode_dev 4y agoFor 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.
- djinnandtonic 4y agoOr use Kotlin, it's near perfectly interoperable with Java and the acclamation period is measured in weeks if you're a Java dev
- pjc50 4y agoAs they point out, they already have some Kotlin and use it interoperably, but "rewrite existing code in Kotlin" is not a realistic demand.
- mdaniel 4y agoMy 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 4y agoA 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!
- lesuorac 4y agoIIUC, 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.
- stjkalsfd 4y agoAnd simply delete however many millions of lines of java code already exists :)
- iLoveOncall 4y agoI 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.
- rr888 4y agoOoof, 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 4y agoWell, 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 4y agoScala 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 4y agoI 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.
- yamtaddle 4y agoIt'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.
- anyonecancode 4y ago> 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.
- sedro 4y agoKotlin interops nicely with Java but it's not null-safe (all references from Java are assumed non-null).
- Larrikin 4y agoThat 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.
- pron 4y agoBut 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).
- jgavris 4y agoMigrating 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.
- dehrmann 4y agoI 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.
- jillesvangurp 4y agoThe 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-kotlin-migration/ 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.
- pjc50 4y agoRelated work: https://devblogs.microsoft.com/dotnet/nullable-reference-types-in-csharp/ 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.
- mhoad 4y agoIn 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 https://dart.dev/null-safety
- endtime 4y agoYeah, 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.)
- munificent 4y agoI 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.
- zasdffaa 4y agoSome 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.
- 4y ago
- revskill 4y agoOK, then i realize GoF design patterns lack the single most important pattern: Option data structure.
- twawaaay 4y agoNobody 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 4y agoA very good argument is that the GoF stuff should be looked at with suspicion because they didn't bother nailing down the fundamentals.
- twawaaay 4y agoI 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 4y agoNobody 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).
- justin_oaks 4y agoThe 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
- brundolf 4y agoIt'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 4y agoYes, 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.
- masklinn 4y agoTBF what Java does was pretty standard back in the 90s when it was released. Though obviously it would be nice if this had been fixed since. Not that there were no grumblings and better alternative but it was very much the little (if at all) questioned norm for procedural langages. It’s even excusable for C# in the early aughts.
- Arbortheus 4y ago
- exabrial 4y agoThis is just covering up design problems. NPEs show you were you have design deficiencies. If you have getAccount().getContact().getPhoneNumber() and contact is null, you'll get an NPE. The question shouldn't be: "How do I shove the NPE under the rug for the next 1337 coder to deal with?", the question should be: "How did I initialize an Account without a Contact?"
- random314 4y agoYes and Null safety forces you to ask that question at compile time instead of runtime
- chrisseaton 4y ago> the question should be: "How did I initialize an Account without a Contact?" Well yeah that's exactly what static analysis can tell you - what code path led to that. You're saying this project should be what it already is.
- commandlinefan 4y ago> How did I initialize an Account without a Contact Actually I already know - he got it from a JSON feed from an external system and ran that JSON through an automated code generator.
- mrkeen 4y ago> This is just covering up design problems. Static analysis (detecting) is the opposite of "covering up". > "How did I initialize an Account without a Contact?" As long as your language lets you do it, your teammates will do it. And in many cases it won't be accidental either. Your teammate will want an Account with its Contact set to null.
- commandlinefan 4y ago> getAccount().getContact().getPhoneNumber() Every time I see people "deal" with this problem, it looks like this: if (getAccount() != null && getAccount().getContact() != null && getAccount().getContact.getPhoneNumber() != null) { // do something } // don't put an else condition in, just keep going and let the program // produce the wrong result in a confusing way when it happens in production I actually blame rampant code generation and an adamant refusal to even consider object-oriented design for this problem - this sort of case ought to be handled by something like: getAccount().callContact() (or whatever you were going to do with the phone number when you got it). Insistence on generating code from SQL schemas or XML DTDs and then writing procedural code makes that pretty much impossible, though.
- driggs 4y agoIt'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; }
- sanedigital 4y agoDon'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.
- tyingq 4y agoSeems common, though it only helps so much with 3rd party libraries.
- mrkeen 4y agoYes, 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)
- BoorishBears 4y agoDo the heuristics used for Kotlin-interop work with notNull(...)?
- lowbloodsugar 4y agoI think he means using @NonNull for compile time checking, not instrumenting it for runtime checking (though that doesn’t hurt either).
- csunbird 4y agoisn'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.
- didip 4y agoJava 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 4y agoOther 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 4y agoBreaking backward compatibility is not the end of the world.
- bismuthcrystal 4y agoIt might not be, but is damn close to it.
- rad_gruchalski 4y agoMaybe 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 4y agoThank god the Java language designers don’t share your point of view.
- mrkeen 4y ago> 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 4y agoWe'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 https://jspecify.dev/docs/start-here
- clumsysmurf 4y ago> 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).
- _ZeD_ 4y agoI really hoped that projects like lastnpe[0] will have more visibility. 0. http://www.lastnpe.org http://www.lastnpe.org
- Traubenfuchs 4y agoI 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.
- rightbyte 4y agoNull 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 4y agoThere 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 4y ago
- thedougd 4y agoAt 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 4y ago@Oracle: Please add this solution to the Java language and make Optional deprecated!
- pron 4y agoWhy 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.
- holysantamaria 4y agoJust use C#. :P
- pjmlp 4y agoNot always an option, and even if that is possible, plenty of existing code doesn't work with non nullable references.
- fedeb95 4y agoBeware of expected nulls and expected NullPointerException in your legacy code when exterminating nulls.
- mbfg 4y agoI 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 4y ago> 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.
- mbfg 4y agoJust trying to learn here, not being combative, couldn't the same compiler support have just enforced the non nullness? At first you think, how do you enforce someEnum = getAnEnumSomewhere(), but at some point there's got to be an original reference being hard-set to null that the compiler could see, right? also i don't understand your point about universal defaults? enum Foo { bar default, baz }; why can't you guard against the default? how is checking a foo enum against bar different then checking against null?
- tinglymintyfrsh 4y agoNull References: The Billion Dollar Mistake by Tony Hoare https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare/ https://www.infoq.com/presentations/Null-References-The-Bill...
- cletus 4y agoSo 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.
- RedNifre 4y agoOh yeah, I wrote one of those, too, complete with safe navigation through type aligned sequences etc. https://github.com/cosee/null4j 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).
- Zak 4y agoIt 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.
- AdmiralAsshat 4y agoDid Meta release Nullsafe out to the world? Or is it an internal tool at the moment?
- __float 4y agoDoes anyone have experience using this at Meta who can compare to https://github.com/uber/NullAway https://github.com/uber/NullAway ?