Say the word static to a developer and see what happens. If nothing at all happens, you should be a little bit worried. As a bare minimum you should get some sort of reaction indicating caution. I keep bumping into scenarios where the presence of static doesn't really solve any problems, but instead just makes things harder.
First, I would like to make it clear that, at least to me, static comes in two forms. Or, well, three forms to be more precise.
There is something called a pure function.
A function is said to be pure if it always returns the same result for the same input, and if it has no observable side effects.
If the above statement is true it should not make any difference whether the function is static or not. Since it should return the exact same result every time for a given input, it can’t use any other data for its work, since any other data may mutate over time. In that sense, pure functions can just as well be made static.
It's important to notice both criteria for a pure function:
As an example we can look at the
DateTime structure in .NET. It has some static methods and properties. If we look at
DateTime.Compare we can see that it takes two
DateTime values as input, and returns a
boolean, indicating whether the two values represent the same instant in time or not. This method will always give the same result for the same input. If you pass in two
DateTime values that represent the same time, it will always return
true. Also, it will have no side effects, it will not modify any state in your system, or in the objects passed to it.
DateTime.UtcNow is also static, and will also not have any side effects by modifying anything. But it's not a pure function, since it will return a new value on each call (at least for the purpose of this discussion; in reality it may return the same value on several calls if they are done within a very short time frame).
As we can conclude is that
DateTime.Compare can be easily tested while
DateTime.UtcNow cannot be easily tested.
If we move on to static state we are talking about data that is shared by all code in a program, and with state it’s a completely different ball game. State is mutable. What one part of the system does, will affect other parts of the system. The results of various operations is “sticky”, it remains in the system.
The one key area where I tend to come across the most trouble in relation to static state is when writing tests for software, and I like my tests. They help me sleep at night. Writing reliable unit tests for software that has static state is hard. If there is no way to mock that state away, it borders to the impossible. State from one test may “leak” to the next test being executed. A common sign of this is when you have a suite of tests where some tests fail when run as part of the suite, but pass when run separately.
Introducing static state also has an effect on life-time management of objects, of course. It may very well be that for the system that you are making, there really should be only one instance of this data in one session. However, this is typically related to the life-time and scope management of the instances of that type, not to the functionality in the type itself.
Also, when introducing static state you pretty much remove the possibility to run unit tests in parallel, since they will suddenly interfere with each other.
When you decide to have a type with internal static state, you are making decisions for the user of that type that the user can’t do anything about. Consuming code is stuck to relate to the fact that there is static state in there. This may work well for a given scenario, but not in another. In this perspective, it’s obviously a lot more useful to not make such decisions on behalf of the consuming code but instead leave that up to the consumer. If a static usage scenario fits the context then that option is still available to the consumer.
Please, please pretty please: with the exception of pure functions, do not make types or functions static unless it actually is the only way to solve your problem. It is much more likely to create problems than to solve one.