Photo by Shapelined on Unsplash
Today I thought I would write about invariants, and why we should always be thinking about them whenever we’re reading or writing code.
So what is an invariant?
An invariant is…
In OOP, an invariant is a set of assertions that must always hold true during the life of an object for the program to be valid. It should hold true from the end of the constructor to the start of the destructor whenever the object is not currently executing a method that changes its state.An example of invariant could be that exactly one of two member variables should be null. Or that if one has a given value, then the set of allowed values for the other is this or that…- Xavier Nodet on Stack Exchange
💡 An invariant does not need to be true during execution of a method.
Classes
Should Enforce InvariantsIn an interview with Bjarne Stroustrup, creator of the C++ programming language, he was asked about invariants and how to apply them:
Bjarne Stroustrup: My rule of thumb is that you should have a real class with an interface and a hidden representation if and only if you can consider an invariant for the class.
Bill Venners: What do you mean by invariant?
Bjarne Stroustrup: What is it that makes the object a valid object? An invariant allows you to say when the object’s representation is good and when it isn’t.
Invariants for modeling domain classes and behaviour
With this idea of invariants in mind, this is a perfect time to look at an example. Domain classes and their associated behaviour are a great candidate for implementing invariants explicitly - we can be specific about exactly what the expectations are of a given domain object up front.
The first example I’m going with today is a really simple bank account class, capturing the name of the account holder and their current balance, and a method for withdrawing money. Here is the code in C#:
BankAccount myAccount = new BankAccount("John Doe", 1000.00m);
// Attempt to withdraw more money than the balance
myAccount.Withdraw(1500.00m);
Console.WriteLine($"Account Balance: {myAccount.Balance}");
public class BankAccount
{
public string AccountHolder { get; private set; }
public decimal Balance { get; private set; }
public BankAccount(string accountHolder, decimal initialBalance)
{
AccountHolder = accountHolder;
Balance = initialBalance;
}
public void Withdraw(decimal amount)
{
if (amount > 0)
{
// Deduct the amount from the balance
Balance -= amount;
}
}
}We can see that the withdraw method checks to see if the amount we are asking to withdraw is greater than 0, so far so good. What if we had another requirement to not let a customer’s account balance dip into negative amounts (ignoring that overdraft is a thing)? Well our current code won’t catch that, it will happily allow John Doe’s account balance to go to -500 - we are not enforcing the invariant that balance cannot go negative. We have a bug.
Fixing the bug by enforcing the invariant
As this is a fairly simple example, the solution to the problem is similarly simple - we modify our withdraw method to check whether the requested amount would put the balance in the negative:
public void Withdraw(decimal amount)
{
if (amount > 0 && amount 0)
{
// Deduct the amount from the balance
Balance -= amount;
}
DoSomethingWithTheNegativeBalance();
Balance += amount;
}Super contrived obviously, I’m not suggesting you would do this - but in this example the invariant is still enforced because before we exit our withdraw method, Balance is back in a valid state.
Object construction and invariants
Time for another example, what is wrong with this code?
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public Person() {}
public void CelebrateBirthday()
{
// Increase the person's age by 1 to celebrate a birthday
Age++;
}
}Quoting Bjarne from his interview again……the constructor establishes the environment for the member functions to operate in, in other words, the constructor establishes the invariant.
So basically, we should not be able to create an instance of an object in an invalid state. Forcing the caller to get the object into a valid state before they can use it is unnecessary cognitive load and invites bugs.
And yet, in our example, calling code wanting to create a new instance of our Person class can do so using the default constructor - meaning that they now have something purporting to be a functioning Person object with no name or age set.
Does it make sense for a person to not have a name or age? Maybe some very small minority of people don’t take a name, or don’t observe their own age - but the overwhelming majority of people do. In our system, we’ve made the design decision that a person must have a name and age. We can start to enforce these as invariants by removing the default constructor, and forcing callers to provide values for name and age:
public class Person
{
public string Name { get; private set; }
public int Age { get; private set; }
public Person(string name, int age)
{
Name = name;
Age = age;
}
public void CelebrateBirthday()
{
// Increase the person's age by 1 to celebrate a birthday
Age++;
}
}This is a step in the right direction, but we can go further. Does it make sense for a person’s age to be 0? What about -999999? Probably not right? Well let’s fix that:
public class Person
{
public string Name { get; private set; }
public int Age { get; private set; }
public Person(string name, int age)
{
// check that the supplied age matches our expectations
if (age
{
new Person("John Doe", -9999);
};
Assert.Throws(act);
}
[Fact]
public void PersonMustHaveAName()
{
Action act = () =>
{
new Person(null, 30);
};
Assert.Throws(act);
}
}So that’s an intro to invariants. Let me know in the comments if you’ve thought about invariants in your code before, or if the idea of invariants is new to you.