XProgramming > XP Magazine > Adventures in C#: Expressing Ideas, Part II
COLLECTED TOPICS: Kate Oneal | Adventures in C# | Documentation in XP | Book Reviews
Adventures in C#: Expressing Ideas, Part II
Ron Jeffries
07/27/2002
After we got our tests in place, Chet and I went back to work improving the code to be more expressive. Here's what happened:

Contents:

The Starting Code

In the previous "Expressing Ideas" article, we had left the code looking like this:

    public void InsertParagraphTag() {
    //
    // On Enter, we change the TextModel lines to insert, after the line containing 
    // the cursor, a blank line, and a line with <P></P>. We set the new cursor
    // location to be between the P tags: <P>|</P>.
    //
      int cursorLine = LineContainingCursor();
    // allocate a new line array with two more lines
      String[] newlines = new String[lines.Length+2];
    // copy line 0 through cursor line to output
      for(int i = 0; i <= cursorLine; i++)  {
        newlines[i] = lines[i];
      }
    // insert blank line
      newlines[cursorLine+1] = "";
    // insert P tag line
      newlines[cursorLine+2] = "<P></P>";
    // copy lines after cursor lien to output
      for (int i = cursorLine+1; i < lines.Length; i++) {
        newlines[i+2] = lines[i];
      }
    // save new lines as result
      lines = newlines;
    // set cursor location
      selectionStart = NewSelectionStart(cursorLine + 2);
    }

To make this better, we just kept pecking away at it, changing things around to make the code more expressive, and in some cases to use approaches that seemed better to us in C#. The first of these ideas was the best. This version uses two arrays of strings, creating a new array, "newlines" to be two more lines than the existing "lines" array. We knew there was an object in C#, ArrayList, which is basically an array that can grow, have insertions, and so on. So our first step was to change to object to use two ArrayLists instead of two arrays. To do this, we needed to change the Lines property:

    public String[] Lines {
      get {
        // there must be a way to cast this??
        String[] textlines = new String[newlines.Count];
        for (int i = 0; i < newlines.Count; i++) {
          textlines[i] = (String) newlines[i];
        }
        return textlines;
      }
      set {
        lines = new ArrayList(value);
        newlines = lines;
      }
    }

There's nothing very magical about this. The interface we chose expects arrays of string, so we are converting to and from ArrayList internally. There may be a better way to do the get operation, but at the Michigan Union, with no C# books, we sure couldn't find it. So we create an array whose size matches newlines.Count (for reasons known only to Microsoft, ArrayList uses Count where Array uses Length). Then we copy the newlines ArrayList into the array. We're hoping to find a way to just "cast" the ArrayList to an array of String, and when we do, we'll change this code to be cleaner.

We also had to change the TextModel constructor to initialize the lines and newlines variables to ArrayLists. (I think we had actually written the constructor during the testing phase described previously, when we built the test of an empty TextModel.

    public TextModel() {
      lines = new ArrayList();
      newlines = lines;
    }  
		

It also turns out that there's some special case code in the InsertParagraphTag method, handling the empty case. I'll just show it here for completeness. We're hoping that when the refactoring is done, it will fall into the general case, but for now it's separate:

      // handle empty array special case (yucch)
      if ( newlines.Count == 0 ) {
        newlines.Add( "<P></P>" );
        selectionStart = 3;
        return;
      }

Now this really sets us up to do some better work. ArrayList has a method AddRange that lets you add to one ArrayList, a contiguous section of another. So we can rewrite all that array copying this way:

      newlines = new ArrayList();
      newlines.AddRange(LinesThroughCursor());
      newlines.AddRange(NewParagraph());
      newlines.AddRange(LinesAfterCursor());

Note that we extracted two methods, LinesThroughCursor and LinesAfterCursor. They look like this:

    public ArrayList LinesThroughCursor() {
      return lines.GetRange(0,LineContainingCursor()+1);
    }

    public ArrayList LinesAfterCursor() {
      int cursorLine = LineContainingCursor();
      return lines.GetRange(cursorLine+1,lines.Count - cursorLine - 1);
    }

And we wrote a method NewParagraph to answer the blank line and paragraph tag pair:

    public ArrayList NewParagraph() {
      ArrayList temp = new ArrayList();
      temp.Add("");
      temp.Add("<P></P>");
      return temp;
    }

The net result of all this is to replace this code:

    
// allocate a new line array with two more lines
      String[] newlines = new String[lines.Length+2];
    // copy line 0 through cursor line to output
      for(int i = 0; i <= cursorLine; i++)  {
        newlines[i] = lines[i];
      }
    // insert blank line
      newlines[cursorLine+1] = "";
    // insert P tag line
      newlines[cursorLine+2] = "<P></P>";
    // copy lines after cursor lien to output
      for (int i = cursorLine+1; i < lines.Length; i++) {
        newlines[i+2] = lines[i];
      }

with this:

      newlines = new ArrayList();
      newlines.AddRange(LinesThroughCursor());
      newlines.AddRange(NewParagraph());
      newlines.AddRange(LinesAfterCursor());

The Current Code

We think that's a lot more expressive. Here's the whole method. See whether you agree that we have made it more expressive, and if we're justified in taking out the comments that we removed:

    public void InsertParagraphTag() {
      //
      // On Enter, we change the TextModel lines to insert, after the line containing 
      // the cursor, a blank line, and a line with <P></P>. We set the new cursor
      // location to be between the P tags: <P>|</P>.
      //
      // handle empty array special case (yucch)
      if ( newlines.Count == 0 ) {
        newlines.Add( "<P></P>" );
        selectionStart = 3;
        return;
      }

      newlines = new ArrayList();
      newlines.AddRange(LinesThroughCursor());
      newlines.AddRange(NewParagraph());
      newlines.AddRange(LinesAfterCursor());

      // set cursor location
      selectionStart = NewSelectionStart(LineContainingCursor() + 2);
    }

This code still isn't done, as we'll suggest in a moment. But you can see what has happened: we have taken code that clearly needed some explanation, and replaced it with code that surely needs less explanation. You might prefer a bit more than we do, but we'll wager that most readers won't want one line of comment per line of code any more.

What's Next?

As I say, the code doesn't look done to us: we have a sense of duplication from that special case that inserts the paragraph tag and such. We'd like to consolidate the special case and the general case. Furthermore, now that we're more familiar with ArrayList, we think we can probably do the editing right in the "lines" ArrayList and get rid of "newlines" altogether. We're going to leave that for another day, because ...

Our Story Works!

That's right. Our first story is now working. Remember, it was:

  • When I'm typing a paragraph inside P tags, and hit a return, create another P tag underneath this one, and put the cursor in between the new tags, in the right typing location.

So we're finished and can move on to the next story, right?

WRONG! Where's Our Customer Test???

We don't have a Customer Test for this story. We've satisfied ourselves as programmers that it works, but how can we satisfy our customer. We owe our customer a Customer [acceptance] Test. In the next article, we'll show how we create a Customer Test for this, our very first story. No framework, no waiting for QA to show up: we'll just create a test that we think our Customer will be able to understand. Watch for it!

XProgramming > XP Magazine > Adventures in C#: Expressing Ideas, Part II
COLLECTED TOPICS: Kate Oneal | Adventures in C# | Documentation in XP | Book Reviews