OK, this isn’t actually part of my SOLID Principles series (always a pity when your best content is a youtube link) but a response to Ryan’s article on Los Techies. I’ve not really got my head around the way that unit testing works in python, but the I get that absolutely everything is being overrideable on an instance or class basis affects the approach.
Let’s talk about Ryan’s list. Now, he argues that Python offers alternatives for the interface segregation principle, open/closed principle, and the dependency inversion principle. I’m going to argue that the principles are actually the same, even if the practice is different. (Like Ryan’s article, pretty much everything said here applies to any dynamic language, but I’ll talk about Python.)
Python and The Interface Segregation Principle
Well, making everything an interface might seem like a valid solution to the Interface Segregation Principle but it’s a bit weird. As I mentioned in my original article, the whole point is the “fine-grained” part. With Python, the interface that a client consumes is exactly the methods it calls. In that respect, all Python code respects ISP by default.
The potential interface surface is fundamentally flexible. Arguably that’s a problem for ISP: You can always just call another method if you want to. I don’t honestly think it matters though.
Ultimately, I don’t think ISP is changed by Python, it’s just kind of irrelevant, for better or worse.
Python and The Open Closed Principle
Well, the open closed principle is a goal, not a design practice, but let’s take a look at the danger points:
- You can’t have non-virtual methods, so Python wins this hands down.
- Your variable can’t be made too specific, so you’re safe there.
- You can still compare against hard-coded values. It’s just as easy to get this wrong in Python as it is in C#.
- Same holds true for Law of Demeter violations. If you pass the wrong object around, your code will be just as fragile in Python as in C#.
Python certainly reduces the scope for some OC violations, but you’ve still got lots of rope to hang yourself. Think you still need to bear the goal in mind.
Python and The Dependency Inversion Principle
Python doesn’t provide an alternative to the dependency inversion principle, it just looks like it. Now, DI isn’t about using an IOC container (which is a slightly crazy/painful thing to do without a static type system), it’s about decoupling. Now, in Python you can override any function, including a class function (which a C# developer would describe as a static method) so everything’s alright.
Except it isn’t.
Let me give an example. You go around your house welding lamps directly into the plug sockets. This is equivalent to calling Lamp() directly from within the Socket’s code. Now, let’s assume you wanted to change one Lamp to an XBox. Well, you can always monkey patch the method so that it behaves like one. Ugly, but possible. Let’s try something harder: change every Lamp to an XBox. Not sure why you’d want to, but it’s your house: you can just change the class to behave like an XBox. Great.
Until your neighbour comes round and asks why all of his lamps just turned into XBoxes.
Let’s quote Ryan:
Now all calls in this runtime, from any module, that reference the Output class will use XmlOutput or HtmlOutput instead.
Yes, but what if I wanted only half of them? Maybe there’s Python techniques I don’t know about (I’m barely competent in the language) but as I see it, I’m going to need to change the code. I don’t think that dependencies can “always” be injected. They can only be done when it won’t cause damage. In his case, he’s worrying about testability. That’s fine, but we all agree there’s more to DI than testability. You will definitely have fewer obvious problems, but if you don’t pass things in using constructors and use abstract factories, you will still run into code fragility, even in a flexible language.
Python still Needs SOLID
None of this is to disparage Python. It’s a cracking language with a great deal of flexibility and extremely productive. But it’s not the Holy Grail. It’s still perfectly possible to violate OCP and it still can find DI useful.