There's quite a bit of bad software design in the industry and that's surprising. After all, there hasn't been any lack of guidance about design over the years. From the earliest days of programming, there have been people who've shared their experience with creating software that is easy to change over time. As a result, we have an incredible number of concepts that we can fall back upon - Coupling and Cohesion, the Single Responsibility Principle, the Law of Demeter, the Dependency Inversion Principle, Postel's Law, RESTful-ness. The list is nearly endless. Surely, we should have good design by now. If only it were that simple.
The fact of the matter is that many organizations do excel at design. Not as many as I wish, but there are quite a few. But, the teams I find most interesting are the ones that miss the mark by being overly allegiant to design guidance. There's always that team that takes some piece of advice too seriously and ends up creating a mess. A classic example is over-doing the Dependency Inversion Principle (DIP). DIP tells us that it is better to depend on abstractions than it is to depend on implementation-esque entities. Some people, in statically typed OO languages, reduce it to sound-bite and write code where every single concrete class has an associated interface. You can't really fault them on following directions, but when you look at the resulting mess, it's obvious that they lack judgment. Or, that they don't feel the wiggle room that is necessary to apply judgment. Recently, I've arrived at the idea that we can fix that.
Most pieces of design guidance are presented in a context free style. Essentially, they are stated as ideals - "a good class in OO should look like this." What would happen if we focused on the problem we're trying to solve rather than the solution?
Imagine that you are working with someone and you see what would classically be called a Single Responsibility Principle violation. It's obvious to you that the class you are working on does several things, and the reason that you can see that is because you have wide experience with software. The person you are working with may not yet have that sort of tuned design sense. What can you do?
Well one thing that you can do is launch into a discussion of Single Responsibility and work through some examples. Your hope at the end of the process is that they've absorbed the idea and, having seen some examples, can apply the concept in new situations. Maybe they can, maybe they can't, but there is a more direct way of approaching the problem. We can simply say "if we were going change this class to do <some new thing> what would we have to change?" Notice that this is completely open-ended. You have a conversation, and in the process you deal with context. You may see that there is no need to split the class, or that in this particular situation you can, but it's not worth it. In any case, you are dealing with something real - a particular piece of code and thoughts about how it has and will change.
It turns out that nearly every design principle or piece of design guidance can be framed as a set of challenges to your code. To approach Liskov Substitution you can ask the question "will a caller be surprised if we replace every object of type A with an object of type B?" We can also approach bigger issues of Open/Closed Principle violations with hypothetical features - "Suppose the business changes in this way and we need to add this feature. What classes will change?" Again, it is all grounded.
I see the use of Design Challenges as a mental hack that is analogous to the move from Test-Driven Development to Behaviour-Driven Development. It's a re-placement of emphasis. I encourage you to try it out. The starting point is to learn how to take things you know about good design and frame them as questions.