quote:Original post by petewood
plenty of articles on eXtreme Programming practices here
there is a pair-programming one in particular
Well, I read through that example and, well... I’ll try to be nice.
First, they really take a long route to get where they are going. The code goes through lots of iterations before it’s done, getting worse before it gets better. This seems to be a result of coding for one test case at a time. They write a program that satisfies the first test case. Then they think up a second test case. Then they realize that what they have can’t work with the second test case and they have to rewrite it. The code goes through many changes that I think are senseless. I don’t think that’s a very good process. Perhaps this example was meant to demonstrate pair programming but not necessarily a good design. However, if this kind of thinking process leads to that kind of design, then I think I would prefer a different process.
The program is not really object-oriented. There’s really only one class, game, and it has all the data and all the code. By making a scorer class at the end, all they’re really doing is grouping procedural code together in its own class. It’s not really object-oriented. There is no “scorer” in the problem domain.
They talked themselves out of a linked list at the beginning because it may not be needed. Well, of course you can avoid using a linked list if you want. You can also use assembly language—Java or C is not really necessary. They didn’t need to use a linked list or use a frame class for this particular program. But in making that choice I think they also abandoned an object-oriented design.
So who cares if it’s object-oriented or not? Well, the reason we make object-oriented code is because it’s a general way to make a program tolerant of future changes when we don’t know what the changes are going to be .
XP doesn’t seem to care about the future as much. You may argue that there’s no sense in writing code that may never be needed. That’s true. But it’s a pretty safe bet that improvements and changes will need to be made in the future. And I think you’re shooting yourself in the foot if you pretend that that’s not going to happen.
I wrote the bowling program in an alternative way, using C++ because I don’t have Java. (And I did use a linked list.)
#include <iostream>using namespace std; class Frame{ public: Frame() :m_pPrevious(NULL), m_pNext(NULL), m_firstThrow(0), m_secondThrow(0), m_nextThrow(1) {} void SetNext(Frame *pNext) { m_pNext = pNext;} void SetPrevious(Frame *pPrevious) {m_pPrevious = pPrevious;} virtual bool Add(char pins); virtual int GetScore(); char GetFirstThrow() { return m_firstThrow; } private: virtual char GetTwoThrows(void); // gets the sum of the score of the next two throws bool IsStrike() { return m_firstThrow >= 10; } bool IsSpare() { return (m_firstThrow + m_secondThrow) >= 10; } protected: Frame *m_pNext; Frame *m_pPrevious; char m_firstThrow; char m_secondThrow; unsigned char m_nextThrow; // starting at one, the next throw to be made for this frame; };// Return the sum of the next two throws.// If this frame is a strike, it will in turn need to query the following frame for it's first throw// in order to sum two throws.char Frame::GetTwoThrows(){ if(IsStrike()) return m_firstThrow + m_pNext->GetFirstThrow(); else return m_firstThrow + m_secondThrow;} int Frame::GetScore(){ int score = m_firstThrow + m_secondThrow; // Accumulate score from previous frames if(m_pPrevious) score += m_pPrevious->GetScore(); if(m_pNext) { // Account for a strike if(IsStrike()) score += m_pNext->GetTwoThrows(); // Account for a spare else if(IsSpare()) score += m_pNext->GetFirstThrow(); } return score;} // Add pins to this frame.// Return true if more throws can be added to this frame,// return false if this frame is done;bool Frame::Add(char pins){ switch(m_nextThrow) { case 1: m_firstThrow = pins; m_nextThrow += (pins >= 10) ? 2 : 1; break; case 2: m_secondThrow = pins; m_nextThrow++; break; }; // return false if this frame is done if(m_nextThrow > 2) return false; return true;} class LastFrame : public Frame{public: LastFrame() : Frame(), m_thirdThrow(0) {} virtual bool Add(char pins); virtual int GetScore(); private: virtual char GetTwoThrows(); char m_thirdThrow;}; char LastFrame::GetTwoThrows(){ return m_firstThrow + m_secondThrow;} int LastFrame::GetScore(){ int score = m_firstThrow + m_secondThrow + m_thirdThrow; // Accumulate score from previous frames if(m_pPrevious) score += m_pPrevious->GetScore(); return score;} bool LastFrame::Add(char pins){ switch(m_nextThrow) { case 1: m_firstThrow = pins; m_nextThrow++; break; case 2: m_secondThrow = pins; m_nextThrow++; break; case 3: m_thirdThrow = pins; m_nextThrow++; break; }; // return false if this frame is done if(m_nextThrow > 3) return false; return true;} class Game{ enum {NUMFRAMES = 10}; Frame *Frames[NUMFRAMES];public: Game(); ~Game(); void Add(char pins); int GetScore() { return GetScoreFrame(m_currentFrame); } int GetScoreFrame(int frame) // first frame is frame 1. there is no frame 0. { return Frames[frame - 1]->GetScore(); } int GetCurrentFrame() { return m_currentFrame; }private: int m_currentFrame;}; void Game::Add(char pins){ if((Frames[m_currentFrame - 1]->Add(pins) == false) && (m_currentFrame < 10)) ++m_currentFrame;} Game::Game() :m_currentFrame(1){ // Allocate Frames for(int i = 0; i < NUMFRAMES - 1; i++) Frames[i] = new Frame; Frames[NUMFRAMES - 1] = new LastFrame; // Setup Previous pointers Frames[0]->SetPrevious(NULL); for(int i = 1; i < NUMFRAMES; i++) Frames[i]->SetPrevious(Frames[i-1]); // Setup Next pointers for(int i = 0; i < NUMFRAMES - 1; i++) Frames[i]->SetNext(Frames[i+1]); Frames[NUMFRAMES - 1]->SetNext(NULL);} Game::~Game(){ // deallocate the frames for(int i = 0; i < NUMFRAMES; i++) { if(Frames[i]) delete Frames[i]; Frames[i] = NULL; }} int main(){ Game game; // Run a sample game for (int i=0; i<9; i++) game.Add(10); game.Add(9); game.Add(1); game.Add(1); cout << " Frame: " << game.GetCurrentFrame() << endl; cout << " Score: " << game.GetScore() << endl; char keypress; cin >> keypress; return 0;}
This version uses a Frame class. Derived from that is a special case frame, the LastFrame (the tenth frame). This frame has different behavior. It’s not perfect and you could probably improve it. But it’s just to illustrate a point.
The disadvantages of this version are that it takes a bit more memory due to the link pointers and the extra m_nextThrow variable I used. Also, it looks like it’s a little longer just in terms of lines. You could use an STL linked list to make it easier and a little shorter, but I didn’t want to use STL for an example.
I believe there are a couple of advantages to this version. First, to me anyway, this is clearer and more easily understood. But I suppose everyone thinks their own code is easier to understand than that of other people, so I won’t be the judge of that.
I also think it is more tolerant of future changes. Let’s say you want to add a feature that let’s you rearrange the frames in a different order to see if you would have gotten a higher score if you bowled in a more “serendipitous” order. This is not hard because you can just rearrange the order of the linked list. In the Java version it is harder because there is no real concept of where frames start and end unless you are sequencing through the list. What if you want to add a function that tells you how many pins you got down in any one frame? Which program would you rather add this functionality to? What if you want to invent a new type of bowling frame that scores differently or has four throws?
In that example, they were able to bank on the fact that the game of bowling doesn’t change. It always has ten frames and it’s always scored the same. But in the real world, most of the things you model will change.
Maybe I’m harping on their design too much and not focusing on the process, which may be the point of the article. But I don’t see how that process is very good; they went through a ton of changes in making a simple program. The alternative program I was able to write straight away. Test-first programming? It seems like what the test-first philosophy is doing for them is causing them to design by contract, which is good. So why not just design by contract?
[edited by - JimH on May 7, 2003 11:25:20 PM]