As a hardcore user of TDD and refactoring, there are a number of what I think of as “second tier” refactorings that I use quite frequently.
In one’s first intro to refactoring, one sees a lot of "rename", "re-order", "inline", and "extract". These are pretty potent tools, don’t get me wrong, but I think of them as, idunno, atoms. I think of these "second order" refactorings as small inorganic molecules.
An example of this would be one I call "swap supplier & supply". Let’s take a look, in this case, at a real one.
Here’s a gist, of a simple little class, ScaleListener.
(AFTER) AspectRatioConstraint.kt
The full code, in situ, is from the kontentment project:
GeePawHill/kontentment
ScaleListener’s single responsibility is this: Enforce a Group and a MediaView’s aspect ratio when its host window changes size.
ScaleListener has a bad name, and there are other first-order infelicities in it, too, but overlook them for a minute if you will. I want to talk about that constructor.
If you’re not a kotlin reader, the first line of the class defines a function to make one, and I’m interested in that argument list: It takes a host window, a target window, and a mediaview.
These are all three JavaFx classes, and, in particular, they’re JavaFx UI classes.
Oh, and be sure to look at the test!
Aye, there’s the rub. There ain’t no test in that snippet.
That is because there ain’t no test in that codebase. That is because I didn’t write one. That is because of that argument list.
“Baby,” as one does, “I can explain.”
All three of those classes being passed in are what we call “awkward” collaborators. What we mean by that is as simple as this: they make a perfectly committed TDD’er not want to write a test.
Awkwardness in a collaborator can come from lots of sources. It really does mean “anything in the situation that makes one wish for a test but not want to write a test”.
In this case, the awkwardness comes from the nature of JavaFx — and nearly all full UI frameworks. In order to use a live JavaFx UI class, which all three of these are, I have to run a live JavaFx UI.
These classes only work when the framework is running. That involves multiple threads, an outer top-level window, and about a half-second of runtime.
Now, be clear, here: I can write a live UI test. That’s not the issue. The issue is, I just don’t want to. My best efforts have never been able to do that in less than 10 lines of code, or faster than a half-second. Or generically.
But you don’t need to know anything about JavaFx to understand “swap supplier and supply”. You just need to know that all three of those classes are suppliers.
The first class supplies properties that can be listened to. The second and third classes supply properties that can be set.
Properties, in JavaFx do not require the framework to be live.
And … bingo … whyn’tcha just pass the properties instead of their supplier?
It’s not like the client doesn’t already have to know all three suppliers. It had to pass them in. Let the client get the supplies, let the client pass them in, and ScaleListener — still a bad name, I know — can get a whole rash of short easy microtests.
But let’s step back from my life in the bush of JavaFx, and think about the larger conceptual cluster. “Swap Supplier and Supplies” has some pretty cool aspects to it.
A cool aspect: the force that drove me. I did this because I wanted some tests around a calculation, but writing them was “known awkward”. Not impossible, just unpleasant. If I hadn’t felt this unpleasantness and reacted to it, the refactoring wouldn’t happen.
A cool aspect: all this refactoring amounts to is a very slight jiggling of a borderline. All API’s are borders. I moved the border between client and service a fraction of an inch, and my payback was extraordinarily high executable confidence the ScaleListener “just works”.
A cool aspect: the word “swap” is important. In this case, it was better to have the supplier on one side and the supply on the other. Sometimes, exactly the opposite is the case. Sometimes, less frequently but not “never”, it’s the supplies that are awkward.
A cool aspect: the size of this thing is miniscule. We’re talking about ten minutes of easy programming, two minutes of GAK (geek at keyboard) testing, and now I’m free to test what I wanted to test automagically, thereby eliminating future GAK testing.
A cool aspect: this instance affects no measurable aspects of the app, but it can have positive impact. Two years ago, I replaced supplier w/supply & optimized a show-stopping performance problem right out of existence: everyone in the call-tree was re-creating the supply.
A cool aspect: remember our change-harvesting notion: human, local, oriented, taken, and iterative? This one case is straightforwardly human by motivation, local by size & complexity, and taken right from the code that was already there.
(And yes, it was oriented, cuz I’m about to eliminate the hardwiring of 16×9, and iterative, cuz right after I did the swap, I saw that the second & third args could be expressed more clearly as a lambda, and changed them again.)
One final cool aspect: once you have seen this, one time, and done it, one time, you’ll see it all over the place. It’s a very easy addition to your second-order refactoring toolbelt.
Second-order refactorings are small molecules, a little heavier than the first ones you use, but not much. Learning them is the next step to being a hardcore TDD & Refactoring geek.
One more note: I have since updated the gist to contain the before (ScaleListener) and the after (AspectRatioConstraint). Sorry not to have done that in the first place.
A Request of the Community
Normally, this is where I plug my site. Today, and for the next while, I have a more important request. I’m GeePaw. My GeeKid, Threy, needs some financial help I can’t give just now.If you like my work, do me a solid, and go to the GoFundMe we set up for him.
https://www.gofundme.com/f/rip-jason-annable-support-fund-for-threy
Help if you can, please.