Yesterday Bill Tucker and Matt Rose organized a Code Retreat in Fort Collins. It was “Global Day of Code Retreat”, and there were events happening all around the world. The basic idea was that you paired up and for 45 minutes used best practices (as if you had all the time in the world to do it) to work on a programming problem. Then everyone deleted their code, stood up and discussed the previous round. Then you paired up with someone new and started over on the same problem.
It was a great event, extremely rewarding on many levels. Read on for more of my thoughts on the event.
The problem we were working on was Conway's Game of Life. This was a good choice because it presented plenty of opportunity for writing tests as part of the development process, was small enough that you could make plenty of progress during the 45 minutes, and had opportunities for variants that could keep it interesting (representing the field as an array, allowing it to be infinitely sized, a torus world)…
One of the nice things about the event was the exposure to various languages and environments. I ended up doing most of them in Python, but did start with an attempt in Ruby, and also made a vain attempt at implementing it in Prolog. That was in vain because neither of us had used Prolog at all recently.
I really enjoyed the opportunity to do a number of iterations using test-driven development.. I've never really gotten comfortable with building the testing scaffolding (remember, I'm not a programmer, I'm lucky if I get to program 5% of the time). This was an opportunity to address that, and I really did get comfortable with it.
Another thing I really liked was the “pair programming” aspect of it. It's something I haven't really done extensively before (we mostly use reviews rather than pair programming, which are very effective). I'm not sure I'd promote pair programming as the default, but I could see it being very beneficial in situations where the problem or solution aren't well defined.
One interesting thing was that last week I had reviewed my personal Wiki because of some vandalism, so I knew there was a recipe in there for setting up unit tests in Python. That let me get going very quickly with the testing, and prevented any spinning of wheels there. Having the right references available is a huge productivity enhancement, that was one of the big barriers in the attempt at Prolog – we were struggling to just express what we needed, despite knowing roughly “the Prolog way”.
Another thing I really liked was the opportunity to just throw away the code and start over from scratch. I've had experience with re-writing code from scratch and making huge advancements because of that (one time I had a day's worth of code go away when I had an argument with my version control system, before backups for that day had run).
However, I did save the code of one attempt and I analyzed the process we used on it last night and found that process extremely rewarding. At one point during a later attempt I did have an urge to go back and review that saved off previous attempt, but I was able to restrain myself. I'm glad I didn't in retrospect, that would have been negatively impacted my experience.
One of the things that was really reinforced for me was testing at the right level. The one attempt that we were able to succeed on, the testing consisted of testing the API (“set(x,y)”, “get(x,y)”, “count_neighbors(x,y)”) which allowed us to define the API. Then we added tests for the 4 rules of the game of life, implementing each rule after building the test.
One of the neat things about this was that it revealed that our implementation of rule 1 also implemented rule 2. So when we added the test and it succeeded we could say “oh, yeah, I guess this part of the rule 1 implementation causes rule 2 to pass as well”. So building the test and running it before we started coding it ended up saving the time of thinking through the code or possibly implementing some code that was redundant.
The implementing of rule 3 introduced a bug which testing caught right away and we were able to address. Then in our implementation of rule 4, we realized that we had a bug in our algorithm, and we were able to address it at that time.
This process of having the right tests, no more and no less, I'm confident significantly increased the speed of development of that round. A later round I did which Jesse, which was basically the same code but implemented with a Python set() rather than dictionary, did not succeed and I believe part of that was due to not having the right testing. In that attempt we were just testing the game of life rules, not the API, but we also were not building tests, running tests, and then coding, we were running in different directions at various times for various reasons…
One thing I thought would be very interesting to try is having a “backseat” position in the groups, allowing for more involvement of more introductory-level programmers, allowing them to see how development goes without having to necessarily be at the skill level where they can drive. I know several people who are interested in learning programming right now that I think would have been at the level they could have benefited from watching, but they may not have been prepared to be more actively involved in the process.
Another thing that was reinforced for me is that a good solution you are familiar with is better in many ways than a better solution you aren't familiar with. In the “infinite world” implementation, I picked dictionaries over sets initially because I was just more comfortable with dictionaries. I know sets are fairly simple, but I did run into a couple of road-blocks that I had to clear when doing the set() implementation that I didn't with dictionaries.
I think that sets are the right data-type for the implementation I was working on, but the best way to implement that would have been to implement it with dictionaries, get all the tests and code running, and then change the implementation to use sets. In that way I would have had basic code working and known the issues were in the set implementation, rather than have been debugging the basic code and the sets and refactoring of another part of the code all at the same time.
That reminds me of a programming class I took back in high school where we had to implement a maze solving program, but we couldn't use recursion. I spent quite a while trying to implement it without recursion, and eventually I gave up and implemented it recursively (which really made sense to me), and then converted the code to not be recursive.
Bill at one point fairly early on, in the break between attempts, asked the question “what changes would you have to do to support an infinitely sized world?” This was a wonderful question, because up until that point I had been using a two dimensional array, and that made me think about the problem differently. Because of that, for a later attempt I tried a dictionary of alive cells, and that ended up being dramatically simpler than the array of arrays. Without Bill asking that question, I likely would have been stuck on the arrays.
In one of the groups we had this great discussion about testing. We were going to return a list of neighbors of a cell. I was thinking we would return the coordinates of the cell neighbors, Jen was thinking we would return whether the cells were alive or not. This turned into a lively discussion about what was the right level of abstraction for this function, how that impacted testing, whether we should encapsulate more or less logic in that function (less logic in the function allows for more accurate pin-pointing with the testing), etc…
This was one instance I really wish we had more time for, because we were basically at the tail end of the discussion when time ran out. And, I had conceded the point in the interest of making progress, where the most valuable thing would have been to have pursued that discussion to the best possible solution.
There was some discussion of having the time be longer so you could explore other things. For me, I think 45 minutes was about right. The goal was that you wouldn't be able to complete it in that time, which I think is a good target. Because if you get into the mentality that you can finish it, then you really need to have more than enough time for everyone to complete it. I say this because if you have just enough time to solve it if you cut corners or you don't do anything inventive, then you probably won't try those other things. And trying those other things is the whole point.
So I'm firmly in the camp of “not so much time”. Though I understand that not every group programs at the same speed, so if you were only getting 10% of the way through it's going to be a different experience than if you're getting 80% of the way through.
There was also some discussion of keeping the same pairing, and in retrospect I am very glad that that didn't happen. Part of the whole reason for the Code Retreat, in my mind, is to get experience with different pairings, so you can get new experiences of how people are working on code. If we had kept the same pairings, I might not have seen the windowed vim setup, the Eclipse development environment, or worked with Ben on a wild-assed attempt to get Prolog working. (“Ok, I'm building a Prolog environment now.” :-).
Those are my thoughts about the code retreat. It was an extremely educational and entertaining experience. I'd highly recommend it to anyone who wants to program.comments powered by Disqus