Menu Menu
Think twice before you introduce a static class

Think twice before you introduce a static class

During our programming experience, each of us has probably, at least a couple of times, made some design or architectural decisions which later have come back to bite us. It was either some pattern used in a wrong place, thus a wrong pattern; or we misjudged some aspect of an application, or similar. But this is quite common. We learn during our work and mostly from the mistakes we have made. Apart from choices at a higher level we can also make mistakes at a much lower, technical level of software development. Using static classes in wrong places, is one of them. Even though at first sight this seems harmless, static classes can make the entire project less readable and harder to maintain. We will limit ourselves to examining static classes in .NET framework.

Let's just do a short briefing of what this actually represents, before we start explaining why static class is dangerous. Static class is compiled into an abstract sealed class. It sounds strange, but it actually makes sense. You cannot inherit a static class, that's why it's sealed, but you also cannot make an instance of it, that's why it is abstract too. So, according to the basic nature of it, static classes are used as helper/utility classes which are stateless. A good example of static class usage is for the extensions methods or for simple helpers which by nature cannot be categorized as extensions of the existing classes. The following picture shows an example of a static class. On purpose, we used a business logic parser in the example, for which we will eventually realize that it's poorly designed as a static class.

public static class DataMapper
{
    public static PersonDTO ParsePerson(Person person)
    {
        return new PersonDTO
        {
            Name = person.Name ?? string.Empty,
            Surname = person.Surname ?? string.Empty
        };
    }
}

Now, let's see what can go wrong when static class is used to model something stateful or has a pretty good chance to become stateful during project development.

1. If you decide to have a static class with init/initialize method, to supply the necessary arguments or dependencies, you should know that clients of your API or your team members (especially those which didn't work on class design) will probably forget to call it. There is no natural way to emphasize this through API you expose. As you can see in the following picture, if mandatory dependency DataCache isn't initialized, DataMapper won't work correctly, and its usage will produce the null pointer exception.

public static class DataMapper
{
    private static DataCache _dataCache;

    public static Initialize(DataCache dataCache)
    {
        _dataCache = dataCache;
    }

    public static PersonDTO ParsePerson(Person person)
    {
        PersonDTO retVal = _dataCache.GetPerson(person.Id);

        if (retVal == null)
        {
            retVal = new PersonDTO
            {
                Name = person.Name ?? string.Empty,
                Surname = person.Surname ?? string.Empty                
            };

            return retVal;
        }
    }
}

Unlike them, non-static classes don't have this problem. Initialization is forced through constructors. So there is no place for mistakes. Let's see how mapper looks as a non-static class…

public class DataMapper
{
    private DataCache _dataCache;

    public DataMapper(DataCache dataCache)
    {
        _dataCache = dataCache;
    }

    public PersonDTO ParsePerson(Person person)
    {
        PersonDTO retVal = _dataCache.GetPerson(person.Id);

        if (retVal == null)
        {
            retVal = new PersonDTO
            {
                Name = person.Name ?? string.Empty,
                Surname = person.Surname ?? string.Empty
            };

            return retVal;
        }
    }
}

If you decide to have a static class with specific initialization in static constructor, bear in mind that static initialization is quite specific and not easy to handle if errors occur. Static constructors are called only once during application lifetime, when class definition is introduced in the memory space of your application for the first time, thus when first instance is created or static member is called.

public static class DataMapper
{
    private static DataCache _dataCache;

    static DataMapper()
    {
        _dataCache = new DataCache();
    }

    public static PersonDTO ParsePerson(Person person)
    {
        PersonDTO retVal = _dataCache.GetPerson(person.Id);

        if (retVal == null)
        {
            retVal = new PersonDTO
            {
                Name = person.Name ?? string.Empty,
                Surname = person.Surname ?? string.Empty                
            };

            return retVal;
        }
    }
}

So, to wrap static constructor with exception handling above, you will have to know where exactly the class will be called for the first time. This is not an easy task. At the end, you shouldn't even think about it. Simply because it's a bad decision from the start. Static constructors are used for simple initializations and, in rare occasions, to load a library or similar. If static constructor throws an exception, CLR (Common Language Runtime) won't call the static constructor again, therefore type will remain uninitialized.

  • a. Think of unit tests too. If you require different initialization data for the static class per unit test, maybe you won't be able to achieve this goal through unit test engines. Some engines share the application domain for multiple tests, so initialization will be done once instead of per each test. Also, most of unit test engines shuffle execution between unit test classes (unless you make ordered lists), so it's possible to get initialization data of some other unit test class, and therefore wrong data.

3. If you decide to model a part of business logic with static class, for example some mapper/translation class, you should expect that project requirements could change and maybe those components will require state. This is quite often the case :) If this happens in a late project development phase and the only valid option to proceed is to convert the classes to non-static, you will have lots of work to do: to change all usages in the project, in the unit tests, etc. This can be quite frustrating.

  • a. Bear in mind that business logic classes have a tendency to work together, so passing data between them is quite a common scenario. If you have static classes in there, the only natural way to communicate between them is through method parameters. Now, imagine 10 static classes which call each other with a bunch of parameters and propagate the same through multiple levels!? Not so readable and easy to track :) Of course, you can encapsulate those parameters into specific classes or provide them through some factories or singletons, but sending dependencies of the class through methods is not a natural way (Although there are specific situations/patterns in which this is desirable). Dependencies are mostly supplied through a constructor. Practice has confirmed that this is the most natural and readable way.

4. All of the abovementioned initialization issues apply to the singleton class or the monostate, whose nature is static too, but they achieve it in different ways. Therefore, before introducing any of those classes, think of those initialization issues.

5. Mocking unit test data is harder if you have a static class or singleton. If those classes perform specific initialization from the inside and they probably will, like in the static constructor, there is no way to do the mocking unless you expose a method for initialization or some static setter.So you will unnecessarily pollute your API just for the unit test purpose. Also, quite often, static classes and singletons are initially hidden from the developer who wants to write a unit test. He maybe won't know that he has to mock static classes and singleton until he gets an error during test execution. And then the fun starts for him :) There are some mocking frameworks which can help mocking singletons and static classes, but it's an extra effort and completely unnecessary.

In special occasions, if you have an available source instead of introducing a static class for extensions, you could decide to append the existing non-static class with static members. This improves readability, it's quite common practice and it entirely makes sense. But take into account that these methods should also be stateless by functionality helpers. But, if you have a requirement to model different helpers per production, then extensions are probably a better choice for keeping your code baseline clean.

In conclusion, be very careful and think twice before you introduce a static class. Think how it will be used, by whom, and try to predict its usage in the future. Know that requirements could and will change. If you decide to use it, make sure you have a pretty good reason for it. Otherwise, use it only as a helper/utility class, which is stateless and simple.

Latest blog posts
ITkonekt 2019 Overview
Front-End Day 2019: Our Impressions
Workshop with Uncle Bob
International Umbraco Conference in Novi Sad: Umbraco Roadshow 2019
How To Send an Email with Attachments Using EPiServer Forms