Session 4

The Role of Continuous Feedback


Agile Software Development

Running Laps

Most mornings, I want to control the pace I am running. Maybe I am doing a tempo run, on which I want to average my 10K pace for a few miles. Maybe I'm doing a speed work-out and need to run several repetitions of a particular shorter distance at a faster pace. I have to be careful when trying to run fast, because it's easy for me to overdo it. Then I run out of gas and can't finish comfortably, or at all. (This is what we'll soon call sustainable pace.) And it's even easier to run too slowly and not get the full benefit of the workout.

Or maybe I want to run slower than usual, as a way to recover from faster work-outs or as a way bump my mileage up. On days like this, I have to be careful not to run too fast, because my body needs the break.

So I need a way to pace myself. I'm not very good at doing that naturally, so I like to use landmarks to monitor my pace.

One place I can do that is on the George Wyth recreation trail near my home. This trail contains a 6.2-mile loop and has four 1-mile segments labeled. When I try to run a steady pace on this route, I used to find that my miles varied by anywhere between 10 and 20 seconds. These days I do better, but sometimes I can't seem to get into a groove that keeps me steady enough.

I used to do all of my weekly speed workouts on the indoor track at the Wellness Center. Running the middle lane of that track requires a bit over 9 laps per mile, and it has signs marking 200m, 400m, 800m, and 1200m splits. Running there I get feedback every 1/9th of a mile, and I can synchronize myself at the longer splits, too. Not too surprisingly, I pace myself much better on the track than on the trail. And more frequent feedback is the reason. When I get off by a second or two for a lap, I make can make a small adjustment to get back on pace -- and I can tell if the adjustment was successful within a 1/9th of a mile.

Doing my Yasso 800s on the small track has been invaluable in helping me get faster. Even better, they have helped me learn to pace myself naturally. Now when I run mile repeats on the trail, I find that my pace rarely varies more than 10 seconds per mile, and sometimes I can clip off several miles in a row all within 3-7 seconds of each other. Getting continuous feedback as I've learned has helped me to develop better "instincts".

I recently took my speed workouts outside to the 1/4-mile track at Price Lab, to enjoy the summer weather more and to lengthen my repeats. Running consistent 1200m repeats on the longer track is tougher, because I don't yet have the instincts for racing fast at a desired pace and because the track gives me feedback less frequently. But I hope that a few weeks of practice will remedy that...

My goal is eventually to be able to find a groove where my pace is steady, comfortable, and right on the mark for a particular marathon time. Continuous feedback plays an important role in training by body and mind to do that.

... I originally posted this on my blog back in June. For a reminder of what happens when one goes too fast, check out what happens when I run too fast.

Optimism is an occupational hazard of programming.
Feedback is the treament.

-- Kent Beck

Revisiting the MemoDatabase

Testing code continuously is a great source of continuous feedback. Your code will tell you a lot, especially about mistakes you make, if only you ask the question.

We still haven't taken our DefaultMemoDatabase very far. After Session 2, we at least had a database that stored a collection of memos and could answer some questions in terms of its collection. Initially, our focus was primarily on programming test-first, with a dose of simple design thrown in, and then our attention shifted to refactoring. Test-first development and refactoring complement each other in important ways.

Our partial list of requirements so far is:

  1. If you insert a key with a value, you can retrieve the value by giving the key.
  2. If you insert a pair with a key that's already been used, then the database doesn't accept the new pair.
  3. The value can be a empty string.
  4. The key can be an empty string.
  5. If you try to remove a non-existent key, the database says no.
  6. If you remove an existing pair, then the key won't be found on a future look-up.
  7. The key can be any length.

[ ... have students identify some more ... normal and exceptional cases ... ]

We may need to write multiple tests in order to capture a requirement. Requirements such as "the key can be an empty string" are simple enough that one test may suffice, but not probably not "if you insert a key with a value, you can retrieve the value by giving the key".

[ ... have students identify tests needed to verify the following requirement ... ]

Consider "if you remove an existing pair, then the key won't be found on a future look-up". You've all written enough code to know that removing data from a collection sometimnes has unexpected effects. In order to test this requirement, you'll need a couple of specific tests:

  1. Add a pair. Remove the key. Try to find the key -- expecting a failure.
  2. Add a pair. Remove the key. Try to find some other key that is still in the database -- expecting success.
  3. Add a pair. Remove the key. Try to find some other key that is not in the database -- expecting failure.
  4. Remove all keys from the database. Try to find any key -- expecting failure.

[ ... have students write code for a couple of these tests ... ]

[ ... implement test and requirement ... and refactor ... JUnit's setUp and tearDown methods ... writing separate TestCase classes for testing different requirements, and then a TestSuite to hold the tests ... TestSuite as a composite ]

... the programmer should let correctness proof and program grow hand in hand. ... If one first asks oneself what the structure of a convincing proof would be and, having found this, then constructs a program satisfying this proof's requirements then these correctness concerns turn out to be a very effective heuristic guidance.

-- Edsger Dijkstra
"The Humble Programmer"
Turing Award Lecture 1972

Writing Tests as You Write Code

Some think that this idea of testing parallel or even before writing code is a new idea. It's not. On eof the pioneers of computing, Edsger Dijkstra, suggested something quite similar over thirty years ago. Dijkstra's suggestion bears a key insight in its wake: a test isn't just a test.

Our JUnit tests above tests aren't (just) tests. They are assertions of expected behavior. They are the requirement, written as code!

They are also design. They define class and method names, and communication paths between objects. They are design in the small. If you can think of programming test-first as design, then you may feel better about doing it when you aren't too sure otherwise.

Simple design keeps you from thinking too far ahead of your program. What are the advantages of thinking ahead? The disadvantages? When building a complex system, you sometimes have to learn about the problem and solution domain. That is the insight behind Brooks's quote above, and it is one of Fowler's arguments against a traditional engineering model for software development.

Do you see how all of these ideas -- test-first programming, simple design, refactoring, frequent releases, and continuous feedback -- work together? How can the removal of one of the practices affect the others?

The Practices of XP

Extreme Programming (XP) is the oldest, best known, and most complete implementation of agile development principles. The ideas that we have been discussing for the last two weeks are a subset of the practices that make up XP:

[ ... discussion of each ... ]

Course Project

Some ideas that have been proposed:

What shall we do?

Wrap Up

Eugene Wallingford ==== ==== August 31, 2004