On Mon, Mar 15, 2021 at 11:42 PM Reuven Lax <re...@google.com
<mailto:re...@google.com>> wrote:
On Mon, Mar 15, 2021 at 9:12 PM Kenneth Knowles
<k...@apache.org <mailto:k...@apache.org>> wrote:
I will be blunt about my opinions about the general issue:
- NullPointerExceptions (and similar) are a solved problem.
* They have been since 2003 at the latest [1] (this is
when the types were hacked into Java - the foundation
dates back to the 70s or earlier)
Huh - Fahndrich tried to hire me once to work on a project
called Singularity. Small world. Also note that Sanjay
Ghemawat is listed in the citations!
Umm, I was confusing names. Fahndrich is actually a former
coworker at Google :)
* Checkerframework is a _pluggable_ system where that
nullness type system is a "hello, world" level demo,
since 2008 at the latest [2].
* Our users should know this and judge us accordingly.
- Checkerframework should be thought of and described as
type checking, because it is. It is not heuristic nor
approximate.
- If your code was unclear about whether something could
be null, it was probably unclear to a person reading it
also, and very likely to have actual bugs.
- APIs that accept a lot of nullable parameters are,
generally speaking, bad APIs. They are hard to use
correctly, less readable, and very likely to cause actual
bugs. You are forcing your users to deal with accidental
complexity you left behind.
* Corollary to the above two points: Almost all the
time, the changes to clearify nullness make your code
better, more readable, easier for users or editors.
- It is true that there is a learning curve to
programming in this way.
I agree with the above in a closed system. However as
mentioned, some of the APIs we use suffer from this.
In a previous life, I saw up close an effort to add such
analysis to a large codebase. Two separate tools called
Prefix and Prefast were used (the difference between the two
is actually quite interesting, but not relevant here).
However in order to make this analysis useful, there was a
massive effort to properly annotate the entire codebase,
including all libraries used. This isn't a perfect example -
this was a C++ codebase which is much harder to analyze, and
these tools identified far more than simply nullness errors
(resource leaks, array indices, proper string null
termination, exception behavior, etc.). However the closer we
can get to properly annotating the transitive closure of our
dependencies, the better this framework will work.
- There are certainly common patterns in Java that do not
work very well, and suppression is sometimes the best option.
* Example: JUnit's @Setup and @Test conventions do not
work very well and it is not worth the effort to make
them work. This is actually because if it were "normal
code" it would be bad code. There are complex
inter-method dependencies enforced only by convention.
This matters: sometimes a JUnit test class is called from
another class, used as "normal code". This does go wrong
in practice. Plain old JUnit test cases frequently go
wrong as well.
And here is my opinion when it comes to Beam:
- "Community over code" is not an excuse for negligent
practices that cause easily avoidable risk to our users.
I will be very disappointed if we choose that.
- I think having tooling that helps newcomers write
better code by default is better for the community too.
Just like having automatic formatting is better. Less to
haggle about in review, etc.
- A simple search reveals about 170 bugs that we know of [4].
- So far in almost every module I have fixed I discovered
actual new null errors. Many examples at [5].
- It is extremely easy to suppress the type checking.
Almost all of our classes have it suppressed already (I
did this work, to allow existing errors while protecting
new code).
- Including the annotations in the shipped jars is an
important feature. Without this, users cannot write
null-safe code themselves.
* Reuven highlighted this: when methods are not
annotated, we have to use/implement workarounds. Actually
Guava does use checkerframework annotations [6] and the
problem is that it requires its *input* to already be
non-null so actually you cannot even use it to convert
nullable values to non-nullable values.
* Beam has its own [7] that is annotated, actually for
yet another reason: when Guava's checkNotNull fails, it
throws NPE when it should throw IllegalArgumentException.
Guava's checkNotNull should not be used for input validation!
- It is unfortunate that IntelliJ inserts a bunch of
annotations in user code. I wonder if there is something
we can do about that. At the Java level, if they are not
on the classpath they should be ignored and not affect
users. Coincidentally, the JDK has had
NullPointerExceptions in this area :-) [8].
I understand the pain of longer compile times slowing
people down. That is actually a problem to be solved
which does not require lowering our standards of quality.
How about we try moving it to a separate CI job and see
how it goes?
In my experience stories like Reuven's are much more
frustrating in a separate CI job because you find out
quite late that your code has flaws. Like when spotless
fails, but much more work to fix, and would have been
prevented long ago if it were integrated into the compile.
I agree with this. I prefer to be able to detect on my
computer that there are failures, and not have to wait for
submission. The complaint was that some people are
experiencing trouble on their local machine however, so it
seems reasonable to add an opt-out flag (though I would
prefer opt out to opt in).
Kenn
[1]
https://web.eecs.umich.edu/~bchandra/courses/papers/Fahndrich_NonNull.pdf
<https://web.eecs.umich.edu/~bchandra/courses/papers/Fahndrich_NonNull.pdf>
[2]
https://homes.cs.washington.edu/~mernst/pubs/pluggable-checkers-issta2008.pdf
<https://homes.cs.washington.edu/~mernst/pubs/pluggable-checkers-issta2008.pdf>
[3]
https://github.com/google/guava/blob/fe3fda0ca54076a2268d060725e9a6e26f867a5e/pom.xml#L275
<https://github.com/google/guava/blob/fe3fda0ca54076a2268d060725e9a6e26f867a5e/pom.xml#L275>
[4]
https://issues.apache.org/jira/issues/?jql=project%20%3D%20BEAM%20AND%20(summary%20~%20%22NPE%22%20OR%20summary%20~%20%22NullPointerException%22%20OR%20description%20~%20%22NPE%22%20OR%20description%20~%20%22NullPointerException%22%20OR%20comment%20~%20%22NPE%22%20OR%20comment%20~%20%22NullPointerException%22)
<https://issues.apache.org/jira/issues/?jql=project%20%3D%20BEAM%20AND%20(summary%20~%20%22NPE%22%20OR%20summary%20~%20%22NullPointerException%22%20OR%20description%20~%20%22NPE%22%20OR%20description%20~%20%22NullPointerException%22%20OR%20comment%20~%20%22NPE%22%20OR%20comment%20~%20%22NullPointerException%22)>
[5] https://github.com/apache/beam/pull/12284
<https://github.com/apache/beam/pull/12284> and
https://github.com/apache/beam/pull/12162
<https://github.com/apache/beam/pull/12162> and
[6]
https://github.com/google/guava/blob/fe3fda0ca54076a2268d060725e9a6e26f867a5e/guava/src/com/google/common/base/Preconditions.java#L878
<https://github.com/google/guava/blob/fe3fda0ca54076a2268d060725e9a6e26f867a5e/guava/src/com/google/common/base/Preconditions.java#L878>
[7]
https://github.com/apache/beam/blob/master/sdks/java/core/src/main/java/org/apache/beam/sdk/util/Preconditions.java
<https://github.com/apache/beam/blob/master/sdks/java/core/src/main/java/org/apache/beam/sdk/util/Preconditions.java>
[8]
https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8152174
<https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8152174>
On Mon, Mar 15, 2021 at 2:12 PM Reuven Lax
<re...@google.com <mailto:re...@google.com>> wrote:
I have some deeper concerns with the null checks. The
fact that many libraries we use (including guava)
don't always annotate their methods forces a lot of
workarounds. As a very simple example, the return
value from Preconditions.checkNotNull clearly can
never be null, yet the nullability checks don't know
this. This and other similar cases require constantly
adding extra unnecessary null checks in the code just
to make the checker happy. There have been other
cases where I haven't been able to figure out a way
to make the checker happy (often these seem to
involve using lambdas), and after 10-15 minutes of
investigation have given up and disabled the check.
Now you might say that it's worth the extra pain and
ugliness of writing "useless" code to ensure that we
have null-safe code. However I think this ignores a
sociological aspect of software development. I have a
higher tolerance than many for this sort of pain, and
I'm willing to spend some time figuring out how to
rewrite my code such that it makes the checker happy
(even though often it forced me to write much more
awkward code). However even I have often found myself
giving up and just disabling the check. Many others
will have less tolerance than me, and will simply
disable the checks. At that point we'll have a
codebase littered with @SuppressWarnings("nullness"),
which doesn't really get us where we want to be. I've
seen similar struggles in other codebases, and
generally having a static checker with too many false
positives often ends up being worse than having no
checker.
On Mon, Mar 15, 2021 at 10:37 AM Ismaël Mejía
<ieme...@gmail.com <mailto:ieme...@gmail.com>> wrote:
+1
Even if I like the strictness for Null checking,
I also think that
this is adding too much extra time for builds
(that I noticed locally
when enabled) and also I agree with Jan that the
annotations are
really an undesired side effect. For reference
when you try to auto
complete some method signatures on IntelliJ on
downstream projects
with C-A-v it generates some extra Checkers
annotations like @NonNull
and others even if the user isn't using them
which is not desirable.
On Mon, Mar 15, 2021 at 6:04 PM Kyle Weaver
<kcwea...@google.com
<mailto:kcwea...@google.com>> wrote:
>>
>> Big +1 for moving this to separate CI job. I
really don't like what annotations are currently
added to the code we ship. Tools like Idea add
these annotations to code they generate when
overriding classes and that's very annoying.
Users should not be exposed to internal tools
like nullability checking.
>
>
> I was only planning on moving this to a
separate CI job. The job would still be release
blocking, so the same annotations would still be
required.
>
> I'm not sure which annotations you are
concerned about. There are two annotations
involved with nullness checking,
@SuppressWarnings and @Nullable.
@SuppressWarnings has retention policy SOURCE, so
it shouldn't be exposed to users at all.
@Nullable is not just for internal tooling, it
also provides useful information about our APIs
to users. The user should not have to guess
whether a method argument etc. can be null or
not, and for better or worse, these annotations
are the standard way of expressing that in Java.