Preconditions (for records, or otherwise)

Kevin Bourrillion kevinb at google.com
Fri Mar 9 21:56:20 UTC 2018


Hello,

I'd claim it's an uncontroversial best practice that method and constructor
parameters should be aggressively *checked for validity*, especially when
that data is stored and used later (when knowledge of where that bad value
came from has vanished).

One thing I've been pushing for as a result is that the design of records
really, really should not impose a disproportionate penalty to adding the
first bit of validation. If to check that a number is positive I have to
change

 record Foo(int num, String unrelated) {}

to

 record Foo(int num, String unrelated) {
   Foo(int num, String unrelated) {
     if (num <= 0) ...;
     default.this(num, unrelated);
   }
 }

... then I'd say that the cost of listing my fields three times instead of
once is too great, and the user may not bother. For records, the right
amount of repetition really is no repetition.

We've discussed addressing this in either a *records-specific* or a
generalized way. The former is (imho) the least we can do to satisfy "first
do no harm". This could be a matter of saying that a record's primary
constructor gets to have various uninteresting boilerplate be *inferred*
(though if we had a way to also get around the traditional annoyance of
parameters and fields both being in scope at the same time with the same
names, that might be even better). So I'd like to figure out what that
would look like. (As a side product, maybe this solution solves the
question of "where does a constructor annotation go?".)

But I don't want to give up too easily on a more *general* approach that
would apply to records, methods, and constructors. That's been sketched at
times as

 void foo(int num, String unrelated)
     requires (num >= 0) {
   ...
 }

where `requires` takes a boolean expression*, which lives in the same scope
as the body proper; if it evaluates to false, an exception is thrown and
the body is never entered.

The main criticism I hear about this is that it feels like a *"method with
two bodies"*. To that I'd point out that

   - it is only an *expression* -- and anything even moderately complex
   ought to be factored out, just like we advise for lambdas
   - this expression isn't implementation; it's contract, so frankly
it *belongs
   *in this elevated place more than it does in the body. It is information
   that pertains, not really to the body, but to the communication between
   caller and body - just like the signature does.
   - this way, the preconditions can be *inherited* by default in an
   overriding method, which seems awfully convenient to me right now. (If you
   have some conditions you wouldn't want inherited for some reason, keep
   those in the regular body. I'm not sure whether these are *technically* LSP
   violations, but in pragmatic terms they don't seem to be, to me)

I bring all this up because some of the upsides seem quite compelling to me:

   - The automatically composed exception *message* will be more useful
   than what 90% of users bother to string together (and the other 10% are
   wasting time and space dealing with it).
   - These expressions can be displayed in generated *documentation* so you
   don't have to write them out a second time in prose.
   - I admit this may feel weird for a core language feature, but you can
   choose the idiomatic exception *type* automatically: if the expression
   involved at least one parameter, it's IAE; otherwise it's probably ISE
   (except in the amusing case of `requires (false)` it is UOE). (Again, maybe
   this is too weird.)
   - Some of these expressions are *verifiable* statically. For example a
   call to `foo(-1, "x")` (using example above) should be caught by javac. I
   suppose we teach it to recognize cases like empty collections through
   compiler plugins.

Note that the other design-by-contract idioms are still addressed well
enough by `assert`; we only need this one because `assert` disclaims this
use case (for good reason).

Lastly... hey, what about just a *library* like Guava's Preconditions
class? I made that thing, and it is extremely popular here. It also gives
extraordinarily small benefit. Yeah, it lets you express your expectation
positively instead of negatively. It lets you create a message with %s.
That's about it. Yawn.

Thoughts?


(*why I say it should take one boolean expression, not a comma-separated
list: I think we might as well let the user choose between short-circuiting
or not, by using && and & directly, which makes it clear to readers as
well. Well, that is, charitably assuming that reader remembers the
difference.)

-- 
Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb at google.com
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.openjdk.java.net/pipermail/amber-spec-experts/attachments/20180309/e6348f1a/attachment.html>


More information about the amber-spec-experts mailing list