A Case for Properties: Programming Language Design
blog.willbanders.dev
blog.willbanders.dev
It's not about being "modern" but about differing opinions on language design. The subject of adding properties to Java has come up numerous times over the past twenty years, and even though we're adding quite a few new features -- most recently, algebraic data types -- properties are not added because we just don't want them. Their "bang for the buck" is low, their potential for abuse is high, and adding them to an established language is even less appealing, as it creates a clear old/new code divide. Most of all, it's unclear whether the style of programming that relies on mutating individual fields is something that should be encouraged in the first place.
Another controversial feature that C# and Kotlin have but we don't want to add to Java is extension methods. It's perfectly reasonable for different languages to reach different conclusions regarding certain features, with some biased more toward happily adding features while others are more conservative, and it's perfectly reasonable for different programmers to have different preferences. The important thing is that features are carefully considered by language designers before being added, and that the language's "feel", philosophy and context is taken into account.
I'm not a Java programmer, but I agree wholeheartedly WRT addin a feature like this to an existing language with wide use. There's a world of difference between adding a feature that's obviously different than what came before and looks obviously different than adding one that changes functionality but looks identical to prior usage. If people learn a language and during that it's made clear that assignments can have arbitrary side effects, they'll expect that. If they learned the language and that wasn't possible, introducing it at some point is going to mean a lot of programmers have ways of thinking about how control flow works and what happens when you make assignments that is subtly (or greatly) wrong, but only sometimes.
As a programmers, what that can feel like is that the language is actively sabotaging your understanding of it. It's not fun.
Edit
This wikipedia article suggests Java has extension methods, or maybe something functionally equivalent (I'm not a Java programmer): https://en.m.wikipedia.org/wiki/Extension_method
Not controversy so much as that they're not close enough to being universally liked.
> This wikipedia article suggests Java has extension methods, or maybe something functionally equivalent
That uses a compiler plugin that changes the language quite considerably.
Personally I don't like them, for the reason labelled in the article as Problem 2: Arbitrary Computation. Computation should look like computation. I like it to be perfectly clear which statements can throw, and which cannot. I prefer List.getSize() over List.size precisely because the implementation should be permitted to perform computation, and it looks wrong when the code implies otherwise. I like to know that after executing Universe myUniverse; myUniverse.answer = 42; the value of myUniverse.answer really is 42. edit Ignoring the slim possibility of overflowing the integer type of myUniverse.answer.
Properties make it harder to reason about these things. Similar madness is possible in languages that allow overloading the assignment operator, such as C++. Come to think of it, you could probably 'fake' properties in C++ this way.
I accept that properties can make for slightly tidier expressions. I also like the point that properties are a single unit, tidily united in a way that getter/setter pairs aren't. Overall though I value 'honesty', and the ability to reason clearly about code, over these things.
I can't imagine Ada ever supporting properties, for the same reasons.
There's more to properties than just syntax-sugar as I initially said (such as being a single unit), and I think I've built more of a case for why using property-based getters/setters over fields are better that working with fields directly. If the syntax sugar isn't your cup of tea but you agree with using methods over fields, I think that's the limit of my argument here.
A tricky one, saying something as tidy as no side effects clearly doesn't work; anything we do has to be a side effect, as our hands are tried regarding return values. I don't think it would be practical to come up with perfectly precise rules for what is appropriate, only guidelines. Even never throw an exception seems too strong, as you might want to do range checks. Never block would complicate logging.
> If the syntax sugar isn't your cup of tea but you agree with using methods over fields
Yes, I think that's where I'm going with this. The advantages of avoiding public fields seem clear enough, we just disagree about the merits of properties.
I don't think Ada supports arbitrary user-specified constraints, but I'm no expert.
[0] https://gcc.gnu.org/onlinedocs/gcc-10.2.0/gnat_ugn/Run-Time-...
[1] https://docs.adacore.com/spark2014-docs/html/ug/en/usage_sce...
Ada has Dynamic_Predicate(s) which "can be any Boolean expression". [0] So, I'm guessing, you can write a pointless Predicate which does not terminate in some cases or takes unreasonably long.
[0] http://www.ada-auth.org/standards/12rat/html/Rat12-2-5.html
> In the case of Dynamic_Predicate, the expression can be any Boolean expression.
Ada functions are permitted to have side-effects (although this is disallowed in SPARK), so I imagine it would be possible to make a real mess if you wanted to.
If you follow standard naming practices in C#, properties are distinct from fields, which means they do "look like" computation once you're used to it.
Add null-conditional operators and null-coalescing ones, and you can get code that is overall much more readable and easy to reason about.
But I agree with you on the Kotlin examples in the article. Even though it's been a while since I last did Java, my gut reaction is that it looks wrong, because it looks like I'm directly accessing a field.
So you need a little bit more than just the feature itself, you need something to distinguish them from fields when you're looking at code, or you're gonna run into surprises. Whether that is casing or syntax highlighting or naming conventions, I don't care very much, as long as there's something.
Of course, lack of discipline can screw it up, but you could name the getter for x "setY()", and make it delete the entire filesystem when you call it. You can't guard against stupidity like that...
The ambiguity is between properties and fields, not between properties and methods. Looking at the style guide, I don't think there's any difference between how public fields and public properties are styled in .Net. [0]
> Whether that is casing or syntax highlighting or naming conventions, I don't care very much, as long as there's something.
Ideally it shouldn't rely on fancy tooling. I'd rather it be clear in the plain text of the source.
> you could name the getter for x "setY()", and make it delete the entire filesystem when you call it. You can't guard against stupidity like that
It's true, we don't yet have a language that prevents poor choice of identifiers.
[0] https://docs.microsoft.com/en-us/dotnet/standard/design-guid...
If you like, make some shorthand for creating a getter/setter pair that link with some field, that would be fine. But keep them functions, so that it is clear that they are functions to consumers.
I have written up some more details here: https://stefan-haustein.com/simplifying-properties
I believe Ruby and Smalltalk more or less work that way.
"Many programming languages do not strictly support the UAP but do support forms of it. Properties, which are provided in a number of programming languages, address the problem Meyer was addressing with his UAP in a different way. Instead of providing a single uniform notation, properties provide a way to invoke a method of an object while using the same notation as is used for attribute access. The separate method invocation syntax is still available."
Could someone please flesh this out for me? I've always found getters and setters to be promoted by people following standard practice, without actually thinking about the underlying principles.
If you're worried about long diffs because you changed many calls from '.x' to '.getX()', good. If it's so coupled that it's called in a thousand places, and you just changed functionality, it's on you to understand those thousand places. If it doesn't affect those places - which you don't know unless you look - then don't make the change.
As for the RgbColor example in the article, not using setters would be better.
RgbColor red = new RgbColor(1,0,0);
red.set(.., .., ..);
// Is it still 'red'?Say you have an int field and you want to change it to an int array field. To support existing code, you add a method that returns the first element of the array (let's assume it's guaranteed non-empty for simplicity).
If you used a field, you have to update all code referencing that field to call the accessor method instead. If your class was part of a public API, it's a breaking change.
Even if it was just internal code, your next commit will contain a zillion changed files where you replaced foo.number with foo.getNumber(). You'll leave a trace in each of those files' histories for a change that really didn't concern them at all. Also code reviewers will have to check that you didn't miss a usage somewhere (hopefully your code would just fail to compile in that case though).
If you had used a property or a getter around the original field, none of that happens. You add a new .Numbers property and replace the old .Number implementation with one that gets or sets the first element of the array.
Will you ever do that in real life? Seriously, why would someone change something from an int to an array...
I see all these Java programmers say that you should always use getters/setters and not expose public field in classes for this reason, yet I have to see an example where this would have happened.
Also the argument that you will need to touch more files in a refactoring. Sure. Not doing so would mean adding a technical debt, a method that exists for the sole purpose of some code that you were too lazy to update. Do that and your software will become spaghetti code easily.
I see this typically Java code that is not readable for all these getters, to the point that a simple class that represent a user is like 200 lines of code long for all these getters/setters that only return or set fields of the class without doing nothing.
Is also a matter of performance (no wonder Java code is that slow), since you are calling a function for each property access (and a function call is an expensive operation, it involves jumps, you have to access the vtable for the object, set up the stack frame) where you could simply access a field in an object (a simple addressing with an offset for the CPU). Sure, a call is not that expensive. But imagine doing that in a loop and it will be a performance problem. It will make also the CPU optimization work more difficult, since you are jumping around.
This happens quite often, when one-to-one relationship is replaced with one-to-many. In this specific example it could be an integer identifier of an object. If you need a real life example, think of an item in online shop, which had a category assigned and you now want to assign multiple categories.
On a side note, nothing I said justifies the use of properties. Refactoring is heavily automated today and users of an API undergoing such a dramatic change would have to review all uses of this value anyway. Having broken build would guarantee that this happens.
Surely these would be trivially inlined?
When a new class is loaded, the analysis checks which codes are no longer valid and mark them for deoptimization.
Fields prevent data validation, read-only fields, and polymorphic behavior. A particular edge case is that you can shadow a field in a subclass, and it can change the field which is accessed in other untouched code.
Getters/setters fix all of these limitations and others without considering API compatibility. IMO, the above are more important because they relate to the correctness of the code.
<input src="Contact.Email" ...>A language that supports properties can make this trivial for both the inward and outward binding.
Otherwise you end up with janky stuff like JavaBeans or whatever.
JavaBeans are not the only possible way to go with providing values for templates and definitely one of the worst.
When properties are implemented in the language proper then wiring everything together is much cleaner: find references, etc, all "just work". If not, there is a distinction between the template language and the host language that has to be addressed with tooling, or (usually) ignored.
For instance, in C#, both System.Text.Json and Newtonsoft.Json will get and serialize all properties, regardless of whether they are virtual "projections" (like the "red" property in the article's RgbColor example) or "plain" properties. Similarly, all properties are deserialized and set when doing the reverse.
This has the consequence that you may have redundant information in your serialization format. That's not the end of the world, and in fact there are some use-cases for this. The bigger consequence is that you can have redundant and conflicting information in the serialized data you read in.
For instance, adapting the author's example to C#:
https://try.dot.net/?fromGist=1bb01bd2b78a3212c52b40ff7955c0...
You get a different deserialization result depending on the order of the properties in the JSON, because the last property wins and the properties are mutually inconsistent with respect to the underlying data model.
Is the conclusion from this that objects which you intend to serialize should avoid using this "properties" feature, or else be extremely deliberate about what properties to include in the serialization format?
The intended behavior is to serialize all fields of an object (some properties have backing fields, others don't). You wouldn't serialize properties themselves for the same reason a getter/setter on it's own wouldn't be serialized - only the data stored.
Put simply, I would say C# has the wrong default behavior here.
Your example looks sloppy but it can get messy as the project evolves. I had some code where I mistakenly removed some XmlAttribute attributes in one release and it was painful to resolve in the next release.
Having said that I will still use it again, just carefully.
I don't see any actual need for properties, when the same effects apart from syntactic sugar can be had using methods, but one really shouldn't.
The idea being not to optimize writing code once, but greater and easier ability to read and change code later.
This puts me in a camp where I don't think it's a great idea to always tie process objects to serialization formats and APIs either. Certainly, layers and MVC would require serious explanation why enforce these structure in code other than theoretical handwaving, ie. what is the value now? Hexagonal and clean architecture? Later, maybe.
Of course, put in front of C# or anything enterpricey, there's no choice but to engage in these things.
They made their debut in Eiffel and Delphi, so 90's post modern.