We use the C# "delegate" feature to come up with a solution to the Bowling problem that is perhaps a bit less OO, but is also only 40 percent the size of the version with the Kicker classes. Fun exercise, but the proof will be in the next stories. [updated, see footnote]

Housekeeping ... Arrgh!

We began the morning with a bunch of housekeeping. We wanted to make a copy of our existing project, to change over to delegates. If there’s a way to do that in Visual Studio, we couldn’t find it. So we created a new project folder, copied files into it, and so on.

Then it turned out we wanted to start differently, so we did it again.

Then it turned out that I had moved the source files instead of copying them. So we had to get the files back from the Code Manager.

Then it turned out that I had gotten back the oldest ones, not the newest ones. So we had to do it again. Arrgh!!

Talk about a Gumption Trap. I was ready to go back home before we even started. Chet calmed me down …

Coding at Last

We’ve copied over just our Game and Frame and their tests. We’ll remove the Kicker stuff from Frame and then try to make it work. This seems somewhat bad – maybe we should be working from the version with all the Kickers in it. But we think we can add in the procedural RollConsumed pretty easily and be green and at a good place to start. Let’s see …

  public class Frame {
    private int score;
    public Frame() {
      score = 0;
    }

    public void AddRoll(int pins) {
      score += pins;
    }

    public virtual bool RollConsumed(int pins) {
      return true;
    }

    public int Score() {
      return score;
    }
  }

  public class TenthFrame : Frame {

    public override bool RollConsumed(int pins) {
      return true;
    }
  }

Pretty much nothing works. GutterBalls test runs, as does TenthFrameConsumesEverything. They don’t test much. We need a RollConsumed method:

  public class Frame {
    private int rollCount;
    private int score;
    public Frame() {
      rollCount = 0;
      score = 0;
    }

    public void AddRoll(int pins) {
      rollCount++;
      score += pins;
    }

    public virtual bool RollConsumed(int pins) {
      if (rollCount == 2)
        return false;
      AddRoll(pins);
      return true;
    }

    public int Score() {
      return score;
    }
  }

This makes the Frame test OpenFrame work, as expected. OpenFrames, the Game test, didn’t run. That’s because the TenthFrame isn’t doing anything. We think it needs to call the base method.

  public class TenthFrame : Frame {

    public override bool RollConsumed(int pins) {
      base.RollConsumed(pins);
      return true;
    }
  }

That makes the OpenFrames test green, as we expected. With any luck, TenthFrame will stay the same henceforth. We’ll see. I’m not feeling very confident.

That’s worth exploring. We had a lot of trouble setting up the experiment, just trying to convince Visual Studio, and ourselves, at the same time, that we had the files we wanted. Now we’re recreating the procedural solution. It feels as if we aren’t starting in the right place. Well, we’re here now, and we’re progressing. Let’s see what happens.

Next step … We’ll go find the best existing procedural solution, which had the Spare working but not Strike, paste it in, and go from there.

    public virtual bool RollConsumed(int pins) {
      if (IsSpare()) {
        AddRoll(pins);
        return false;
      }
      if (rollCount >= 2) 
        return false;
      AddRoll(pins);
      return true;
    }

    private bool IsSpare() {
      return rollCount == 2 && Score() == 10;
    }

That makes SpareFrame and SpareFrameWithZeroBonusWork. Not bad. Except I have a better idea.

The scheme we have in mind is to build five delegates that handle FirstBall, SecondBall, and so on, as the Kickers do, only more simply (we hope). So pushing forward to make the tests green procedurally and then putting in the delegates doesn’t really make sense, because they won’t refactor in with simple “extract method” kinds of operations: we’ll be moving to a state machine solution from our procedural one. Let’s back out this last change and put in the delegates now. We’re back to this:

    public virtual bool RollConsumed(int pins) {
      if (rollCount == 2)
        return false;
      AddRoll(pins);
      return true;
    }

Now the idea is this: there will be a delegate variable, into which we will plug various concrete methods, in exactly the style of the Kickers, each one handling one state in our little state diagram:

image

Here we go. Tests running now are basically the open frame ones. We may break more before we’re done, but I hope not. I’m still feeling edgy about this. Here’s the first cut, which makes the open frame tests run again:

  public class Frame {
    private int rollCount;
    private int score;
    delegate bool ProcessRoll(Frame frame, int pins);
    private ProcessRoll processor;

    public Frame() {
      rollCount = 0;
      score = 0;
      processor = new ProcessRoll(ProcessFirstRoll);
    }

    private bool ProcessFirstRoll(Frame frame, int pins) {
      rollCount++;
      score += pins;
      processor = new ProcessRoll(ProcessSecondRoll);
      return true;
    }

    private bool ProcessSecondRoll(Frame frame, int pins) {
      rollCount++;
      score += pins;
      processor = new ProcessRoll(IgnoreRoll);
      return true;
    }

    private bool IgnoreRoll(Frame frame, int pins) {
      return false;
    }

    public void AddRoll(int pins) {
      rollCount++;
      score += pins;
    }

    public virtual bool RollConsumed(int pins) {
      return processor(this, pins);
    }

    public int Score() {
      return score;
    }
  }

It seems straightforward now to put in the other delegates. Let’s do the Spare logic. It goes in the SecondRoll and it calls for a new delegate, OneBonus:

    private bool ProcessSecondRoll(Frame frame, int pins) {
      rollCount++;
      score += pins;
      if (score == 10) 
        processor = new ProcessRoll(OneBonusRoll);
      else
        processor = new ProcessRoll(IgnoreRoll);
      return true;
    }

    private bool OneBonusRoll(Frame frame, int pins) {
      score += pins;
      processor = new ProcessRoll(IgnoreRoll);
      return false;
    }

This makes the Spare tests all work. I’m feeling more confident now. Also we noticed that the Frame parameter was a case of YAGNI. I put it in because the Kickers had it. But we are the Frame, we don’t need a reference to it. I’ll remove those now … or should I? I’m on a red bar. Well, heck, sue me, I’m on a red bar anyway. Out they go. The compiler will tell me which ones to change. Here’s the whole Frame for your reference:

  public class Frame {
    private int rollCount;
    private int score;
    delegate bool ProcessRoll(int pins);
    private ProcessRoll processor;

    public Frame() {
      rollCount = 0;
      score = 0;
      processor = new ProcessRoll(ProcessFirstRoll);
    }

    private bool ProcessFirstRoll(int pins) {
      rollCount++;
      score += pins;
      processor = new ProcessRoll(ProcessSecondRoll);
      return true;
    }

    private bool ProcessSecondRoll( int pins) {
      rollCount++;
      score += pins;
      if (score == 10) 
        processor = new ProcessRoll(OneBonusRoll);
      else
        processor = new ProcessRoll(IgnoreRoll);
      return true;
    }

    private bool OneBonusRoll(int pins) {
      score += pins;
      processor = new ProcessRoll(IgnoreRoll);
      return false;
    }

    private bool IgnoreRoll(int pins) {
      return false;
    }

    public void AddRoll(int pins) {
      rollCount++;
      score += pins;
    }

    public virtual bool RollConsumed(int pins) {
      return processor(pins);
    }

    public int Score() {
      return score;
    }
  }

Now for Strike. We modify FirstRoll, and implement TwoBonusRoll to roll over to OneBonusRoll:

  public class Frame {
    private int rollCount;
    private int score;
    delegate bool ProcessRoll(int pins);
    private ProcessRoll processor;

    public Frame() {
      rollCount = 0;
      score = 0;
      processor = new ProcessRoll(ProcessFirstRoll);
    }

    private bool ProcessFirstRoll(int pins) {
      rollCount++;
      score += pins;
      if (score == 10)
        processor = new ProcessRoll(TwoBonusRoll);
      else
        processor = new ProcessRoll(ProcessSecondRoll);
      return true;
    }

    private bool ProcessSecondRoll( int pins) {
      rollCount++;
      score += pins;
      if (score == 10) 
        processor = new ProcessRoll(OneBonusRoll);
      else
        processor = new ProcessRoll(IgnoreRoll);
      return true;
    }

    private bool TwoBonusRoll(int pins) {
      score += pins;
      processor = new ProcessRoll(OneBonusRoll);
      return false;
    }

    private bool OneBonusRoll(int pins) {
      score += pins;
      processor = new ProcessRoll(IgnoreRoll);
      return false;
    }

    private bool IgnoreRoll(int pins) {
      return false;
    }

    public void AddRoll(int pins) {
      rollCount++;
      score += pins;
    }

    public virtual bool RollConsumed(int pins) {
      return processor(pins);
    }

    public int Score() {
      return score;
    }
  }

The tests all run, which I expected them to do. Somewhere in the last two steps, my confidence came back.

We have a total of about 95 useful lines, still twice as many as the procedural, but about 40 percent of the version with the Kicker classes. Once we recovered from all the startup hassle and got rhythm going, the implementation was pretty straightforward.

Assessment

Chet points out that we have methods here instead of classes. We have some duplication within those methods that we could work to remove. This is the first time he’s seen delegates, and it seems to him that it’s not too weird. He is, of course, used to Smalltalk’s #perform:, which works like this only easily.

One question is whether the delegate is deeper in the bag of tricks than the Kicker classes, or more shallow. Delegates are very language specific, so the class solution might be easier to grasp out of the box. Chet feels that it has a sort of hidden flavor, but that once you looked it up you’d be OK.

What else: we don’t need rollCount, so let’s remove that. (We may have to put it back if we have to score frames as Strikes or Spares, but we’ll leave that until we get such a story.) Tests run fine without it.

Most of the delegates include “score += pins;” but we don’t see any good way to remove that duplication. We could probably have a delegate within a delegate to do the accumulation, but that would just glom up the delegate setting part of the code. We don’t see any solution that leads to less duplication and keeps the code even somewhat clear.

We could probably rename some of these methods, and rearrange the code so that you don’t have to read so much if you don’t want to. We might want to bring out the state machine aspect, for example. That should be done.

Comparing the delegate to the Kicker classes, we don’t see much to prefer one over the other. Although the Kicker classes have more code, we don’t feel that they’re less clear. Comparing either of these to the procedural versions of the program, we think they are much more complex. And to what end?

We have versions now of size 1 (procedural), size 2 (delegate) and size 5 (kicker classes). It seems fair to assume that the latter two take twice as long to complete, and five times as long respectively. What justification can we make for doing these more complex versions? If we look at time to deliver this initial functionality, what could possibly justify taking five times as long to do it?

Well, maybe adding some new feature that we don’t know about would be easier in the more elaborate version. But is that the way to bet? And how much easier would it have to be to pay back the 5x initial cost? Can we say that with a straight face? Suppose this new feature would cost only 1 in the elaborate version, and would cost 5 in the procedural: then the two approaches break even, except that the simpler one ships sooner!

Now if one or the other version isn’t “done” yet, in that it doesn’t pass the tests, or doesn’t express all our ideas, or has duplication, then we may need to refine that solution. That refinement might well lead to some objects, but in the case in hand, we have built a perfectly clear procedural version, and it really doesn’t seem to “want” any objects.

Conclusions So Far

We cannot justify these more complicated solutions on the basis of time to get the simple problem done. We probably can’t justify them based on clarity: at least to Chet and me, they’re not more clear than the simple procedural version. So the only possible justification would be faster development of later features … and it would have to be a lot faster. We’ll explore that next. We propose two likely new features: support for a GUI interface, and support for a different game, Candlepins.

Our bet, right now, is that there will be no substantial velocity improvement in the sophisticated versions, but we’ll find out. We’ll keep rough track of the amount of code written or changed, the time involved in each solution, and our intuitive assessment of how easy or hard it was.

Tune in next time and find out! Meanwhile, keep those cards and letters coming.


Carl Manaster writes:

It looks to me like you can drop the AddRoll() function from Frame; I wouldn't think anything outside would be calling it and none of the internal functions are. ProcessFirstRoll() and ProcessSecondRoll() could call it, but it doesn't imnsho make things clearer.

Paul is certainly correct that AddRoll() isn’t needed. I’m not sure it doesn’t make things clearer, but I’d agree that it’s not worth much. It seems to me that Chet and I noticed the method at one point, perhaps after we stopped coding and started eating our lunch. If we decide to do our experiments on this version – and we may not – we’ll decide what to do about AddRoll, either use it or lose it.

Thanks, Carl!