Let's assume for a moment that the "goodness" of a design is inversely proportional to the cost of change. In other words, if a team makes a change to some software and that change is easy to make, the design is "good." If the change is hard to make, the design is "bad." (Let's also assume that the software works, is scalable, performant, etc: we're talking about internal quality here, not external quality.)
An interesting side-effect of this definition is that the quality of a design is context sensitive. It depends on the feature you're being asked to implement and the team that's doing the implementation. A design that's good for an expert team isn't necessarily good for a novice team, and a design that's good for one kind of change isn't necessarily good for another.
Pretty straightforward stuff, perhaps even obvious. Now let's talk about what this means for design. Most people, when they think of design, think of predictive design. In predictive design we:
anticipate future needs,
predict how the design will change,
and invent a design that can accomodate those changes easily.
Because predictive design focuses on predicting the future, it values experience and forward thinking. To become better at predictive design, people study design patterns, which at their best are the condensed wisdom and experience of dozens of programmers. When utilizing predictive design, designers will think about what is likely to change and create abstract classes, interfaces, and plug-in points to allow programmers to easily add classes to support specific kinds of new features.
There's another side to design, though, one that experienced designers use every day. We just don't talk about it much. Reflective design - In reflective design we:
analyze existing code,
identify design flaws,
and fix them using refactoring.
Reflective design focuses on analysis and modification of existing code, so it values code clarity and refactoring. To become better at reflective design, people study code smells, which at their best are concise heuristics for recognizing design flaws. When utilizing reflective design, designers will create simple code that has no unused infrastructure and eliminates duplication, allowing programmers to easily modify existing classes to support arbitrary change.
Two Sides, One Coin: These two approaches are not only compatible, they can be used concurrently. A project can use predictive design at the high level while simultaneously using reflective design at the low level. According to Lean Software Development, Raymonde Guindon studied design in 1990 ("Designing the Design Process") and found that experienced designers constantly reviewed and modified their design as they designed an elevator control system. This matches my experience with programmers as well. I would argue that programmers have always used reflective design.
Neither one of these approaches is necessarily better than the other. Both predictive and reflective design are equally valid approaches to design. Good designers use the same underlying heuristics to judge "good" design regardless of the approach they're using.