Like Yegor Bugayenko, whenever I see code making heavy use of utility classes, I strongly suggest alternatives that better fit the domain. I have also noticed that where Utility classes are found, their evil cousins — Constants classes — tend to hang around as well.
When I see a Constants class in a module, I can predict that one or both of these things is true about its design: (a) there is a missing domain object or concept; (b) encapsulation is being violated.
Here is a simple example of the first problem:
static class Constants { public const int MillisecondsPerSecond = 1000; public const int SecondsPerMinute = 60; public const int MinutesPerHour = 60; public const int HoursPerDay = 24; // ... }
Presumably there is code elsewhere in the system that does calculations like int totalMinutes = durationInMS / (MillisecondsPerSecond * SecondsPerMinute)
. While the named constants here help with readability, they also make it obvious that we are missing the concept of a TimeSpan. This example is easy to fix, in that we could simply replace all of this with the use of TimeSpan and be done with it. But even if there is no standard library type that does what you want, you can (and should!) use the whole value pattern to come up with your own.
For the second problem, consider this:
static class Constants { public const string ConfigFilePath = @"Resources\Data\Config.json"; public const string ConfigSectionName = @"ApplicationSettings"; public const string ConfigNamespace = "my.app.config"; // ... }
The developer here is most likely trying avoid having repeated or hardcoded names and values in the config module. This is a good instinct, but the solution is being applied at the wrong level and creating global visibility of local data. In the cases I commonly see, such values are not used more than once; in that situation they can still be constants but they should be redefined closer to their use in the object graph, for example:
class ConfigStore { private const string FilePath = @"Resources\Data\Config.json"; // ... }
Notice that as a result of moving the code to a more appropriate place, the name prefix is no longer needed — a good sign that we have discovered the correct “namespace” as it were. If the constant is instead used more than once in different places, then we are back to problem #1; we need a domain object to factor out this conceptual duplication and give it a good name.
Beyond the design aspect, .NET programmers must take particular caution when using const
values across multiple assemblies. A constant is replaced at compile time with its literal value. This means if your program App1.exe references const N in Shared.dll, it will never see changes to N unless both Shared.dll and App1.exe are recompiled. To me, this pitfall is a rather strong argument in favor of encapsulation.
I encourage you to take a look at your codebases and see how you can change their use of constants. You might be surprised at the improvements you can make when focusing on a seemingly minor detail.