Jeff Atwood has a pretty good rant and comment trail up on OOP for OOP's sake. I can't say that I have the same problems that he's describing. Heck, I think I'd like to run into people that obsess over OOP.
Other than bumping into a handful of nasty runaway inheritance trees (got one that runs 12 deep at my client), I haven't seen that much OO abuse. What I mostly see in the legacy code I get to take over is almost purely procedural code that's just screaming for some OO structuring and better separation of concerns. My experience is that many developers stop learning about programming as soon as they master if/then and looping constructs.
Some of the complaints Jeff Atwood and the commenters repeat may just be shining some light on the downsides of our mainstream static typed OO languages. Pulling out an interface for only one implementation isn't necessarily done for extensibility, it's probably for testability. That problem goes away with "Duck Typing." Design Pattern abuse was inevitably brought up. Almost as inevitably, someone else brought up the point that many design patterns are just repeated workarounds in our programming languages (C#/Java) that aren't necessary in other languages. There might be some truth to that one.
The handful of times that I have seen terrible Object Oriented designs,
they were caused by adopting a complicated scheme
for extensibility or premature generalization way too early in the development cycle instead of
growing a design in response to continuous learning.
My little drams of advice for this morning?
- Favor composition over inheritance. There are a lot of little reasons, but I'd put forward understandability as the primary reason. Testability and extensibility also come to mind.
- Do learn about Design Patterns, but that also means learning why and why not to use each design pattern, not just the usage mechanics. For that reason, the best patterns book I would recommend is Refactoring to Patterns.
- Avoid speculative abstraction. Every OO abstraction has to earn its right to exist by providing value. Your OO models will generally be better if you keep things simple upfront and only add more abstraction defensively. Complicated upfront designs almost never turn out to be worthwhile. The combination of automated refactoring tools and code written with TDD can push down the mechanical costs of evolutionary design. Might as well take advantage of that.
- Throw away bad abstractions and OO models. Sometimes the very best thing you can do on a project is delete code. All, and I do mean all, of the worst designs took a bad design idea and kept right on going. All it would have taken was for one or two people to sit up straight and loudly say "this just can't be right."
For the record, in English at least, "POO" is just "Plain Old Object" to denote a business object that isn't encumbered by special infrastructure requirements (think EJB's). "PONO" or "POCO" just doesn't have the same ring to it as "POJO" does in the Java world. Although I'll admit that I do like a couple of Poco songs, but that's nothing to be proud of.