Refactoring Pro-Tip: As soon as I move beyond small-scope refactorings, I ask whether my change would be easiest side-by-side or in situ.
Before we even start this conversation, remember remember remember: easiest nearest owwie first, and keep repeating it. This conversation happens after we’ve found everything we can do in a scope of one or two classes. We write code iteratively in the modern synthesis. Our designs don’t burst Athena-like from our heads, fully-grown and ready to provide wisdom to all. Instead, they develop, over time, in an atmosphere that is suffused with the desire for continuous small increases in valuation.
This approach has profound consequences. Today, I want to talk about the fact that all this locality and temporality, though it lets us continuously ship more value, can also introduce major design rework as we go. Specifically, let’s talk about two ways to approach that rework.
The kind of rework we’re talking about here is typically this: two iterative approaches in different subtrees of the app’s dependencies amount to doing “the same thing”, but in two different ways. We want to heal that disparity, by some blending of the two subtrees. That subtree business is kinda thick. Let me say it another way. Some portion of your app, consisting of multiple objects, collaborates to perform a function. Some other portion, also multiply, collaborates to perform the same or very similar function.
The two parts of the app don’t interact. And one of them is preferred. The goal then, is to make that preferred one the only one. To perform the same function in only one way. (Remember remember remember, the odds of actually noticing that are vanishingly small if you haven’t done all the local narrow refactoring first. Easiest nearest owwie first. Sorry to keep harping, but that’s critical.)
The problem: right now we have a bunch of code depending on the “bad” version, and some other code depending on the “good” version. We want all the code to depend on the “good” version, so we can delete the bad.
There are basically two choices. We can 1) go in and change all the bad-dependent stuff right on the spot. Or we can 2) gradually shift the bad-dependent stuff over to depend on the good.
Answer #1, changing it in situ, is, I say this with a warm smile, every noob’s first response to every problem. But Answer #2 may actually be a much preferable way to go, depending on circumstance. I say may, meaning that we’re going to have to make a judgment call, so we need to grasp the factors that go in to that call.
The first and biggest factor: how long will the program be broken while this change proceeds? I follow a branchless strategy, so for me, that question rephrases as how long will I not feel okay pushing my code to head? (I am a very strong advocate of working branchlessly, and you can check the blog for articles about it, but I won’t defend it here, but take it as a given.)
My advice to one and all: the answer should always be “not very long”. Specifically, a day is too long. For me, I admit to being an old hand here, two hours is too long. I start getting tense around 45 minutes if I haven’t been able to push. I hope some of you are unphased by that and others are shocked. That’d be a win. The number has nothing to do with actual clock time. It’s a stand-in for “mental bandwidth”.
Any change I can’t push in an hour is a change that has too many moving parts for me to be able to hold in my head simultaneously. When I can’t see all the way through, I have to move slowly. When I move slowly, I’m not shipping more value faster. This can be hard for very bright folks to understand, so let me say it tight: I can usually bull my way through a change that’s too big to hold in my head. It’s not about limits-to-capacity. I don’t bull my way through, because it’s about optimizing my productivity.
The second factor in the decision: am I certain the good version can do exactly the same thing the bad version does? I mean certain. If not, then I’m at risk of taking a long and pointless journey through the code. Again, notice the judgement-based nature of this second factor. The truth is I won’t be sure until it’s done. So instead I have to just gauge my feelings. It isn’t really “is it guaranteed”, it’s “am I confident”, a.k.a “do you feel lucky, punk?”
So. If I can’t freely push my code to head, or if I’m not sure whether the good version can really replace the bad version, then answer #2 is often a significantly better choice: make the change “side-by-side”. Making a larger-scale change side-by-side includes it’s own whole set of techniques. It’s not something one learns in one-off projects or your first year or two of geekery. Simply put, if you don’t know it’s even possible, or how to do it, every answer you give will be #1.
So we need to talk about some techniques. I’m up for that, and that’s the next muse, so stay tuned, tomorrow or the next day.
Meanwhile, it’s Wednesday afternoon, and I’m cheerfully all tuckered out. I hope you’re there or will get there soon!