I don't think there's a language-agnostic answer to this since what constitutes a “property” is a language-specific question, and what a caller of a “property” expects is a language-specific question as well. I do think the most fruitful way to think about this is to think about what it looks like from the point of view of the caller.
In C#, properties are distinctive in that they are (conventionally) capitalized (like methods) but lack parentheses (like public instance variables). If you see the following code, absent documentation, what do you expect?
var reciprocalHeading = myHeading.Reciprocal;
As a relative C# novice, but one who’s read Microsoft’s Property Usage Guidelines, I would expect Reciprocal
to, among other things:
- be a logical data member of the
Heading
class
- be inexpensive to call, such that there’s no need for me to cache the value
- lack observable side effects
- produce the same result if called twice in succession
- (maybe) offer a
ReciprocalChanged
event
Of these assumptions, (3) and (4) are probably correct (assuming Heading
is an immutable value type, as in Ewan's answer), (1) is debatable, (2) is unknown but also debatable, and (5) is unlikely to make semantic sense (though whatever has a heading should perhaps have a HeadingChanged
event). This suggests to me that in a C# API, “get or calculate the reciprocal” should not be implemented as a property, but especially if the calculation’s cheap and Heading
is immutable, it’s a borderline case.
(Note, though, that none of these concerns have anything to do with whether calling the property creates a new instance, not even (2). Creating objects in the CLR, in and of itself, is pretty cheap.)
In Java, properties are a method naming convention. If I see
Heading reciprocalHeading = myHeading.getReciprocal();
my expectations are similar to those above (if less explicitly set out): I expect the call to be cheap, idempotent, and lacking in side effects. However,
outside the JavaBeans framework the concept of a “property” is not all that meaningful in Java, and particularly when considering an immutable property with no corresponding setReciprocal()
, the getXXX()
convention is now somewhat old-fashioned. From Effective Java, second edition (already more than eight years old now):
Methods that return a non-boolean
function or attribute of the object on which they’re invoked are usually named with a noun, noun phrase, or a verb phrase beginning with the verb get
…. There is a vocal contingent that claims that only the third form (beginning with get
) is acceptable, but there is little basis for this claim. The first two forms usually lead to more readable code… (p. 239)
In a contemporary, more fluent API, then, I would expect to see
Heading reciprocalHeading = myHeading.reciprocal();
-- which would again suggest that the call is cheap, idempotent, and lacks side effects, but would say nothing about whether a new calculation is performed or a new object is created. This is fine; in a good API, I shouldn’t care.
In Ruby, there's no such thing as a property. There are “attributes”, but if I see
reciprocalHeading = my_heading.reciprocal
I have no immediate way of knowing whether I’m accessing an instance variable @reciprocal
via an attr_reader
or a simple accessor method, or whether I’m calling a method that performs an expensive calculation. The fact that the method name is a simple noun, though, rather than say calcReciprocal
, suggests, again, that the call is at least cheap and probably doesn’t have side effects.
In Scala, the naming convention is that methods with side effects take parentheses and methods without them don’t, but
val reciprocal = heading.reciprocal
could be any of:
// immutable public value initialized at creation time
val reciprocal: Heading = …
// immutable public value initialized on first use
lazy val reciprocal: Heading = …
// public method, probably recalculating on each invocation
def reciprocal: Heading = …
// as above, with parentheses that, by convention, the caller
// should only omit if they know the method has no side effects
def reciprocal(): Heading = …
(Note that Scala allows various things that are nonetheless discouraged by the style guide. This is one of my major annoyances with Scala.)
The lack of parentheses tells me the call doesn’t have side effects; the name, again, suggests that the call should be relatively cheap. Beyond that, I don’t care how it gets me the value.
In short: Know the language you’re using, and know what expectations other programmers will bring to your API. Everything else is an implementation detail.
Heading
as an immutable type andreciprocal
returning a newHeading
is "pit of success" good practice. (with the caveat at the two calls toreciprocal
should return "the same thing", ie they should pass the equality test.)