Feedback for records: Accessors name() vs. getName()
Brian Goetz
brian.goetz at oracle.com
Thu Aug 27 15:16:18 UTC 2020
Thanks you for taking the time to capture your thoughts here.
I think we could summarize your observation as: "many tools in the
ecosystem interpret the `getXxx` naming convention as a proxy for purity
and side-effect freedom."
Of course, in a well-designed API made to be consumed by humans, this is
rarely confusing; methods with side-effects will have verb names
(`launchMissiles()`) and "pure accessors" will have noun names
(`name()`). (But, when it comes to any human-readable language, there
are always confusions about whether words like `count()` are meant as
nouns or as verbs. But as humans we can read the docs, assuming there
are any.)
When it comes to APIs to be consumed reflectively, there is no such
judgment in the loop, so such consumers must fall back on structure,
types, or convention. What I think you're saying is that the `getXxx`
convention is so common in the Java ecosystem that we all benefit from
reinforcing it.
Of course, this isn't really true; one does not have look very hard to
find `getXxx` methods that (say) perform side-effects. And I suppose
your answer to that would be "sure, but it's still better to have a 95%
convention that works well enough than no convention." We could surely
have an argument about whether that is actually better or not (and in
another life this might have been an enjoyable pub distraction), but I
doubt it would be all that dispositive.
At root, though, an argument about naming conventions is mostly a
distraction, because at best, naming conventions are noisy proxies for
something more fundamental. So let's talk about what's more fundamental.
What you are really looking for is to formalize the notion of "state
accessor", because from the statement "`x()` is a state accessor", we
can reasonably conclude that calling `x()` will not mutate the state of
the receiver in inscrutable ways or perform dangerous side-effects such
as launching missiles, will offer some degree of idempotency, etc. And
indeed, we agree that the more reliable we can make such inferences, the
better off developers are. (Pause for obligatory "then you idiots
should just do properties" interjection, hopefully sotto voce.)
And indeed, that is exactly what records have taken a first step at.
The important thing to understand about records (and, we apparently
cannot say this enough times) is that they are _not really about
syntactic concision_; syntactic concision is merely a pleasing
side-effect of something more important, which is that a record is
expected to have certain semantics (which, of course, uncooperative
authors or code mangling tools can undermine.) Records introduce into
the language the first glimmer of a notion of accessor; the method `x()`
in record `Foo(int x)` is not just a method, it is an accessor, and
reflection tells you so. This is far more reliable than a mere naming
convention! Surely this is better than guessing based on the first
three letters. The language is really telling you something about the
semantics of this method.
(Another thing the language is telling you, that you used to have to
guess at: that the `x` in the accessor `x()` really is the same `x` as
the corresponding constructor argument, and eventually the deconstructor
binding too. It might be a good bet that `getX()` and `setX(x)` are
describing the same abstract property, but we're really guessing, aren't
we?)
As I've hinted in other places, we want to generalize this notion of
accessor to classes that are not records; one can view a deconstruction
pattern as a generalization of an accessor, which retrieves multiple
properties at once, and we can derive accessors for those properties
from a suitably declared deconstructor. So while the above is
record-specific for now, there's a richer story coming for those classes
that cannot participate in the record fun.
Your sketch of doing so with an annotation is aimed at the same concept,
just with a different set of tools; a framework developer would
naturally reach for annotations (when the language doesn't give them
that ability), because they can. We'd rather the language do the job,
but in the meantime, if people want to do this with annotations, this is
a totally reasonable thing to do.
Bottom line: sure, a convention is OK, but we'd like to do better.
A few directed comments:
> Now, that looks very confusing, even from a Java programmer's standpoint,
> since there is a common practice to start a method name with a verb.
Only if you assume that Java developers are unable to integrate new
concepts into their world view. The other way to look at it is that the
verbing imperative has weirded our code, and we should stop. Why
wouldn't we use verbs to imply effects, and nouns to imply unchanging
quantities, as we do in natural language?
I make this point not to nitpick, but to make the meta-observation that
Java, like living organisms, must always be evolving to adapt to a
changing world. Obviously, if we "evolved" too quickly, we'd create
distortions, so we aim to evolve at a rate that developers can easily
integrate new ideas into their worldview. Surely Java developers can
adapt to the idea that we can use verbs and nouns effectively in API
design without too much disruption.
> E.g. my question is whether the auto-generation of accessors with matching
> names for records is just a Java language feature, or is it intended to be
> a part of an ABI for any record in JVM?
The names of record components are reified in the `Record` classfile
attribute. Reflection serves up record component metadata, including a
Method for their accessor, and reflection expects that these methods are
present and have the expected names. And it is reasonable for a client
who observes `clazz.isRecord()` to expect that fetching the accessors,
and then fetching their values, just always works, because records are a
semantic feature. tSo, a code generator that generated a class
extending `java.lang.Record` without a `Record` attribute, or a `Record`
attribute without the members reflection expects to find, would surely
be a Bad Java Ecosystem Citizen.
Cheers,
-Brian
On 8/26/2020 8:13 AM, Roman Elizarov wrote:
> Hello,
>
> Let me add my 5 cents to the accessor naming thread from the standpoint of
> various JVM languages (EL, Groovy, Kotlin, etc). I do not recall this
> specific aspect of the issue being raised before, but since I was not
> following it closely, I'm sorry if it was already discussed. I will also
> propose one more potential solution for consideration that does not involve
> any kind of getXxx naming convention. For the record (pun intended), I do
> represent the Kotlin language design team, but I have a strong reason to
> believe that the same conundrum is going to be faced by designers of many
> JVM languages that support properties in their syntax. I cannot possibly
> provide the full list of potentially affected languages here and the
> representative subset I've picked is totally arbitrary and subjective on my
> part, it is not intended to diminish the contribution of any other JVM
> languages.
>
>>> 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.
> There is, in fact, a very big reason, why languages with properties rely on
> the getXxx naming convention. Consider the following Java interface, as a
> trivial example.
>
> // the original version
> public interface TimeService {
> Instant getCurrentTime();
> ServerInfo getTimeServerInfo();
> Instant synchronizeTimeWithServer();
> }
>
> This interface follows a well-established convention that side-effect-free
> methods are named using the verb "get", while other business methods use
> other domain-specific verbs. You can find this pattern followed all over
> various JDK APIs (with some exceptions, of course) and in many enterprise
> applications. This is a chief reason for a number of JVM languages to
> piggy-back onto this convention to distinguish methods that could be
> syntactically represented as a property like "timeService.currentTime" and
> those methods that, despite having no parameters, are conceptually
> inappropriate to be available in the syntactic form of a property, without
> a programmer explicitly writing some sort of method invocation expression
> like "timeService.synchronizeTimeWithServer()" (mind the brackets) or using
> them in a method-invocation context (as it happens in EL).
>
> Now consider a record TimeConfigSnapshot(Instant currentTime, ServerInfo
> timeServerInfo). So far, so good. It is not a major problem for any JVM
> language with properties to recognize that records are, by definition, pure
> data containers and that all their components should be treated like
> properties allowing a "timeConfig.currentTime" syntax without an explicit
> method call.
>
> However, let us see what happens over time with the design of Java
> applications that use records. What if there is a business-domain need to
> extract a common interface between a TimeService and a TimeConfigSnapshot
> record since both of them essentially provide a pair of "currentTime" and
> "timeServerInfo"? Now, the understandable desire to avoid extra boilerplate
> would likely result in the introduction of the following kind of interface
> which is automatically implemented by the TimeConfigSnapshot record without
> additional code.
>
> public interface TimeConfig {
> Instant currentTime();
> ServerInfo timeServerInfo();
> }
>
> The desire to keep the original TimeService interface compliant with this
> newly introduced TimeConfig interface will call for its refactoring to
> rename its methods according to the naming convention of the record
> component accessor methods.
>
> // the refactored post-record version
> public interface TimeService extends TimeConfig {
> Instant currentTime();
> ServerInfo timeServerInfo();
> Instant synchronizeTimeWithServer();
> }
>
> Now, that looks very confusing, even from a Java programmer's standpoint,
> since there is a common practice to start a method name with a verb. It is
> even more confusing from the standpoint of any property-supporting JVM
> language as there is now no clear way to tell which methods of the
> TimeService interface are conceptually properties and which are not.
>
> A potential solution that I can throw onto the table is to introduce some
> sort of a standard JVM attribute or an annotation to explicitly mark
> methods that correspond to the record component accessor methods and by
> which the automatically-generated record component accessors are marked.
> Tools will help here with the learning curve. When one goes to extract an
> interface out of TimeConfigSnapshot record, the tools will make sure to
> carry forth the corresponding annotation (attribute), producing something
> like this (an appropriate name is TBD):
>
> public interface TimeConfig {
> @__ComponentAccessor Instant currentTime();
> @__ComponentAccessor ServerInfo timeServerInfo();
> }
>
> This annotation or attribute will be especially helpful when carried forth
> onto the larger interface like TimeService and will serve as a clue both to
> the reader of this interface and to the various JVM languages and tools
> that those methods are intended to be simple accessors to the underlying
> data, something that was historically expressed by the "get" verb in the
> name.
>
> Technically, Java itself does not have to standardize this kind of
> annotation or attribute. An annotation to tag those accessor/property
> methods can and likely will be introduced as a part of those JVM language
> runtimes that need it. However, this will have a negative effect on the JVM
> ecosystem as a whole as those various annotations will end up being
> incompatible with each other. So, please consider this as a part of the
> Java records design effort.
>
> Sincerely,
> Roman Elizarov
More information about the amber-dev
mailing list