Easy code changes are additive: introduce a new database table, network call, or screen.
Difficult changes are transformative: split a database table, combine a few network calls, or change a component that’s used on many screens. These changes tend to motivate refactoring:
- Introduce feature flags
- Extract interfaces
- Move code between modules
- Add hooks for metrics or testing
Refactorings like these are good and often necessary, but they also increase the amount of change. I make mistakes in my refactors! Figuring out why a large pull request introduced an unexpected behavior change is difficult and stressful.
Reviewing large pull requests also makes me grumpy. At a certain size I lose the ability to connect the goal of the pull request to the edits in the code.
I like to author and review pull requests that have a small number of deltas; say less than 100 lines of (non-test) code. These are low-risk, easy to understand, and isolated to roll back if there’s a problem.
I also like to author and review mechanical changes, even if they touch hundreds of lines of code. Mechanical code changes are either:
- automated, such as IDE-driven symbol renames.
- systematic, such as replacing JUnit asserts with Truth asserts. This might be assisted by find-and-replace, sed, or it could be truly manual.
When reviewing mechanical changes, I can track how each delta serves the refactoring goal. If I’m familiar with the codebase, I might also be able to predict which deltas I will see.
But Never Both
The best way to make a big change is as a sequence of steps, where each step is either small or mechanical.
Kent Beck says it like this:
for each desired change, make the change easy (warning: this may be hard), then make the easy change
Jessica Kerr heard similar advice from Jason Swett:
When you refactor during a change, you make the change bigger.
When you refactor before the change, you make the change smaller.
Github pull request retargeting, and tools like Graphite make it easy to manage pipelines of pull requests.