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
When you refactor during a change, you make the change bigger.
When you refactor before the change, you make the change smaller.