We’re all likely familiar with the platitude “the whole is greater than the sum of its parts”. In fact, there’s a whole cottage industry built around creating and proliferating motivational phrases on the topic of teamwork. We know the classics: “there’s no I in team”, “it takes teamwork to make the dream work”, “TEAM stands for Together Everyone Achieves More.” Each of these might induce a groan, but they’re so often repeated for a reason. While individuals can achieve great success working alone, truly impressive acts are generated from groups of humans working in concert toward a single goal, accomplishing things that could simply not be done by a single person.
Part of the impressiveness is from the ability of a group to work together, given the difficulty around collaboration. Collaboration is a powerful but fickle thing - each individual human, with their own wants, needs, struggles, weaknesses, and strengths, must all align into a singular force, focused on achieving a certain result. When all pushing in the same direction, humans can move mountains. But often it feels like when working as a group we can all be pushing in different directions, yielding almost no progress.
Today, I’d like to talk about the collaborative process I take in every day as part of my work as a software engineer. I find that when people visualize software development, they tend to imagine a single person in a hoodie click-clacking away on their laptop a la Mark Zuckerberg in The Social Network. While this might be the most prevalent image in the common consciousness, the process of developing software is often much more collaborative. It can take giant teams composed of individuals of disparate disciplines to develop, deploy, and maintain enterprise-level software.
Specifically, I’ll talk in chronological order about the collaborative practices of pairing, in which multiple software developers write code together, code review, in which all members of the team engage in evaluation and feedback on a proposed piece of code, and post-mortems, in which team members contribute to writing a root cause analysis when something goes wrong. Each of these practices require the ingredients needed for productive collaboration: trust and respect between team members, psychological safety1, an assumption of positive intent, and a desire to achieve a common goal. I use the term “practice” in the same sense that someone may describe a yoga or meditation practice: these processes are continuous and will grow and evolve over time, as will the individuals participating in them.
The first component of software development collaboration I’ll talk about is pairing. This is a practice in which myself and another developer work side-by-side on a piece of code over the course of an hour or two. In an office, this might mean physically sitting next to each other at a pairing station, in which there are two keyboards and two mouses plugged into the same computer. When working remotely, this often means screen sharing, with each pair partner having control over the keyboard and mouse. In either situation, both pair partners have equal access to write, modify, or delete code.
Sometimes, we’ll write a piece of code from scratch, starting with a blank canvas. Other times, we may improve on some work that has been half written, or implemented in a manner with much room for improvement. The goal is to blend our two backgrounds, strengths, and experience to produce a final result that is better than what either of us could have completed on our own. Having another developer beside you means introducing a new perspective as you begin to examine the problem and determine the solution. Having someone there to bounce ideas off of or provide some input when you get stuck brings a whole new, helpful element to the usually independent action of writing code. I’ve found that this usually results in my assumptions about a problem being broken, and the resulting code wildly different than I imagined when the pairing session. It is the most accessible way to implement diversity of thought, as two minds work together on a finite task. It usually results in better code, and a sense of shared ownership as there are multiple authors on the same piece of code.
The second collaborative practice is code review. The previous point was discussing the act of writing code. Once that’s written, whether individually or through the pairing process, it must be reviewed by the wider team. Before any code can be included in the final product, it must go through this process. This ensures that team members can feel a sense of ownership over the code in the project, as they can engage with new pieces of code through the acts of writing, pairing, and review.
Review involves examining and providing feedback about the proposed changes. It can be thought of like the editing process of an essay or article. Team members go through the proposed code changes, offering suggestions for improvement, asking questions where things may be unclear, or highlighting exemplary areas. In this practice, bugs can be found, performance can be improved, and ambiguities can be ironed out.
Each team member brings their unique perspective and motivation to the table. My impulse is to look for places where things could be made simpler; if it takes me more than a few moments to figure out what a couple lines of code are doing, I’ll often zoom in and see if there’s a way to clarify the segment of code in question. Others may look to find corner cases, where the author’s proposed implementation may work for 95% of parameters, but the reviewer can discover a few situations in which the original proposal wouldn’t work as intended. Often, an author may defend his or her implementation if a reviewer highlights a potential issue or offers up an alternative. The review process often results in stimulating discussions around the best way to implement something in a specific situation, leaving both the author and reviewer with an improved understanding and the confidence that the proposed changes have been properly vetted and okayed by the team.
This practice of code review is not to tear down or point out the flaws in someone’s work. Rather, it’s a team process that results in iterative improvement for individuals and provides a high return-on-investment for the amount of time put in. It is much less costly in both time and money to identify and fix problems during the review process before they end up out in customers hands. However, issues will sneak through, and that leads me to the third topic around software development collaboration.
It is a fact that failure will happen. Fear around failure results in paralysis and reluctance to deploy code changes, which stifles product development. If teams understand that failure is inevitable, they can invest in solutions to help identify and mitigate mistakes when they happen, and hopefully fix issues before they evolve into disasters. To do this, teams need to be able to know what caused the issue, and fix the process that allowed the issue to make its way into the product.
When things go wrong, and after the issue has been fixed, teams will often hold a post-mortem meeting. Someone will produce a report that includes the timeline of the issue, i.e. when it was introduced, when it was noticed, and when it was fixed, along with the participants involved in both causing and fixing the issue, and the remedies attempted. As a team, action items will be chosen to patch the process or lessen the chances a similar issue happens again in the future. This can take a variety of forms: this could mean more telemetry into how the system is performing, not allowing code to be deployed during the weekend, or adding more automated testing around the code that caused the issue. These meetings attempt to be blameless or blame-aware, in which the assumption is that teams had the best intentions and acted as best they could with the information they had.
The collaboration in this process is less obvious than in pairing and code review, where there is physical cooperation between team members. Here, the collaboration is more rooted in the trust and psychological safety between team members. It takes a lot to admit a mistake, especially in a professional setting, and it also takes a lot to not react with finger-pointing or shaming.
In the post-mortem culture, “every mistake is seen as an opportunity to strengthen the system.”2 For systems to improve their resiliency, and for teams to be able to learn from their mistakes, the mistakes must be made known and be able to be discussed openly. This can only be done in a collaborative culture, where individuals care about the team and the goal the team is aiming to achieve. The individual may suffer from temporary embarrassment by a mistake they caused, but it will improve both the team and the system. In a non-collaborative environment, mistakes would be hidden, bystanders would be blamed, and the system would suffer from the same issues over and over again.
All three of these practices aim to foster a collaborative environment, in which the burden of work, responsibility, and ultimately success, is shared across the entire team. Many may see software development as an action best suited for an individual - this is the story we are fed of technical wunderkinds, building software companies alone all while wearing a hoodie and living off ramen - but it is truly a collaborative process by nature, requiring many hands and minds to work in concert to build castles in the air.3 In my current role, we engage in collaborative practices each day, to improve both ourselves and the system we work on, sharing ideas and feedback through many different mediums. The whole is truly greater than the sum of its parts.