Quality by Design
Creating robust, extensible, maintainable applications is simple. But, as everyone knows, simple doesn't mean easy. Developing great software requires a lot of thought, and taking the time to think through issues is not easy when deadlines loom. Paradoxically, incremental development helps developers find time to think through design issues, because it familiarizes developers with deadlines. By providing a regularly scheduled stream of deadlines, incremental development enables developers to become much better at estimating what they can achieve in a short period of time. This in turn prevents developers from over-committing, so they have more time to think about the implications of their design decisions.
As soon as the pace of a project is reduced to give developers time to think, ignorance becomes less of an issue because developers have time to ask questions and to learn. This won't happen immediately, though. The first few increments are typically fraught with strife as developers learn how to do incremental development.
One really common issue is that few developers have much experience with incremental design; even fewer have experience with incremental requirements capture. The idea that it's possible to gracefully and seamlessly add functionality to an existing application just doesn't seem feasible. Indeed, many people are still arguing that incremental development is not feasible and/or not economical, even though practically all software is developed incrementally (nearly every successful version 1.0 release is eventually followed up with a new version).
Seamlessly integrating new functionality into existing applications means that developers have to think about how extensible their software is. Rather than the usual practice of hardcoding everything and liberally scattering design decisions throughout the code, developers have to think about encapsulation and controlling visibility of design decisions. Once this has been done, it's surprising how robust an application can become merely from the fact that every design decision is implemented only once. The result is code that's also easier to maintain, because when a change is made the same change doesn't have to be repeated in many different places within the same application.
Yes, developers have to think continually about refactoring, but this is just the natural price of incremental development. As long as developers clean up after themselves by refactoring, the code won't end up a putrefying mess that nobody can understand. Some teams delay the cleanup until their code is quite messy, others clean up after themselves daily, but the fact remains that the cleanup is necessary to avoid rotten code.
No matter how good the original design for the software was, unless you design your development process to allow time for maintaining the quality of the code, eventually you will hit problems. Avoiding rotten code is simple; all you have to do is allow enough time for the developers to learn what quality looks like for their application. Once they've learned that, they'll keep their code clean because they'll see that there's a viable alternative to struggling with rotten code.
The real paradox in all of this is that haste slows down development. In order to get rapid delivery of great software, teams have to proceed at a relaxed, unhurried pace.