Refactoring! The musical
by e(Liz)abeth (Mars)ton
Don’t worry, I haven’t gone anywhere! I’m currently hard at work refactoring the blog toolkit I authored in order to create this blog, which I named sbstr8.
Refactoring, as other engineers probably know, is the art of generalizing or otherwise mangling code into a new shape. I’m doing a major refactor, but I’ve committed to not break the build at every step!
First of all, I made myself a fresh git
branch.
git checkout -b refactor
And the whole time, to ensure I haven’t broken the build, I have the site up and running in development mode at http://localhost:3000
with npm run dev
. Crucially, I never break the build; each commit is a functional snapshot of this website! Yes, this is a flex :3
In this new git
branch, I’m moving source files individually from e.g. /src/components/foo.tsx
into /src/sbstr8/components/foo.tsx
.
Were I using VS Code, there’d probably be some fancy tool to move and refactor files, but I’m neovim all the way, baby, which means I need to improvise with some regular expressions.
sed
is of course the usual tool that one would use under a unix-like OS, but venerable sed
lacks previews, and besides, it’s boring.
So instead, I’m using sad
, an amazing, efficient CLI tool that lets you opt in and out of each change.
Under the hood it uses delta
for diff visualization and the lovely and powerful fzf
for listing, with fd
for speed and sanity.
The full incantation (from the repo root) looks like:
pushd src; fd | sad '@/components/foo' '@/sbstr8/components/foo’; popd
I exploit the fact that I’m using @
-prefixed import paths to build regular expressions that neatly target just the imports.
When I’m satisfied, I stage the result with git add -p
, walking through the changes (thanks to delta
again for the highlighting.)
Sometimes I’m also renaming components as I go; for this, I do another find/replace (messier and more ad-hoc, a pattern enabled by sad
) search for e.g. Foo
and approve or disapprove of each offered conversion (to Bar
, say.)
Then, I git mv
the files so they are actually in the right place:
git mv src/components/foo.tsx src/sbstr8/components/bar.tsx
I then commit the change. Crucially, each change is atomic. So I can roll back the refactoring of any given file.
The next step is to add Jest snapshot tests and Storybook stories under ./src/sbstr8
, so I can see what is changing and how.
Then, I’ll copy the ./sbstr8
directory back into the sbstr8 repo. At this point, I’ll also strip out style information (I don’t want every sbstr8 site looking like lizmars.net; I want the blog engine to be nearly ‘headless’ (visually unopinionated).
When everything is kosher over at sbstr8
, I’ll merge into lizmars.net
from sbstr8.
And this is the fancy part! At this stage, I’ll revert most of the commits I made above.
As I revert each commit, I’ll wind up with:
- a stylized, lizmars.net component under
src/components/
, and - an unstyled base component under
src/sbstr8/components
.
I’ll then copy the snapshot jest tests and stories (which I will need for the next step) from the ./src/sbstr8/components
directory into ./src/components
.
I’ll then use those tests as a so-called ‘harness’ while I modify each of the lizmars.net components to add styling to the now-unstyled sbstr8 base component.
There will be some other steps as well, with a similar philosophy, for e.g. handling sitemap.ts
. This absolutely will take another day or two.
But! The payoff for this elaborate dance is pretty epic: I’ll have an unbroken build the entire time.
And that’s how you do a major refactor with both efficiency and caution.
Don’t believe me? You’re reading these very words in the refactored branch, halfway through refactoring!
Photo Wikimedia Commons. CC0 1.0. Universal Public Domain Dedication