Moving from cowboy coding to agile development

Wednesday, June 21, 2006

Team Experience

I have been reading Hunt and Thomas's The Pragmatic Programmer (which I probably don't have to recommend to anyone anymore) and was pretty astonished when I read the section "Pragmatic Teams" in chapter 8. Everything I read seemed to ring a familiar bell somewhere in the back of my head. And every ring could be translated to "been there, done that"; I was part of a team like that once.

There were four (and a half) of us. We joined the company one by one and worked separately at first. Later, when the company hired more programmers, we became one of two teams working on the company's two (partly overlapping) products. Unofficially, we were the "senior" team, since all of us had worked in the company longer than anyone in the other team. (Nowadays I would say such division is probably not the way to go...) But to many of us, this was the first (IT-related) job and our skills were based only on academic studies and personal spare-time projects.

We still managed to triumph. (When I say "we", I mainly mean the others. Although I did my share of some improvements, I was more like an innocent bystander compared to the others.) Perhaps the biggest reason for this was that things were in a horrible state. There was no source code control system. There were no builds (and I am not talking about automated builds here, but builds in general). There was no way of knowing which version of the product each client had. (Well, since there were no versions, this was quite understandable.) There was no documentation whatsoever on the database tables the products used. I guess nobody liked it that way. Things had just happened when there were a couple of people writing code as fast as they could, and soon most of the work was putting out small fires everywhere. However, even the bugs that were found were not reported anywhere.

The team managed to change much of this. We started using source code control. We started writing small descriptions of database tables we added or modified, and put those in a common binder. (It would have been better to use plain text documents and store them in the version control system, but at least we did something...) We built a small intranet that included a bug-reporting service and a bug database. The customer service people could add bug reports and follow the fixing process as we assigned each bug to one of us and kept track of its status. Using the version control, we started making builds and keeping track of the versions each customer had. (We also made a proper installation media for the software but I think it was mainly done in the other team.)

We also did things that were not that obvious but which Hunt and Thomas mention in their book (and their book was out by then, so this probably wasn't a coincidence - even though I personally didn't read it back then). We came up with a name for the team and used it in all of our documents, and pretty soon the rest of the organization started using the name, too. We shared knowledge by making small handouts of books we had read and sharing them with other members of the team and everyone else in the company who was interested. We added a page in the intranet where people could write short reviews of new books. We even had names for the builds we were making (thanks to Pekka and his imagination :) ). And we sat together in one small corner of the company office, talking out loud when we faced problems.

Even though we all eventually left the company to find new (and probably not so many) challenges, I can truly say we all learned a lot. And while doing it, we had some serious fun. I can't say I miss working there in whole. But I definitely miss working as a part of that team. *sob*

Monday, April 24, 2006

Abusing TDD genetically

I recently made a small program as an example of a genetic algorithm. It was a minor school assignment, so the algorithm type to use was set in advance. Trying to use TDD as much as I can nowadays, I once again started writing code tests first.

After learning about how tests should drive design earlier, I now faced another interesting situation. Part of the design (the algorithm type bit) was already set, so the tests should actually adapt to that. I thought about how I should write tests in a case like this, and decided to experiment a little. Instead of writing tests for existing code (that didn't actually exist yet, but was quite thoroughly planned and only waited to be written), I tried to come up with tests that could have forced me to choose the algorithm I had already thought about. I know this was not anything like the approach TDD is meant to motivate, but it was fun - and writing unit tests still really paid off.

I managed to write tests that drove me towards the algorithm I had already partly designed and made the original design, in a sense, justified. Writing really demanding tests also made me improve the original design by showing that some details in the algorithm worked only occasionally as I had meant them to work. "Occasionally" is an important thing here, since genetic algorithms form a partly random approach to finding solutions in large search spaces.

Next I will present some examples of how unit testing helped me while writing a simple genetic algorithm. If you're not familiar with the concept, you might want to quickly browse Wikipedia's article on the subject. If you want to skip that, let's just say the algorithm is all about evolution (of solutions to the problem) and leave it at that. :)

Uncle Bob's blog post Extract Class provided me with a good way to test randomness. For example, I implemented the mutation of individuals so that the likelihood of mutation depended on the fitness of the individual: a perfect individual should never mutate, a strong individual should mutate only with a small likelihood, and a very poor individual should almost always mutate. So, I wrote tests for each case, writing a loop in which I called the mutation control method and counting the number of times a mutation actually happened.


public void testStrongIndividualVeryUnlikelyMutates() {
Individual original = new Individual("88888");
Individual after;
int changeCount = 0;
for(int i=0; i<1000; i++) {
after = new Individual("88888");
after.mutateIfBadFitness();
if(!original.equals(after)) {
changeCount++;
}
}
assertTrue((changeCount < 250) && (changeCount > 50));
}



One of the things unit tests helped me improve was generating offspring for two parent individuals. Since I didn't want the individuals to produce exact copies of themselves (since that would not give me any new information), I wrote a test that ensured the offspring was different from both parents. I was surprised to see the test fail with randomly generated individuals, since I thought a good selection of the crossover point alone would solve this. I soon realised that if the parents had identical "DNAs" on one side of the crossover point (e.g. "123|456" and "888|456"), the offspring would be an exact copy of one the parents. This was really easy to solve by forcing the offspring to mutate at birth, but I might not have found the flaw without a good test.

All in all, the task was fun and educating, although my approach probably added a (big) twist of solid coding horror to TDD.

Wednesday, March 22, 2006

Doing What I Love

While doing some study-related research, I stumbled upon a nice article on doing what one loves by Paul Graham:
http://www.paulgraham.com/love.html

Although I might disagree with one or two particular things, I think Graham has some excellent points. Especially his musings on people's motivations sound really familiar and accurate. They reminded me of Terry Pratchett's words: "Too many people want to have written." Luckily the hacker culture probably isn't as tempting as writing novels, and "Too many people want to have written killer apps" isn't excruciatingly true. At least, not yet.

P.S. If someone has read Graham's book Hackers & Painters, let me know what you think about it.