I’ve given a lot of thought to the notion of boundaries in software lately, ever since I watched this excellent talk titled “Boundaries” by Gary Bernhardt at Ruby Conf 12 a few months back. It spurred in me a new appreciation for boundaries in software, particularly as it relates to design and testability, imperative versus functional approaches, etc.
Largely for amusement, but also as an act of gentle self reinforcement, I reread Erik Dietrich’s colorful blog post titled “Visualization Mnemonics for Software Principles” that is a great overview of SOLID principles and Law of Demeter. It struck me that there is a common thread across all of these principles: boundaries.
Each principle more or less establishes a boundary, and instructs how it should be respected.
First, there’s the Law of Demeter. It basically states that you shouldn’t hand over more information to a method or component than is necessary for that method or component to function. The invocation of that method or component is the boundary, and at that boundary, provide only what is necessary.
This continues with the SOLID principles.
The Single Responsibility Principle, or SRP, is pretty self explanatory. A component/class/function/whatever should only do one thing. This essentially promotes composability, where you can assemble a larger thing that does many things from smaller, singular pieces. When SRP is violated, boundaries between responsibilities is blurred.
The Open/Closed Principle states that components should be open for extension, closed for modification. It’s describing a boundary. Here’s this component that may or may not have multiple responsibilities, but you aren’t permitted to meddle with those responsibilities directly. Instead, a specific interface — a boundary — is provided that allows you to alter the overall behavior by extension, preserving the default behaviors.
The Liskov/Substitution Principle, which is pretty specific to OOP, says that all derived types should be able to act as stand-ins for their ancestors. When this principle is violated, you end up with a derived object that only appears to be like all the others. It’s a boundary-within-a-boundary, wherein the imposter derivatives are disrespecting the boundary its ancestors have established.
The Interface Segregation Principle favors smaller, more digestible interfaces instead of larger, heavier ones. In a way, it’s just applying SRP to interfaces. It also has a Law-of-Demeter feel to it, given that smaller interfaces require less overall definition to be satisfied. This principle is reinforcing boundaries between responsibilities.
The Dependency Inversion Principle, which is practically at odds with encapsulation, calls for components to code against abstractions rather than the concrete. It forces a boundary where perhaps there previously was none. Instead of a component taking responsibility for instantiating dependencies, there’s a boundary where, abstractly, that dependency can be supplied, or “injected.”
Another boundary-oriented principle that is familiar to many, but is not a member of the SOLID elite, is Don’t Repeat Yourself, or DRY. It is, I think, oft misunderstood, as it is applied literally by squashing code duplication. But, it can and should be applied more generally to concepts. By consolidating a concept into a single place, be it a component or function, you’re establishing a firm boundary around it. When a concept is scattered about, the boundary is once again blurred.
These widely accepted principles are hardly orthogonal; they are bound by boundary.