16

I am very curious as to thoughts and industry best practices regarding static members, or entire static classes. Are there any downsides to this, or does it participate in any anti-patterns?

I see these entities as "utility classes/members", in the sense that they don't require or demand instantiating the class in order to provide the functionality.

What are the general thoughts and industry best practices on this?

See the below example classes and members to illustrate what I am referencing.

// non-static class with static members
//
public class Class1
{
    // ... other non-static members ...

    public static string GetSomeString()
    {
        // do something
    }
}

// static class
//
public static class Class2
{
    // ... other static members ...

    public static string GetSomeString()
    {
        // do something
    }
}

Thank you in advance!

Thomas Stringer
  • 2,237
  • 2
  • 16
  • 19
  • 1
    The [MSDN article on Static Classes and Static Methods](http://msdn.microsoft.com/en-us/library/79b3xss3.aspx) provides a pretty good treatment. – Robert Harvey Mar 19 '14 at 00:34
  • 1
    @Robert Harvey: Although the article you referenced is useful, it doesn't provide much in terms of best practices and what some of the pitfalls are when using static classes. – Bernard Mar 19 '14 at 02:53
  • recommended reading: **[Why is asking a question on “best practice” a bad thing?](http://meta.stackexchange.com/a/142354/165773)** – gnat Mar 19 '14 at 04:08

2 Answers2

24

In general, avoid statics - especially any sort of static state.

Why?

  1. Statics cause trouble with concurrency. Since there is only one instance, it is naturally shared amongst concurrent execution. Those shared resources are the bane of concurrent programming, and often do not need to be shared.

  2. Statics cause trouble with unit testing. Any unit testing framework worth it's salt runs tests concurrently. Then you run into #1. Worse yet, you complicate your tests with all of the setup/teardown stuff, and the hackery you'll fall into to try and "share" setup code.

There are a plenty of small things too. Statics tend to be inflexible. You can't interface them, you can't override them, you can't control the timing of their construction, you can't use them well in generics. You can't really version them.

There are certainly uses of statics: constant values work great. Pure methods that don't fit in a single class can work great here.

But in general, avoid them.

Telastyn
  • 108,850
  • 29
  • 239
  • 365
  • I'm curious to the extent of inability for concurrency you mean. Can you expand on that? If you mean that the members can be accessed with no segregation, that makes perfect sense. Your use-cases for statics are what I typically use them for: constant values and pure/utility methods. If that's the best and 99% use-case for statics, then that gives me a level of comfort. Also, +1 for your answer. Great information. – Thomas Stringer Mar 19 '14 at 01:13
  • @ThomasStringer - just that the more touchpoints between threads/tasks means more opportunity for concurrency issues and/or more performance loss doing synchronization. – Telastyn Mar 19 '14 at 01:19
  • So if one thread is currently accessing a static member then other threads need to wait until the owning thread releases the resource? – Thomas Stringer Mar 19 '14 at 01:40
  • @ThomasStringer - maybe, maybe not. Statics are (very nearly, in most languages) no different from any other shared resource in this regard. – Telastyn Mar 19 '14 at 01:44
  • @ThomasStringer: Unfortunately it is actually worse than this, unless you mark the member `volatile`. You just get no assurances from the memory model without volatile data, so changing a variable in one thread may not reflect immediately or at all. – Phoshi Mar 19 '14 at 10:05
  • For me, the more important issues are about big-picture design. Statics often make good practices like [inversion of control](https://stackoverflow.com/a/3140/138757) impossible, thus making dependencies "invisible" and harder to discover, which makes the software harder to refactor and maintain over time. – Phil Jun 08 '18 at 09:42
24

If the function is "pure" I see no problems. A pure function operates only in the input parameters, and provide a result based on that. It does not depend on any global state or external context.

If I look at your own code example:

public class Class1
{
    public static string GetSomeString()
    {
        // do something
    }
}

This function does not take any parameters. Thus, it is probably not pure (the only pure implementation of this function would be to return a constant). I assume that this example is not representative of your actual problem, I am merely pointing out that this is probably not a pure function.

Lets take a different example:

public static bool IsOdd(int number) { return (number % 2) == 1; }

There is nothing wrong with this function being static. We could even make this into an extension function, allowing client code to become even more readable. Extension functions are basically just a special kind of static functions.

Telastyn correctly mentions concurrency as a potential problem with static members. However, since this function does not use shared state, there are no concurrency problems here. A thousand threads can call this function simultaneously without any concurrency issues.

In the .NET framework, extension methods have existed for quite some time. LINQ contains a lot of extension functions (e.g. Enumerable.Where(), Enumerable.First(), Enumerable.Single(), etc.). We don't see these as bad, do we?

Unit testing can often benefit when the code uses replaceable abstractions, allowing the unit test to replace system code with a test double. Static functions prohibit this flexibility, but this is mostly important at architectural layer boundaries, where we want to replace, for example, an actual data access layer with a fake data access layer.

However, when writing a test for an object that behaves differently, depending on whether some number is odd or even, we don't really need to be able to replace the IsOdd() function with an alternative implementation. Likewise, I don't see when we need to provide a different Enumerable.Where() implementation for the purpose of testing.

So let's examine the readability of client code for this function:

Option a (with the function declared as an extension method):

public void Execute(int number) {
    if (number.IsOdd())
        // Do something
}

Option b:

public void Execute(int number) {
    var helper = new NumberHelper();
    if (helper.IsOdd(number))
        // Do something
}

The static (extension) function makes the first piece of code much more readable, and readability matters a lot, so use static functions where appropriate.

Gurgadurgen
  • 421
  • 3
  • 6
Pete
  • 8,916
  • 3
  • 41
  • 53