Today’s video is another step outside the book “Switch: How to Change Things When Change Is Hard” to apply its concepts in software engineering organizations. I’m going to talk about ways to script the critical moves in a software engineering practice called Refactoring.
Refactoring is a software engineering practice in which you make improvements to the design of a piece of software without changing its functionality. The reason you’d want to do this is to reduce what is called “technical debt”. Technical debt makes it hard to add new functionality or fix bugs in a piece of software. Ward Cunningham describes technical debt as the accumulated distance between your current understanding of the domain and the understanding that the system reflects.
Michael Feathers uses a different definition that defines technical debt in relation to features and emphasizes refactoring before adding a feature rather than treating refactoring as “clean up” work. In Toward a Galvanizing Definition of Technical Debt, he defines Technical Debt as “the refactoring effort needed to add a feature non-invasively”.
So how we do we produce a change, going from software engineers who don’t refactor as part of the work of delivering features to software engineers who make refactoring a part of delivering a feature every time it’s needed? We need to make it clear when and how to do refactoring – we need to script the critical moves. The Switch for Organizations Workbook gives us five steps we can follow in devising such a script.
1. Be Clear About How People Should Act
First, be clear about how people should act. Our old friend Test-Driven Development helps us with this because it says, “Write a failing test”, “Make it pass”, and then “Refactor”. It is very clear when you need to refactor.
2. Pick One Place to Start
Second, pick one place to start. You may have a codebase badly in need of refactoring pervasively, but where do you even begin when the problem is so large? You also need to have a goal when you refactor or else you’ll be asking, “What is driving this process?” Putting a story in your backlog whose only purpose is to refactor some module is going to cause you problems here because the business value is not obvious and you aren’t providing direction. Instead, make refactoring part of the next feature you write.
3. Consider the Best Approximation
Third, if you can’t nail it exactly, consider the best approximation. You don’t always get to make the code perfect in the module you refactor. Maybe there isn’t enough time or maybe you don’t understand the problem well enough yet. Instead, focus on what gives you the largest benefit for the time invested. If you’re adding a new feature to improve throughput by making something that was single-threaded multi-threaded, you might have to refactor to eliminate some shared state. Maybe you can’t eliminate it all and have to settle for putting some kind of mutex around the remaining shared state. Let people know that refactoring isn’t about achieving perfection.
4. Kill the Abstractions
Fourth, kill the abstractions. Not everybody can name all the SOLID principles or can identify every possible code smell. Describing refactoring in these terms is too abstract for your audience this early on. But thinking in concrete terms, software engineers know when a design makes multi-threading difficult, when a class becomes too bloated with collaborators, and why global variables and other forms of shared state make maintenance difficult. Starting with these concrete examples that motivate refactoring, you can begin to demonstrate the benefits of slowly chipping away at technical debt on each and every feature by incorporating an appropriate amount of refactoring.
5. Evaluate your Critical Move Candidates
Fifth, go back and evaluate your critical move candidates. The Heaths enumerate seven questions to help with this evaluation in their Switch for Organizations Workbook.
Does it evoke emotion?
I don’t know about you, but I can get pretty frustrated dealing with a codebase when I’m trying to add a tiny feature and I can’t because the code surrounding it is so poorly factored. Working in a well-designed code base, by comparison, is bliss.
Does it feel doable?
Again, this is where you want to scale back the challenge to what can be accomplished while delivering a single feature. Don’t refactor the world when changing the color of a button on a form. Some code bases have accumulated so much debt that refactoring them is intimidating to the inexperienced. For someone new to refactoring, a smaller, newer codebase that hasn’t yet racked up so much debt may give them something to practice on.
Was it part of success stories in the past?
This is where your bright spots come in. As in the TDD example, people in your company who’ve improved tech debt on their projects can share stories and tips that your team may be able to relate to.
Will your team see connection to the big picture?
This is why refactoring needs to have a goal. What is tech debt costing you today? Can you quantify how many times a story took longer to finish because of tech debt? Get your team to imagine how much easier it will be to add features in the future when tech debt has been addressed.
Would it provide a quick win?
This is why small refactorings are best at first. Large refactorings require significantly more skill and more advanced practices like the Mikado method.
Would it create positive peer pressure?
This depends on how much your co-workers interact with each other. If they share the same codebase, one engineer’s refactoring will be appreciated by the others. They can also hold the other engineers accountable for not letting tech debt grow.
Is it consistent with the way people think about themselves?
Maybe not at first. But as the opinion leaders on your team start to adopt refactoring as a part of their daily practice of writing new features, the rest of your team will follow. Most engineers like to think of themselves as skilled practitioners. The more refactoring is considered a fundamental skill of a software engineer, the less acceptable it will be to lack this skill.