DEV Community

Cover image for Merge Branches Sooner with Synchronous Code Review
Steven Lemon
Steven Lemon

Posted on

Merge Branches Sooner with Synchronous Code Review

Code review has the potential to be one of the most impactful activities we do in our day. It ensures our codebase remains readable and maintainable, catches bugs, spreads knowledge across the team, and increases our confidence in what we are about to release.

However, poorly structured code review processes can actively harm the team and their work. When the turnaround time of a code review stretches into days, code review chokes the flow of work through the team. We still gain the benefits of review, but each review blocks follow-on work, and the team finds itself pulling in additional streams of work while they wait for the review to complete.

Slow reviews create a negative feedback loop, where attempts to reduce the overhead of code review increase the size of each branch. Bigger, longer branches mean more difficult code reviews that take longer to complete. Feedback is delayed and comes after the developer has done enough work that pivoting is hard. The team takes on a lot of additional cognitive load, tracking many large, different features simultaneously.

Asynchronous Code Review involves a lot of wait time as discussion and feedback is staggered over several days. The branch takes 6 days to merge, blocking all subsequent work during this time.
Asynchronous Code Review involves a lot of wait time as discussion and feedback is staggered over several days. The branch takes 6 days to merge, blocking all subsequent work during this time.

Our typical code review process is dictated by the defaults of the platform we use. We open a pull request, add reviewers, the code review platform emails them, and when they have time, they will look at the review and leave comments. These comments are sent back to the original developer via email notification, who will answer questions and make code fixes. Work continues back and forth asynchronously until the reviewers are happy for the code to be merged.

Not all teams are slow at code review, but many are, with the above process typically taking 3–4 days to resolve. A lot of effort can be spent tweaking the above code review process and attempting to reduce its impact with little success: increasing the reviewer pool, increasing reminders, and adding even more processes trying to remove ambiguities.

Or, we could dramatically shift how we structure code reviews — by making them synchronous.

Rather than the code reviewer participating at some undefined time after the review is opened, they join the reviewee at their desk, or via video chat, and go through the review together. The review becomes a conversation, simple issues are fixed in the review, and once the reviewer is happy, the code is merged immediately. Rather than being a separate status with its own process, Code Review is a short window of peer programming that occurs as soon as the development of a task is finished.

Synchronous Code Review removes the wait time, replacing it with pair programming.
Synchronous Code Review removes the wait time, replacing it with pair programming.

The most significant impact of Synchronous Code Review is that it strips almost all of the wait time from your code review process. While it might take a few hours for a reviewer to be available, once begun, most reviews complete within a quarter of an hour. Eliminating wait time decreases a branch’s total time in code review from days to minutes. Synchronous Code Reviews let us remove all the problems caused by slow code reviews.

Benefits of Synchronous Code Review

Reduced context shifting (for the reviewer)

The cost of switching tasks is very high. After a disruption, it takes more time than we think to get back to where we were. We only get so many shifts per day before exhaustion adds up and starts decreasing our capabilities. Any team’s process needs to carefully account for the disruption and context shifting it causes the team.

Traditional Asynchronous Code Reviews involve a lot of context-switching for the developer seeking the review. Every time they make fixes or reply to comments, they need to switch not only contexts but also branches, local setup, etc. The original developer has likely started a different task while they wait for the review, and wants to wait until they have reached a stopping point before resuming replying and fixing. Worse, this task is typically unrelated because the feature they were working on is blocked on the pending review. Reluctance to context-switch is often why async code reviews run for such a long time.

Code reviews are often delayed because we wait for other work to reach a stopping point before returning to make fixes.
Code reviews are often delayed because we wait for other work to reach a stopping point before returning to make fixes.

Synchronous Code Reviews remove this context-switching because the developer never leaves their original context; code review is done in line with the task itself. (Except for a small wait for a reviewer to be available.)

Mentoring and learning

One of the often discussed benefits of code review is the potential for knowledge sharing. Ideally, this works in both directions, with the reviewers exposed to new changes to the application and the reviewee learning about best practices, language features, design patterns, etc. However, in Asynchronous Code Reviews, we tend to lose the latter. The text-based format does not encourage additional exposition. Comments will suggest a change, but few will elaborate on how to make the change or, more importantly, why it is required.

It’s often easier and quicker to explain ourselves vocally than by typing. To offer more explanation for a proposed change or to start tangential discussions based on what you’re reading. This communication works both ways, and a reviewer unfamiliar with a particular technique or pattern can ask for elaboration. Learning not just how it works, which they could garner from googling during an async review, but why the original developer chose that solution and what alternatives they considered.

Testing as part of the review

In my experience, code reviewers don’t tend to run the code they are reviewing. It’s time-consuming to stash what you’re currently working on, switch branches, pull down dependencies, then figure out how to reproduce the scenario covered in the review. In Asynchonous reviewers, reviewers tend to try to minimise their own context switching, even at the cost of the quality of the review.

Synchronous Code Reviews remove that barrier, as in front of you, you have the exact setup used to create what you are seeing in the code review. Access to a demo can add a surprising amount of helpful context to the review — particularly as we think about the things that weren’t done. Seeing the code alongside the demo can help identify bugs and problems that you wouldn’t have spotted from the code alone.

Even if your team has dedicated testers, a quick test as part of code review will often identify issues that save a few cycles back and forth during the manual test phase.

Reduced ambiguity

Asynchronous Code Reviews end up with many ambiguities that slow down the process and create frustration, uncertainty and doubt for the individuals involved. It can often be hard to tell:

  • Was the reviewer happy with the response to a comment?
  • Can I disagree with that reviewer’s feedback?
  • Is that comment a must-fix or just a suggestion?
  • Is the review still open because the original developer is finishing something else up before merging, or were they waiting on more feedback?
  • Was a reviewer wanting to look at the review again? Are they happy with it, or have they forgotten about it?
  • Does someone not want to do the review? Did they miss the email, or has it slipped their memory? And I could go on. Work frequently get stuck in review because each participant thinks they are waiting on the other. Asynchronous Code Reviews require a lot of discussion about their status in other channels.

Synchronous Code Reviews avoid all doubt about the status of a review and can only end in one of two ways — the reviewer is happy, and the branch merges, or the reviewer pauses the review after providing a list of fixes they would like made before they return.

Less to keep track of

Long-lived branches add to the team’s cognitive load. As reviewers, some attention will be taken keeping track of the discussion and changes in the review they are active in. Even if you’re not a reviewer in a pull request, you’ll often still need to be aware of the long-lived incoming branches as they affect the application you are working on.

Software applications are complex enough without also having to keep track of multiple variations over time. Needing to keep being aware of how your work will mesh with other in-progress branches can be as draining as context switching.

Synchronous Code Review helps here because it let branches merge as soon as development is complete. They help us get closer to the codebase having a single version, and a single source of truth. The development team no longer needs to consider code in a transient state.

Easier to highlight positives

Code Reviews shouldn’t just be solely about finding problems but should also include identifying positives and expressing gratitude. Not only does this make the review experience more pleasant, but it also helps reinforce positive actions. This often takes the form of little comments such as “that’s a clean way of doing that,” “I like what you’ve done there,” and “thanks for fixing that; it’s been bugging me for a while.”

In my experience, this doesn’t happen as much in Asynchronous Code Reviews. In comparison, when conducting an in-person or video call code review, positive feedback tends to happen organically.

Rubber-ducking

Explaining a problem will often yield additional insights to the explainer. This effect can also occur during Synchronous Code Review; as you’re explaining your work to the reviewer, it’s not uncommon to realize something you missed. Perhaps it’s a little bit late realizing at the code review stage, but these insights are often better late than never.

Balancing out the downsides of Synchronous Review

Only one reviewer
The biggest downside of Synchronous Code Review is that it puts a hard limit on the number of reviewers. Even teams practicing Asynchronous Code Review and requiring a single approval will still tend to end up with two to three people looking at the review.

The more people you have looking at the review, the more chances you have to spot issues. You get people with different perspectives, experiences, and levels of thoroughness. While a single person performing a Synchronous Code Review tends to be more effective than one person performing an Asynchronous Code Review, they will spot fewer issues than two people performing an Asynchronous Code Review.

In addition, fewer reviewers means fewer opportunities to spread knowledge. Only two people on the team are now familiar with the change in the review. This limitation is less important if the rest team is clustered on the same feature since they will get to see it organically as they continue to iterate on the feature.

It’s worth considering how much pressure we’re putting on expecting perfect code reviews. Some organizations treat code reviewers as the “guardians of quality,” which tends up be an unhelpful attitude. Further, decreasing the quality of code reviews to increase flow will see that quality picked up elsewhere. When the team can cluster on a feature, you benefit from the aggregate of everyone’s contributions. In comparison, a blocking-heavy process ensures only one person works on a feature at a time, limiting everyone else’s involvement to solely the code review. Increasing flow lets us decrease the size of each branch and the subsequent pull request, decreasing the size and risk of each code review.

Finally, it’s important to refrain from restricting who can perform code reviews. The team might be nervous about the only reviewer on a task being a junior developer. However, limiting the pool of code reviewers increases the time spent waiting for review, pushing it over the threshold where the original developer maintains their context. It limits knowledge sharing further, prevents others from learning how to code review, and breaks code review’s social contract of reciprocation.

Increased Context switching for the reviewer

A Synchronous Code review is much more disruptive for the reviewer than the async approach. Whereas async reviews can be filler activities, fitting into gaps and wait times, Synchronous Code Reviews require more time commitment, particularly if you end up pair programming fixes. Additionally, Synchronous Code Reviews operate on a much shorter time frame, as a reviewer, you need to get to the review within a couple of hours, whereas async gives you a couple of days — even though it really shouldn’t.

As reduced code review turnaround decreases the duration of our branches and lets us create smaller and more frequent code reviews, we also need to balance this against the additional disruption this causes. Working in small batches is generally good, but getting too small can be disruptive. Finding the right balance is something the team will need to discover as they go.

There are many ways to reduce the impact of context-switching — as a reviewer, give estimates of when you will be ready, “I’ll come over at one” will give a lot more information and time to prepare than “I’ll be free sometime later this afternoon.” Reviewees should be as prepared as possible, read through everything in your code review at least once, make sure the branch is up to date from the main branch, check that continuous integration is passing, and have a demo ready to go.

Letting explanations replace self-evident code

In a Synchronous Code Review, reviewers will tend to give the reviewer a guided tour of the code. Walking them through what they have done and highlighting critical areas before letting the code reviewer scroll through at their own pace. Taking this additional time makes the review quicker and more accessible for the reviewer.

However, we need to be careful, code needs to be self-explanatory. Someone should be to be able to read this code a year later and be able to understand it without needing to ask the author for explanation. Asynchronous Code Review doubles as a test of how self-documenting and clear the code is. Your work needs to make sense with no additional context to pass the review.

When performing a Synchronous Code Review, we must be careful that the code is still clear without explanation. It’s helpful to hear the walkthrough, but at the same time, we need to be careful that the walkthrough isn’t replacing good code. As a general rule of thumb, if the reviewer needs to ask why something was done, or how something works, it should be a hint that additional clarification is needed. Done carefully, Synchronous Code Reviews can be beneficial for code quality because they let developers hear in real time what code causes confusion, is harder to parse, or is not immediately apparent.

No written record of the discussion

In theory, Asynchronous Code Reviews mean that the history of the review’s discussion is retained in the repository. Switching to Synchronous Code Reviews will mean you lose that history.

In reality, the conversation tends to leak out, with developers discussing particularly tricky things in person or via video. Additionally, it’s rare that we go back and look at old review discussions, so I’m unsure of the value we are losing here.

Other advice

Retain the ability to do Asynchronous Code Reviews

Sync code reviews could allow you to remove the Code Review status from Jira entirely. Code Review is no longer a separate step that needs tracking and instead folds into the end of the “in dev” status. However, it’s still be worth retaining the ability to do Asynchronous Code Reviews when required.

There will be special cases that warrant async reviews. You may need feedback from other teams, you need feedback from multiple people, or key people are away.

Small reviews that are non-blocking might not be worth the disruption of a Synchronous Code Review. Similarly, work that is simple, non-controversial, and likely to elicit little feedback doesn’t have much to gain from Synchronous Code Reviews.

And, of course, nothing says you need to commit fully to either approach. You could have a mix and let the developer creating the review decide whether a Synchronous or Asynchronous review is more appropriate.

Don’t try to fix too much within the review

There’s a careful balance to deciding what changes can be fixed in the review and what changes the reviewer should return for. Returning to a review later, after fixes are made, is another context switch for the reviewer. Trying to do too much within the review consumes more of the reviewer’s time, and can lead to rushed changes.

However, this is another problem that should reduce as the team increases its flow. Smaller, more frequent code reviews that happen sooner in the development of a feature will tend to need fewer fixes.

Spread the code review load equally

Avoid ending up with a single person doing most of the reviews. Not only does it consume a lot of their time, but it also limits the spread of knowledge across the team. Everyone in your team should be able to review everyone else, regardless of experience or specialization. In most situations, people should request reviews from the entire team rather than directly asking a single reviewer.

Switch up who is driving

During the review, both the reviewee and reviewer should have access to the keyboard and mouse. A review might start with the reviewee describing the problem they were tackling and giving a brief walkthrough of how they solved it. Control should then switch to the reviewer so they can read through the code at their own pace. If fixes are required, control might switch back and forth as changes are made. The demo, which could make sense at any point in the review, should be driven by both participants — the reviewee might want to demonstrate specific scenarios, while the reviewer will want to check for scenarios they think the reviewee might have missed.

When performing an in-person review, switching control back and forth happens organically. However, during a video call, you will need to be more mindful of letting control switch back and forth.

Where to use Synchronous Code Review

Neither synchronous nor asynchronous is clearly better than the other. For everything you gain from Synchronous Code Review, there are a lot of downsides to consider. These problems eventually shrink as an increase in flow lets you shrink the size of your branches and reviews. The downsides can also be mitigated by choosing the right approach for the team and situation.

For example, Synchronous Code Review works best in the following situations:

  • The team tends to overlap and cluster on features. Having fewer reviewers matters less because the entire team is working in that area. For this team, the cost of blocking branches is much higher than for a spread-out team.
  • The team already has a high degree of communication and may lean towards extraversion. The team has high trust and is comfortable giving honest feedback to each other.
  • Everyone brings the right attitude to code reviews—everyone is willing to set their ego aside and spend the time required to ensure the long-term health of the codebase.
  • The team has a lot of juniors and intermediate developers who would benefit from the extra mentoring opportunities afforded by Synchronous Code Review.

To summarize,

For reviewers:

  • Set reasonable estimations for when you will be ready to join a review, don’t drop what you’re doing to do a review.
  • Know when to walk away from the review and let the developer make changes by themselves.
  • Talk a lot, give positive feedback, and call out when something is difficult to parse. Explain why something is confusing. Explain why you want a change made. Go on educational tangents.
  • Ensure you have access, at the appropriate moments, to the keyboard and mouse so that you can go through the code at your own pace and test scenarios yourself.

For reviewees:

  • Pay attention to when the reviewer needs help understanding what they are reading, don’t just explain in person—add a comment, or refactor the code to clear the confusion.
  • Be comfortable giving the reviewer time to read quietly without interruption.
  • Be prepared to wait several hours for the reviewer to get to you, have another task with low context-switching impact to move onto in the meantime.
  • As you explain your review, you will occasionally realize something you missed. It happens more often than you think, don’t be embarrassed or try to hide it. Fix it in the review, or ask the reviewer to return later.
  • In most cases, request reviews from your team channel rather than from a specific reviewer.
  • Be kind to the reviewer, be as prepared as possible, and have a demo ready.

Top comments (0)