Back to basics: Usage of static members | Patrick Smacchia

:

Using the static keyword should be considered as an unnatural design act in a OOP world. The problem is that developers get punished for using static fields and methods, months or years after having creating them. And indeed, reasons why static members are harmful are not obvious. But I won’t bash static members here since they can also be pretty useful in precise scenarios. Let’s develop a bit.

Static fields: use them only for constant and immutable states

In C#, the keywords const and readonly are used to mark a field as constant. de-facto const fields are static and immutable but it is not the case for readonly fields. Having a readonly static fields means that the object referenced by the static field will remain the same during the outer AppDomain lifetime. It doesn’t mean that the object is immutable. For example, one can have a readonly static field referencing a dictionary instance. And dictionaries in .NET are not immutable because one can still add/remove some key/value pairs at any time.

readonly static IDictionary<string, string> s_CountryCodeCorrespondance =
   new Dictionary<string, string> {
    {"+1" , "USA" }, {"+33" , "France" }, {"+44" , "UK" }, /*...*/
  };

In the example above, the dictionary instance is not by-design immutable, but its semantic makes it immutable (it is pretty rare that a new country code is created or changed). There are several ways to transform this semantical immutability to a by-design immutability. Maybe the easiest way would be to nest the static dictionary inside a dedicated class that would limit accesses to the dictionary:

static class CountryCodeCorrespondance {
   internal static bool TryGetCountryFromCode(string code, out string country) {
      return s_CountryCodeCorrespondance.TryGetValue(code, out country);
   }
      readonly static IDictionary<string, string> s_CountryCodeCorrespondance =
      new Dictionary<string, string> {
       {"+1" , "USA" }, {"+33" , "France" }, {"+44" , "UK" }, /*...*/
   };
}

Here, in my opinion, we have a static field that will never provoke any pain because it is both constant (in the sense the instance will remain unique) and immutable (there is no way to tweak the state of the object).

But why did I come to the conclusion that non-constant or non-immutable static fields, are harmful? Static fields are appealing for programmers because they represent an immediate way to maintain and provide a state across a program (the global state syndroma). But a code base will necessarily become more complex in the future. This means that no matter the static field is private or not, direct and indirect accesses to the static field at runtime will become chaotic. Several part of the code will read and mutate the field states and at a point, predicting the static field’s state at run-time will be close to impossible. Obviously it gets even worse in a multi-threaded environment.

Concerning relying on the popular singleton pattern, I attest that I always ended up removing all singletons I formerly created. Most of the time a singleton object maintains directly or indirectly one or several mutable states and this provokes a contention point. Singletons  makes things even more complex to debug, since by nature, a singleton encapsulates and then hides the mutable state(s) maintained, making potential debugging drama luring.

Another – less obvious – point against singletons and all kind of static mutable states is that they are out of scope of any future potential context. The context of a static field is by-design its parent AppDomain. They are some circumstances where developers wish a finer grained context. I learn it the hard way when NDepend became a Visual Studio addin. Addins can be enabled and disabled at whim by the VS user at VS run-time. But under-the-hood this takes place in the same AppDomain. So if a VS addin relies on some static mutable states, it is its responsibility to initialize and reset all static mutable states of the program at addin enabling and disabling time.

For sure, not every program will end up being a VS (or another host) addin. But most of program comes with a unit tests battery. Typically, tests are executed consecutively in the same AppDomain. And each test represent a mini context. At test run-time, static mutable fields are accessed and mutated again and again by tests executed in a random order (one should never rely on tests execution order). Hence it is the responsibility of each test, or at least each test suite, to set correctly all static mutable states. This makes unit-test activity more error-prone and harder to write.

Static methods

For all the reasons enumerated, having some non constant or mutable static states will provoke pain sooner or later. However, static methods are pretty harmless as long as they don’t mutate any static state (which is obviously impossible in a program that doesn’t have any mutable static state). Personally, I end up implementing a lot of complex algorithms in one or several static methods, often encapsulated in a dedicated stateless static class. Many algorithm are easier to read and maintain when they are written in a procedural fashion. It is not by chance that  algorithms are often exposed through a procedural pseudo-code sample.

But the killer reason for implementing algorithm with a stateless procedural style, through static methods, is that it lets the programmer formalize inputs and outputs through a single method call. As an immediate benefit, this makes corresponding unit-tests much easier to write.

A distinction can be made between algorithm that mutate inputs, and pure algorithms (or pure static functions) that keep all inputs constant. Obviously having pure static methods is preferable, but personally, I can’t remember a situation where I’ve been doomed by non-pure static methods.

Of course OOP purists can blame me for advising some procedural code style (and indeed, I got blamed for using many static methods in NDepend.Helpers.FilePathDirectory). But for all reasons exposed, I consider they help me write more readable, more testable and more maintainable code as long as no static state gets mutated.