The keyword static is somewhat awkward in a pure Oriented-Object world. I would like to explain here what are the right usages of static I came up after 25 years of OO Programming.
The rule of thumb is that static doesn’t mix well with mutable state, I mean state that can change during the program lifetime. As a consequence the rules of thumb are:
A: Static fields must be declared read-only (or const if possible)
B: Static fields must reference immutable state: state that cannot be changed during the program lifetime
C: A static method must be a pure functions i.e methods that compute outputs from inputs parameters, without changing the state of inputs nor any other state (like a class static field).
Static classes are ok as long as their fields and methods follow these guidelines.
What’s wrong with static fields whose state can change? The underlying motivation for creating a static field is to create an object that lives during all the program execution lifetime. Often this is a cornerstone object – like a context holder object – accessed by many methods. At first glance making it static and largely visible makes it more convenient than providing the object as a parameter cascading to all methods that need it. Let’s have a look at problems introduced:
Your code must be runnable within session boundaries
First, everytime I stumbled on a need for an object that lives during all the program execution lifetime I found out – sooner or later (generally later) – that I was wrong. There is no such thing. For example imagine that your program becomes hosted as a plugin of a larger application. Plugin can be unload and reloaded. Thus the program execution lifetime become plugin session lifetime. And you certainly don’t want that the global object lives between sessions.
The key point here is the concept of session. And there are two common kinds of session: Web request and Automatic tests. Both can be seen as mini-session. And in both situations, sharing mutable states across sessions sounds awkward. This makes the code much less testable : all these mini-sessions must now worry about the shared state to be correctly initialized and correctly finalized.
Tests are generally ran sequentially but we can choose to run tests concurrently. In such situation you need to synchronize accesses to the static mutable state. And synchronizing accesses is not only a pain, but also a performance killer practices.
The same reasoning applies to web requests sessions that de-facto run concurrently.
It might be required to re-cycle the same state between sessions for states that are expensive to build. For example a large chunk of DB data consumed by several tests: in such case using test
TearDown() makes sense to initialize once and finalize once such state, but it doesn’t need to be declared as static. And since you shouldn’t assume a tests running order, if some modifications occur this introduces side-effects that should be carefully handled.
Managing state in a concurrent environment
When it comes to managing mutable states in a multi-threaded environment the idea of Thread / Resource affinity eliminates the need for synchronization. The idea is as simple as it is efficient: making sure that an object is always accessed by the same thread. Basically each thread hold its own instance. You can still use a static field tagged with the ThreadStaticAttribute for that. Concurrent accesses are then eliminated but each mini-sessions still has to worry about initialization/finalization or in other words: what happened and what will happen to the state. This is why creating a dedicated object for each thread and passing it across every methods that needs it is more elegant than a static field tagged with ThreadStaticAttribute : you can then trace properly the history of state modifications.
Global state and Maintainability
Finally, the third problem with static fields comes from the fact that they make the program much less maintainable. Static fields represent global state. A global state can be accessed from anywhere in source code that views it. Thus from any concerned scope, the developers can potential create a dependency to the global state (no matter if it is encapsulated in a static property or not). With time a global state necessarily leads to anarchical accesses. It is virtually impossible to rule accesses to a global state and the result will be error-prone code, uncontrolled side-effects, and tedious debugging sessions. Thus will come the need to refactor the global state into a state passed to methods that need it. Better do the right things first.
Prohibits Singleton implemented with static holding mutable states
A consequence of all this is that the well-known singleton class Design Pattern, whose original implementation is based on a static field, must be prohibited. From what we just explained, the single static field of singleton makes the program harder to test, harder to maintain, and harder to handle in a concurrent environment. In my opinion, the problem with singleton is that it is the first and easiest OOP design pattern learnt. As a consequence, OOP learners use – and abuse – singletons.
Don’t get me wrong, it is often legit to restrict a class to have a single instance, for example for a settings holder object. But instead of using a static field, just make sure that the object gets created once and then gets passed to all methods that consumes it. Then enjoy how much testable is now your code with this approach.
The third rule C) states that a static method should be a pure functions i.e methods that compute outputs from inputs parameters, without changing the state of inputs nor any other state (like a class static field).
Object-Oriented is a great mean to model a problem and create proper abstractions. However, too often classes contain functions that computes outputs from their inputs without using the object instance fields. As a consequence these functions can be declared as static. Doing so result in fat classes, hard to understand and maintain. This is why, in case of complex logic, I prefer to nest such implementation within side-helper static classes. Think at the static class
System.Math which is a giant helper to work with primitive number types like
double. It is preferable that the structure
double doesn’t implement
Pow() because the amount of math operations on
double can grow indefinitely.
Not only this makes my instance classes smaller and clearer, but also those static classes are highly testables: a test just needs to initialize the input, call a static method, and check the output. There is no hidden state to worry about. This might also reduces the need for mocking. Also experience shows that it sometime makes sense to encapsulate a mini-object model within such helper class, that models its own mini-concern to simplify the complex logic implementation.
Finally with the C# extension method syntax we can choose to not only separate an object from its behaviors, but also makes calls looks natural. The
System.Linq.Enumerable static class illustrates that well. It is so great that
Take() Select() Where() Single() First() … are static extension methods that don’t modify the input sequence but return a new one instead. I still remember the thrill when I learnt them and realized how powerful this approach is.
The keyword static is a concession to pure OOP. However it is a double-edged keyword that doesn’t mix well with mutable state. Not using it wisely necessarily leads to significant troubles. But it can be a good compagnon if used wisely. Our tool NDepend provides a category of rules and queries named Immutability to enforces what have been explained: