My Secret Life as a Spaghetti Coder
home | about | contact | privacy statement
On Monday (Jan. 29, 2007) we had our first meeting of the UH Code Dojo, and it was a lot of fun. Before we started, I was scared that I'd be standing there programming all by myself, with no input from the other members. But, my fear was immediately laid to rest. Not only did everyone participate a lot, one of the members, Matt (I didn't catch his last name), even took over the typing duties after about 45 minutes. That turned out to be a good thing - since I still can't type worth a crap on my laptop, and he was much faster than me.

Now, it had only been a couple of months since I last used Java - but it still amazes me how little time off you need to forget simple things, like "import" for "require." I found myself having several silly syntax errors for things as small as forgetting the semicolon at the end of the lines.

Overall, we started with 7 people, and had 5 for most of the meeting. We finished with four because one person had tons of homework to do. It seemed like those five of us who stayed were genuinely enjoying ourselves.

In any case, we decided to stick with the original plan of developing a tic-tac-toe game. You would think that a bunch of computer scientists could develop the whole game in the two hours we were there. But, you'd have been wrong.

I came away from the meeting learning two main points, which I think illustrate the main reasons we didn't complete the game:
  1. YAGNI is your friend
  2. Writing your tests first, and really checking them is very worthwhile
Both points are things most of us already know, but things you sometimes lose sight of when working like that all the time, or without paying attention. But, I was paying attention on Monday, so let me explain how I came to identify those points.

We started by trying to identify a class we could test. This was harder than it looked. We initially decided on Game. There was a lot of discussion on making it an abstract class, or an interface, or should we just make it support only tic-tac-toe? After everyone was convinced that we should first make it concrete, because we would not be able to test it otherwise, we started trying to figure out what we could test. So, we wrote a test_hookup() method to check that JUnit was in place and working.

In the end, we also decided that we would probably want to try to make it abstract after we tested the functionality, so we could support multiple games. I think that decision proved to be a bad one - because none of us could figure out a single peice of functionality that a Game should have. For me, I think this was due mostly to the fact that I was trying to think of general functionality, so I only came up with run(), basically the equivalent of play().

After thinking for a couple of minutes about things we could test, we decided we should go with something more tangible for us - the Board class. Once we did that, we never had a problem with what to test next. But, we still ran into problems with YAGNI. We were arguing about what type of object we should use, so I just said "let's use Object, then anything will fit and we can be more general when we get back to Game." This led to trouble later, when we had to have checks for null all the time. So, we changed it to a string whose value could be "x", "o", or "". That made life, and the code, a lot simpler.

Those are the two main places where ignoring the YAGNI prinicple hurt us. There are also a couple of places where not writing our tests first hurt us.

The most obvious one is that we were just looking for a green bar. Because of that, we actually were running the wrong tests, and some of ours hadn't worked in the first place. We were running the test from the Game class, not the Board. It wasn't too hard to fix the ones that were broken though.

Other than that, we spent a lot of time trying to figure out if an algorithm to determine a winner would work (in our heads). If we had just written the test first, we could have run it and seen for sure if the algorithm was going to work correctly.
Overall, the only other thing I would have changed about how we developed would have been to decide on several tests in advance, so we would have plenty to choose from, rather than thinking of a test, then immediately implementing it.

For reference, I've posted our code below.

// Board.java
public class Board {
    private String[][] _board;
    private boolean current_player;
    public Board(int row, int col)
    {
       _board = new String[row][col];
       current_player = true;
       for (int i = 0 ; i < row ; i++)
       {
          for (int j = 0 ; j < col ; j++)
          {
             _board[i][j] = "";
          }
       }
    }

    public boolean isEmpty()
    {
       for (int i=0; i<_board.length; i++)
       {
          for(int j=0; j<_board[i].length; j++)
          {
             if (!_board[i][j].equals("")) return false;
          }
       }        return true;
    }

    public void setMarkerAtPosition(int row, int col)
    {
       if (current_player)
          _board[row][col] = "x";
       else
          _board[row][col] = "o";
          current_player = !current_player;
    }

    protected Object getMarkerAtPosition(int row, int col)
    {
       return _board[row][col];
    }

    protected boolean isGameOver()
    {
       return false;
    }

    public boolean isRowWinner(int row)
    {
       for (int j = 0 ; j < _board[0].length - 1 ; j++)
       {
          if (!_board[row][j].equals(_board[row][j + 1]))
          {
             return false;
          }
       }
       return true;
    }

    public boolean isColWinner(int col)
    {
       for (int i = 0 ; i < _board.length -1 ; i++)
       {
          if (!_board[i][col].equals(_board[i][col]))
          {
             return false;
          }
       }
       return true;
    }

    public boolean isDiagWinner()
    {
       boolean diag_winner1 = true, diag_winner2 = true;
       for (int i = 0 ; i < _board.length -1 ; i++)
       {
          if (!_board[i][i].equals(_board[i+1][i+1]))
          {
             diag_winner1=false;
          }
          if (!_board[i][_board[0].length-1-i].equals(
             _board[i+1][_board[0].length-1-(i+1)]))
          {
             diag_winner2=false;
          }
       }
       return diag_winner1||diag_winner2;
    }
}


// TestBoard.java
import junit.framework.TestCase;

public class TestBoard extends TestCase {

    private Board _b;
    public void setUp()
    {
       _b= new Board(3,3);
    }

    public void testBoard()
    {
       assertTrue(_b != null);
       assertTrue(_b.isEmpty());
    }

    public void testSetMarkerAtPosition()
    {
       _b.setMarkerAtPosition(0, 0);
       //assertEquals(_b.getMarkerAtPosition(0,0),"x");
    }

    public void testIsGameOver()
    {
       assertFalse(_b.isGameOver());
       for (int i = 0 ; i < 3 ; i++)
       {
          for (int j = 0 ; j < 3 ; j++)
          {
             _b.setMarkerAtPosition(i,j);
          }
       }
       //assertTrue(_b.isGameOver());
    }

    public void testIsRowWinner()
    {
       _b.setMarkerAtPosition(0,0);
       _b.setMarkerAtPosition(1,0);
       _b.setMarkerAtPosition(0,1);
       _b.setMarkerAtPosition(1,1);
       _b.setMarkerAtPosition(0,2);
       assertTrue(_b.isRowWinner(0));
    }

    public void testIsColWinner()
    {
       _b.setMarkerAtPosition(0,0);
       _b.setMarkerAtPosition(0,1);
       _b.setMarkerAtPosition(1,0);
       _b.setMarkerAtPosition(1,1);
       _b.setMarkerAtPosition(2,0);
       assertTrue(_b.isColWinner(0));
    }

    public void testIsDiagWinner1()
    {
       _b.setMarkerAtPosition(0,0);
       _b.setMarkerAtPosition(0,1);
       _b.setMarkerAtPosition(1,1);
       _b.setMarkerAtPosition(0,2);
       _b.setMarkerAtPosition(2,2);
       assertTrue(_b.isDiagWinner());
    }

    public void testIsDiagWinner2()
    {
       _b.setMarkerAtPosition(0,2);
       _b.setMarkerAtPosition(0,1);
       _b.setMarkerAtPosition(1,1);
       _b.setMarkerAtPosition(0,0);
       _b.setMarkerAtPosition(2,0);
       assertTrue(_b.isDiagWinner());
    }
}


As you can see, the board object really didn't do what its name implies. That should have been game, I think. Also, the three check for winner functions are only temporarily public - we planned on combining them to check for a winner in just one public function. Other than that, we also need some negative tests, and obviously to complete the game.

Finally, thanks to everyone who showed up for making it an enjoyable experience! I can't wait for the next one!

Hey! Why don't you make your life easier and subscribe to the full post or short blurb RSS feed? I'm so confident you'll love my smelly pasta plate wisdom that I'm offering a no-strings-attached, lifetime money back guarantee!


Comments
Leave a comment

oh my god!

Posted by a on Feb 01, 2007 at 06:20 PM UTC - 6 hrs

What'd I do!?

Posted by Sam on Feb 01, 2007 at 07:41 PM UTC - 6 hrs

Aside from respond to a person whose name is a, whose website is b, and whose email address is c@c.com?

Posted by Sam on Feb 01, 2007 at 07:42 PM UTC - 6 hrs

Leave a comment

Leave this field empty
Your Name
Email (not displayed, more info?)
Website

Comment:

Subcribe to this comment thread
Remember my details
Google
Web CodeOdor.com

Me
Picture of me

Topics
.NET (19)
AI/Machine Learning (14)
Answers To 100 Interview Questions (10)
Bioinformatics (2)
Business (1)
C and Cplusplus (6)
cfrails (22)
ColdFusion (78)
Customer Relations (15)
Databases (3)
DRY (18)
DSLs (11)
Future Tech (5)
Games (5)
Groovy/Grails (8)
Hardware (1)
IDEs (9)
Java (38)
JavaScript (4)
Linux (2)
Lisp (1)
Mac OS (4)
Management (15)
MediaServerX (1)
Miscellany (76)
OOAD (37)
Productivity (11)
Programming (168)
Programming Quotables (9)
Rails (31)
Ruby (67)
Save Your Job (58)
scriptaGulous (4)
Software Development Process (23)
TDD (41)
TDDing xorblog (6)
Tools (5)
Web Development (8)
Windows (1)
With (1)
YAGNI (10)

Resources
Agile Manifesto & Principles
Principles Of OOD
ColdFusion
CFUnit
Ruby
Ruby on Rails
JUnit



RSS 2.0: Full Post | Short Blurb
Subscribe by email:

Delivered by FeedBurner