Code reviews are becoming more and more standard in the software industry at large. Pair programming, on the other hand, is a rarer practice. These practices are not as disparate as they may initially seem. In fact, I'd argue that pair programming, or pairing for short, is a natural refinement of the practice of code review.
Let's start by looking at some of the benefits of code reviews, in no particular order:
- Higher-quality code
- Potential to find alternative solutions
- Fewer defects
- Shared ownership of the code
- Context sharing
There are probably a few I am missing, but that seems like a sound and uncontroversial list.
There is a trick; however, these benefits don't come from just any old code review; they come from well-run code reviews. There are a few pitfalls.
I've seen code reviews go wrong in a few different ways:
- The amount of context to understand the feature is too high. IE, the author has more context on the problem then the rest of the team, and no one else on the team can give them meaningful feedback.
- The "oops" rewrite everything problem. The author did not fully understand the context of the feature under review before they started to write code. IE, another teammate has more context than the author and wasn't able to share context effectively beforehand.
Other things can go wrong, feature branches that never end, the code review turning into a human linting session, but for the most part, these problems stem back to the communication issues listed above.
The precarious part of any code review process is shared context. But wait, wasn't that one of the benefits of code reviews we listed above? Well, the trick is, even if you live in a perfect world and everyone on your team understands every single line of code in your codebase, you still just developed a bunch of new context in your code review. Anyone reviewing that code now needs to climb the mountain to understand what you just wrote. So even in an ideal world, there is some work to build context. That's not the end of the world, we all know reading code is the hard part, but it's well worth the effort for the benefits. Since we can't avoid this overhead, how do we fix the two scenarios above? The answer is feedback, or more specifically, the feedback cycle.
The trick is to move to a quicker cycle for reviews, taking an approach of "review early, review often." If we want to avoid context gaps and reap benefits at a faster cadence, keep reviews short, and get that code merged in post review.
Flawless plan, right? No, you say? Well, I guess you are right, there are two problems here.
- Some features/stories/tasks don't lend themselves to small commits.
- Everyone else has work to do. We can't just stop everything to code review all the time.
These problems are opposite sides of the same coin. They both are part of the reality of engineering work that leads to pull requests sitting idle for hours or maybe even days (or maybe even weeks). This leads to even greater context holes, as the original author has moved on to other features and now needs to pay their own context penalty when they context-switch back to the code review they requested.
So what's the solution? Well, I gave it away at the beginning, but it really is pair programming. All those benefits I listed for code review above, you get them all. Not only that, but you get those benefits at a much higher cadence. It also solves the two problems with the review early, review often approach.
No one is waiting on any code reviews anymore because your pair is beside you and invested in getting the commit done. If you adhere to a two pizza team you now have a non-trivial percent of your team working on any given commit. If a commit is exceptionally hairy or you are lacking some domain expertise, either start the pair with the engineers you need involved or call them over when the task is done and do the code review in person. I guarantee this does wonders for context sharing, and the need to do a three-person review drops as your team works on the codebase together.
For larger features, you now have the focus on the entire pair. Better yet, if a feature is going to carry over to another day, you have an opportunity to establish a regular cadence of pair rotation. Keep one member of the pair on the feature and rotate another engineer on. At the end of the cycle, the context shared would be higher than any code review I've ever participated in.
Wait, you want two people to do the same job?
Yes, I do. Code reviews are widespread because there is a widespread acceptance of the benefits. What I am saying is that pair programming is just a refined version of code reviews with all the benefits and without several major drawbacks.
But this code is trivial and not worth the time of two engineers
I hear this a bunch when discussion of pair programming comes up. If the code is worth reviewing, it's worth pairing on. If it's not worth reviewing, well, I see a more substantial smell in having a non-trivial amount of "trivial" code in a codebase. That feels like a missing abstraction.
Do I really have to pair all the time?
You don't. There are points were splitting off makes a lot of sense. For instance, if the pair needs to read some documentation, or needs to take some time and experiment with different solutions. In general, you should be paying attention and favoring writing production code with a pair, but there are good reasons to split. The important thing is to make sure you are splitting because it makes sense and not because you are avoiding pairing or (worse) your pair, which brings me to the next point.
Pairing sounds great for other people, but I just don't think that way
So here is something I don't think gets said enough, pairing is hard, especially when you first start. Some come more naturally to it than others, but pairing is a skill. It is a skill that develops over time. You need to be patient with your pair and, more importantly, yourself when you are learning this new skill. Don't be afraid to take breaks. If you are struggling trying switching pairing styles or slowing down and spending extra time externalizing your thoughts. I've been pair programming professionally pretty much every day for about 3 years now, and I still occasionally get movements where I stumble over my words or go silent for a bit while I work out how to best communicate a concept.
If it is that hard, why do it?
Here is the thing, it works. I had a client tell me once, "the code I am writing now is the best code I feel like I've ever written in my career." That client was not a junior, he was a seasoned engineer with a long career, and he walked into pairing with a skeptics mind and got something out of it. Honestly, after reflecting over the last 3 years, I think I agree with him. The code I am writing now feels like the best code I've written. I go back to code I wrote with a pair weeks or months ago, that code just reads better. It's gotten to a point where I wish I could pull someone aside on weekends occasionally to pair on some of my hobby projects.
So there it is, my case for evolving your code reviews into pair programming. I've only scratched the surface, as there other benefits that come along with an established pair programming practice that go above and beyond the benefits of code review. Hopefully, what I've written is enough to pique your curiosity and convince you to give it a try. After evolving your code reviews practice to pair programming, you'll find you spend less time on the mechanics of code review and more time writing quality code.