Great Code Reviews — The Superpower Your Team Needs
There is a general consensus that code reviews are an important aspect of highly effective teams. This research paper is one of many exploring this subject. Most organizations undergo code reviews of some form.
However, it’s all too common to see code reviews that barely scratch the surface, or that offer feedback that is unclear or hard to act upon. This robs the team the opportunity to speed up learning, share knowledge and context, and raise the quality bar on the resulting code.
At Shopify, we want to move fast while building for the long term. In our experience, having strong code review practices has a huge impact on the growth of our engineers and in the quality of the products we build.
A Scary Scenario
Imagine you join a new team and you’re given a coding task to work on. Since you’re new on the team, you really want to show what you’re made of. You want to perform. So, this is what you do:
- You work frantically on your task for 3 weeks.
- You submit a Pull Request for review with about 1000 new lines of code
- You get a couple comments about code style and a question that shows the person has no clue what this work is about.
- You get approval from both reviewers after fixing the code style and answering the question.
- You merge your branch into master, eyes closed, shoulders tense, grinding your teeth. After a few minutes, CI completes. Master is not broken. Yet.
- You live in fear for 6 months, not knowing when and how your code will break.
You may have lived through some of the situations above, and hopefully you’ve seen some of the red flags in that process.
Let’s talk about how we can make it much better.
Practical Code Review Practices
At Shopify, we value the speed of shipping, learning, and building for the long term. These values — which sometimes conflict — lead us to experiment with many techniques and team dynamics. In this article, I have distilled a series of very practical techniques we use at Shopify to ship valuable code that can stand the test of time.
A Note about terminology: We refer to Pull Requests (PR) as one unit of work that’s put forth for review before merging into the base branch. GitHub and Bitbucket users will be familiar with this term.
1. Keep Your Pull Requests Small
As simple as this sounds, this is easily the most impactful technique you can follow to level up your code review workflow. There are 2 fundamental reasons why this works:
- It’s mentally easier to start and complete a review for a small piece. Larger PRs will naturally make reviewers delay and procrastinate examining the work, and they are more likely to be interrupted mid-review.
- As a reviewer, it’s exponentially harder to dive deep if the PR is long. The more code there is to examine, the bigger the mental map we need to build to understand the whole piece.
Breaking up your work in smaller chunks increases your chances of getting faster and deeper reviews.
Now, it’s impossible to set one universal standard that applies to all programming languages and all types of work. Internally, for our data engineering work, the guideline is around 200–300 lines of code affected. If we go above this threshold, we almost always break up the work into smaller blocks.
Of course, we need to be careful about breaking up PRs into chunks that are too small, since this means reviewers may need to inspect several PRs to understand the overall picture.
2. Use Draft PRs
Have you heard the metaphor of building a car vs. drawing a car? It goes something like this:
- You’re asked to build a car.
- You go away for 6 months and build a beautiful Porsche.
- When you show it to your users, they ask about space for their 5 children and the surf boards.
Clearly, the problem here is that the goal is poorly defined and the team jumped directly into the solution before gathering enough feedback. If, after step one, we created a drawing of the car and showed it to our users, they would have asked the same questions and we would have discovered their expectations and saved ourselves 6 months of work. Software is no different; we can make the same mistake and work for a long time on a feature or module that isn’t what our users need.
At Shopify, it’s common practice to use Work In Progress (WIP) PRs to elicit early feedback whose goal is validating direction (choice of algorithm, design, API, etc). Early changes mean less wasted effort on details, polish, documentation, etc.
As an author, this means you need to be open to changing the direction of your work. At Shopify, we try to embrace the principle of strong opinions, loosely held. We want people to make decisions confidently, but also be open to learning new and better alternatives, given sufficient evidence. In practice, we use Github’s Draft PRs — they clearly signal the work is still in flow and Github prevents you from merging a Draft PR. Other tools may have similar functionality, but at the very least you can create normal PRs with a clear WIP label to indicate the work is early stage. This will help your reviewers focus on offering the right type of feedback.
3. One PR Per Concern
In addition to line count, another dimension to consider is how many concerns your unit of work is trying to address. A concern may be a feature, a bugfix, a dependency upgrade, an API change, etc. Are you introducing a new feature while refactoring at the same time? Fixing two bugs in one shot? Introducing a library upgrade and a new service?
Breaking down PRs into individual concerns has the following effects:
- More independent review units and therefore better review quality
- Fewer affected people, therefore less domains of expertise to gather
- Atomicity of rollbacks, the ability of rolling back a small commit or PR. This is valuable because if something goes wrong, it will be easier to identify where errors were introduced and what to roll back.
- Separating easy stuff from hard stuff. Imagine a new feature that requires refactoring a frequently used API. You change the API, update a dozen call-sites, and then implement your feature. 80% of your changes are obvious and skimmable with no functional changes, while 20% are new code that needs careful attention to test coverage, intended behaviour, error handling, etc. and will likely go through multiple revisions. With each revision, the reviewer will need to skim through all of the changes to find the relevant bits. By splitting this in two PRs, it becomes easy to quickly land the majority of the work and to optimize the review effort applied to the harder work.
If you end up with a PR that includes more than one concern, you can break it down into individual chunks. Doing so will accelerate the iteration cycle on each individual review, giving a faster review overall. Often part of the work can land quickly, avoiding code rot and merge conflicts.
In the example above, we’ve taken a PR that covered three different concerns and broke it up. You can see how each reviewer has strictly less context to go over. Best of all, as soon as any of the reviews is complete, the author can begin addressing feedback while continuing to wait for the rest of the work. In the most extreme cases, instead of completing a first draft, waiting several days (and shifting focus), and then eventually returning to address feedback, the author can work almost continuously on their family of PRs as they receive the different reviews asynchronously.
4. Focus on the Code, Not the Person
Focus on the code, not the person practice refers to communication styles and relationships between people. Fundamentally, it’s about trying to focus on making the product better, and avoiding the author perceiving a review as personal criticism.
Here are some tips you can follow:
- As a reviewer, think, “This is our code, how can we improve on it?”
- Offer positive remarks! If you see something done well, comment on it. This reinforces good work and helps the author balance suggestions for improvement.
- As an author, assume best intention, and don’t take comments personally.
Below are a few examples of not-so-great review comments, and a suggestion on how we can reword to emphasize the tips above.
Ultimately, a code review is a learning and teaching opportunity and should be celebrated as such.
5. Pick the Right People to Review
It’s often challenging to decide who should review your work. Here are some questions can use as guidance:
- Who has context on the feature or component you’re building?
- Who has strong skills in the language, framework, or tool you’re using?
- Who has strong opinions on the subject?
- Who cares about the result of what you’re doing?
- Who should learn this stuff? Or if you’re a junior reviewing someone more senior, use this as an opportunity to ask questions and learn. Ask all the silly questions, a strong team will find the time to share knowledge.
Whatever rules your team might have, remember that it is your responsibility as an author to seek and receive a high-quality code review from a person or people with the right context.
6. Give Your Reviewers a Map
Last but definitely not least, the description on your PR is crucial. Depending on who you picked for review, different people will have different context. The onus is on the author to help reviewers by providing key information or links to more context so they can produce meaningful feedback.
Some questions you can include in your PR templates:
- Why is this PR necessary?
- Who benefits from this?
- What could go wrong?
- What other approaches did you consider? Why did you decide on this approach?
- What other systems does this affect?
Good code is not only bug-free; it is also useful! As an author, ensure that your PR description ties your code back to your team’s objectives, ideally with link to a feature or bug description in your backlog. As a reviewer, start with the PR description; if it’s incomplete, send it back before attempting to judge the suitability of the code against undefined objectives. And remember, sometimes the best outcome of a code review is to realize that the code isn’t needed at all!
What’s the Benefit?
By adopting some of the techniques above, you can have a strong impact on the speed and quality of your software building process. But beyond that, there’s the potential for a cultural effect:
- Teams will build a common understanding. The group understands your work better and you’re not the only person capable of evolving any one area of the codebase.
- Teams will adopt a sense of shared responsibility. If something breaks, it’s not one person’s code that needs fixing. It’s the team’s work that needs fixing.
Any one person in a team should be able to take a holiday and disconnect from work for a number of days without risking the business or stressing about checking email to make sure the world didn’t end.
What Can I Do to Improve My Team’s Code Review Process?
If you lead teams, start experimenting with these techniques and find what works for your team.
If you’re an individual contributor, discuss with your lead on why you think code reviews techniques are important, how they help effectiveness and how they help your team.
Bring this up on your next 1:1 or your next team synch.
The Importance of Code Reviews
To close, I’ll share some words from my lead, which summarizes the importance of Code Reviews:
“We could prioritize landing mediocre but working code in the short term, and we will write the same debt-ridden code forever, or we can prioritize making you a stronger contributor, and all of your future contributions will be better (and your career brighter).
An enlightened author should be delighted to have this attention.”
We’re always on the lookout for talent and we’d love to hear from you. Please take a look at our open positions on the Data Science & Engineering career page.
Originally published at https://engineering.shopify.com.