Before starting today's effort, Chet and I did a quick retrospective of yesterday's, um, experience. We drew a couple more lessons over those in yesterday's report, "Warts and All".
We try always to write code that expresses our intention. (See, for example, the chapter "Test First, by Intention" in Extreme Programming Installed.) Good code has methods with names that reveal their intention. And a good method that does two things "procedurally" needs to be refactored, using Extract Method, to express what those things are.
When we program, we always have some intention in mind: "First, I'll scan through all the files, looking for the ones that are updated. Then I'll make copies of those." Sometimes we then immediately write, first, a few lines of code to get the files, then a few more to make the copies. When we do this, the code does not express our intention. Here's an example of some code, from our CustomerTest.cs, that doesn't express intention as well s it might:
private String[] ArrayToEnd(StringReader reader) {
ArrayList result = new ArrayList();
String line = reader.ReadLine();
while (line != null && line != "*end") {
result.Add(line.TrimEnd());
line = reader.ReadLine();
}
String[] answer = new String[result.Count];
result.CopyTo(answer);
return answer;
}
Let's read the code above. First, we build an ArrayList containing all the lines of the input StringReader, up to "*end" or the end of the reader. Then, we allocate an array of Strings of the right size, and copy the ArrayList into it. Then we return the array.
That code might be better if it could look something like this:
private String[] ArrayToEnd(StringReader reader) {
ArrayList result = ArrayListToEnd(reader);
return ArrayListToArray(result)
}
It might be even better with better names -- we could talk about it. In any case, we could argue that we should use Extract Method to refactor out those two methods, and Compose Method to create the new version of ArrayToEnd.
Programming by Intention goes a step further. When we program by intention, we reflect on what we intend to do, and we write the simple method first. If we know that we're going to create the array of strings by first creating an ArrayList and then copying it into an array of Strings, we say so. We write the short version of the method first, then we write the two sub-methods.
There are a number of advantages to proceeding this way. First, we don't have to remember so much. As soon as we know what we intend to do, we write it down. Then we just refine it until the code is there. Second, the code comes out well-factored without as much refactoring. We get better code, and it takes less time!
So why don't we do that all the time? We're not sure. We think it has to do with unfamiliarity with the language. We're so focused on how we might write legal statements in C#, and on which objects in C# might help us, that we forget to play our best game. So our first resolution is to return to programming by intention. Keep an eye on us and see if we live up to our resolution.
I may not have mentioned it before, but we have a simple code manager that we use as we develop this application. It's written in Ruby, and all it does is this: whenever we type "cm" in a command window, all the files of the source directory are copied to an archive directory. The file foo.cs, if dated today at 3:47:21 PM, will be named foo.cs.20020821154721 in the archive. So all we have to do is type cm once in a while, and all our code is backed up. Works well enough for most of what we do, which is typically small programs. We have a little restore function, of course, that copies all the files back, with their value as of some given time.
You're probably asking yourself, if they used this cm thing, why did they have to control-Z yesterday to get back to a safe point? Did they have six saved versions, or only five? Well, to tell you the truth, in all the excitement, we kind of lost count ourselves. We forgot to type cm at crucial points in the development. So we didn't have a good point to restore to, other than the beginning of the day.
So that was today's second resolution. Have more points to back up to. That will make it easier to back up when we make a mistake, and we'll be less encouraged to keep plunging on, deeper and deeper into the depths of coding depravity.
Our little code manager is written in Ruby. Maybe we'll share it with you in some future article. For today, I'll just give a quick look at what we did with it. We decided that we wanted it to just hang in a loop, checking for files that change. When they change, poof, it copies them to the archive.
So we put our Ruby hats on, and wrote a test to find changed files in the CodeManager's source directory. That worked pretty quickly, once Chet figured out that I was checking the wrong directory for changes. We paused for lunch at Red Hawk. We ran into Bill Tozier and Carl Simon at Red Hawk, chatted with them for a while, then walked back to the Union with Bill and talked about a project that he's working on.
Finally we got back to work. We quickly made a test for backing up changed files, made that work, and refactored the code a bit. Then we wrote a simple timer loop method that sleeps, checks for changes, backs up if there are any, and sleeps again. From now on, we'll use that cm to watch our work.
We know we'll want some new features. First few times we have to restore, we won't mind doing it manually, but we know we'll get tired of that. So we wrote a few new stories for the code manager:
Our customer will schedule those when they're needed.
Just for fun, we made a couple of changes to the Notepad project and watched the cm put them in the archive. Then we broke for the day.