Popularized by Extreme Programming enthusiasts a couple of decades ago, the act of programming together with another developer on the same workstation gained massive adoption within Thoughtworks. Nowadays, Pair Programming is seen as an effective way to keep our folks happy, productive and learning, as is described in the article.
As with any widely adopted practice, there are quite a few variations in style and technique. In this article, we’d like to focus on the most common style in use at Thoughtworks currently: two programmers physically share a workstation, with a single view of the desktop (either in one monitor or with two mirrored screens).
In that style, actively sharing the input devices can be problematic: except for a few multiplayer games, it’s hard to find software out there that supports two people typing and clicking around at the same time. To avoid confusion and unnecessary use of the backspace key, pairs naturally agree on who gets to use the keyboard and mouse at any given time, and those arrangements can vary widely depending on a number of factors.
To us, an extremely important part of such agreements is what to do when a programmer is not typing. The term “navigator” (as the opposite of “driver”), used to describe that role, likely comes from rallying – where it’s usual to have a navigator sitting next to the pilot or driver handing out coordinates and calling attention to troublesome spots along the way.
Much like in racing though, the driver gets a disproportionate share of the credit and attention, and often, the mechanics of good navigation have been somewhat neglected.
First things first: basic etiquette of sharing a physical and a virtual working space with another person should apply, and the pair should be responsible for ensuring, before a programming session starts, that the working area is clean and comfortable for both individuals.
They should avoid any unnecessary noise or smell and respect each other’s personal space. Additionally, there’s a need to consider a healthy number of breaks during the day, as tiredness can develop more quickly due to the more active communication that pair programming usually demands.
With a clean physical area to work on, the pair should also ensure the virtual workspace is taken care of to avoid introducing unnecessary environment-related distractions. Any unrelated work is cleared, at least visibly, and the workstation is configured consistent with other development environments and meets the expectations of both developers.
Editor settings and dotfiles can be easily shared across with the team and kept under source control, but it’s worth paying attention to other things like font sizes, screen resolution, other running applications and pop-up notification settings that might creep up and become distractions. Navigators should have free reign to remind over-enthusiastic drivers changing those, and of the benefits and trade-offs of consistency against personal preference.
Before the programming starts, it’s important for the navigator to ensure their pair knows and is able to clearly state the goals they are headed towards. Getting to that state might involve discussing details of a story or task with product or technical leaders of the project, and breaking down any larger items into bite-sized chunks. Usually, pairs will do that together at the beginning, and the navigator takes the responsibility of keeping a “goal stack” up-to-date as the work progresses:
Working with a stack has another advantage: while thinking through the tasks needed to complete the programming session, the navigator has to flip between thinking backwards (in what state do we want to leave this system in?) and forwards (what do we need to do to get to the next step?), and we find that a stack lends itself to that kind of back-and-forth quite naturally.
Reviewing the changes needed to the code usually leads to more items and detail to be added to the stack. Sequencing and prioritizing that work effectively requires some practice, in particular when balancing the stack: ensuring any chances to do opportunistic refactoring are taken into consideration while ensuring to not take on too much additional work.
In some teams, pre-flight checklists are put at the top of the goal stack: before work begins, some pre-defined tasks have to take place like moving the card referring to that story in a Kanban wall, creating a feature toggle or branch, or refreshing a local copy of a production database are recurring examples. Similarly, post-flight checklists might require developers to move the card again, do a desk-check with product and technical leaders, demo the functionality to a user, etc.
As they go through the goal stack, driver and navigator will eventually detour into some unknown part of the system, or a corner case in the acceptance criteria of the functionality. At these points, a navigator has to be conscious of their role in ensuring the pair is going through the planned tasks or readjust the plan accordingly.
While it’s acceptable to leave some unknowns or dirt behind in order to get the job done, a good navigator knows when to say “not right now”. To that effect, multiple funny-looking acronyms have been created like DTSTTCPW, KISS, YAGNI and so on.
These acronyms may even be used as the navigator as commands, analogous to the rally navigator who says “stay to the left, 300 metres” or a well-placed and thought through “you ain’t gonna need it”, when discussing bringing in a third-party library for example, can stop a lot of unnecessary stress. A good navigator, freed from dealing with the mechanics of driving, should be able to spot those moments and see pitfalls coming from further away than the driver. That is not to say that the opinion of the driver shouldn’t count, of course it should. Negotiating these decisions is a job for both the driver and navigator, but ultimately the responsibility for doing it effectively and pragmatically lies with the latter.
Another common “command” a navigator might use is “let’s start with a test for this.” It might seem mundane at first, but we found that when the navigator takes responsibility for keeping the TDD cycle in check, the red-green-refactor cycle becomes a more fluid experience.
It’s easy for novice navigators, even more so those who are seasoned programmers, to get carried away into treating the driver as an order-taker or IDE operator with no particularly good judgment of what’s going on. This undermines the value of pair programming, as the point is to make joint design and implementation decisions, which is best avoided.
Still, sometimes the driver gets stuck on how to proceed on a less abstract problem like issues with an unfamiliar tool, language or API pop up rather frequently. A trap many well-meaning but less experienced navigators fall into fairly often is to offer up advice as soon as that happens. Good navigators know when to wait a little bit before pointing out a missing semicolon somewhere, and will do it when there’s a natural pause in the driving. A very large number of interruptions rising from unfamiliarity of the driver might be a good indication that it’s time to swap roles, even if for a very short amount of time.
For all the more interesting and more abstract issues, though, an experienced navigator is good at communicating intent – the what, not the how, and uses inclusive language (“us” and “we”, rather than “I” or “you”) as much as possible while at it, so the driver is invited to revisit some of the motivations behind intents they might not necessarily agree on.
Even when intent has been communicated clearly, we find that explicitly asking for confirmation is a good practice as it validates understanding and opens the conversation to feedback from the driver.
Another set of tools a navigator has at their disposal in this regard is the plain old pen. Drawing and sketching are some of the best ways to show someone what a path might look like.
Some algorithms and data structures can be a little unclear in UML. Pseudocode is usually most employed when discussing small parts of the code like a caching strategy implementation, but can be useful at the beginning of a programming session, when creating the goal stack. At that point, each “line” of pseudocode could thought of as an item in the stack.
Most systems will have their features implemented following a consistent architectural pattern. In a typical database-backed web application, for example, many of the features can be explained by adding in some more detail to the following diagram:
Jotting down this diagram on a piece of paper large enough to accommodate new collaborating boxes and changes to arrowheads can be a good way for a pair to start thinking about the current state of the system, or which modifications and additions are needed.
Though it has somewhat fallen out of flavor, UML is an excellent way to describe systems in a visual manner. We prefer to use UML as a sketch while explaining a class hierarchy or set of interactions between systems.
Forward and reverse engineering are equally well served by sketching some UML. Being able to erase parts of a whiteboard selectively can be useful in detailing the steps of a larger refactoring, for example.
We find Pair Programming to be one of the most effective ways to keep our developers productive, sharing knowledge and experience. It has certainly helped us build strong teams, reduce our defect rate and keep people happy. It is important to remember that both roles in the pair are equally important with specific expectations and responsibilities during pairing. Also, neither driver or navigator responsibilities last forever; it is encouraged to change roles several times during the course of a pairing session, and both wheels have to be in good shape.
Disclaimer: The statements and opinions expressed in this article are those of the author(s) and do not necessarily reflect the positions of Thoughtworks.