First off, a definition: A helper class is a class filled with static methods. It is usually used to isolate a "useful" algorithm. I've seen them in nearly every bit of code I've reviewed. For the record, I consider the use of helper classes to be an antipattern. In other words, an extraordinarily bad idea that should be avoided most of the time.
What, you say? Avoid Helper Classes!?! But they are so useful!
I say: they are nearly always an example of laziness. (At this point, someone will jump in and say "but in Really Odd Situation ABC, There Is No Other Way" and I will agree. However, I'm talking about normal IT software development in an OO programming language like C#, Java or VB.Net. If you have drawn a helper class in your UML diagram, you have probably erred).
Why laziness? If I have to pick a deadly sin, why not gluttony? 🙂
Because most of us in the OO world came out of the procedural programming world, and the notion of functional decomposition is so easy that we drop to it when we come across an algorithm that doesn't seem to "fit" into our neat little object tree, and rather than understand the needs, analyze where we can get the best use of the technology, and place the method accordingly, we just toss it into a helper class. And that, my friends, is laziness.
So what is wrong with helper classes? I answer by falling back on the very basic principles of Object Oriented Programming. These have been recited many times, in many places, but one of the best places I've seen is Robert Martin's article on the principles of OO. Specifically, focus on the first five principles of class design.
So let's look at a helper class on the basis of these principles. First, to knock off the easy ones:
Single Responsibility Principle -- A class should have one and only one reason to change -- You can design helper classes where all of the methods related to a single set of responsibilities. That is entirely possible. Therefore, I would note that this principle does not conflict with the notion of helper classes at all. That said, I've often seen helper classes that violate this principle. They become "catch all" classes that contain any method that the developer can't find another place for. (e.g. a class containing a helper method for URL encoding, a method for looking up a password, and a method for writing an update to the config file... This class would violate the Single Responsibility Principle).
Liskov Substitution Principle -- Derived classes must be substitutable for their base classes -- This is kind of a no-op in that a helper class cannot have a derived class. (Note my definition of a helper class is that all members are static). OK. Does that mean that helper classes violate LSP? I'd say not. A helper class looses the advantages of OO completely, an in that sense, LSP doesn't matter... but it doesn't violate it.
Interface Segregation Principle -- Class interfaces should be fine-grained and client specific -- another no-op. Since helper classes do not derive from an interface, it is difficult to apply this principle with any degree of seperation from the Single Responsibility Principle.
Now for the fun ones:
The Open Closed Principle -- classes should be open for extension and closed for modification -- You cannot extend a helper class. Since all methods are static, you cannot derive anything that extends from it. In addition, the code that uses it doesn't create an object, so there is no way to create a child object that modifies any of the algorithms in a helper class. They are all "unchangable". As such, a helper class simply fails to provide one of the key aspects of object oriented design: the ability for the original developer to create a general answer, and for another developer to extend it, change it, make it more applicable. If you assume that you do not know everything, and that you may not be creating the "perfect" class for every person, then helper classes will be an anathema to you.
The Dependency Inversion Principle -- Depend on abstractions, not concrete implementations -- This is a simple and powerful principle that produces more testable code and better systems. If you minimize the coupling between a class and the classes that it depends upon, you produce code that can be used more flexibly, and reused more easily. However, a helper class cannot participate in the Dependency Inversion Principle. It cannot derive from an interface, nor implement a base class. No one creates an object that can be extended with a helper class. This is the "partner" of the Liskov Substitution Principle, but while helper classes do not violate the LSP, they do violate the DIP.
Based on this set of criteria, it is fairly clear that helper classes fail to work well with two out of the five fundamental principles that we are trying to achieve with Object Oriented Programming.
But are they evil? I was being intentionally inflammatory. If you read this far, it worked. I don't believe that software practices qualify in the moral sphere, so there is no such thing as evil code. However, I would say that any developer who creates a helper class is causing harm to the developers that follow.
And that is no help at all.