foundev.github.io

Take 3: Python, ISP, IoC, and OCP need a fundamental rethink.

In response to Julian’s thoughtful Dynamic Languages and SOLID Principles I’d have to argue he is about 95% there but is missing the last critical links needed to view this in a whole different light. For ISP, Julian says it all:

Ultimately, I don’t think ISP is changed by Python, it’s just kind of irrelevant, for better or worse.

Exactly!  Why even bother mentioning in the context of dynamic languages. A developer that understands it fully and one that does not will code the same way. Whereas LSP is still taught in dynamic languages, I’ve never seen it called that but I’ve seen many admonishments about subclass consistency, and not making client code aware of the differences, and is something that requires mentioning. With ISP we could ignore it’s entire existence and developers would code the same.

For OCP:

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.

Bolding emphasis is mine. Enough of OCP is stripped and the primary ways its taught and demonstrated do not apply to the language at all. Focus on Law of Demeter, but having something that stands for “Open for extension” whenever everything is open and “Closed for modification” when nothing is closed would fall on completely deaf ears when presenting any of the associated ideas to a Python developer.

For Dependency Inversion/ IoC:

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.

Ok firstly, I didn’t just mention testability, in fact that the code you’re referencing there was no unit test for, it was about flexibility.  Secondly, call Output an “Abstract Class”,and then call XmlOutput and HtmlOutput concrete types and then add them to an IoC container like Windsor then let me ask yourself the question you asked me “what if i wanted only half of them”.  There would be a need for custom code to make this resolution work for this custom circumstance.  All I’m advocating is taking that same exact custom code and place it somewhere else in Python (yes you can override per method, per instance, etc, etc). 

I think there needs to be a principle that addresses the benefits of explicit interfaces and well formed custom composition cases…and in those cases DI makes a lot more sense, but certainly not as a defacto convention and certainly is not required to provide flexibility to existing implementations.  It certainly becomes apparent when you start looking into the actual way IoC works in static languages its all too similar to how a dynamic language runtime works. That code you’d use in a static language IoC to apply AOP, or custom composition rules, could just as easily be applied in a component in Python, because the primary mechanism for an IoC to work is as a factory, and the benefits and features they bring are all derived from having access to how objects are created and then returned, when you always have access to how objects are created and returned anyway as in dynamic languages, why should you implement it a second time. 100% DI’d code will be more effort to maintain because it would be in C# too…if not for that fact the language is so “structured” that the flexibility and testability benefits gained far outweigh the slightly increased cost (which is only brought down with the awesomeness of auto registering components).  In a language already with flexibility and testability whether you use DI or not AND without auto registering IoC containers the cost to maintain a full DI code base is I’d argue unacceptable.

Summary, for SOLID to make any sense whatsoever in a dynamic context it has to amended and modified to fit that world. Maybe make SOLID into SELL for:

S.RP

E.xplicit empty class for required overrides

L.SP

L.aw of Demeter

Maybe there is something containing other more valid concepts for the dynamic world that I’m probably not qualified to design or come up with. Anyway, I’ve enjoyed the conversation and I hope it brings about a firmer understanding of SOLID, and whatever we’ve not come up with yet for dynamic languages.