Feedback for records: Accessors name() vs. getName()

Brian Goetz brian.goetz at oracle.com
Wed Aug 5 20:43:39 UTC 2020


Obviously, you already have an opinion -- I'm guessing you think getX is 
"just as good" as any other convention, and, if all the conventions are 
equally good or bad, then why not just go with the one that is most 
common?  (I think that's the argument you're implicitly making, and you 
wouldn't be the first.)  And, since "they are all just as good as each 
other" is a subjective belief, I can't argue you out of it, which is 
fine.  But I'd like you to consider the possibility that this is purely 
familiarity bias.

One thing you are missing is that the bar for what a language does is 
way, way higher than for what any corpus of code in that language 
happens to do.  It's fine to have naming conventions, as as conventions 
go, getX/setX is an OK one.  (Not a good one, but an OK one.)  But, for 
the language to pick a convention for naming synthetic members, the bar 
is significantly higher.  To a Java programmer, the language, JDK, 
runtime, libraries on Maven Central, IntelliJ, Spring, etc, may all be 
one undifferentiated bucket of "Java" to them, but there's a reason why 
there is a boundary between runtime and language, between language and 
core libraries, between core libraries and third-parth libraries.

Quite plainly, there is no way we could justify the language "blessing" 
a convention as silly and ad-hoc as `getX` (except if x is boolean, then 
it's `isX`, and by-the-way the case of the first letter has to get 
changed.)  Languages should not be in the business of exposing name 
mangling like this; this is the domain of libraries and frameworks.  
(You might argue that even picking `x()` is dangerously close to 
blessing a convention, and that's fair too.  But if there is any 
justifiable choice here, `x()` is really the only possible one -- and 
even that is at the very ragged edge of the sort of name-picking a 
language should be doing.  (We're lucky that fields and methods are in 
different namespaces, otherwise even that wouldn't have been available 
to us.))

The convention looks even more sickly as the industry starts to be more 
aware of the huge value of immutable data structures; we associate 
`getX` as naturally having a `setX` companion, which was fine in the 
days when JavaBeans were only intended to model visual components in a 
GUI builder and the object lifecycle was based on calling a no-arg 
constructor and then setting properties.  We're far, far from those 
days, and the motivations for those conventions would not stand up to 
scrutiny for what they are used for today -- we're just on autopilot.  
And, they were never really very good conventions to start with; they 
imply a model where we pay all the cost of encapsulation, but get none 
of the benefits -- indeed, the main benefit of `getX` / `setX` is use by 
reflective frameworks, not direct consumers of the API (but which 
describes a fairly small subset of the classes that have adopted these 
conventions.)  The best thing we can say about them is that they have 
_some_ reasonable uses, but they are surely overused.  (This is probably 
the point where someone says "then Java should have had properties"; 
while that would surely have made this particular convention 
unnecessary, that ship has sailed, and there's no point in rehashing it 
today.  Languages in 2020 need better support for dealing with immutable 
data, not better support for more concisely writing mutable data 
carriers.  We must avoid the temptation to over-rotate to solving 
yesterday's problems.)

Library designers seem to agree that these conventions don't carry their 
weight; many sophisticated library designers have migrated from using 
`getX()` to `x()` in recent years, including most of the newer APIs in 
the JDK.

Obviously, you can have a different opinion, but these are some of the 
considerations that led us to this position.  I hope you find it helpful.



On 8/5/2020 4:01 PM, Kamil Ševeček wrote:
> Hi Brian
>
> I take all your arguments about "just" naming conventions of getName().
> On the other hand, I was unable to come with a single reason why name()
> would be better than getName().
> Can you give an example, please?
>
> Regards
> Kamil Sevecek
>
> On Wed, 5 Aug 2020 at 15:43, Brian Goetz <brian.goetz at oracle.com> wrote:
>
>> Thanks for the feedback.  Some comments inline.
>>
>> The good news is that most of your concerns are transient; frameworks (and
>> there are only a few of them) will have to update to recognize the new
>> conventions (and it's easy for them to do so.)  We expect, within a year of
>> or so of records coming out, that most frameworks will have figured this
>> out.  One of the reasons to do preview features is so that frameworks can
>> see these things coming; I expect, for example, that Spring is likely to be
>> record-ready by the time records are a permanent feature of the language.
>>
>> 1. java.beans.Introspector not recognizing accessors
>>
>>
>> This one was raised on this list before; there's no reason Introspector
>> can't learn about records -- all the reflective machinery is there to do
>> so.  Someone at one point offered to contribute this change, but so far I
>> haven't seen it.  We would be happy to take back such a patch.
>>
>> I was able to find this email (https://mail.openjdk.java.net/pipermail/amber-dev/2020-January/005479.html)
>> regarding the same issue but the only thing I could see was a proposal to
>> shoehorn Introspector to recognize records instead of making records
>> standards-compliant and not having changing anything in the APIs above
>> java.lang.
>>
>>
>> Most of the arguments about accessor naming seem to stem from the same bit
>> of wishful thinking -- that the getX/setX (and don't forget about isX)
>> convention is somehow some sort of "standard" (if it were, it would be a
>> terrible one -- we tend to forget how much pain frameworks go through to
>> try to recover design intent from naming conventions, because most of this
>> code was written 22 years ago.)  But, this is simply not the case; it is
>> merely a convention.  As language stewards, we have to balance what is good
>> for the short- and long-term benefit of the ecosystem.  We surely did
>> consider whether it was worth adopting this convention for the sake of
>> short-term migration, but in the end, we decided that would be "doing the
>> wrong thing" for the long term.
>>
>> As I said, most of these have a relatively short-term solution -- we just
>> have to remind maintainers of frameworks that the language has evolved, and
>> that they need to do some work to evolve with it.  And it's not a lot of
>> work.
>>
>> 2. Unified expression language (EL) in JSP, JSF
>> --------------------------------------------------------------
>> Web expression language will look quite weird as well (address() method
>> versus customer and city properties):
>>
>>
>> Again, there's no reason why EL can't treat record component accessors the
>> same way as methods who happen to be named g-e-t-something.  And there's
>> no reason why the () would be needed; it's just that they're not caught up
>> with the language yet.
>>
>> We encourage you to request that the maintainers of EL add this to their
>> bag of magic tricks; it's not hard.
>>
>> 3. Spring Framework hacks, Spring (SpEL)
>>
>>
>> Same story.  I'll bet, though, that Spring is already on top of this.
>>
>> 3. Groovy inconsistency
>> -----------------------
>> You can access getName() getters in Groovy using short syntax:
>>
>>
>> Same.
>>
>> 4. Kotlin inconsistency
>> -----------------------
>> The same as in Groovy also applies to Kotlin:
>>
>>
>> Same, and again, I'll bet they're already on top of it.
>>
>> 5. All libraries will have to be adapted
>> ----------------------------------------
>> There are a lot of libraries that depend on the get/set convention and you
>> would prevent their usage for records. Such as BeanTableModel (swing
>> TableModel implementation), Jackson JSON parser and many others.
>>
>>
>> Yes, there will be some cost for the ecosystem to adapt -- but I think its
>> less than you're making it out to be -- and less about the naming that you
>> might think.  There are plenty of these libraries, but it's not thousands
>> -- I'd bet a few tens would probably cover 97% of uses.  And the cost for
>> any individual library is literally a few hours work (its actually less
>> code to find that a class is a record and that x() is a component accessor
>> than, say, to determine that setX() and getX() form a matched pair.)  This
>> code is, essentially:
>>
>>      if (clazz.isRecord()) {
>>          Stream.of(clazz.getRecordComponents())
>>                       .forEach(c -> addReadOnlyAccessor(clazz, c.getName(),
>> c.getAccessor()));
>>      }
>>
>> where `addReadOnlyAccessor` is whatever you do to add the mapping that
>> "component N of class C is accessed by method M".  All these frameworks
>> work by building a "symbol table" indexed by receiver class and component
>> name, and map to some description that bottoms out in a java.lang.Method
>> (which is what RecordComponent::getAccessor returns.)  This is much less
>> work than walking inheritance chains to find methods that conform to a set
>> of ad-hoc naming conventions, and correlating similarly-named methods to
>> infer the presence of a synthetic property.
>>
>> Further, more of the cost here is not about the naming convention, but the
>> fact that frameworks that reconstruct objects reflectively are used to
>> calling a no-arg constructor, and then calling setters.  That's not an
>> option with records, regardless of the naming convention chosen for
>> accessors.   So Jackson is going to have to adapt no matter what -- and the
>> adaptation to immutable objects is far more work than the adaptation to a
>> new naming scheme (which has reflection support, see above.)
>>
>> 6. Impossibility to retrofit existing record-like classes as records
>>
>>
>> This one is different from the others, as it speaks not to "it will take a
>> little time for the ecosystem to catch up to the language", but about what
>> ordinary users have to do.  And, this speaks to a design tension we
>> wrestled with quite a bit.
>>
>> The tension is, basically, old code vs new.  Obviously, if its possible,
>> we want for a new feature to work great for both old AND new code.  But
>> where there is a conflict between "the way things have been done", and how
>> they might be done better in the future, there is a tension.
>>
>> In records, we made the deliberate choice to design the feature for new
>> code than for catering to the quirks of existing code.  (With something as
>> quirky as the JavaBean naming conventions, we could never be
>> quirk-for-quirk compatible anyway; consider the getX vs isX issue, which is
>> inconsistently applied throughout the ecosystem, because, well, it's a
>> convention, not a standard.)   The model for records (immutable,
>> transparent carriers for a fixed state vector) is different enough from
>> mutable JavaBeans that, even if we had made this concession to legacy, I
>> don't think people would have been much happier.
>>
>> Also, the claim that it is "impossible" to retrofit existing record-like
>> classes is vastly overstated.  If you have a class that meets the
>> requirements to be a record (representation == construction protocol ==
>> access protocol == equals/hashCode/toString protocols), you can surely
>> migrate it to a record, and then, if you have to for legacy reasons,
>> declare some old-style getters to delegate to the new ones.  That is far
>> from "impossible" -- and was considered in the design (same for having
>> additional constructors.)  I think what you mean is that then you don't get
>> to write a one-line class declaration?  And that's true -- because you've
>> got a dependency on a framework that uses information from outside the type
>> system to infer the semantics of your class.  So you pay a few-line "tax"
>> for migrating these classes to records.  So yes, new code will pay less tax
>> than old code, but its not the case you can't migrate to records and get
>> some benefit.  It's just that if you have extra requirements, you have to
>> write some extra code.  (Further, even this is likely to be transient; once
>> frameworks catch up, the need for these "bridge" accessors would go away,
>> and you can delete them.)
>>
>> I realize its easy to jump from "we've been doing it this way for 22
>> years" to "this is the One True Absolutely Right Way To Do It Forever"
>> without realizing it, but the language has a responsibility to look forward
>> as well as backward, and balance the needs of existing code with the needs
>> of new code.  Taking a bad library naming convention and burning it into
>> the language forever would have been the worse choice.
>>
>> Cheers,
>> -Brian
>>
>>



More information about the amber-dev mailing list