Ruby DCamp begins with a code retreat: that is, over the course of a day, each developer rotates through pairing with six other developers to program Conway’s Game of Life. Between each session, all of the code must be deleted, and everyone must start fresh at the beginning of the next round. Some rounds have interesting or silly constraints to push the devs outside their comfort zones. It’s a great exercise in meeting new people and learning more about Ruby.
Though I did start fresh from session to session (Evan, I promise), I also took screenshots of the code from each of my pairing sessions. In this post, I’ll walk through five of those sessions and share the lessons that I learned from my pair partners.
Partner: Max Miller
A thing that I learned: In Ruby, you can assign values to variables in the method call. By writing
def initialize(x=0 y=0, game_board=nil), I automatically assign each of those things to the stated values provided they are not otherwise assigned in the method call. So, later on, if I say:
c = Cell.new(1, 1, board)
then I am going to have a new cell with an X value of 1 and a Y value of 1 on the
board game board. By contrast, if I just say:
d = Cell.new
then I will have a new cell with an X value of 0, a Y value of 0, and a
game_board value of nil. This replaces ||= in the method body.
A thing that I wonder about: Given that this can be done in the argument declaration or with ||= in the method body, which way should I use…and why? Or should I use a different method altogether? Opinions or sources most welcome in the comments.
A thing that I learned: The
.select method. When called on an array, this method returns an array of the items in the previous array for which a given method returns true. For example, in the method
neighbors here, we take an array of all of the positions surrounding the cell in question and return an array of positions for which there is no cell contained therein(that is, if
get_cell returns nil). We then use this in the
live_neighbors method to determine whether a cell is…alive or dead. Wait a second.
A thing that I wonder about: Why did we structure the data this way? Why isn’t a dead cell equivalent to just a nil space? What is the difference between a dead cell and an empty space? If this is an infinite board, why did we do this instead of keeping track of live cells? I don’t remember. I don’t. In our defense, we had 45 minutes.
A thing that I learned: splatting!!! I knew this had to be possible, and I love that it’s called splatting. Splat! Splat.
This is when you declare a method that can take any finite number of arguments and delivers them to the method in the form of an array. So the initialize method of the Board class allows us to instantiate a Board with as many live positions as we want—zero, one, two, ten, a hundred, you name it. Those get used in the method as an array of positions, hence why we can pass it into
make_live_cells (as an array) and call .each on it.
A thing that I wonder about: Basically, I wrote a test wrong(expect b[ ][ ]…), and Chris, rather than correcting the test, interpreted it as a challenge to perform the programming gymnastics necessary to make the test pass, resulting in a Column class complete with a redefinition of the [ ] method. I would love to walk through exactly what we did here and how it works.
You’ll notice that this screenshot is super-commenty. The reason for this is that we had a constraint on our work for this round…we were not allowed to talk. Most of the comments you see are from the first ten minutes of our work together. After that, we discovered that we could communicate clearly while getting work done by…playing ping pong as well as possible.
Programmer ping pong, for those who don’t pair so often, goes like this: Player 1 writes a test. Player 2 makes it pass. Then Player 2 writes a test and Player 1 makes it Pass. Repeat.
There was another constraint on this round: we were supposed to do the most antagonistic, annoying, and counter-productive thing possible to make our partner’s tests pass. Admittedly, some of the more advanced programmers at dcamp took this suggestion and ran with it more than we did (OH from Avdi: “I had to resort to generative testing because Betsy was so good at finding edge cases that skirted my tests”). Chris and I, meanwhile, were so star-struck by our ability to program effectively together without speaking that we didn’t go too far to break the spell with creative antagonism.
That said, if there was an obvious and easy solution to make the test go green, like returning a hard-coded value, we would do it. In part, we were playing a game of chicken in which the loser was going to have to implement the more complex functionality. But also, we were learning how to write more expressive tests. So…
A thing that I learned: How to go about, in practice, triangluating a solution through testing. When I say triangulate, I mean it the way Kent Beck means it in Test Driven Development By Example: write a test, make it pass the easiest way possible, then write another test for the functionality that the easy solution will fail, until eventually you have a sort of hammock of tests that can only all pass when you have implemented a fully functional solution.
A thing that I wonder about: Would it be practical to conduct entire pairing sessions like this—without speaking? I imagine, that, while designing and planning, the answer might be no. That said, while we were programming Game of Life for the fourth time that day, many pairs reported getting more work done in silent mode than in any other round of the retreat.
This round had a different constraint: we could not use the return values of any of the methods to do anything. At first, this sounded extremely crippling, but Sam and I got closer to a solution in this pairing session than either of us had done in any of our previous pairing sessions. That result surprised both of us—in a good way! I suspect that this might have had to do with the fact that the constraint prevented us from using the solutions that we had worked on in the other pairing sessions and forced us to look at the problem from a different perspective.
A thing that I learned: To ask myself, while solving a problem, “What might the solution look like if I only use one class (or, rather, in this case, zero classes) and the most obvious methods that I’ll need?” In other words, “How would a six-day programmer approach this problem?”
I call it that because I remember solving problems in this fashion…when I had been programming for six days. I knew enough to picture what a method looked like, but I didn’t understand classes, and I had never written a program that occupied more than one file. Essentially, I had beginner’s mind.
To some degree, this exercise restored my beginner’s mind. That’s kind of cool.
And maybe useful, too. Maybe, by test-driving our ersatz beginner’s mind on new problems, we won’t find the best-organized solutions to a problem. But we might free ourselves of the constraints that come with getting too close to a problem, find a skeleton upon which to build our future efforts, or, at the very least, learn something.
A thing that I wonder about: In which situations would an approach like this prove useful before we begin coding? In which situations can we confidently skip it…and in which situations should we skip it? Once again, opinions welcome.
Regarding default parameter values, if you leave defaults out of the parameter declarations then you’ll always have to supply them explicitly when calling the method, or Ruby will fail with an argument error.