Enforcing Immutability in Multi-Threaded Projects with NDepend
Editorial Note: I originally wrote this post for the NDepend blog. You can check out the original here, at their site. While you’re there, have a look around at some of the features you get by downloading NDepend.
Early in the days of object oriented programming, we really got a lot of mileage out of the language and paradigm features. For instance, if your memory extends back far enough (or your application is legacy enough), you’ll see really, really deep inheritance hierarchies. Since inheritance was a language feature, it stood to reason that we should get our money’s worth, and boy did we ever. We look back on that as an anti-pattern now with our 20/20 hindsight.
The same was true of application state. The classic notion of object oriented programming was one in which objects of state and of behaviors. For perhaps the most iconic early conception of this notion, consider the active record design pattern. This pattern addressed the so-called impedance mismatch by presenting us with an in-memory, object-based representation of a database table. The object’s state was the table and the behaviors were records that you could perform on it, such as saving or deleting or what have you.
While active record, particularly in some communities, has not been as roundly rejected as deep inheritance hierarchies, it no longer enjoys the favor that it did more than a decade ago. And a big part of the reason that it, and other state-based patterns don’t, is that the object-oriented community has come to favor immutability, meaning that any data stored in an object is read-only for the lifetime of that object.
Immutable objects are, first and foremost, easier to reason about. Take, for instance, the humble object collaborator.
public class ServiceConsumer { public Service TheService { get; set; } public void UseTheService() { var aBeer = TheService.GetMeABeer(); } }
This may or may not work, depending on what people come along and do with TheService. What happens if they null it out? To make life easier, we move away from mutable state implementations in favor of approaches like this.
public class ServiceConsumer { private readonly Service _service; public ServiceConsumer(Service service) { if (service == null) throw new ArgumentNullException(nameof(service)); _service = service; } public void UseTheService() { var aBeer = _service.GetMeABeer(); } }
Now there’s no reason to worry about the service being valid. At the time of object construction, we enforce any preconditions, and then we don’t need to worry that _service.GetMeABeer() is going to generate a null reference exception or wind up calling a different implementation than the last time it was invoked. ServiceConsumer is now immutable with respect to Service.