I’m an unapologetic advocate of Object-oriented programming (OOP) and if you’re using an OOP programming framework I’m here to argue the case for burning your public property setters with napalm fire.
As you probably know if you’re reading this, OOP is the discipline of expressing concepts as objects in code. A ubiquitous example is “Person”.
// This creates a new person called "Fiona Smith"
Person fiona = new Person("Fiona Smith");
A person can have “properties” which are attributes like name, age, height, etc, and they can do things, which in programming involves using (or “calling”) a function or method.
In the above example we set the person’s “Name” property to “Fiona Smith” when we created the Person object. Now that we have the “fiona” (lowercase) variable, we can make Fiona do things:
fiona.Walk();
fiona.Somersault();
And we can even change how fiona does those actions by passing in “parameters” to those functions. Parameters allow code to become very flexible and reusable.
fiona.Walk(miles: 500);
And in most common OOP programming languages you can update the properties directly like this:
fiona.Name = "Fiona Jones";
This is called a property setter, and I’m going to show you why you should never, ever do this again.
Your “Person” is an object, and changing an object is a big deal, it shouldn’t be done willy-nilly. How would you feel if somebody just made you shorter or added 5 kilograms to your weight? Exactly.
The same consideration should be taken when dealing with objects in code. Changing the state of a software system should only be done for very good reasons, and this is where properties are dangerous – they generally don’t allow you to infer why something is changing and that means your system isn’t semantic, it’s just a big bag of key-value pairs.
We call this an anaemic domain model – where code models a concept but doesn’t do it to any great depth. And this has tremendous impacts on the rest of your system.
Consider an alternative, making the setter private and using functions on the Person to modify the name:
public class Person
{
// Serializers and class members can access the setter
public string Name { get; private set; }
}
There are a few reasons why you might change your name, you lost a bet that involved a trip to deed poll for the loser or took some potent hallucinogenics for example, but most commonly it’s because you get married and take your partner’s surname.
void GotMarried(to: Person, takeSurname: Bool)
{
// Code that runs when this person gets married
}
...
// Using the code:
// Fiona has got married. Yay!
fiona.GotMarried(to: johnSmith, takeSurname: true);
You may be saying “but the end result is the same! The surname changes!”.
You’re right of course. Partially.
Unless you work in a very, very strange organisation, you will never see a business requirement that asks for “a name property setter”. Instead you will get requirements that relate more to the real world, e.g. “be able to change the person’s name as a result of getting married”.
And now we’re starting to get to the important stuff.
If you have a function on a Person object called gotMarried()
and it takes the name of the person they were married to, and whether they took their partner’s name, you can start to do amazing things:
- You can show a Product Manager the code and even if they are not coders they will be able to understand from the name of the function what action has been implemented because you are talking the same language. In Domain-Driven-Design we call this having a ubiquitous language, and it makes communication and verification much easier and more accurate because there aren’t business-to-tech translations going on.
- You can incrementally start adding richness to these behaviours. For example, when someone is married, you can store the name of their Spouse, or the date of their marriage, which could be used for identity verification when they call customer support. You could trigger a congratulations email and send them a 10% discount voucher, turning your new method into a sale you might otherwise have missed.
void GotMarried(to:Person, takeSurname:Bool)
{
this.Spouse = to;
this.LastName = to.LastName;
}
- You can encapsulate other common tasks, such as updating the “LastUpdated” property on the Person. Requiring consumers of your Person object to always remember to do this is unreasonable and error prone.
void GotMarried(to:Person, takeSurname:Bool)
{
this.Spouse = to;
this.LastName = to.LastName;
this.SetLastUpdated();
}
- You can propagate the knowledge of the marriage using specific domain events. A “PersonMarried” event has a very specific meaning and can include semantic information such as who they were married to (even if they didn’t change their name!), whereas a “NameChanged” event is much more broad and would only be triggered if the person’s name actually changed.
void GotMarried(to:Person, takeSurname:Bool)
{
this.Spouse = to;
this.LastName = to.LastName;
this.eventPublisher.Publish(new GotMarriedEvent(this, to));
}
- You can provide meaningful, semantic validation
void GotMarried(to:Person, takeSurname:Bool)
{
if(this.Spouse != null)
{
throw new AlreadyMarriedException(spouse: this.Spouse);
}
...
}
- You can start asking questions of the data you couldn’t otherwise, such as “who has been married more than once?”. Try asking that from a simple assignment to a Name property!
- You can start writing automated tests written in the language of the domain (i.e. the words non-coders use to talk about the behaviour) which means you can publish your automated test run results to business people and they’ll have a half-decent chance at understanding exactky what is and isn’t working, which can lead to informed decision making around whether to release or not.
Hopefully this shows the benefits of better-designed, richer domain models, which is a complicated way of saying, use functions with names non-technical people will understand to change state.
fiona.AgreesWith(whom: rich, percentage: 100);