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