<div dir="ltr">Hello,<div><br></div><div>I'd claim it's an uncontroversial best practice that method and constructor parameters should be aggressively <b>checked for validity</b>, especially when that data is stored and used later (when knowledge of where that bad value came from has vanished).</div><div><br></div><div>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</div><div><br></div><div><font face="monospace, monospace"> record Foo(int num, String unrelated) {}</font></div><div><br></div><div>to</div><div><br></div><div style="color:rgb(34,34,34);font-size:small;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;text-align:start;text-indent:0px;text-transform:none;white-space:normal;word-spacing:0px;text-decoration-style:initial;text-decoration-color:initial"><font face="monospace, monospace"> record Foo(int num, String unrelated) {</font></div><div style="color:rgb(34,34,34);font-size:small;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;text-align:start;text-indent:0px;text-transform:none;white-space:normal;word-spacing:0px;text-decoration-style:initial;text-decoration-color:initial"><font face="monospace, monospace">   Foo(<span style="color:rgb(34,34,34);font-size:small;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;text-align:start;text-indent:0px;text-transform:none;white-space:normal;word-spacing:0px;background-color:rgb(255,255,255);text-decoration-style:initial;text-decoration-color:initial;float:none;display:inline">int num, String unrelated) {</span></font></div><div style="color:rgb(34,34,34);font-size:small;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;text-align:start;text-indent:0px;text-transform:none;white-space:normal;word-spacing:0px;text-decoration-style:initial;text-decoration-color:initial"><span style="color:rgb(34,34,34);font-size:small;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;text-align:start;text-indent:0px;text-transform:none;white-space:normal;word-spacing:0px;background-color:rgb(255,255,255);text-decoration-style:initial;text-decoration-color:initial;float:none;display:inline"><font face="monospace, monospace">     if (num <= 0) ...;</font></span></div><div style="color:rgb(34,34,34);font-size:small;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;text-align:start;text-indent:0px;text-transform:none;white-space:normal;word-spacing:0px;text-decoration-style:initial;text-decoration-color:initial"><span style="color:rgb(34,34,34);font-size:small;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;text-align:start;text-indent:0px;text-transform:none;white-space:normal;word-spacing:0px;background-color:rgb(255,255,255);text-decoration-style:initial;text-decoration-color:initial;float:none;display:inline"><font face="monospace, monospace">     default.this(num, unrelated);</font></span></div><div style="color:rgb(34,34,34);font-size:small;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;text-align:start;text-indent:0px;text-transform:none;white-space:normal;word-spacing:0px;text-decoration-style:initial;text-decoration-color:initial"><span style="color:rgb(34,34,34);font-size:small;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;text-align:start;text-indent:0px;text-transform:none;white-space:normal;word-spacing:0px;background-color:rgb(255,255,255);text-decoration-style:initial;text-decoration-color:initial;float:none;display:inline"><font face="monospace, monospace">   }</font></span></div><div style="color:rgb(34,34,34);font-size:small;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;text-align:start;text-indent:0px;text-transform:none;white-space:normal;word-spacing:0px;text-decoration-style:initial;text-decoration-color:initial"><font face="monospace, monospace"> }</font></div><div style="color:rgb(34,34,34);font-family:arial,sans-serif;font-size:small;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;text-align:start;text-indent:0px;text-transform:none;white-space:normal;word-spacing:0px;text-decoration-style:initial;text-decoration-color:initial"><br></div><div style="color:rgb(34,34,34);font-family:arial,sans-serif;font-size:small;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;text-align:start;text-indent:0px;text-transform:none;white-space:normal;word-spacing:0px;text-decoration-style:initial;text-decoration-color:initial">... 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.</div><div style="color:rgb(34,34,34);font-family:arial,sans-serif;font-size:small;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;text-align:start;text-indent:0px;text-transform:none;white-space:normal;word-spacing:0px;text-decoration-style:initial;text-decoration-color:initial"><br></div><div style="color:rgb(34,34,34);font-family:arial,sans-serif;font-size:small;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;letter-spacing:normal;text-align:start;text-indent:0px;text-transform:none;white-space:normal;word-spacing:0px;text-decoration-style:initial;text-decoration-color:initial"><span style="font-weight:400">We've discussed addressing this in either a </span><b>records-specific</b> 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 <b>inferred</b> (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?".)</div><div style="color:rgb(34,34,34);font-family:arial,sans-serif;font-size:small;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;text-align:start;text-indent:0px;text-transform:none;white-space:normal;word-spacing:0px;text-decoration-style:initial;text-decoration-color:initial"><br></div><div style="color:rgb(34,34,34);font-family:arial,sans-serif;font-size:small;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;letter-spacing:normal;text-align:start;text-indent:0px;text-transform:none;white-space:normal;word-spacing:0px;text-decoration-style:initial;text-decoration-color:initial"><span style="font-weight:400">But I don't want to give up too easily on a more </span><b>general</b> approach that would apply to records, methods, and constructors. That's been sketched at times as</div><div style="color:rgb(34,34,34);font-family:arial,sans-serif;font-size:small;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;text-align:start;text-indent:0px;text-transform:none;white-space:normal;word-spacing:0px;text-decoration-style:initial;text-decoration-color:initial"><br></div><div style="color:rgb(34,34,34);font-size:small;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;text-align:start;text-indent:0px;text-transform:none;white-space:normal;word-spacing:0px;text-decoration-style:initial;text-decoration-color:initial"><font face="monospace, monospace"> void foo(int num, String unrelated)</font></div><div style="color:rgb(34,34,34);font-size:small;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;text-align:start;text-indent:0px;text-transform:none;white-space:normal;word-spacing:0px;text-decoration-style:initial;text-decoration-color:initial"><font face="monospace, monospace">     requires (num >= 0) {</font></div><div style="color:rgb(34,34,34);font-size:small;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;text-align:start;text-indent:0px;text-transform:none;white-space:normal;word-spacing:0px;text-decoration-style:initial;text-decoration-color:initial"><font face="monospace, monospace">   ...</font></div><div style="color:rgb(34,34,34);font-size:small;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;text-align:start;text-indent:0px;text-transform:none;white-space:normal;word-spacing:0px;text-decoration-style:initial;text-decoration-color:initial"><font face="monospace, monospace"> }</font></div><div style="color:rgb(34,34,34);font-family:arial,sans-serif;font-size:small;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;text-align:start;text-indent:0px;text-transform:none;white-space:normal;word-spacing:0px;text-decoration-style:initial;text-decoration-color:initial"><br></div><div style="color:rgb(34,34,34);font-family:arial,sans-serif;font-size:small;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;text-align:start;text-indent:0px;text-transform:none;white-space:normal;word-spacing:0px;text-decoration-style:initial;text-decoration-color:initial">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.</div><div><br></div>The main criticism I hear about this is that it feels like a <b>"method with two bodies"</b>. To that I'd point out that<div><ul><li>it is only an <b>expression</b> -- and anything even moderately complex ought to be factored out, just like we advise for lambdas<br></li><li>this expression isn't implementation; it's contract, so frankly it <b>belongs </b>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.</li><li>this way, the preconditions can be <b>inherited</b> 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 <i>technically</i> LSP violations, but in pragmatic terms they don't seem to be, to me)<br></li></ul></div><div>I bring all this up because some of the upsides seem quite compelling to me:</div><div><ul><li>The automatically composed exception <b>message</b> 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).</li><li>These expressions can be displayed in generated <b>documentation</b> so you don't have to write them out a second time in prose.</li><li>I admit this may feel weird for a core language feature, but you can choose the idiomatic exception <b>type</b> 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.)</li><li>Some of these expressions are <b>verifiable</b> 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.</li></ul><div>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).</div></div><div><br></div><div>Lastly... hey, what about just a <b>library</b> 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.<br></div><div><div style="color:rgb(34,34,34);font-family:arial,sans-serif;font-size:small;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;text-align:start;text-indent:0px;text-transform:none;white-space:normal;word-spacing:0px;text-decoration-style:initial;text-decoration-color:initial"><br></div>Thoughts?</div><div><br></div><div><br class="gmail-Apple-interchange-newline">(*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.)</div><div><br></div><div><div>-- <br><div class="gmail_signature" data-smartmail="gmail_signature"><div dir="ltr"><div><div dir="ltr"><div><div dir="ltr"><div style="line-height:1.5em;padding-top:10px;margin-top:10px;color:rgb(85,85,85);font-family:sans-serif"><span style="border-width:2px 0px 0px;border-style:solid;border-color:rgb(213,15,37);padding-top:2px;margin-top:2px">Kevin Bourrillion |</span><span style="border-width:2px 0px 0px;border-style:solid;border-color:rgb(51,105,232);padding-top:2px;margin-top:2px"> Java Librarian |</span><span style="border-width:2px 0px 0px;border-style:solid;border-color:rgb(0,153,57);padding-top:2px;margin-top:2px"> Google, Inc. |</span><span style="border-width:2px 0px 0px;border-style:solid;border-color:rgb(238,178,17);padding-top:2px;margin-top:2px"> <a href="mailto:kevinb@google.com" target="_blank">kevinb@google.com</a></span></div></div></div></div></div></div></div>
</div></div></div>