I have been lucky enough to have a pairing session with a very experienced programmer lately. In my day job, I pair all the time. But it is usually with experienced, yet resistant programmers and very rarely is it rewarding for me. This experience has been the opposite and a good reminder of why I do like pairing and want to do more of it collegially. Usually, I am pairing either as the experienced half or a coach but rarely on equal(-ish) footing. What a treat! Some background, we have been writing a graph database in Scala and are now four days in – I usually write line-of-business applications rather than tool libraries. We started with a list of features (XP style) and we have been TDDing, test-first all of the code. We also have been using a BDD-style Given/When/Then style test suite in JUnit. Mostly we have been mocking with occasional classical-TDD assertions and one fake implementation.
Here are some notes to myself that are related between pairing itself and problems we discussed.
Technical debt still grows quickly
We just spent most of day four dealing with the decisions to move on. I couldn’t believe how many issues where in the code that we had marked TODO which at the time allowed us to move on. Each small decision (if I remember correctly) was very good at the time but to have some of these come and bite us by day four was amazing. Note to myself we should actually have a running count of the number of TODOs and graph it or put some upper limit.
Be careful about code (and domain concepts) not integrated into core
This is probably my greatest learning and reminder. Hopefully, I can explain. There was a point that we decided to write the code that serialised byte arrays to disk. Neither of us had a working knowledge of the libraries needed so we decided to reduce risk in this area. We effectively spiked this code albeit with tests. It was all production-ready code. However, it was integrated into the core code base. We called this programming on a leaf domain concept – maybe orphaned leaf might be more appropriate. The leaf is on the outer of the domain concepts. To make this more concrete, the core manages all the nodes and relationships in the database, then we manage persistence and flushing and finally out on the ends we read/write to disk. We working on the disk read/write and yet hadn’t integrated into the persistence code that would call it. The problem was that as we started to add functionality to the code code, the leaf code became hard to work with. In the end we treated the original leaf code as a spike and coded from the core to integrate the leaf. I think this is closer to the style of GOOS.
Pairing changes the rhythm (and rhyme) of the day
The rhythm is generally evenly paced throughout the day – often it feels slow and plodding. Rarely, do I quite get the excitement of individual programming when I feel on a roll whacking out the code – but neither do we get the lows and stagnation. Instead, there is much discussion, whiteboarding and mostly the coding revolves around writing the tests (particular with mocks spelling out the interactions and responsibilities). We ping-ponged between test writing for one person and then the other passing the test and starting the next failed test before handing back to the other person to pass the tests and start all over (with refactoring on green by either). Our day was between 6 and 10 elapsed hours with an hour off for lunch. It was sustained, engaging work. At the end of the day we were surprised how many features were ticked off. Slow and steady was the rhyme;Ping-pong, red-green-refactor was the rhythm.
Domain confusions are still the biggest wasters of time
Our biggest waster was not knowing how to do something and this was worst when we had confusions already existing in the code. Particularly on day four we had to refactor them out of the code. We made best progress when we had an even understanding between us – and this occurred when we had concrete examples mapped out on the whiteboard (specification by example). So I’m still not worried about productivity arguments about doubling up in pairing – that might be the case with trivial implementations
Deal with complexity through examples to build simplicity in implementation
Complexity here is the interaction (and side effects) between parts of system. We did too much of this through discussion and holding ideas in our head and trying to isolate complexity through separation and encapsulation. For example, a test might isolate the caching and dirty-tracking mechanism for nodes but often to understand that it was talked about in a wider context. I think that we could have used examples that work through the system to encapsulate the complexity rather than keep it limited to our discussions. This sounds to me that it is a mixture of specification by example and GOOS-style tests. Sorry if that isn’t very clear.
Live with your own confusions and ambiguity but be careful about letting them into the code base
These are related to above. There were times that I was willing to live with ambiguity and confusion and my pairing partner knew what he was doing – so why wouldn’t I go with the flow?! A couple of these places turned out to be poor abstractions that took us time to refactor. Perhaps, I will be bolder next time. I think I would insist on more concrete end-to-end examples (rather than simply tests)
h3. Multiple assertions in a test
Because we are using mocks, of course, there are always multiple assertions. Nonetheless, we always had to ensure that we were describing what the system did and focus on one aspect. But this was just a good reminder asserting a single thing is not the same as only making one assertion. The code was more readable and in fact we spent time wrapping jmock to get readable, concise assertions.
Symmetry-style tests are a really useful type of test
I haven’t explicitly used symmetry-style tests within one test before. I usually do across a test class. In our case, we needed to serialise and deserialise byte-arrays of datas (numbers, strings, properties). Usually, I would separate out these tests (also one per class and here we had a reader and writer) and then combine using a Setup. We just put them in the same test, it was far more readable and appropriate to the level of complexity of the task
Fake implementations are markers for debt and rework
I often use fake, concrete implementations in tests to get me going. I see them as a way to keep moving. We were acutely reminded that they are debt in the system. We found that in the implementation had gone too far and we used it in more than one test class. As such, we we came to take it out it touched a number of tests that we needed to refactor. It had also stopped us seeing some assumptions. The note to myself is to make sure that such implementations are private to the tests.
CI & source control even for the single-programmer environment is gonna save you
Because we are working in Scala some of the IDE support is not quite there. We lost time without CI. It’s there now – well, almost
Pairing with you reminds you (acutely) of accommodations you make and your own limitations
Pairing is a good mirror – your get to look back at yourself some quietly and sometime not so! All those little things that I do are accentuated: the little key presses I don’t remember, the quirky naming conventions, the IDE features I could improve. I love the little reminders to keep improving.