A presentation at Reactathon in December 2020 in by Becca Bailey
For those of you who are not closely following American and/or Japanese popular culture, Marie Kondo is an author, Manga character, Net ix star, and the world-renowned patron saint of tidying.
A couple years ago, I decided to read her book and attempt to put her principles to practice in my messy 600 square-foot Chicago apartment. I picked up her book expecting to hate it, but actually, I didn’t.
In addition to learning some new folding skills, I learned about how reducing the number of things I own helps me to better appreciate the things I have. And when we know and appreciate the things we have, we’re less likely to leave them lying around on the floor.
This got me thinking about how to connect these practices to the code I write. There are many days when none of my code brings me joy, but unfortunately just throwing it away is not a viable option. This brought me back to the concept of refactoring.
What is refactoring?
According to book ~Refactoring~, “Code refactoring is the process of restructuring existing computer code—changing the factoring— without changing its external behavior.” –Martin Fowler
Factoring refers to the internal parts of a program or the implementation details. Note that making breaking changes to the public-facing API of a library or top-level component, while often necessary, is not by-definition Refactoring. Refactoring means changing the inside without changing the outside.
This brings me to my personal definition of refactoring. To me, refactoring is an opportunity to change your mind to ask yourself, what decisions would I make differently if I could write this code again?
Sometimes when we talk about refactoring, we are referring to the process of modernizing our syntax.
In React, this might involve refactoring from class to function components, replacing deprecated lifecycle methods, or converting state and lifecycle methods to hooks.
I think this is a separate concern from refactoring for code style and readability. Modernizing your syntax is a part of refactoring, but it’s not the entire reason you refactor.
You might discover that using hooks, context, suspense, or other modern React APIs might aid you in your pursuit of human-readable code, but in my view, modern syntax is a tool, and not an end-goal when refactoring.
Refactoring isn’t just about code quality either. In my experience, the pursuit of artfully crafted software can lead to a lot of pride about your work, but unless you’re deliberate about respecting deadlines, embracing alternate viewpoints and and collaborating with your team, it really just makes software engineering more exclusionary to people who don’t subscribe to a specific set of engineering principles.
Now one of the things I really loved about Marie Kondo’s philosophy is that it’s not just about buying stuff from the Container Store and re-decorating your house to make it look like a Martha Stewart magazine cover. Likewise, the end goal of refactoring isn’t to have perfect or beautiful code for its own sake.
When do I refactor?
When I reorganized my apartment a couple years ago, my goal was to feel less stressed every time I came home from work. After dealing with this anxiety for a few months, I knew it was time to reorganize. Likewise, there are some things that tell me it’s time to refactor my React code.
My general rules are to refactor when your code is: 1. Hard to test 2. Hard to understand 3. Hard to change
If you find yourself mocking out a lot of external libraries, walking on eggshells around your codebase, or scratching your head a lot when you try to read your code, it’s might be time to refactor.
Out-of-control props Nested conditionals Confusing variable names Components doing too many things
But more specifically, here are some things I see in React projects that tell me it’s time to refactor.
These are general best-practices as I know them. But if you do your job for a while, do regular code reviews, and practice refactoring, I promise you will develop your own sense for knowing what to refactor.
A couple more things to note as you go on your refactoring journey… First of all, It’s really common to think about refactoring as a negative thing- as something that happens only when we write “bad” code.
I call this the “git blame refactoring”.
However, refactoring doesn’t mean that you (or someone else) did something wrong. Maybe you made the best choice you could with the knowledge you had in the moment, but now you have more information or more experience and you have changed your mind. It’s okay to change your mind. There’s no shame in refactoring, or having someone else refactor your code.
A key tenet of Marie Kondo’s philosophy is to start with discarding. While I know most of us have some amount of legacy code to maintain and we don’t have the ability to throw out everything that doesn’t bring us joy, it’s also not unusual for us to hold onto code that we don’t need anymore.
Maybe it’s a class that your coworker wrote a year ago that you don’t quite know what to do with, or some utility functions that are no longer useful, or some old comments that don’t apply anymore. Perhaps we’re afraid to get rid of code because we didn’t write it, we don’t understand it, or we’re uncertain about whether we’re going to need it someday.
git checkout .
Discard it. Worst case scenario, we can use three magic words to bring it all back again. This is one of my favorite things about programming—no matter how messy things get it’s easy to reset everything and try again.
I don’t know about you, but the practice of refactoring isn’t always a joyful experience. Making changes to the structure of our components can result in confusing errors, test failures, and unintentional changes to the behavior of our components. Have you ever introduced a bug by refactoring? I know I have.
This is why I love writing tests. Testing sounds stressful, especially if you’re not used to doing it. But believe me when I tell you that this is the number one thing you can do to transform your refactoring work into a more relaxing experience.
In order to do this, I need to write tests that tell me about the behavior of my component. Testing is important, but not all tests are made alike. Here’s an example.
Let’s say I have a simple class component. I’ll call this a drawer, since Marie Kondo loves putting things in drawers.
I have some state
And a render function with some conditional logic. I don’t know about you, but this logic isn’t super easy for me to understand, so this is why I’m refactoring this component.
I can test that this component works.
And let’s say this component has some existing tests. This is a little less common now, but if this code has been around for a few years there’s a good chance I’m using a test renderer like Enzyme to test my code.
Enzyme allows me to access the props and state of my component in order to make test assertions against them.
I run my tests, and everything works great so far.
Okay now let’s start refactoring. I know I said that we shouldn’t refactor just to modernize syntax, but hooks are cool so I’ll start by switching this class component out for a function with a useState hook.
Woah, hold on now. When I run my tests again something is broken, even though I know the component works the same way it did before.
This tells me something about my tests - the things I was testing were implementation details. If I can break my tests by changing the name of my state variable or changing an internal function name, this makes refactoring difficult. The thing that I care about when refactoring is the way this component behaves.
If I look at this component from the perspective of a user, I can see that when I click the open button, the contents are visible. When I click the close button, the contents are hidden again.
While I can still use Enzyme to make these kinds of assertions as well, I’m going to switch my next test to use React Testing Library, which is a test renderer built for these kinds of behavior-driven integration tests. What’s the difference? Well, now I’m not using any internal component APIs.
Now I’m rendering my component
Clicking the button with the text “Open”
And expecting my content to appear in the HTML. I no longer care about my inner props or state.
This test passes for both versions of my component. Once I add a couple more tests, I should be good to start making more changes to this component without fear of breaking things.
Here’s the part that makes refactoring really satisfying for me: having the ability to make small, incremental changes while relying on my tests as the source of truth.
Now, I can gradually untangle these nested conditionals.
The final result might tell you a little about the kind of code I really appreciate - I haven’t made it any shorter, but instead of three conditionals there is just one. There might be a little bit more repetition, but it’s easier for me to tell what is going on.
Now this isn’t strictly related to refactoring, but along the way you might discover another pain point, which is file structure and code organization.
Marie Kondo would say that the goal of re-organizing isn’t just to make it easy to find your belongings, but also to put them away. For example, the shoes you wear every day probably shouldn’t be stored in a bin under your bed, because you’re never going to remember to put them there.
Likewise, the goal of file organization isn’t just to make it easier to find things, but to make it easier to know where to put new code.
There are lots of different philosophies on this, but my basic rule of thumb is to group things that are used together, and to avoid deep nesting. If your imports are a mile long, it might be time to re-think your component organization.
Every time we refactor, we have an opportunity to leave something a little better than we found it.
Now I haven’t sent out an official survey, but I’m willing to bet that the number one reason why we don’t refactor all our code all the time is because we don’t have time. When there are looming deadlines, and any number of things going on in the world right now, the struggle is real.
Rather than refactoring all at once, the more we can make refactoring a part of our daily routine, the easier it will be to find the time and to get buy-in from our team. It doesn’t have to be a prioritized item on your backlog—it can just be something you take an extra few minutes to do while you’re working on a feature. I don’t want to get anyone in trouble here, but I’m going to say that maybe you don’t even need to mention to your product manager that you’re doing it.
And speaking of product managers, how do I get other people on board, especially when we have bigger refactoring work to do? Because sometimes this is really where it gets tricky.
If we don’t have a team that values thoughtful code reviews and collaboration, chances are there will be someone organizing their one little drawer while the rest of your house stays messy. Or worse, you will all be organizing your drawers, but there will be a different system for each one.
One of the best things you can do for your team is to be consistent in code quality and style so the goals when refactoring are clear.
Sometimes it helps to document our style and testing practices. Even if there are inconsistent parts of the codebase, it helps to have a shared idea of what we’re working towards when refactoring.
Like the end goal of tidying up is to better appreciate the things you own, the end goal of refactoring is to better understand your own code and build systems that other people can also use and understand.
Now, this might be a little out there for you, but I think Marie Kondo would say that the key to refactoring well is gratitude for the work you have already done.
Gratitude is the antidote to the scarcity mindset that tells us we need to stockpile twenty-three pairs of jeans just in case the apocalypse happens and we can never go to the Gap again (which admittedly sounded a lot crazier when I was writing this talk nine months ago)
We can have gratitude, even for the things we are getting rid of.
“You’ll be surprised at how many of the things you possess have already ful lled their role. By acknowledging their contribution and letting them go with gratitude, you will be able to truly put the things you own, and your life, in order.” –Marie Kondo
When I approach my refactoring process with gratitude, I can acknowledge that I learned something from this code, even if the thing I learned is that I don’t want to write code like that anymore.