C# Design Patterns – Handling ‘Free Functions’ Cleanly

cdesign-patterns

I was recently reviewing a few Helper-style "utility bag" static classes floating around some large C# codebases I work with, things basically like the following very condensed snippet:

// Helpers.cs

public static class Helpers
{
    public static void DoSomething() {}
    public static void DoSomethingElse() {}
}

The specific methods I've reviewed are

  • mostly unrelated to each other,
  • without explicit state persisted across invocations,
  • small, and
  • each consumed by a variety of unrelated types.

Edit: The above is not intended to be a list of alleged problems. It's a list of common characteristics of the specific methods I'm reviewing. It's context to help answers provide more relevant solutions.

Just for this question, I'll refer to this kind of method as a GLUM (general lightweight utility method). The negative connotation of "glum" is partly intended. I'm sorry if this comes across as a dumb pun.

Even putting aside my own default skepticism about GLUMs, I don't like the following things about this:

  • A static class is being used solely as a namespace.
  • The static class identifier is basically meaningless.
  • When a new GLUM gets added, either (a) this "bag" class gets touched for no good reason or (b) a new "bag" class gets created (which in itself is not usually an issue; what's bad is that the new static classes often just repeat the unrelatedness problem, but with fewer methods).
  • The meta-naming is inescapably awful, non-standard, and usually internally inconsistent, whether it's Helpers, Utilities, or whatever.

What's a reasonably good & simple pattern for refactoring this, preferably addressing the above concerns, and preferably with as light a touch as possible?

I should probably emphasize: All the methods I'm dealing with are pairwise unrelated to each other. There doesn't seem to be a reasonable way to break them down into finer-grained yet still multi-member static class method-bags.

Best Answer

I think you have two problems closely related to each other.

  • Static classes contains logically unrelated functions
  • Names of static classes do not provide helpful information about meaning/context of the contained functions.

These problems can be fixed by combining only logically related methods into one static class and moving the others into their own static class. Based on your problem domain, you and your team should decide what criteria you will use for separating methods into related static classes.

Concerns and possible solutions

Methods are mostly unrelated to each other - problem. Combine related methods under one static class and move unrelated methods to their own static classes.

Methods are without explicit state persisted across invocations - not a problem. Stateless methods are a feature, not a bug. Static state is difficult to test anyway, and should only be used if you need to maintain state across method invocations, but there are better ways to do that such as state machines and the yield keyword.

Methods are small - not a problem. - Methods should be small.

Methods are each consumed by a variety of unrelated types - not a problem. You have created a general method which can be re-used in many not related places.

A static class is being used solely as a namespace - not a problem. This is how static methods are used. If this style of calling methods bothers you, try using extension methods or the using static declaration.

The static class identifier is basically meaningless - problem. It is meaningless because developers are putting unrelated methods in it. Put unrelated methods into their own static classes and give them specific and understandable names.

When a new GLUM gets added, either (a) this "bag" class gets touched (SRP sadness) - not a problem if you follow idea that only logically related methods should be in one static class. SRP is not violated here, because principle should be applied for classes or for methods. Single responsibility of static classes means to contain different methods for one general "idea". That idea can be a feature or a conversion, or iterations(LINQ), or data normalization, or validation ...

or less frequently (b) a new "bag" class gets created (more bags) - not a problem. Classes/static classes/functions - are our(developers) tools, which we use for designing software. Does your team have some limits for using classes? What are maximum limits for "more bags"? Programming is all about context, if for solution in your particular context you end up with 200 static classes which understandably named, logically placed in namespaces/folders with logical hierarchy - it is not a problem.

The meta-naming is inescapably awful, non-standard, and usually internally inconsistent, whether it's Helpers, Utilities, or whatever - problem. Provide better names that describe expected behaviour. Keep only related methods in one class, which provide help in better naming.