Helper classes are normally static classes, so that you don't need to instantiate them. There is no great cost in instantiating a managed .NET object (especially helper classes), it is just a matter of convenience.
It is extremely tempting to just put together a static class with minimal helper methods and get the job done. They have their place in code, and can be used especially when there is deterministic input/output. e.g. ComputeHash of a string, Find Average of numbers etc.
But the one reason, Static classes are discouraged is because they normally interfere with unit testing and present all sorts of problems. (Fakes, Moles, Private Accessors etc.)
An interfaced based approach for even helper classes, helps with the unit testing of the overall code. This is especially true for big projects which involve workflows such that the static helper methods are only a part of the workflow.
e.g. Suppose you need to check if the current year is a leap year. It is tempting to write a quick static method.
public static class DateHelper
{
public static bool IsLeapYear()
{
var currentDate = DateTime.UtcNow;
// check if currentDate's year is a leap year using some unicorn logic
return true; // or false
}
}
and if this method is used in your code somewhere like:
public class Birthday
{
public int GetLeapYearDaysData()
{
// some self-logic..
// now call our static method
var isLeapYear = DateHelper.IsLeapYear();
// based on this value, you might return 100 or 200.
if (isLeapYear)
{
return 100;
}
return 200;
}
}
Now, if you go and try to unit test this method public int GetLeapYearDaysData(), you might end up in trouble since the return value is indeterminate.. i.e. depends on the current year and it is not recommended to have unit tests behaving unpredictably/deteriorate as time progresses.
// this unit test is flaky
[Test]
public void TestGetLeapYearDaysData()
{
var expected = 100;
// we don't know if this method will return 100 or 200.
var actual = new Birthday().GetLeapYearDaysData();
Assert.AreEqual(expected, actual);
}
The above problem happens because we cannot control/mock the method IsLeapYear() in the above code. so we're at its mercy.
Now imagine the following design:
public interface IDateHelper
{
bool IsLeapYear();
}
public class DateHelper : IDateHelper
{
public bool IsLeapYear()
{
var currentDate = DateTime.UtcNow;
// check if currentDate's year is a leap year using some unicorn logic
return true; // or false
}
}
Now our birthday class can be injected with a helper:
public class Birthday
{
private IDateHelper _dateHelper;
// any caller can inject their own version of dateHelper.
public Birthday(IDateHelper dateHelper)
{
this._dateHelper = dateHelper;
}
public int GetLeapYearDaysData()
{
// some self-logic..
// now call our injected helper's method.
var isLeapYear = this._dateHelper.IsLeapYear();
// based on this value, you might return 100 or 200.
if (isLeapYear)
{
return 100;
}
return 200;
}
}
// now see how are unit tests can be more robust and reliable
// this unit test is more robust
[Test]
public void TestGetLeapYearDaysData()
{
var expected = 100;
// use any mocking framework or stubbed class
// to reliably tell the unit test that 100 needs to be returned.
var mockDateHelper = new Mock<IDateHelper>();
// make the mock helper return true for leap year check.
// we're no longer at the mercy of current date time.
mockDateHelper.Setup(m=>m.IsLeapYear()).Returns(true);
// inject this mock DateHelper in our BirthDay class
// we know for sure the value that'll be returned.
var actual = new Birthday(mockDateHelper).GetLeapYearDaysData();
Assert.AreEqual(expected, actual);
}
As you can see, the moment the helper methods were Interface based, they were easily testable. Over the course of a big project, many such smaller static methods ultimately result in bottlenecks in testing key functional flows.
So it pays to be aware of this pitfall in advance and make the additional investment upfront. Basically identify what classes/methods need to be static and what shouldn't be.