Recently I was teaching a course about “Software Crafting” to a group of developers, all from the same company. We agreed that I would prepare three days of training (or workshops), and then we would spend two days on whatever they wanted. The original idea was to mob program on their produciton code in those last two days, so we could work on problems they have right now.
But after the three workshop days, they asked me whether we could practice more.
“We want to do a longer example and practice everything we learned so far again!”
So we did that, and it went really well. And here I want to share with you what I have learned in those two days.
But first, let’s talk about the…
Artificial Examples Problem
In my workshops and trainings, I usually do very small examples. We write some tests and some code for an hour or two, and then we move on to ne next thing.
I think this approach is necessary for what I want to teach. You can learn a lot from doing those small examples. Even if you do not produce some “done” software. Even if you delete the code afterwards.
You still keep the learnings.
Most people like it. But sometimes I hear comments like…
This is so annoying. We never finish something.
These small examples are unrealistic. How do you do that in the real world?
And I do understand those concerns. But workshops and trainings are almost always too short to do longer, more realistic examples. So, I was really glad that this group decided they wanted to spend their two extra days practicing!
Starting the Two Days
We did not prepare much up-front. The people at the company prepared an empty git repository on their GitLab server and they set up GitLab CI to run their tests on every commit
I thought about what we could do in those two days and decided that we implement “Four in a Row”. I also came up with a rough plan for the two days: Do a longer planning at the beginning of each day, then have three to four short iterations (2 hours max) where the team should produce working software.
In the beginning, we did some release planning. We talked about the rules of “Four in a Row”, how to do the UI (text-based), some other business requirements and nice-to-have features (like, playing against bots or a grapical user interface). After that, we had a single flip chart page full of text, describing what we want to implement.
Then I told them to create work packages from that. I tried to avoid Scrum terminology because they do not use it in their day-to-day work, so we called those cards “Features”.
First, they wanted to split the work along technical layers (We need a console input. And the rules of the game. And…). I explained that I wanted features that we can implement end-to-end and provide value to the user, yet are small and independent.
Creating features like that was hard for them. But that is OK, it is also still hard for me. We came up with a plan, after some discussion, and it was good. At least good enough to start.
There were 7 attendees, so I asked them to work in three groups - Two pairs and one group of three. All of them would commit to the main line.
We took the first two cards to implement in the first iteration, but I asked them to first finish one feature together, then work on the next. To be able to split the work, we had a short design discussion. They came up with a design that had three modules, and a fourth one coordinating the other three (Conway’s law? Coincidence? I do not know…).
Roughly 1.5 hours after starting the workshop, we were ready to start coding. That was quick, I thought.
Towards the end of the first iteration, all three groups declared that they were “basically done, just have to fix a little thing and push”. They didn’t consider that in software development, the first 90% take just as long as the second 90% and the third 90% of a feature ;) (Nobody ever does) We ran out of time and went for lunch, without delivering working code.
After lunch. Two iterations left. We did a short retrospective of “What went well? / What is missing? / Do more of? / Do less of?”. There were some minor concerns that we could address quickly. But one attendee wrote down “We have not integrated anything. The game loop is still missing.”
This was my main concern too, so we decided to concentrate on integrating and shipping the first feature in this iteration.
And they did deliver that, and even the second feature.
But every one of the groups was writing more code than strictly necessary. They wrote code that they knew they would need in later features. This is something very natural for programmers. We write more code than we need to. For me, that is too still a very hard thing - To only write the code that is required, not what I think is required.
Because they wrote more code than necessary, we still did not have a release at the end of the second iteration. The two features worked, but there was code for some of the next features that was only partly working.
In the third iteration, progress took off. They implemented almost all of the small-but-important features and they had a working program that they could try. Even the tests were green most of the time. But near the end, someone accidentally pushed a test that asked for user input. So, still not green bar after the third iteration.
Two other interesting things happened on day one.
At first, the team could not agree on whether to merge or rebase when pulling from git, so some merged, some rebased. In the afternoon, they saw that this is not working (without me saying anything; that was hard for me, keeping quiet for so long). They had a whole-team discussion and decided to rebase.
Something similar happened about software design. I saw that two pairs were working on related functionality but had completely different approaches (both were valid). I told them, and they quickly decided that they should use the same approach. But then they had a long discussion about which approach to take.
That time was not wasted, though: Now they have a software design that is better than before (IMHO) - and even more important, one that everyone agrees on.
The Half-Way Retrospective / Planning
On the first day we focused a lot on using git as a team and on progress. The attendees learned a lot about that, and they wrote a lot of production code and test code, so now it was time to focus on different things.
To focus more on the process, I wrote pink sticky notes with extra tasks for the team.
They wanted to try code reviews with pull requests, for example. So, I wrote a sticky note saying “Merge stable code to master” and I told them to do that after every iteration (of which we had multiple per day).
I also wanted to make them solve the “we don’t have a stable version” problem by doing that. They had this problem by implementing too much at once, so I wanted to nudge them to stabilize the code towards the end of the iteration instead of starting new things.
I also tried to make clear that I wanted them to deliver working software after every iteration. That it does not matter how much or how little code they delivered - The code had to work. Later, they said that this had put pressure on them.
But I think this pressure was good - It made them try harder, they made some mistakes, they learned from those mistakes.
The team also modified the board on their own. They wanted to know who is currently working on what, so they created the three orange stickies near the bottom.
Every pair would put a sticky note on one of the three large, orange ones when they started something new. When others saw that a pair was moving the stickies there, they could ask questions or synchronize.
We had a long(ish) code review with lots of remarks. I made clear that the code review is there to to critizise code, not people. So, no personal comments, but also: Do not take comments of others personally.
And they did just that - They critiziesed the code harshly but were very fair and helpful. I never had to intervene, they just sometimes asked me questions about code quality or “which of those two ways is better?”.
We came up with a list of improvement ideas and the team decided that one pair should fix them and merge to master while the others would continue. This caused problems later because the team needed much longer than they thought for fixing and merging. But the others swarmed and helped them.
And then we had a stable, demoable version that already did something interesing!
It was time to work on the more difficult tasks, since one could already play the game. I wanted them to implement the feature “Game recognizes when a player has won”.
They immediately wanted to start, but I told them that this feature was much too large for a 1.5-hour iteration. They had to split it.
But not along technical lines / layers. That would be wrong, because then a single feature would again not deliver any value. They agreed, but did not know where to start the splitting.
I asked them: “Is there any situation where determining the winner is easy?” - Yes, if you place a stone in the right-most column when there are three stones in a row left to that column, it is easy to determine the winner. So, this was one of the split features, where the game could only determine the winner in this case. We ended up with three features that would provide value on their own, and together would make the game able to determine all winning scenarios correctly.
One pair messed up the git repository during a rebase. They did not run the tests after every step, and committed and pushed something broken. The others committed on top of that. The mess was hard to un-tangle, but another pair stopped their own work and helped the first pair. I helped a little too, and we soon had a working build again.
We implemented winning and some other, smaller features. In the end, we had a game that one could play, but there were some quirks because some minor features were missing.
What did they learn? Enough to fill a flip-char page:
There were many interesting things that happened in those two days that I could not teach in the “normal” training, but that the attendees experienced here:
- How hard it is to create working software early and often
- How important it is to only write code that is strictly necessary to achieve that
- That working togehter on the same mainline branch and on the same classes can work
- That merge conflicts happen and are generally easy to solve - Especially when you work in small steps and communicate with the others
- That messing up the code in the repository can happen, but that git is a great tool to fix it
- To watch the automated build
- To run tests locally after every step
- To split requirements and features in a way so that the smaller feature still provides value
- To distribute tasks for a feature to pairs in a way so that the team can swarm
I think I told them most of those things during the training before, but you have to experience them to really learn them.
Those two days were a great experience for me. And the attendees loved them too. They practiced many techniques again they learned during the training - but in a more “realistic” setting. And this was the first time they tried swarming - i.e. to work on one feature, as a team, before moving to the next.
I thoroughly enjoyed facilitating this exercise / experiment and would love to do it again!
Do you want to try something like that, or do a different workshop? Find out how hiring me as a trainer could work out!