The sense and nonsense of pull requests (Sander Hoogendoorn)

Recently, I posted my doubts about the effectiveness of pull requests on LinkedIn. Surprisingly, my simple post led to a bonfire of discussions and eventually had over 350,000 views. I struck a nerve.

So, what is a pull request? Essentially, a pull request is a proposal to merge changes from one code branch to another. Other people working on the codebase review and discuss this proposal before integrating the changes into the other branch, usually the main one. This procedure allows collaborators to reason about the quality of the proposed changes and, in doing so, uphold the quality of the entire codebase.

Sounds straightforward, right?

Pull requests make perfect sense when you don’t trust the changes proposed to your codebase. The most common scenario is open-source projects. In open-source projects, anyone can suggest improvements to the codebase. This is precisely how and why open-source works.

Open-source projects reap the wisdom of the crowd. As one of the authors of the open-source framework Easy.ts, I see this in practice every day. We don’t always personally know the people who propose changes, so we can’t always value their experience or personal preferences, which often are even a matter of personal taste.

This is where pull requests come in handy. They allow us to discuss people’s coding decisions before merging their code into our projects. It’s an excellent opportunity to share skills and create an understanding of different approaches.

Trust

The keyword here is trust. When we don’t trust the proposed code changes, we review and discuss them with the proposers. Here, pull requests make sense.

Let’s step back from open-source and examine everyday projects and code written in organizations and teams. For starters, my own team at ibood.com. It comprises fifteen people with varying experience, backgrounds, and knowledge of different programming languages. Some have decades of experience, and some have two years.

Ours is a relatively small team. To maintain the quality of our code, we apply a variety of simple techniques:

  • We standardized our software architecture, and we all comprehend its layers and patterns. Consequently, we can open any of our 150+ repositories and understand what we find there, even if we haven’t seen a repo before.
  • We often work together closely, for instance, using pair and mob programming. Even remotely. So, most changes are crafted by multiple people. Hence, there is much less of a need for additional checks afterward.

    We have weekly knowledge-sharing using the lean coffee approach. During these sessions, anyone can discuss any topic, offering rapid and immediate learning.

  • We write unit tests for all our code, often applying test-driven development.
  • Our CI/CD pipelines are fully automated and provide building, linting, running tests at various stages of the pipelines, static code analysis, and deployments, even to production. Our pipelines provide many quality checkpoints.

As a consequence, we don’t do pull requests.

 

Each team member pushes their changes directly onto the main branch. We practice trunk-based development; we don’t branch. We have built an environment where trust rules, independent of the individual’s level of seniority.

The Law of Raspberry Jam

Pull requests are valid in environments where trust is low. This is the case with open-source, where we know little about the people who propose changes. But in teams like my current and many others I’ve worked with, trust is not an issue.

 

Trust, however, has become an issue in many organizations. Quite some comments on my recent post demonstrate this, often literally: “We can’t trust individual developers to commit to the main branch directly, especially juniors. There is too much at stake.”

 

Lack of trust usually comes with scaling. The more people work on your codebases, the harder it becomes to maintain quality. With a strong ambition to grow teams rapidly and a scarcity of good developers, organizations hire less experienced people and, consequently, struggle with quality.

 

It is Jerry Weinberg’s Law of Raspberry in action: the further you spread it, the thinner it gets. And so, pull requests to the rescue.

Timeliness

However, using pull requests and reviewing changes has a severe drawback. The keyword here is timeliness.

 

Most organizations that apply pull requests formally are large or scaling up to become large. The people doing reviews are often the more senior in the development organization. They are also the busiest people—not least because they need to review all pull requests.

 

Reviewers frequently become bottlenecks.

 

And so, reviews often don’t happen immediately. When you do a pull request, time goes by before a reviewer picks it up. Sometimes, this is a matter of hours, but if reviewers are in meetings, have a day off, the weekend starts, or one of their kids is sick, the wait becomes longer. Days go by.

 

Delays have several consequences. When they have to wait, developers will pick up another feature. When a review returns several days later with feedback that requires action, the developer must return to the previous feature. If developers have multiple pull requests open—not uncommon—they constantly need to refocus. This is called context switching. It is costly. Multitasking is a myth.

 

Second, as a developer, you want to avoid unnecessary long feedback loops. So, you start to prevent pushing small changes by collecting them until you have finished all the work on a feature. Worse, you only push the changes when you are confident that everything people would have wanted and more is there. You build too much. You over-engineer.

 

The longer you wait, the bigger your change becomes. The bigger the change, the harder it is to merge it back to the main branch, as others will have made changes in the meantime. Despite your good intentions, pull requests continuously increase delays in situations like these.

Missing the bus

So what’s the optimum? Trust is not much of an issue in smaller organizations and teams than in larger ones. Someone replied to my post saying they work with thirty teams on their mobile app and release it once every two weeks. They are strongly biased towards pull requests and review every change someone makes. Their strategy is risk-averse.

 

At ibood.com, we are not. We immediately push every change to our e-commerce platform to production. Yes, severely checked and tested in our pipelines, but still immediately. To us, making a mistake in production is not the end of the world. As Jason Gorman says, “Missing the bus is not a big issue if you know that the next one will be there in five minutes.”

 

Ultimately, it all comes down to this: How bad is it if you miss the bus? Large organizations that often work remotely need more trust. The more risk-averse they become, the more they apply pull requests and reviews.

 

Conversely, when your teams are small and coherent, many techniques help build trust and speed. Pair programming, mob programming, test-driven development, and fully automated delivery pipelines all help avoid dreadful waits and context-switching.

 

At ibood.com, the next bus comes in five minutes. When does yours?