Automating JavaScript refactoring
Refactoring? Is that a swear word?
I often hear (and say) “We need to refactor this!” and it is something that is received with suspicion.
Sometimes we are using the term wrongly: we mean redesigning or rewriting part of the software. That is not refactoring, and it involves a lot of effort. It might mean having to stop developing for a while and end up with the same problems later. I understand why this is scary!
So what’s refactoring?
“Refactoring is the process of changing a software system in a way that does not alter the external behavior of the code yet improves its internal structure.” (Martin Fowler)
That’s a process of daily maintenance, and it’s done in small steps. It’s owning our codebase and being responsible for what needs fixing (and no, I’m not talking of bugs only).
And even then, refactoring has a longer term return of investment compared to developing features or fixing bugs, because it’s not immediately visible to the end user. Refactoring might cause better performances, but it is not the main point. Refactoring is not resolving bugs, Refactoring is invisible. It’s shortening the time that a developer takes to develop a new feature, but how would you declare that when you are doing a release? So, then, how do you justify the effort in making the same change across the whole codebase when it comes to Sprint planning? Unless, that is, you automate it.
Code-mod, what is that?
A code-mod is writing code to modify code into new code. Wait, what?
Ok, let’s start with small steps… If you haven’t heard of JavaScript AST you better get a refresher!
With AST you can represent you code as a tree structure, yet, behind the scenes, it’s a JSON. And we can manipulate a JSON easily!
There are already collections of code-mods you can run on your code, for example js-codemod and react-codemod, but you can write your own: Facebook has created a tool for that: jscodeshift.
At the beginning I struggled a bit, I tried to edit a comment, but comments aren’t treated as nodes in AST and it’s not that easy to deal with them. But don’t be discouraged! Learn from my mistakes and read this tutorial.
It should be noted that no ethically-trained software engineer would ever consent to write a DestroyBaghdad procedure. Basic professional ethics would instead require him to write a DestroyCity procedure, to which Baghdad could be given as a parameter. (Nathaniel Borenstein)
Let’s assume that some of your colleagues (it’s never you, of course!) has written a non-ethical code and you want to refactor it.
const destroyBaghdad = () => {
// code here
}// used as:
destroyBaghdad();
You want to change it to be ethical, but how can you? You can easily change the function definition to pass a new parameter, and since it’s only one occurrence you don’t need to automate it. But for the rest?
export default function transformer(file, api) {const j = api.jscodeshift;return j(file.source)
.find(j.Identifier, {name:'destroyBaghdad'})
.forEach(path => {
j(path).replaceWith(j.identifier('destroyCity'));
if (path.parentPath.value.type === "CallExpression") {
path.parentPath.value.arguments =
[j.literal("Baghdad")];
}
}).toSource();
}
It might seem complicated at first, but what it does is a fancy find and replace toward an Identifier. And if the function is not defined, but called (the type of the parent being a call expression) it modifies the arguments adding Baghdad. The result is the following:
const destroyCity = (city) => {
// code here
}// used as:
destroyCity('Baghdad');
I hope you have now an idea on how to automate the refactoring of your code. Go and make your codebase better!
If you are interested in writing code-mods why don’t you join us?