The abuse of static classes can be considered bad practice. But so can the abuse of any language feature.
I make no distinction between a non-static class with only static methods and a static class. They are effectively the same thing, except that static classes allow the compiler to enforce the developers intent (no instantiating this class, convenient syntax for accessing its functionality, etc).
As you say, a proliferation of "Helper" classes can get you into trouble (design, maintainability, readability, discoverability, other-abilities...). No argument here. But can you argue that a "Helper" class is never appropriate? I doubt it.
Indeed, responsible use of static classes can have great benefits to your code:
- The
Enumerable static class provides a set of extension methods most of us have come to love. They are a logical set of functionality / business logic that isn't related a instance of any particular type.
- Services provided by the environment/context: eg Logging, Configuration (sometimes)
- Others (that I can't think of at the moment :))
So no, in general its not bad practice. Just use them wisely...