Skip to content

Static Variables & Methods are Evil?

Sponsor: Using RabbitMQ or Azure Service Bus in your .NET systems? Well, you could just use their SDKs and roll your own serialization, routing, outbox, retries, and telemetry. I mean, seriously, how hard could it be?

Learn more about Software Architecture & Design.
Join thousands of developers getting weekly updates to increase your understanding of software architecture and design concepts.


You might have heard the recommendation to steer clear of static variables or methods. But is that really good advice? Let’s dive into why people say that, with a little nuance, and clarify the topic with some examples.

YouTube

Check out my YouTube channel, where I post all kinds of content accompanying my posts, including this video showing everything in this post.

Deterministic Behavior

First off, let’s talk about determinism. I have a method called is18YearsOrOlder, which takes a DateTime argument representing your birth date. The method checks if you’re 18 years or older by adding 18 years to your birth date and comparing it to today’s date. Sounds straightforward, right?

But here’s the catch: this method is not deterministic. Each time this method runs, it uses DateTime.UtcNow, which gives a different value every time. You’ll often hear people discussing pure functions, and what they mean by that is determinism. If I pass a specific value, I should always get the same result back. But in my case, that’s not happening, and my tests reflect that.

For instance, I had a test named NotOver18 where I passed in yesterday’s date. Yesterday it passed, but today it fails because today is exactly 18 years ago from yesterday.

To manage this, I created two more tests using DateTime.UtcNow so they would always pass.

So, is this a reason to label static methods or variables as bad? Not quite. It just shows that non-deterministic methods can complicate testing and reasoning.

Managing Non-Determinism

As another example: I have a PlaceOrder class with a Process method that returns a new order and includes a Processed property to indicate when the order was processed.

In my tests, many assertion libraries will check the date and time, but I can’t know exactly what that date is. I might set a tolerance, saying it should be within the last second, but that’s still non-deterministic. In some contexts, that’s not a big deal, but in others, it can be problematic.

For instance, if you place an order on a Friday and you get a 50% discount, how do I test that? The answer lies in making it deterministic. I modified the PlaceOrder method to accept a fake DateTime input. By injecting a time provider into the process method, I can set a specific date for testing, ensuring consistency.

Coupling and Global State

Next, let’s discuss tight coupling with static methods. Using static methods limits flexibility because you can’t override their behavior. If a static method is non-deterministic, it can make your own methods non-deterministic, complicating testing and reasoning.

Another common reason to avoid static variables is their global nature. Global variables can lead to uncertainty about their initialization and state. For example, I created a Global class with a static Cache property. If it’s null, has it been initialized? Do I need to connect to it first? These uncertainties are why global static variables can be problematic.

Mutable static variables can also be an issue in multi-threaded environments. If I have a static cache containing customer data that’s not thread-safe, running a Parallel.For can lead to failures. In such cases, you need to ensure that your static variables are thread-safe. In .NET, there are thread-safe collections, such as ConcurrentBag available that can help mitigate these issues. If I change my List<Customer> to a ConcurrentBag<Customer> then we are now thread-safe. But you must be thinking about this in advance if you’re using static properties and how they will be accessed.

Utility of Static Variables

So, are static variables entirely terrible? Not really. Their utility often depends on context. For example, I changed my customer collection to a ConcurrentBag, which is thread-safe, and guess what? Problem solved! The tests passed without worrying about concurrency issues.

Let’s look at a static method like milesToKilometers. It takes in a number of miles and always returns the same value based on the parameters passed, making it deterministic. This illustrates that static methods can indeed be useful.

Static Variables & Methods

To wrap things up, the reasons people often suggest avoiding static variables and methods include:

  • Tight coupling and lack of flexibility
  • Global state uncertainty
  • Testing complications, especially with non-deterministic methods
  • Thread safety concerns in multi-threaded environments

But static variables aren’t the devil. They have their place, especially when used deterministically and with consideration for concurrency.

Join CodeOpinon!
Developer-level members of my Patreon or YouTube channel get access to a private Discord server to chat with other developers about Software Architecture and Design and access to source code for any working demo application I post on my blog or YouTube. Check out my Patreon or YouTube Membership for more info.