Where Does This Ocean Go?
There are two particular problems I've seen with various upfront designs. The first is inflexibility, instead of realizing that user requirements will change through the course of the project, the initial designs were highly detailed, in fact, they pretty much laid out the entire structure of the code for you, minus some logic. The diagram solved the problem of the user requirements as set forth initially. The problem here is that when the requirements change you will be hard pressed to adapt to them. Your diagrams will quickly become out of date, and your code, if you've produced any, will be extremely hard to alter to meet these new requirements.
The second problem is equally as serious, you've designed for ultimate flexibility. Here we have an issue of complexity. You can modify the system to an extreme extent, so in theory you can adapt to any changes the customer makes. In practice you find the code to be just as impossible to change as before due to the increased complexity of the systems. Dependencies build up, and things don't work as simply as they could. You have ultimate flexibility, but it's so complex that using it is unwieldy. In this case you often find a lot of various patterns (such as decorator and visitor) have already been built into the system to add this extra flexibility.
So how can we solve this conundrum. I mean, for all intents and purposes it would appear to be a catch 22. But we do have a solution. It's the ability to apply abstraction. Now, when I say that, most people will start to think of inheritance hierarchies and interfaces. That's not what I'm talking about. Remember, in this case we aren't dealing with code but with designing before we code.
The idea is to diagram the systems, and how you think they will interact, but to avoid the minor details that will end up being refactored into or out of the system. Yes, user requirements will change, in fact we expect them too. But instead of designing for it, we design for the present but at a sufficiently abstract level that when the changes do come in we can simply perform some minor refactorings and the changes will be implemented. The diagrams then become much simpler to keep up to date, and our code will end up much cleaner too. There are exceptions to this, of course. One particular one that comes to mind is the case of knowing that a requirement is going to change, and how it will change. In this case you should design for it ahead of time, it will save you some time refactoring later on.