My Authors
Read all threads
Another small refactoring today: replace or eliminate primitive what-to-do arguments. Let's take a look at the general theme, then consider a couple of variant ways to handle it.
A primitive what-to-do argument is one passed to a method hat tells it which of multiple things it should do using the rest of its arguments. A very simple example: start(car:Car, flag:Boolean), which will either start the car, if the flag is true, or stop it, if it's false.
There are lots of variants of this. Sometimes we have a long arg list, and pass a bunch of nulls to change the behavior. Which arguments are null controls what the method does. Sometimes we pass a "type code", basically an enum to represent range of options bigger than boolean.
Now, be sure: some situations, especially when we're close to the metal, require this. I'm not a never-always kinda guy, and this isn't a never-always. But primitive-what-to-do's have cost, and we want to reduce that cost when we can.
The first cost is in simple scannability, and it's actually obvious right in that little method signature above: it's in the cognitive dissonance between the named method, 'start' and the flag that magically makes 'start' mean 'end'.
The way human minds handle things bigger than their bandwidth is by chunking and labeling. To have *any* function is to make a chunk. But the bond between a chunk & its label is of critical value, especially when one is scanning, which is what we spend most our day doing.
A secondary aspect of this scannability cost is also demonstrated in my trivial case. At the calling site, "true" or "false" have no obvious contextual meaning. This actually forces the would-be scanner to go investigate the body of start to understand what she's seeing.
This is true of most primitives. Primitive types exist in a programming language precisely because they are so generic, so "usable in any context". When we see them in a domain context, we have to a) grok them and b) build something in our head to remember them. That's a cost.
The second cost isn't really shown in my dumb example, but you'll see it in grown-up code all the time. Inside our method that takes the what-to-do, we'll branch around its value. Might be an if, might be a switch, whatever. No matter, we think, we got to branch *somewhere*.
But here's the thing. As the method grows, we'll take that same condition, "flag was true", and we'll test for it more than once. More than twice, often enough. We'll get in to start and see that it's *riddled* with tests on that condition.
You might think, "oh, that old worry-wort," and believe that won't happen. You gotta trust me, here: it *will* happen. And it's code kudzu, and will spread to cover your entire three million line codebase if you don't spot it and fix it quickly.
An aspect of these two costs I really want to highlight: they're a tax you start paying the very second you write this kind of code. It's an eensy-teensy micro-payment, exactly the kind of micro-payment that has added the verb "google" to our everyday vocabulary.
Anyway, what do we do when we see this? There are two large-scale answers that are, interestingly, exactly the opposite. 1) Eliminate the flag-passing. 2) Emphasize the flag-passing. Each large-scale answer has some variants, and either one of them might be best.
The easiest way to eliminate the flag passing is to add a new API for each what-to-do primitive value. In our start, above, this means creating 'start()' and 'stop()', with no flags at all.
You can do this with methods whose primitive-what-to-do has more values than just two, of course. You can do it until the cows come home.
Another variant of the "eliminate" variety: overload the method, which means letting the compiler interpret the different signature and call a different method. start(car:Car) vs start(car:Car,key:Key) vs start(car:Car,cable:Jumpers).
That variant is heavily used in either factory methods or constructors. The cleverness here is in letting all the methods be called start, but letting the compiler read the arguments to disambiguate the what-to-do that's wanted.
Those tricks, and there are some other minor ones, are about eliminating the what-to-do. Sometimes, though, what we want to do is *emphasize* the what-to-do. Doing this is actually a little bigger than a small refactoring, but I'll lightly expose you to it anyway.
We passed that flag or type code because our underlying algorithm was, in some sense, "basically the same", regardless of its value. Sometimes, we're using that value, often repeatedly, to introduce little inline tweaks to the method.
It's always do A, do B, do C, do D, but in some cases, B is a little different and D is a little different. The flag/type code is used to detect those cases.
In the Strategy pattern, we don't pass a type code, we pass a Strategy. Our method still does A, B, C, D, but inside the B and D, it uses the Strategy object to do the work for us.
This might produce startBy(car:Car, strategy:Strategy), where that strategy could be Hotwire, Jump, Key, Rolling. This kind of thing is especially useful if we want that strategy to be permanently associated with the Car.
When we create the Car, we choose the strategy we're gonna be using, and every time we start the car, we use that pre-determined Strategy. (Note: Don't try to use Rolling starts on an automatic car.)
I don't really have room here to go into the ins and outs of using the Strategy pattern. I just wanted to a) handwave it as a choice, which we did, and b) point out something really interesting about refactoring.
The really interesting thing: a great deal of refactoring isn't about eliminating complexity, it's about re-arranging it so that a) it's isolated and b) it's explicit. Those are so valuable because they lead to c) easier change.
And once again, let me remind you: we don't refactor cuz we're neat-freaks. we don't refactor cuz we're artists. we don't refactor cuz we're decent, or kindly, or patriotic. We refactor because we are in the change business, and refactoring makes our changes take less time.
So when I see a primitive what-to-do argument, I always ask myself if I can either eliminate it or emphasize it. They're a very simple basic smell, and even when they're unavoidable, I wrap them quick, keeping them from kudzu'ing my code base.
Today's pitch: This material I spew here live is captured and turned into blogs & podcasts that come out a bit later.

If you don't want to miss anything, go to geepawhill.org and subscribe now!

Even though it's free -- and spam-free -- it helps me when you do this.
Missing some Tweet in this thread? You can try to force a refresh.

Enjoying this thread?

Keep Current with GeePaw Hill

Profile picture

Stay in touch and get notified when new unrolls are available from this author!

Read all threads

This Thread may be Removed Anytime!

Twitter may remove this content at anytime, convert it as a PDF, save and print for later use!

Try unrolling a thread yourself!

how to unroll video

1) Follow Thread Reader App on Twitter so you can easily mention us!

2) Go to a Twitter thread (series of Tweets by the same owner) and mention us with a keyword "unroll" @threadreaderapp unroll

You can practice here first or read more on our help page!

Follow Us on Twitter!

Did Thread Reader help you today?

Support us! We are indie developers!


This site is made by just three indie developers on a laptop doing marketing, support and development! Read more about the story.

Become a Premium Member ($3.00/month or $30.00/year) and get exclusive features!

Become Premium

Too expensive? Make a small donation by buying us coffee ($5) or help with server cost ($10)

Donate via Paypal Become our Patreon

Thank you for your support!