How to do code review that enforces high standards while avoiding common problems on your team.
Code review is the hardest part of being a developer.
Whereas most of our work is between you and a computer – a machine you just have to operate correctly and indicate clear intentions to – code review is a thing between humans. And like it or not, those humans are squishy, opinionated, emotional creatures. And those traits make code review a challenge.
In principle your goal in a code review is simple: examine another person’s code and make sure their pull request (“PR”) is “good enough” to merge.
But in reality there are a gazillion other things swirling around in your brain.
- Urgency – is this a hot fix for a major production outage?
- Seniority – am I allowed to decline this senior developer’s PR?
- Reaction – will someone yell at me if I decline this PR?
- Grudges – will you decline my next PR just because I declined this one?
None of these are related to the code quality, but they are legitimate concerns – you might really rub people the wrong way if you review code unkindly.
So how do you avoid these problems? And how do you set up a team for successful code reviews?
Set the Team Up for Success
Before anyone even writes any code, set clear expectations. Anticipate the obvious problems and just avoid them.
- Use a code linter – many arguments in PRs are over style. And thats just not the place for it. You can easily prevent most of these disagreements from making it as far as the pull request by agreeing on a set of linter rules.
- Have a code style document – some things aren’t reasonably enforceable by a linter, but are still just conventions. Write these down in a central place and have everyone on the team agree to them. Getting buy-in from your team gives everyone accountability. If the team really did all agree on these rules its much easier to enforce them. All you need to do is link to that page and remind the developer that this was a team decision.
- Do design meetings – when starting a big task, consider having a design meeting. This might feel like a waste of time but it’s not. Having the team contribute to the design before the developer starts work will avoid large-scale issues from cropping up when it comes time to review. You don’t want to start a task over because it has a fatal design flaw. And you really don’t want to be forced into accepting a fatally flawed design because you’ve run out of time to go back and start over.
- Establish relationships and respect – people are more empathetic and kind when they know each other, and preferably even like each other. Invest in relationships on the team and you’ll avoid some conflicts caused by people just not taking the time to care about each other’s feelings.
- Write a Definition of Done – this is a formal, generic list of requirements that apply to all/most tasks. For example maybe “all code must have 70% unit test coverage, relevant documentation must be updated, the CI build must pass, and you must demo your work for your manager.” If any of these aren’t true, the PR should indicate that it is a work in progress, and it shouldn’t be allowed to be merged yet. This makes it clear the submitter is looking for early, broad feedback.
Submitting a Pull Request
Recognize that reviewing code is hard, and make it as easy as possible for your reviewers to give you useful feedback. Remember – you want them to find the problems with your PR. It saves your future self a bunch of headaches.
- Write a PR summary – include a brief explanation of the purpose of this PR. Link to the ticket this PR is addressing. If there’s anything odd in your solution, explain it. If there’s a reason you truly couldn’t write unit tests, give it.
- Add the right reviewers – don’t add your whole company. Maybe not even your whole team. Recognize that people will take time from their day to help you improve your code when you ask for their review. Just add the people who will give you the most useful feedback – perhaps the subject matter expert of the file you’re working on. Or the person who filed the bug you’re fixing. You will also likely need to add a senior developer who has the authority to accept your work.
- Accept the feedback – if someone provides feedback and you can’t articulate a good reason not to, just accept their suggestion. Make their requested change. This may require you to set aside a bit of the pride you have in the great work you’re submitting (and that pride is good!), but it’s not going to hurt anything to accept their suggestion, and you’ll avoid lots of unneeded conflict.
Reviewing a Pull Request
Keep these things in mind when you go to review a PR.
- This PR doesn’t need to be perfect. It needs to make the code better. Follow-up PRs are totally ok in many situations, as long as the current PR works correctly, and doesn’t impose an unreasonable maintenance or refactoring cost on others working in the same codebase.
- Start with the big picture – pull up the ticket for PR and confirm that this actually does what it’s supposed to. Does it actually work? Does it meet the Definition of Done? Is it clearly missing anything like unit tests? Provide this feedback early so that the submitted has time to rejig the whole PR if needed. There’s no sense in starting with identifying 20 typos (which the submitter may immediately start fixing) only to then suggest they take a completely different approach that will require them to throw all those fixes away anyways.
- Next look at the design – how have the classes been broken down? Are the components too big or have too many responsibilities? Do they define clear, testable boundaries? Provide this feedback using the verbiage of established engineering principles. “This design isn’t testable” is much less useful than “Using dependency injection here would allow you to mock out component X in your unit test”.
- Next review the “central class(es)” – identify the class that is the guts of the change, and start here. If there are 10 changed files in a PR, most likely only 2-3 of these contain the key logic, and the rest are helper classes or usages of the renamed API, etc. Focus your best effort on the most important classes while you still have the most energy and are providing the best feedback. Then circle back around to the other classes afterwards.
- Set a comment limit for yourself – It is difficult for the submitter to address 50 comments on a PR unless they are all trivial nitpicks and spelling errors. It’s also really discouraging to see that huge number of comments – it gives the impression of poor work and may really slow down the developer in future tasks.
- Consider in-person reviews for “bad” PRs – if there really are 50 significant flaws in the PR, take the review offline. As someone who can see these flaws, it is your responsibility to help the submitter learn. So sit down and go through the PR together, IRL. Be patient and compassionate, and don’t rush through it or make the submitter feel like they’re taking up your valuable time (even if they are). You are investing in your teammate – it will pay dividends.
- Consider in-person reviews for big PRs – sometimes a PR “blows up” and becomes a huge number of changes. In these cases it will likely be impossible (or unreasonably time-consuming) for a reviewer to figure out where to start. So do a group review, and have the submitter briefly present their work. The team can ask a few questions and get their bearings, and either do the full review together, or go back to separate desks and review online now that you have a better sense of what’s going on.
- Identify critical vs nitpicks – Be clear about which changes you think are unacceptable, and which are minor suggestions or opinions. These minor items can start with “Nit” as in “Nit: This might be clearer if you renamed this local variable to
a“. Also be clear if a comment is just a question, or if you’re unsure of whether your feedback is valid or not.
If They Always Make the Same “Mistakes”
Over time, you may want to give a teammate the same feedback on many PRs. This is where it becomes easy to be a jerk. Resist. Nobody wants to work with a jerk, and they certainly won’t be receptive to feedback if you treat them poorly.
- Remind yourself they are a professional – Nobody writes bad code on purpose. Nobody. Even that lazy sarcastic developer you don’t like for whatever reason doesn’t. So start by trying to help, and take the mindset that you are helping – not criticizing or “fixing” them.
- Ask yourself “is this my opinion?” – Just because you disagree with it doesn’t mean it was a mistake. Perhaps they have a valid reason for continuing with the pattern you don’t like, and perhaps that’s ok. You need to let other people win sometimes and recognize what is an opinion. There are many ways to solve all software problems, and we are constantly judging where to draw compromises. You may simply fall on opposite sides of the compromise here – if their code is functional and meets the agreements your team has set out then maybe you should just let it be and tolerate their alternative solution.
- Just talk about it – sometimes having a regular old human conversation about the problem will help them understand better why it even is a problem. Have the conversation at a time when they seem fresh and open to feedback, and do it in a place where they won’t feel embarrassed – don’t call them out in front of the whole team, or make them look or feel bad in front of their manager. Choose your first words carefully. “Hey I noticed something I think I can help with – do you have a second?” will be better received than “So you keep making this mistake – we need to talk about it”.
- Lead by example – people learn by copying each other. If you demonstrate the kind of thing you wish they were doing, they might just pick it up without you even having to confront them about it. This is also especially important if you do chat about it – make sure that you are on your extra best behaviour and demonstrate your expectations. They’ll be looking for a good example to help solidify the “right way”.
You can do it!
Reviewing code is a super important skill. It helps make your team’s code better, and makes your life better. You need to work and live with this code, so you’re making things easier for yourself by keeping a high bar in your code review. But you also need to be kind to your friends so they stay friends.
Hopefully these tips will help you be a better code reviewer! Good luck!
This article is just great all companies should be doing this by default as a cultural approach to PRs.
Thanks for sharing!
Totally! That’s a great point. If you can set up a healthy culture around your code review you’ll avoid many of the worst outcomes before they even become a problem.
If I may add something, I’d suggest that a developer shouldn’t be afraid of breaking a PR down into multiple ones. Some PRs get too large unexpectedly, and it’s those PRs that generally cause much tension.
Yeah that’s a great point. It’s also a good reason to keep your commits discrete as much as possible – it would be easy to just cherry pick commits in groups of 5 or whatever to review in pieces.
You could also have these little PRs target a “approved” branch and then once the little steps are approved separately reopen your big combined PR, knowing it’s all been approved already and merge that into master/develop with more confidence.
Sadly most PR reviews are toxic, subtly destroying a team over time. I wish that were not true, but I’ve seen time and again as a consultant. Miscommunication is the norm. Adopting a handful of PR comment classifiers often helps. Most geeks, though, haven’t a clue about the art of subtle communication via text.
Very good article. Thank you!
I think it helps to get everybody in a room to discuss the review culture, ask what’s going well, what’s not, and try to come up with action items and new policies to address the things that aren’t working. When I did this at my last company, I thought maybe only a couple people who cared about reviews would come, but to my surprise nearly the entire engineering team came! It did mean that the meeting took longer, but it was great to see that everyone cared about having a good review culture and I think the simple fact that everyone could see that we all cared went a long way towards improving the culture.