Weekly Dev Tips
Why is Immutability Desirable?
Why Immutability is Desirable
This week's tip is on the topic of immutability, and why it's often considered a good thing for your data structures. I'll share my thoughts on the topic in a moment, but first a quick note from this week's sponsor.
Sponsor - devBetter Group Career Coaching for Developers
If you're not advancing as quickly in your career as you'd like, you may find value in joining a semi-formal career and technical coaching program like devBetter.com. I launched devBetter a few months ago and so far we have a small group of motivated developers meeting every week or two. I answer questions, review code, suggest areas in which to improve, and occasionally assign homework. Interested? Learn more at devBetter.com.
Show Notes / Transcript
Let's talk about immutability. This topic is on my mind because I just wrote an article about getting language support for immutability and, more broadly, DDD value objects, in C#. Value objects area DDD pattern, and one of their defining characteristics is that they're immutable. I keep using that word, so I should probably define it.
An immutable data structure (an object in C#) is one that, once created, cannot have its state modified. I mention wanting language support for this feature in C# - that's not to imply that you can't create immutable objects today. It's just a lot of manual work, and easy to get wrong or screw up in the future because nothing enforces immutability at the class level. Typically in C# to create an immutable object you create a class with properties that lack setters, and then you assign values to these properties in the class constructor. Short of some reflection trickery, instances of this type cannot have their properties modified once they've been instantiated. Why might we want this?
The biggest advantage I get from immutability in objects is knowledge that instances of these types are always in a valid state. That means any method using such types doesn't have to waste effort trying to verify they're in a valid state. Here's a common example. Imagine your system needs to work with date ranges that include a start and end date. You might have many methods that take in two DateTime types, and in these methods you always expect the end date to be later than the start date. So, being a good programmer, you write a guard clause to ensure start date precedes end date. This logic ends up scattered all over the place, and maybe sometimes you forget or don't bother with it, so not it's not even enforced consistently, allowing bugs to creep in. What if instead you created an immutable DateRange class, passed the start and end dates into its constructor, and ensured they were valid there? If not, you'd throw an appropriate exception. Now, any method that was accepting a start and end date can just take in a DateRange instead, shortening these methods' parameter lists. And they can remove all of their validation on start date and end date because that's now done in the DateRange class. Why can you trust that it was done? Because of immutability. If it was valid when it was created, it must still be valid now since it couldn't be changed in the meantime. Your validation logic only has to be performed in one place, and immutability gives you guarantees that it will be applied so you don't have to defensively code for it everywhere.
Another advantage immutability offers is thread safety. You don't have to worry about race conditions or synchronization issues between different threads when they work with immutable objects. Why not? Simply because the objects can't change. Operating on immutable objects may produce new instances of objects, but this typically doesn't pose an issue for multi-threaded applications. The issue is more commonly something like an instance that two threads are referencing, and each thread tells the instance to increment a counter at the same time. The end result may be unexpected due to how the calculation may be completed. This is typically overcome through the use of locks, but you can pass around immutable objects between threads all you want and never have to worry about this issue.
Have you ever passed an object to a method, and then found yourself surprised when the method modified the object? I generally dislike this kind of thing, and C# even has a new keyword in
that will ensure this doesn't happen. I'll link to more on the in
keyword in the show notes, but it has some restrictions and hasn't seen widespread adoption, yet. Another way to ensure that the state of an instance you pass to a method isn't modified within that method is to use an immutable object. This makes it much easier to reason about and debug your code, and can have performance benefits since objects that won't be modified can always be passed by reference, without copying them to the stack.
Immutable types are much easier to test than other types, and for this as well as the above reasons using them appropriately can lead to better, more maintainable code. Eliminate the primitive obsession code smell, better encapsulate concepts that tie together several values, and force validation (and other business rules) to live with these values instead of in the types that use them. You should find over time that your domain model becomes much cleaner as a result.