Refactoring More and Faster

Clouds in Colorado

I’ve been deep in the refac­tor rab­bit-hole. You know—that aw­ful (but strangely sat­is­fy­ing) space where the ma­jor­ity of your com­mits are small pedan­tic ed­its that don’t change the end-user-ex­pe­ri­ence at all?

These past few weeks, I’ve been work­ing so hard on Harper’s Chrome Extension and build­ing sys­tems for trans­for­ma­tion-based learn­ing, I haven’t had a chance to touch any of the ac­tual gram­mar rules the soft­ware is sup­posed to fix. As such, a few glar­ing tools for rule au­thors have gone unim­ple­mented. These are tools that—be­cause they’re so tied into the core sys­tem—need a lot of ex­per­tise and fine-tun­ing to work prop­erly.

If you take a look at the diff, you’ll find that there are a lot of small ed­its. Since each one takes a min­i­mal amount of cog­ni­tive ef­fort, it is easy for me to slide into a trance. A trance where I am tech­ni­cally mov­ing to­wards my goal (in this case, cre­at­ing an ex­pres­sion sys­tem that en­com­passes—but is more pow­er­ful than—our ex­ist­ing Pattern sys­tem), but I’m not do­ing so in a way that’s truly pro­duc­tive. I would like to cover some of the in­di­vid­ual strate­gies I’ve found that have helped me speed up my refac­tor­ing pro­ces. Af­ter all, if the process of refac­tor­ing is the process of pay­ing back tech debt, refac­tor­ing is in­cred­i­bly im­por­tant to main­tain­ing ve­loc­ity.

I am do­ing this for two rea­sons. First, be­cause I be­lieve this is valu­able in­for­ma­tion for any de­vel­oper. Sec­ond, be­cause I want to so­lid­ify these ideas in my mind to fur­ther im­prove my refac­tor­ing down the line.

Use Your Tools, but Not Too Much

In what­ever lan­guage you’re work­ing with, there are likely spe­cial­ized tools for refac­tor­ing. For Java, I’ve used IntellJ IDEA. For Harper, I’m us­ing rust-analyzer. These pro­vide neat func­tions for chang­ing iden­ti­fiers or mov­ing mod­ules, all while up­dat­ing rel­e­vant ref­er­ences.

These tools are im­per­fect, how­ever, and of­ten fail to up­date ref­er­ences in parts of code that re­quire a higher level of se­man­tic un­der­stand­ing.

It’s a com­mon pat­tern for au­thors to cre­ate an in­stance of a class and as­sign it to a vari­able with the same name.

#[derive(Default)]
struct Foo {
    // Implementation details
}

fn main() {
    // The author of this code named the variable after the class
    let foo = Foo::default();
}

In these cases, when some­one changes the name of the class, the as­so­ci­ated vari­able name will not get up­dated.

#[derive(Default)]
struct Bar {
    // Implementation details
}

fn main() {
    // Variable name is not updated by our static analysis tool.
    let foo = Bar::default();
}

When do­ing a refac­tor like this, you need to read every changed file to en­sure the code still makes as much sense to a hu­man as it does to the com­piler.

Plan It Out, but Not Too Much

I can’t be­lieve I need to say this, but it’s chron­i­cally un­der-dis­cussed. If you’re do­ing a fun­da­men­tal change to how a sys­tem works, it is vi­tal that you read the code first and plan it out. The plan does­n’t need to be spe­cific nor need to be com­plete.

Having a good idea of what the start and end state should look like will save you hours of time. While I am a pro­po­nent of dis­cov­ery cod­ing for new fea­tures and code, I find that it is lack­lus­ter when it comes to ma­jor re-writing of code.

To in­struct peo­ple to plan out their changes may feel ju­vinile or ba­sic, but I think it’s worth men­tion­ing.

The Joy

Refactoring helps me write new fea­tures, faster. Refac­tor­ing helps me find bugs in ex­ist­ing code. Refac­tor­ing makes it eas­ier for new­com­ers to join and con­tribute to a pro­ject.

It is one of my fa­vorite pas­times. I en­joy refac­tor­ing, but also want to do it well. I hope you feel the same.