There is a fine line between building a maintainable architecture and over-engineering. I think experience of being at both ends of the spectrum can help you "feel" the right level of generalization.
At first your code is an unmaintainable cesspool of special cases, then you educate yourself "proper" design and compulsively try to make your code as uniform as possible and you end up with a cesspool of design patterns. After being on both sides a few times, you should have a better idea of what's worth generalizing in a specific domain.
This would be a much better article if everything before "But Seriously" was omitted.
Much of the first half is a barely related ramble that could be argued either way depending on context. For example, Angry Birds is an excellent game comprised of the same code running different data for its many many levels.
The skill in these cases is knowing when to add exceptions and when to invest in re-factoring your code to work for the general case. I'm getting better at it, but I've generalised too early in some instances and left exceptions which soon became messy in others.
Another very good posting from coderoom.wordpress.com. I like it especially because this way of thinking is uncommon in the industry but very important.
At first your code is an unmaintainable cesspool of special cases, then you educate yourself "proper" design and compulsively try to make your code as uniform as possible and you end up with a cesspool of design patterns. After being on both sides a few times, you should have a better idea of what's worth generalizing in a specific domain.