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

Remi Forax forax at univ-mlv.fr
Thu Aug 6 00:48:33 UTC 2020


getX is a very poor convention.
toUpperCase/toLowerCase are Locale dependent [1][2] so most of the code that doesn't use the BeanIntropsector is not correct.

regards,
Rémi

[1] https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/lang/String.html#toUpperCase()
[2] https://web.archive.org/web/20170726160048/http://java.sys-con.com/node/46241

----- Mail original -----
> De: "Brian Goetz" <brian.goetz at oracle.com>
> À: "Kamil Ševeček" <kamil at sevecek.net>, "amber-dev" <amber-dev at openjdk.java.net>
> Envoyé: Mercredi 5 Août 2020 22:43:39
> Objet: Re: Feedback for records: Accessors name() vs. getName()

> 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