Although the book is totally focused on Ruby, the OO practices it presents are easily applicable to other OO languages, including static languages like C#. This makes it one of those timeless books you can be happy to have on your shelf knowing it's not going to be outdated in a year. I highly recommend it!
I intend to write a few posts highlighting some of the good ideas that struck me the most from this book, but in this post I just can't help but take a few shots at it's treatment of static vs. dynamic languages.
My programming language lineage started by dabbling in C, then taking classes in C++, followed by Java, and finally C#. Most of the real world code I've written has been in static languages, and I've been programming professionally in C# for the last 8 years. This makes me a static language guy.
When I learned ruby, about 5 years ago, I fell in love with it's clean syntax and amazing flexibility. I wrote a few simple tools in it for work, and I've written alot of rspec/capybara tests, plus I dabbled a bit with Rails. I feel I have a decent understanding of the language, but I'm by no means an expert and I definitely still think in classes and types.
I tell you this to explain where I'm coming from. Static languages are what I know best and are what I'm used to. I'm not a dynamic language hater, I'm just comfortable with static langs. Which brings us back to POODR.
POODR talks a lot of about "Duck Types" which are defined in the book as:
Duck types are public interfaces that are not tied to any specific class. These across-class interfaces add enormous flexibility to your application by replacing costly dependencies on class with more forgiving dependencies on messages.I was surprised at this definition because it describes the "Duck type" as being a thing, but in Ruby there is no thing that can represent this across-class-interface. Most treatments of duck typing from Rubyists I've seen usually just talk about how it's a feature of the dynamic nature of the language. They talk about "duck typing" but not "duck types."
In C# we have interfaces, which can be used as explicitly defined duck types. The Dependency Inversion Principle and the Interface Segregation Principle are both trying to get you to use interfaces in this way, instead of just as Header Interfaces. It's good OO because it focuses on messages instead of types. As POODR says, "It's not what an object is that matters, it's what it does."
I think there is a lot of power in Ruby's implicit "duck types," but I also think the lack of explicit interfaces is a serious liability, and I was very entertained by how many hoops POODR jumps through to try to work around this problem, all while trying to claim that it isn't a problem at all, and in fact, it's great!
At the end of Chapter 5, there's a section that tries to convince you that Dynamic typing is better than Static typing. Unfortunately, it just builds up a straw man version of static typing to make it easier to tear down. What it leaves out is interfaces:
Duck typing provides a way out of this trap. It removes the dependencies on class and thus avoids the subsequent type failures. It reveals stable abstractions on which your code can safely depend.If statics langs didn't have interfaces, this might be true. But they do have interfaces! And worse, interfaces represent a significantly more stable abstraction that is dramatically safter to depend on than these invisible "duck types." POODR demonstrates this itself with examples where the "duck type" interface changes, but not all "implementers" of the interface are updated. There's no compiler to catch this. And standard TDD practices wont catch it either. Your tests will be green even though the system doesn't work. So you have to write manual tests that you can share across all the implementers to make sure the message names and parameters stay in sync. Nearly all of Chapter 9 is devoted to testing practices that simply wouldn't be needed if there was even just a rudimentary compiler that could verify just inheritance and interface implementations.
The lack of explicit "duck types" just seems so problematic to me... Keeping them in sync is a chore, and a potential source of error. The worst kind of error too, because the same code may work in one context but break in another based on which "duck type" is used.
Another problem I've run into is when trying to understand some code that takes in a "duck type", how do you figure out the full story of what will happen? How do you find all the implementers of that "duck type"? Just search your code base for one of the method names? Try to find every line of code that injects in a different duck type?
Not being able to surface an explicit interface leaves you stuck in a situation where you have to infer the relationship between your objects by finding every usage of them. Seems like a lot more work, as well as being a recipe for tangled and confusing code.
So what do you think Dynamic language people? Am I making a bigger deal out of the problems of dynamic typing just as Sandi made a bigger deal out of the problems of static typing? Is this just a lack of experience problem? Do you just not run into these issues that often in real world usage?
Here's an interesting presentation by Michael Feathers about the power of thinking about types during design. I felt like it had some relevance to the conversation here.