Solid Is Not Solid
Solid Is Not Solid
1
One thing that’s great about programming is that it has a pretty
low barrier to entry. Anyone who has access to a computer can
learn to program. Despite that, the field has always been rife with
gatekeepers who try to describe what real programming is. But,
those gates slowly are coming down and it turns out that lots of
people are good at programming. Even computer science majors.
This has a downside, though.
That downside is opinions, hot takes, thought leaders, conference
speakers, and book authors2 all giving their opinions about their
experience and trying to generalize those into rules about how to
be a good programmer. . . all without any real evidence other than
survivorship bias.
it makes stored procedures look like Elixir. Debugging it makes you pine for an
error message as straightforward as “undefined is not a function”. That said, there’s
really nothing better after all this time.
4 It’s actually illuminating when you come to understand and accept that al-
most everything you hold dear about programming has no scientific basis for
being useful. Pairing, TDD, MVC, Rails, Style Guides, Cucumber, and Functional
Programming have never been evaluated as to their fitness to produce good soft-
ware in any real way. Vim is the only thing proven to make you awesome. I can’t
find the paper right now but I’m positive vim has been double-blind control-trialed
many, many times. Trust me.
2
So this brings us to SOLID5 .
tainable, Slow, and Heavy software. All respect to Smalltalk, but that was just never
gonna work out.
3
a fundamental truth or proposition that serves as the foun-
dation for a system of belief or behavior or for a chain of
reasoning.
Fundamental truths, these are not. Pretty much all they say is
this:
to know of him and it’s a bit more amusing to refer to him that way.
4
thority, since so much of what he had written about had validated
my own experience. And there’s a core to Uncle Bob’s message
over the years that is good. He clearly cares very deeply about
writing good software and helping others to do the same.
And because of this, along with his major impact on object-
oriented programming, his ideas are not only worth criticizing,
but I think they must be criticized and examined. Someone in
power must have their ideas challenged or things get bad. Heck, I
dream of the day someone learns enough LaTeX to criticize my
work!
But that’s all I’m aiming to do: criticize ideas.9 I don’t know Un-
cle Bob at all and I assume that all of his work over the years
comes from a place of good intentions. But even if it doesn’t, it
doesn’t matter. This is about some of his ideas and how they are
interpreted, not about him.
So, without further ado, let’s dig into the Single Responsibility
Principle, also known as the Why Did You Deign To Write More
Than One Line of Code In A Method Principle.
9 This isn’t all Uncle Bob. Some of his contemporaries get some drive-bys after
I take down SOLID, because shit like YAGNI and KISS is a bit problematic. I also
promise that if this book sells well, the follow up, “XP: The Way to Motivate Little
Children To Close JIRA Tickets At A Big Horrible Technology Company Run By
People Who Do Not Understand Technology”, will be dedicated to Kent Beck. He’s
given us some real doozies over the years.
5
2
Single Responsibility
Principle, or: Chasing
Three Lines of Code
Over 18 Files Just to
Make One Change
1 [Link]
7
A class should only have a single responsibility, that is, only
changes to one of the software’s specifications should be
able to affect the specification of the class
a comma.
8
a web form. This controller will indicate which form fields are
allowed, it will validate the values, and then either save the record
or re-render the form to show the user what they screwed up.
We’ll use the extremely common domain that everyone is familiar
with instinctively, which is managing a database of professional
wrestlers.3
Here’s a controller for saving a new wrestler:
class WrestlersController
def create
@wrestler = [Link](wrestler_params)
if @[Link]?
redirect_to wrestlers_path
else
render :new
end
end
private
def wrestler_params
[Link](:wrestler).permit(
:name, :hails_from, :finishing_move, :weight)
end
end
If you look up “vanilla” in the dictionary, you will find this con-
troller method. It’s exactly how Rails wants you to work and it’s a
pretty focused routine.
Does it violate the Single Responsibility Principle? The answer
depends on how we define “responsibility”. If we say that the re-
sponsibility of this controller is to manage the workflow of making
new wrestlers, then this controller passes with flying colors!
But, we could also say that this controller validates parameters,
interacts with the database, and handles user view rendering.
3 I’m almost certain that top wrestling leagues use software like this to keep
track of their roster. You can be sure if your local promotion is struggling, it’s
absolutely due to a lack of software. I mean, what problem isn’t due to a lack of
software, amirite?
9
That is three responsibilities which, to be very explicit, is more
than one. A violation!
This is the problem with these “principles”. They tell you what to
do with no nuance, no context, and no freaking backstory. What
problem are we even trying to solve by counting responsibilities?
The problem we are solving is that we want our code to be easy to
change when we get new requirements.
class WrestlersController
def create
@wrestler = [Link](wrestler_params)
if @[Link]?
→ FrontOfficeMailer.new_wrestler(@wrestler).
→ deliver_now!
redirect_to :index
else
render :new
end
end
private
4 [Link]
5 Probably why Rails doesn’t scale.
10
def wrestler_params
[Link](:wrestler).permit(
:name, :hails_from, :finishing_move, :weight)
end
end
11
The clue is the phrase “strength of the relationship between the
methods and data of a class and some unifying purpose or con-
cept”. This is what we’re aiming at.
A cohesive class is one in which its contents are unified around
some concept. Maybe we could call this a “responsibility”, but I
like to use words in the right way, since words have, you know,
meaning and stuff.
“Responsibility” is just not the right word for what we are talking
about here (neither is “reason to change”). In fact, any code review
where the Single Responsibility Principle is invoked makes this
problem plainly clear, because the developers stop talking about
the code and start talking what a responsibility actually is.
It’s not surprising that the consultants who signed the Agile Mani-
festo might not have actually run across this problem. The power
dynamics in play whenever an expert is brought in make it hard
to have a real discussion. I mean, who better to tell us what is
and is not a responsibility than the guy that coined the principle,
right? But it doesn’t change the fact that if you smooth out the
power dynamics, yelling slogans and proclaiming the answers by
fiat just doesn’t work. You actually have to discuss the code itself!
Anyway, why is it bad if our code isn’t cohesive?
It depends on how it’s not cohesive, and there are two ways this
happens. Code can either lack cohesion, in which case a single
concept is spread out all over the place, or it can be incoherent,
where a single blob of code ensconces multiple intermingled
concepts.
It’s the second problem that the Single Responsibility Principle
wants to solve, but it usually does it by aggressive decoupling,
thus creating the first problem.
12
So look, ye, upon our Rails controller, and know that it is bad,
for it has too many responsibilities, since it validates parameters,
writes to the database, and yet also routes users to a view based
on the validity of their form submission.
Let us now separate these woefully disparate concerns so that our
code might become maintainable.
First, we’ll extract that pesky routing logic into a WrestlerRouter
class.
class WrestlerRouter
def new_wrestler(controller, wrestler)
if [Link]?
controller.redirect_to :index
else
[Link] :new
end
end
end
We’ll then remove all that nasty code about actually creating a
wrestler from parameters into a WrestlerCreator class.
class WrestlerCreator
def create(params)
[Link](wrestler_params(params))
end
private
def wrestler_params(params)
[Link](:wrestler).permit(
:name, :hails_from, :finishing_move, :weight)
end
end
Armed with these two classes that each have only one reason to
change, we can recreate our WrestlersController.
13
class WrestlersController
def create
@wrestler = [Link](params)
[Link].new_wrestler(self,@wrestler)
end
end
class WrestlerRouter
def new_wrestler(controller, wrestler, on_valid=nil)
if [Link]?
→ unless on_valid.nil?
→ on_valid.(wrestler)
→ end
controller.redirect_to :index
else
[Link] :new
end
14
end
end
class WrestlersController
def create
→ on_valid = ->(wrestler) {
→ FrontOfficeMailer.new_wrestler(wrestler).
→ deliver_now!
→ }
@wrestler = [Link](params)
→ [Link].
→ new_wrestler(self, @wrestler, on_valid)
end
end
I bet you didn’t think we could make the code worse, did you?
This is just. . . eck. Instead of a design where a simple change
required the addition of single line of code, we adhered to the
Single Responsibility Principle and created a situation in which a
simple change required changing the public API of a class (which
you’ll discover in the next chapter is not allowed), and then adding
several lines of code overall.
The reason is not because we miscounted responsibilities. It’s
because our design of a “bunch of small single purpose classes”
lacked cohesion. None of the classes we created represents a
full concept, based on the current definition of those concepts.
Creating a wrestler is not complex, so why did we make it that
way?
We did it because Uncle Bob says we must have only one reason to
change, but he did not define what a reason is, so we made a very
reasonable assumption and created a set of pure nonsense that
junior developers will copy and senior developers will curse and
all because we did what some principle said because principles
15
are there for us to follow and not guidelines which would invite
thinking through nor are they design patterns which are usually
clear about when and how they should be applied so that we can
make sure we are balancing all the right tradeoffs and not just
appealing to authority.
We could’ve prevented this with two simple questions:
In our case, the original code was short and cohesive; it imple-
mented the entirety of wrestler creation. The change to send an
email didn’t affect its cohesion, so we should’ve just done that.
Granted, cohesiveness is hard to quantify as it requires alignment
across the team about what a concept is. Keep in mind that “con-
cept” here really is a “domain concept”. If the team isn’t aligned
around that, you have much bigger problems.
But why would Uncle Bob create this principle? He’s not stupid
and has a ton of experience that we can’t just ignore. He wouldn’t
just make up nonsense.8
Imagine that we continue development and keep getting new
requirements over a long period of time. We address them one
line at a time, as we did with the first new requirement. Even if
that code is all part of wrestler creation conceptually, there’s a
point at which there is just too much code.
Would this be when to count responsibilities?
No. We can still use cohesion to get a clearer understanding.
16
• The front office must be notified on all new wrestlers. . .
• except if that wrestler is from Canada. In that case we notify
our Canadian office. . .
• unless that wrestler is under 200 pounds (14.3 stone). In this
case we notify the cruiserweight division. . .
• unless that wrestler has no finishing move. In this case we
notify the developmental territory.
class WrestlersController
def create
@wrestler = [Link](wrestler_params)
if @[Link]?
if @wrestler.finishing_move.blank?
DevelopmentalMailer.new_wrestler(@wrestler).
deliver_now!
elsif @[Link] < 200
CruiserweightMailer.new_wrestler(@wrestler).
deliver_now!
elsif @wrestler.hails_from =~ /canada/i
CanadianOffice.new_wrestler(@wrestler).
deliver_now!
else
FrontOfficeMailer.new_wrestler(@wrestler).
deliver_now!
end
redirect_to wrestlers_path
else
render :new
end
end
private
def wrestler_params
[Link](:wrestler).permit(
:name, :hails_from, :finishing_move, :weight)
17
end
end
Wow, it’s not looking so good, but it’s still all unified around this
concept of creating wrestlers. So it’s cohesive, but it’s also complex.
What do we do?
Cohesion is about concepts and they can be fractal. We can now
pretty clearly see that the concept of emailing someone about
new wrestlers has gotten complex. It stands on its own.
We shouldn’t change it now just because it looks gross, but if we
get another requirement around sending emails, that might be a
time to think about changing things.
So let’s suppose we get such a change. The developmental ter-
ritory needs to know about wrestlers that hail from “Parts Un-
known”, because even for professional wrestling that is a bit of a
stretch.
Knowing that our controller method is incoherent (multiple
fleshed-out concepts are intermingled), and with the need to go
into the code and change it, now is a good time to refactor. We
just have to do it such that the resulting code is cohesive. The
thing we want to avoid is decoupling so much that we lose all
cohesion.
You might be thinking we should’ve done this earlier. You might
even think if we had done it from the start we wouldn’t be in
this pickle. But we didn’t know how many of these changes were
coming down the pike, so we did the best with what we knew at
the time. Maybe the fourth requirement should’ve triggered the
refactor we’re about to make. I like to look for patterns on the
third such data point, but you do you. Just remember that one
data point doesn’t represent a trend.
Let’s extract all that email logic into a NewWrestlerEmailer:
class NewWrestlerEmailer
def new_wrestler(wrestler)
if wrestler.finishing_move.blank?
DevelopmentalMailer.new_wrestler(wrestler).
deliver_now!
18
elsif [Link] < 200
CruiserweightMailer.new_wrestler(wrestler).
deliver_now!
elsif wrestler.hails_from =~ /canada/i
CanadianOffice.new_wrestler(wrestler).
deliver_now!
else
FrontOfficeMailer.new_wrestler(wrestler).
deliver_now!
end
end
end
class WrestlersController
def create
@wrestler = [Link](wrestler_params)
if @[Link]?
→ [Link].new_wrestler(@wrestler)
redirect_to wrestlers_path
else
render :new
end
end
private
def wrestler_params
[Link](:wrestler).permit(
:name, :hails_from,:finishing_move, :weight)
end
end
With this refactor, we have the same size of change to account for
wrestlers hailing from parts unknown, but we now have a nice
19
cohesive piece of code that has the email logic. . . and nothing
else9 .
These two classes don’t lack cohesion, as they represent two well-
defined concepts on our domain: creating wrestlers and emailing
someone when they got created. And neither class is incoherent,
since they only have code about their respective concepts.
So did we just create the “Single Concept Principle”? And isn’t all
this what the Single Responsibility Principle was trying to tell us?
they are also pretty straightforward, and you know they work. You Java developers
are probably cool right now, but you Rubyists, I just know you want to meta-
program those if statements away. Admit it. I feel it, too. Resist. if statements are
nice if you just get to know them.
20
mostly because they are harder to understand and more useless.
In my experience, the Single Responsibility Principle is the only
SOLID principle anyone bothers with, and after everyone’s argued
out about what is and is not a responsibility, there’s no time left
for anything else.
21
3
Open/Closed Principle
or: The Test Of Have You
Actually Read SOLID
The Open/Closed Principle tells us—directly and in no uncertain
terms—that you cannot change source code.
You think I’m kidding? The principle states that classes1 should
be “open for extension, but closed for modification.” Here’s how
Uncle Bob defines a class that is “closed for modification”2 :
You want to know the truth about the solution or have you always
known? You’ve just hidden it away. You know the truth. Say it.
1 Remember, this is a principle so the implication is not some classes, but all
classes.
2 [Link]
om/resources/articles/[Link]
3 According to the dictionary this word means “free or safe from injury or
violation”, which is pretty strange way to talk about source code. One wonders
where Uncle Bob got his thesaurus from.
23
3.1 Inheritance
According to the principle, “abstraction is key” in dealing with
classes whose source code no one is allowed to change. We must
create a class that has all the necessary extension points so that
we can use inheritance to override its behavior, and then we must
never ever use the class directly, but instead rely on an abstract
base class.
Let’s see an example. Suppose we need to know which wrestlers
are hard workers and which aren’t. We can do that by averaging
up the length of their matches. Anyone who averages more than
20 minutes is a hard worker.
We’ll use a generic class called Averager to abstract the logic of
calculating an average.
class Averager
def average(list_of_numbers)
total = 0
list_of_numbers.each do |number|
total += number
end
total / list_of_numbers.size
end
end
class Wrestler
def hard_worker?
average_match_length = [Link](
[Link](&:length))
average_match_length > 20
end
end
24
addition-as-a-service startup and it was from the same venture
capitalist that provided us our seed round and now we have to
integrate with them so that Addr can put our logo onto their
homepage and make the board think they are doing a good job at
enterprise sales?
Think of the horror of having to modify this source code!
Fortunately, we can apply the Open/Closed Principle to fix this
egregious design mistake.
First, we’ll need an AbstractAverager that has all the flexibility we
could ever need. Ruby doesn’t have abstract methods, but I’ve
added them as documentation so future SOLID developers know
how to extend things (I’m not a monster).
class AbstractAverager
def average(list_of_numbers)
initialize_total
iterate_over_list(list_of_numbers) do |element|
add_element_to_running_total(element)
end
compute_average(list_of_numbers.size)
end
def initialize_total
raise "Implement this to initialize your total counter"
end
def iterate_over_list(list_of_numbers)
raise "Implement this to yield each number. " +
"_each_ number. Get it?"
end
def add_element_to_running_total(element)
raise "Hey, I like +, but maybe you like +=. " +
"Or maybe you like tight loops and bit " +
"shifting. We push up a big tent here."
end
def compute_average(list_size)
raise "Don't let those pesky data scientists " +
25
"tell YOU how to average numbers. This " +
"is DEEP LEARNING here!"
end
end
With this clean abstraction, we can now create the dirty concrete
implementation that hopefully we won’t have to touch once we’ve
written it.
def total
@total
end
def iterate_over_list(list)
[Link] do |element|
yield element
end
end
def add_element_to_running_total(element)
update_total(total + element)
end
def update_total(new_total)
@total = new_total
end
def compute_average(size_of_list)
total / size_of_list
end
end
class Wrestler
def hard_worker?(averager)
average_match_length = [Link](
26
[Link](&:length))
average_match_length > 20
end
end
CD-ROM/dp/0136291554/ref=sr_1_1
27
Chance on a Stairway” but also hoping their C library would now
be built and ready to be copied to a CD-ROM for distribution.5
Having to repeat this cycle was expensive, so it makes sense to try
to think through ways to deal with the need to extend code that
you had stamped into a spinning rusted metal disk four states
away without having to recompile and redistribute the entire
thing.
Nowadays, this isn’t quite the problem it was then. Your aver-
age JavaScript project can literally use every single version of the
isarray module6 at the same time and there’s no problem7 .
To make a long story short, Meyer was trying to find a way to
manage versioning of shared libraries in a compiled pre-Internet
world in which his product was code libraries that people paid for
and installed on disks manually. That is not a problem we have
today. Did you know that some languages aren’t even compiled
anymore?
download, but I read somewhere that network bandwidth is infinite (at least in
Menlo Park), so the way Node does package management is fine. Totally fine.
28
So yes, consider versioning issues when making changes. Con-
sider who is using your code and by all means try to make back-
wards compatible changes. But don’t add in tons of flexibility the
first time just because you might need to change stuff later. You
aren’t gonna need it.
29
4
Liskov Substitution
Principle or: Don’t Use
The One Thing
Stroustrup Actually
Didn’t Want in C++
Anyway
I’m starting to think Uncle Bob likes inheritance. The Liskov Sub-
stitution Principle takes a computer science paper about subtyp-
ing, and, over 11 pages, tells us not to look at the type of objects
at run-time or bad things will happen.
I’m inclined to just stop here, because in what world is this such a
problem that it needs a principle to help us avoid?
That said, I do need to bail out Barbara Liskov, whose name got
attached to this principle. Unlike us programmers, Barbara Liskov
is a legit computer scientist. She does real research, writes real
papers, and won the Turing Award! We owe it to her to explain
how her name got on this, because she did not win the Turing
Award for this principle.
31
4.1 Papers We Love But Have Not Really Read
Barbara Liskov and Jeannette Wing authored a paper1 that ex-
plores the definition of subtypes as it related to program correct-
ness. They state that if an object y has all the properties of object
x, then we can safely use y anywhere we use x and that y is a
subtype of x. If y does not have all the properties of x, it is not a
subtype of x.
Neat.
Most programming languages implement a somewhat. . . looser
definition of subtyping. Java, for example, allows any object y
to be used in place of any object x so long as y’s class inherits
(directly or transitively) from x’s, or if x and y implement the
same interface. In Ruby, pass whatever to whatever, it’s cool, don’t
worry about it, we have duck typing and if it quacks like a monkey
with a duck taped to its stomach then everything will work fine
until Rails 6.2 which deprecates taping, but you can install the
rails-duck-taping gem to get that feature back.
So what does this have to do with object-oriented design? Not
much.
class WrestlingEvent
# ...
end
1 [Link]
32
class HouseShow < WrestlingEvent
end
def spam_social_media(events)
[Link] do |event|
if event.kind_of?(HouseShow)
[Link] do |influencer|
authentically_post_to_the_gram(influencer,event)
end
elsif event.kind_of?(TVTaping)
ask_russia_to_put_it_on_facebook(event)
elseif event.kind_of?(PPV)
local_sportsball_games.each do |game|
run_tv_ad(game, event)
end
end
end
end
om/resources/articles/[Link]
33
Functions that use pointers or references to base classes
must be able to use objects of derived classes without know-
ing it.
Pointers? References? It’s like 1996, and we’re all doing the
Macarena again3 . It also doesn’t say anything about the actual
problem outlined in the 11-page paper. Why can’t these goddamn
principles just come out and say what they mean? Ugh.
The paper, after outlining the problems with basing logic on run-
time types, goes seriously off the rails with a convoluted example
using a Square class that extends Rectangle and in the end there
is no actual advice on what problem we were solving or what to
do.
While the Open/Closed Principle was a test of how well one ques-
tions authority in the face of utter nonsense, this principle exists
to provide the “L” needed to make the SOLID acronym work. Poor
Wing never had a chance.
3 Yes, I know that Go has these things and it’s modern language. I like to think
34
5
Interface Segregation
Principle or: The Reason
Functional
Programmers Are So
Damn Smug
The Interface Segregation Principle states that “no client should
be forced to depend on a method it does not use”. Do we get a
definition of “forced” or “depend”? No we don’t.
Much like the Single Responsibility Principle, the draconian abso-
lutist wording of this only creates problems. Fortunately, it only
creates problems in a static language like Java or Scala. If you
have to work in those languages, you have much bigger problems
than SOLID.1
For completeness though, let’s see what the hubbub is about.
the 90’s? And this is what powers the Walden Books competitor where we run our
virtual servers? What a time to be alive.
2 [Link]
0MzFjLWJjMzYtOGJiMDc5N2JkYmJi/view
35
The ISP acknowledges that there are objects that require non-
cohesive interfaces; however it suggests that clients should
not know about them as a single class. Instead, clients
should know about abstract base classes that have cohe-
sive interfaces. Some languages refer to these abstract base
classes as “interfaces”, “protocols” or “signatures”.
What this is saying is that if, for some reason, we need to have a
class defined with a ton of methods that are not cohesive (which
would be impossible since we are following the Single Respon-
sibility Principle!), and that users of this class only need to use
some of those methods, we should create interfaces or abstract
base classes with only those methods being used so that users can
depend on these cohesive interfaces.
I will now show you where this principle leads and it’s nowhere
good. Remember, I have read the backstory of this principle and
came up empty. This means I know enough to ignore it, but I
guarantee most developers haven’t done that, and will follow the
principle as stated.
What they will do is make every single method its own interface.
I’m not joking.
For this principle, we can’t use Ruby, because Ruby has no way to
force dependence on anything, and by default all Ruby code com-
plies with this principle (or none of it does, either way, Rubyists
can safely ignore this since they totally can’t follow it).
We have to use Java, but hey, we’re all about polyglot and using the
right tool for the right job, and there’s no better tool for outlining
36
the complexity of static typing than Java.3
In our Java application, we have a database of wrestling
matches, and we need access to that database. We’ll create a
MatchRepository class like so:
class MatchRepository {
public Match load(int id) {
// ...
}
We want to write some code that saves a match once it’s been
completed. That looks like so:
Do you see the horrible design flaw in our code? You might be
thinking that MatchRepository has a pretty cohesive interface and
3 Yes, I’m aware of Scala, but it’s so batshit that you wouldn’t be able to learn the
complexity since you’d be constantly trying to google what that stupid underscore
means. Your eyes would start to glaze over as you suddenly find yourself sitting
between someone who desperately wants to use Haskell at work, but can’t, and
someone who realized that you can’t get six figures writing research papers about
category theory and had to go work at some fintech startup instead.
37
so what is the problem? You clearly don’t see the deep need to
segregate interfaces!
Remember, the principle states that “Clients should not be
forced to depend upon interfaces they do not use”. Do you see
MatchCompleted calling search or load? I don’t. How dare we force
that poor class to depend on those interfaces!
The solution—and I have absolutely seen this done as a direct
result of the Interface Segregation Principle—is to create single-
method interfaces that the implementation class implements.
interface MatchLoader {
public Match load(int id);
}
interface MatchSaver {
public void save(Match match);
}
interface MatchSearcher {
public Set<Match> search(String query);
}
// code as before
}
38
→ private MatchSaver saver;
If we read the text of Uncle Bob’s paper (which, you might no-
tice, only exists online as a Google Doc that someone has thank-
fully linked to from Wikipedia making it not exactly accessible to
someone who wants to really understand it4 ), it does talk about
cohesion. And if you really think about it, there wasn’t a real rea-
son to break up the MatchRepository interface since it was pretty
cohesive. But that’s not what the principle says.
Perhaps it could’ve said “Classes should ideally depend on cohe-
sive interfaces” or maybe even “Interfaces should be cohesive”. It
even provides the precious “I” we so desperately need to make
the acronym work!
What I’m left with here is just a convoluted re-statement of think-
ing about cohesion. If developers just focused on that, they don’t
need this strange principle to guide them (especially given that it
guides them in the wrong direction). And yes, I have totally seen
code like the above. It was so dumb!
4 And
if they did, how long do we really think it’s going to be before Google
sends Docs off on its incredible journey into the sunset?
39
6
Dependency Inversion
Principle, or: Why
2000s-era Java Code
Was Mostly Written in
XML
This principle makes me the most angry, because when you dig
into it, it’s all about perpetuating the lie sold to us by object-
orientation, which is that re-use and flexibility are good, desirable,
and possible.
Like all good principles, this starts off with a straw man argument
so transparent it makes me deeply believe in my heart of hearts
that this principle was retconned from how to deal with unit
testing in Java.
In his paper1 , Uncle Bob outlines—for once!—the problems the
principle he’s going to present exists to solve: Bad Design!2
He outlines three aspects of bad design that the Dependency
Inversion Principle will help us avoid.
om/resources/articles/[Link]
2 I didn’t say they were detailed problem statements, just that they existed.
41
2. When you make a change, unexpected parts of the
system break. (Fragility)
3. It is hard to reuse in another application because it
cannot be disentangled from the current application.
(Immobility)
The first two are real problems. And I believe that a focus on
cohesion—and decoupling your implementation when your code
becomes incoherent—can address those two problems pretty well.
And I also don’t think there’s any hard and fast rule to magically
fix those, otherwise we’d have a programming language that does
it. We definitely do not have such a programming language.3
Instead, the paper focuses on the third problem which is, let’s be
honest, entirely invented to make this principle happen. Re-use is
such a disingenuous lie, I’m surprised anyone with any real world
experience still promotes it as a benefit of object-orientation.
void Copy() {
int c;
while ((c = ReadKeyboard()) != EOF)
WritePrinter(c);
}
42
public static void copy() {
Keyboard keyboard = new Keyboard();
Printer printer = new Printer();
int c;
while ((c = [Link]()) != -1) {
[Link](c);
}
}
OK, better. Now, the problem with this, according to the paper,
is that while [Link]() and [Link]() are “nicely
re-usable”, copy is not.
It’s left as an exercise to the reader if copy actually has to be re-
usable. If copy is supposed to copy input typed into a keyboard
over to a printer, I’d say it’s pretty bang-on. I might enterprise the
name up a bit and call it copyFromKeyboardToPrinter, but only if
we really need that disambiguation.
This is the foundation upon which the remaining horrors are laid
upon us. You see, the Dependency Inversion Principle states a
few things:
43
would fail. If Uncle Bob had used the canonical domain of profes-
sional wrestling, he would’ve seen that such a leap of abstraction
would be difficult without more than one use case.
This code no longer works, because we have to now call copy with
some arguments so that stuff typed at the keyboard gets copied
to the printer. We need a main method anyway, so let’s make App,
which holds the main method of our program and call the now
highly-reusable copy method with a Reader and Writer:
Uh oh, App now depends on the wretched details we just took out
of copy! main isn’t re-usable!!!!! We need flexibility people, because
we’re using objects!
44
6.2 I Will Make FizzBuzz Enterprise Edition Look
Like Rails
Since we’re not given any bounds or details about when it might be
OK for a high level module to depend on details and concretions,
we clearly have to keep going. You did see how we created dirty
actual concrete objects using new right? These details have no
place in our clean code!
To resolve this design dilemma, we will accept the names of the
classes to use on the command line in order to dynamically create
them. If some lesser programmer wants to sully their good name
with details, they can put the class names in a bash script that
invokes our clean Java program.4
45
method to invoke. The remaining arguments will be the names of
classes we’ll instantiate and pass into that method.
Ah, so much better now, right? We have way fewer details and
our code is much more flexible.5 And, it’s teased out some new
5 Though there is a huge risk that the value of 2 will change and we’ll forget
to update it everywhere we need it. Remember that time they changed the value
of “2” in us-east-1 and the entire Internet went down, except for Netflix, because
Netflix knows how to make abstract constants to use instead of literals? That’s why
they do all the cool conference talks.
46
domain concepts we weren’t aware of before! This code has too
many responsibilities! We have the basic need to invoke a method,
but we also have code to find that method and convert the args
into params.
Let’s assume we have tests and Ruthlessly Refactor.
We need a class for finding a method:
47
}
}
We now can use those in App. While we’re in App, we’ll avoid the
needless repetition of details like 0 and 1 by using the Oats6 of
object-orientation: constants.
I’m coming back to this part and will repeat this until I have enough pages. I will
dynamically create a class whose methods’ implementations are specified in a
.properties file if I have to.
48
6.3 .java Files are for Clean Coders Only
→ MethodFinder methodFinder =
→ (MethodFinder)[Link]("MethodFinder");
→ ArgsToParamsConverter argsToParamsConverter =
→ (ArgsToParamsConverter)[Link]("ArgsToParamsConverter");
→ [Link](doer, [Link](
args,INDEX_WITH_THE_METHOD_NAME));
}
What does our configuration file look like? If you guessed “YAML”,
well, I have some bad news.
49
<beans>
<bean id="MethodFinder" class="MethodFinder" />
<bean id="ArgsToParamsConverter"
class="ArgsToParamsConverter" />
</beans>
The great thing about this is that when that Principle Engi-
neer we hired8 sees the horrors of MethodFinder and creates
QuickSortBasedMethodIntrospectionLocatorBeanImpl, we don’t
have to change the source code to use it!9
<beans>
<bean
id="MethodFinder"
class="QuickSortBasedMethodIntrospectionLocatorBeanImpl" />
<bean
id="ArgsToParamsConverter"
class="ArgsToParamsConverter" />
</beans>
because we don’t have to change source since who in their right mind would think
that XML is actual source code and even if it were, surely it would not be the most
critical part of the application and surely if we did do that, we wouldn’t require
some massive opaque container (not Docker) to execute it, right? Right?
50
And speaking of agile aphorisms, a lot of them just rub me the
wrong way. They feel a lot like SOLID: vague, unhelpful, and
slighting demeaning. Let’s have a look.
51
7
Agile’s Infantilizing
Sloganeering Diminishes
Us All
A theme running across my criticism of the SOLID principles is
that, as written, they are unclear, vague, and open to potentially
dangerous interpretations. It would be better for everyone if the
advice they claim to impart was just stated directly.
But SOLID has nothing on some other slogans used in the agile
community, starting with DRY.
anniversary-edition
53
Every piece of knowledge must have a single, unambiguous,
authoritative representation within a system
mistake and cop to his dirty lies. Instead he rationalizes being a total jerk instead
of realizing the gravity of the situation and preparing Luke for the task at hand. It’s
a classic terrible manager tactic. Rather than give Luke a problem to solve (“Defeat
the Empire”) and the agency to solve it, he dribbles out bits of information to get
him to do what he wants the way he wants it. Still, that’s better than Holdo who,
knowing that Poe is a hot-head take-charge kinda guy, doesn’t tell him the freaking
plan or that there even is a plan. What does Poe do? Take charge! What was Holdo
expecting to happen? If she had instead made with some leadership and given
Poe even a little bit of information, we could’ve been saved from that entire stupid
Space Vegas sequence in what was an otherwise pretty awesome Star Wars movie.
In conclusion, do not look at Star Wars for leadership lessons.
54
}
}
}
}
What’s really going on with DRY is that you should avoid situa-
tions where you have to make the same change multiple times.
However, even this can lead to problematic outcomes, because
our test code often must duplicate some logic of our real code. If
we overly “DRY up” our test code, we are left with tests woefully
coupled to the code they are testing. This can result in tests that
don’t fail when the code under test is broken.
Instead of yelling “DRY” in a code review, we should be talking
about the cost of a particular duplication and being clear what it is.
For example, when thinking about data in a database, there should
be a single authoritative representation of a fact in the system,
but there can be (and often must be) several non-authoritative
representations such as caches. That is duplication. That is a form
of “repeating yourself”, but it is necessary.
55
I also don’t think it’s too hard to just say “Keep it simple” or
“don’t build software to solve problems you don’t have” or “build
for only what you need”. Sure, they don’t result in nice English
acronyms that we can scream at junior developers who are just
trying their best, but if we just said directly what we mean and ex-
plain why. . . wouldn’t that be better than calling everyone stupid?
56
The specific words of this phrase lead us astray. We should be
trying to write software that actually does work, and setting that
part of this aside, we are left with “simple”, and thus we have a
third agile maxim telling us to keep our code simple and to not
solve problems we don’t have. Perhaps if the progenitors of XP had
just said this directly, we wouldn’t have wound ourselves around
three silly acronyms that don’t provide real guidance.
The thing is, if you know what BDUF means, you don’t need this
slogan, and if you don’t know what it means, it will provide ab-
solutely no help. “No BDUF” means that you don’t do a massive
detailed design of everything before you start. It does not mean
that you don’t do any design.
The other effect this has is to shame people who can’t simply just
start coding, but need to sketch things out, think them through,
or make a rough plan first. And you throw pair programming into
the mix and now you have perfectly capable developers thinking
they are stupid because someone said “NO BDUF BRUH DO YOU
EVEN CODE?”
57
7.6 User Stories Paint a Picture of Fairy Tales and
Form Validations
What is a “story”? According to the dictionary, it is:
That does not sound like the basis for breaking down the require-
ments of a software system to me. It only gets stranger when we
look into the definition of “user story”.
The coiner of the phrase defines it as a “promise for a conversa-
tion”. Is that supposed to be a joke? I guess we put these stories in
the parking lot next to all the chores, right?
The way most people treat user stories is that they are specific
requirements for how the software should work, written from the
user’s perspective. For example, “As a wrestling booker, I want to
make the main event of Monday Night Raw a cage match”.
Being user-focused is a good thing and coercing requirements to
be written from a user’s perspective makes sense. But we are not
children attending grade school who must be coddled into doing
our jobs, nor are the people asking us to write software. In fact,
we are all adults4 and professionals.5
I’m actually not sure why we can’t just say “user requirements”.
The barest interpretation of this phrase is “stuff that the user
4 It is worth remembering that there are a lot of people running companies who
do not know how software or technology works and also do not trust professionals
by default, so they do, in fact, need to be treated like babies in order to allow
software to be developed. In these cases, infantile words and cute aphorisms can
absolutely help, but I think we do ourselves a disservice by saying this is how it has
to be or is the best way to describe this stuff. I think the world would be a better
place if people running technology companies understood how software worked.
Kindergarten language doesn’t get us there.
5 That being said, we have to also keep in mind that a lot of programmers, if
left unattended, will create massive complexity and problems writing software
because writing software is a helluva lot of fun. Sometimes it’s just kinda boring to
build out a simple web form based on server-rendered views, so tools to keep the
developers focused are good, too. But I still think it would be better (and maybe
even more effective) if we just said what we meant directly and clearly instead of
dressing it up in childish slogans.
58
requires of the software”, and that is what we are trying to suss
out, right?
A secondary goal of user stories is to encourage developers to
break down complex requirements into small, shippable units
that can be demonstrated for the purpose of getting feedback.
The reality of software is that users don’t exactly know what they
want until they have something to react to. So the way user stories
are defined encourages us to get things in front of users early and
often. This is good.
But why must we misuse language and be overly cute to dance
around the point? Is it really so hard to explain it directly? “Let’s
break this feature down into small, shippable chunks we can
demonstrate because then we can get feedback quickly about
how well we’re doing.”
If you’ve been on an agile project, you have no doubt wasted a
sizeable chunk of your life debating what is a “story” and what is
a “task” and what is a “chore” and on and on. This is telling you
that the language you’ve chosen to adopt is failing you.
Would it not be simpler to call each and every thing a developer
does a “task”? A “task” is “a piece of work to be done”. Simple,
right? Aren’t we all about simple?
And how do we figure out the tasks? We write down what the
requirements are in plain language and try to ship some of that.
Calling those “user stories” and fitting them to a template isn’t
exactly helping. If the only way we know to be user-focused is
some template, then I’m sorry, but we’re in more serious trouble
and JIRA can’t help.
We really do have to think it through and no cutesy language is
going to keep us from having to do that. No templates will make
us magically good at it when we otherwise aren’t. We have to do it
and do it and do it. We have to keep at it to become experienced.
We have to repeat ourselves, right?
59
8
no one understands O, L, and I, and D only makes sense for Java projects and if I
have to work on another Java project, I will pull my hair out and go sell Brutalist
bird houses from an Etsy store instead.
61
8.2 Some Words of Warning
Developers in positions of power who can’t articulate the problem
being solved really hate being asked to do so. Some developers
just cannot handle being asked to explain themselves. You could
make your life miserable by getting on the bad side of these people.
Be careful.
But if you feel safe being bold enough to question authority, you
will find solidarity. Every time you ask some senior engineer why
they keep going on and on about immutability, there are five other
engineers with the same question and you all deserve an answer.
62
9
have not heard of Kubernetes. Those that have, think if you can learn Kubernetes,
you can learn anything. They have not heard of LaTeX.
2 [Link]
3 [Link]
4 [Link]
5 [Link]
6 [Link]
63
Avant Garde Gothic7 . The code listings use Inconsolata8 and the
dimensions of the pages and text block are related to the golden
ratio.9
You don’t have to do any of that, but be warned: arguments made
in Arial do not hold.
7 [Link]
8 [Link]
9 The EPUB and Kindle versions probably look like crap and I’m sorry, but this
is what happens when the e-reader market is dominated by a company that makes
most of its money selling overpriced servers that you configure with JSON.
64