<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/css" href="/stylesheets/rss.css"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/">
  <channel>
    <title>Object Mentor Blog: PySweeper: Un-refactoring, Tuple Madness, and Injection</title>
    <link>http://blog.objectmentor.com/articles/2007/02/17/un-refactoring-tuple-madness-and-injection</link>
    <language>en-us</language>
    <ttl>40</ttl>
    <description></description>
    <item>
      <title>PySweeper: Un-refactoring, Tuple Madness, and Injection</title>
      <description>&lt;p&gt;&lt;p&gt;I figure that to understand the code before me, I need to
  start testing. I&amp;#8217;ll start with the constructor. The
  constructor actually is doing a lot of work. In order to test out
  my refactoring tools, I recklessly broke out two routines without
  adding any testing. I don&amp;#8217;t recommend that on any project that
  needs to work, but I also don&amp;#8217;t recommend using a real product to
  learn your tools.&lt;/p&gt;

&lt;p&gt;Today, the constructor looks something like this:&lt;/p&gt;
&lt;pre&gt;
    def __init__( self, rows = 16, cols = 16, mines = 10): # 40 ):
        """Initialize the playing field.

        This function creates a playing field of the given size, and randomly
        places mines within it.

        rows and cols are the numbers of rows and columns of the playing  field, respectively.  
        mines is the number of mines to be placed within the field.
        """ 
        self._validate_parameters( rows, cols, mines )

        self.rows = rows
        self.cols = cols
        self.mines = mines
        self.cleared = 0
        self.flags = 0
        self.start_time = None

        minelist = []
        self.freecoords = {}
        for col in range( cols ):
            self.freecoords[col] = range( rows )

        self._place_mines( mines, minelist )

        self.board = []
        for col in range( cols ):
            self.board.append( [( -2, 0 )] * rows )
            for row in range( rows ):
                if ( row, col ) in minelist:
                    self.board[col][row] = ( -1, 0 )

    def _validate_parameters( self, rows, cols, mines ):
        for var in ( rows, cols, mines ):
            if var &amp;lt; 0:
                raise ValueError, "all arguments must be &amp;gt; 0" 
        if mines &amp;gt;= ( rows * cols ):
            raise ValueError, "mines must be &amp;lt; (rows * cols)" 

    def _place_mines( self, mines, minelist ):
        while mines &amp;gt; 0:
            y = random.choice( self.freecoords.keys() )
            x = random.randrange( len( self.freecoords[y] ) )
            minelist.append( ( self.freecoords[y][x], y ) )
            del self.freecoords[y][x]
            if not self.freecoords[y]:
                del self.freecoords[y]
            mines = mines - 1
&lt;/pre&gt;

&lt;p&gt;I feel a little guilty for breaking out functions without
testing them. I start to write tests for
&lt;code&gt;_validate_parameters&lt;/code&gt;, but I cannot call it
in isolation. I have to have an instance of Field to call it, and
it is also called in Field&amp;#8217;s constructor.  I can&amp;#8217;t test weird values
as easily as I&amp;#8217;d like. I need something like a static method (c++/java).
I add the &lt;code&gt;@classmethod&lt;/code&gt; decorator and I can
call it freely from my unit tests.&lt;/p&gt;

&lt;p&gt;I remind the reader that the game runs and is playable, so the
addition of tests should pretty much be a bit of pedantic
formalism in preparation for refactoring. However, look at the
code carefully and see if you can see a loophole:&lt;/p&gt;
&lt;pre&gt;
    @classmethod
    def _validate_parameters( self, rows, cols, mines ):
        for var in ( rows, cols, mines ):
            if var &amp;lt; 0:
                raise ValueError, "all arguments must be &amp;gt; 0
        if mines &amp;gt;= ( rows * cols ):
            raise ValueError, "mines must be &amp;lt; (rows * cols)" 

&lt;/pre&gt;
&lt;p&gt;A perceptive reader will realize that the code is checking that the
value is &lt;em&gt;less than&lt;/em&gt; zero, but reporting that it must be &lt;em&gt;greater
than&lt;/em&gt; zero. I found this with tests before it jumped out at me
visually.&lt;/p&gt;

&lt;p&gt;I write and run several tests and then get a nag from PyDev.
I guess somehow I missed the part where PyDev told me it was not
free software. The nag messages tells me I&amp;#8217;m in a  trial period
and I&amp;#8217;m expected to eventually buy or quit. Bummer.  PyDev is
not expensive. I just have to decide if I want to afford it.&lt;/p&gt;
&lt;p&gt;I am also playing with
&lt;a href="http://www.die-offenbachs.de/detlev/eric.html"&gt;Eric3&lt;/a&gt;
which is a python-specific &lt;span class="caps"&gt;IDE&lt;/span&gt; with refactoring, and it seems to
have better integration with PyUnit. It is not as nice in terms
of automatic completion and seems to have trouble with the context
menu for refactoring. I immediately miss the cool way that PyDev would
stick the word &amp;#8220;self&amp;#8221; in so I didn&amp;#8217;t have to type it.&lt;/p&gt;

&lt;p&gt;In the last installment, I extracted a method called &lt;code&gt;_place_mines&lt;/code&gt; but
it&amp;#8217;s a bit hard to test.  It&amp;#8217;s actually a very poor refactoring, done to test a 
tool and not really to improve the code. As is, it wants to muck about with an &lt;a href="http://tottinge.blogsome.com/2007/02/12/the-unspoken-parameter/"&gt;
unspoken parameter&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;
    def _place_mines( self, mines, minelist ):
        while mines &amp;gt; 0:
            y = random.choice( self.freecoords.keys() )
            x = random.randrange( len( self.freecoords[y] ) )
            minelist.append( ( self.freecoords[y][x], y ) )
            del self.freecoords[y][x]
            if not self.freecoords[y]:
                del self.freecoords[y]
            mines = mines - 1  

&lt;/pre&gt;

&lt;p&gt;It&amp;#8217;s small, but not obvious. Nor is it tested. It is using
&lt;code&gt;freecoords&lt;/code&gt; rather heavily. Breaking out
that method was a mistake, and I put it back inline. It seems that
&lt;code&gt;freecords&lt;/code&gt; is a list of all the positions in the grid that do
&lt;em&gt;not&lt;/em&gt; contain mines. It is only used in the constructor
and one other place.  In the other place, it&amp;#8217;s used and then
deleted from the object.&lt;/p&gt;

&lt;p&gt;The names of variables &lt;code&gt;mines&lt;/code&gt; and
&lt;code&gt;minelist&lt;/code&gt; aren&amp;#8217;t helpful because the names mean the
same thing, even though the variables do not. They each mean
&lt;em&gt;more than one mine&lt;/em&gt;, but one is an integer count and the
other is a list of the coordinates of the mines. That should
change right away. I rename the integer to &lt;code&gt;numberOfMines&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;With the &lt;code&gt;__init__&lt;/code&gt; reconstituted, I notice that I
don&amp;#8217;t even keep the minelist. It&amp;#8217;s discarded at the end of the
&lt;code&gt;__init__&lt;/code&gt;method. Maybe I can do this more simply.&lt;/p&gt;

&lt;p&gt;I realize now that the code suffers from &lt;em&gt;Tuple
Madness&lt;/em&gt;. The cells are represented by two-tuples. The first 
and second item are not distinguished by a name, only by subscripts 0 and
1. There can hardly be anything less descriptive of the content.
The initial values are (-2,0), but sometimes they are replaced 
with (-1,0). The code gives no obvious guidance to the significance 
of the first parameter versus the second parameter, and no real 
guide to the meaning of the integer-coded values. If one were 
going to document this code, it would be helpful to explain the
use of anonymous data structures. Of course, that&amp;#8217;s selfish because
my goal is to obviate any comments I find.  I just want it to be
easy.&lt;/p&gt;

&lt;p&gt;I dig in and discover that subscript
zero really is the count of adjacent cells with mines.  Since 
you can&amp;#8217;t have less than zero mines, the number -2 tells us that
we&amp;#8217;ve not counted yet. The number -1 is a code telling us that
the cell itself contains a mine. I&amp;#8217;ll want to split these out
later into named attributes.&lt;/p&gt;

&lt;p&gt;Tuple Madness is such an easy trap to fall into with Python or Ruby
(and I&amp;#8217;m told Lisp as well).
Each has such great native tuple and list types that we may 
forget to create real data structures for small data sets. I&amp;#8217;ve 
done it, the PySweeper author fell into it, and so have my
coworkers. If we had n-tuples in C, the resulting code would have
been so inscrutible that the software practice might never have
recovered. Yet two-tuples can be very good things. Just not here. 
I vow to create a data structure for the
cell as soon as I have this body of code sufficiently under test.&lt;/p&gt;

&lt;p&gt;As I learn about the two-tuples I write a couple of tests,
as if there were more descriptive functions.  I write calls to
such functions as &lt;code&gt;hasAMine()&lt;/code&gt; to check if subscript
zero has a -1 value, and &lt;code&gt;cellAt&lt;/code&gt; to get a cell at a 
given location, etc. Explanatory functions are a powerful documentation
technique 
&lt;pre&gt;
    def cellAt(self, x, y):
        return self.board[x][y]

    def isOpened( self, x, y ):
        return self.cellAt(x, y)[1] == -1

    def countAt(self, x,y):
        result = self.cellAt(x,y)[0]
        if result &amp;lt; 0:  result = None  # indicator value
        return result

    def hasMine(self, x,y):
        return self.cellAt(x,y)[0] == -1
  &lt;/pre&gt;
I have a small-but-growing body of tests. I should soon 
have enough confidence to do bigger refactorings.&lt;/p&gt;

&lt;p&gt;Note the ugly comment in &lt;code&gt;countAt&lt;/code&gt;. This is variable
reuse.  Rather than have a separate indicator (another element in
the tuple maybe), one element is serving double-duty.  It is too 
entrenched right now, so I&amp;#8217;ll work on making it obvious and easier
to separate later. Soon my body of explanatory methods will compose 
an opaque interface, and  there will be little or no upset when I 
change the implementation of those methods.&lt;/p&gt;

&lt;p&gt;Note also that we&amp;#8217;re in violation of the &lt;span class="caps"&gt;SRP&lt;/span&gt;. The class has methods
related to the field and also to specific cells in the board.  Maybe
I will eventually have classes for the cells, the game, and the board.&lt;/p&gt;

&lt;p&gt;Time to stop reading and do more testing. I did learn enough
to know that the game tries to be &amp;#8220;fair&amp;#8221;.  If there is a mine
on the very first square you select, it moves it out of the 
way. My &amp;#8220;gaming the system&amp;#8221; gene took over. I should be able 
to fill a three-by-three grid with eight mines, and then 
click (open) the center square.  That should guarantee me that
I have this structure:
&lt;table&gt;
    &lt;tr&gt;&lt;td&gt;mine&lt;/td&gt;&lt;td&gt;mine&lt;/td&gt;&lt;td&gt;mine&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;mine&lt;/td&gt;&lt;td&gt;empty&lt;/td&gt;&lt;td&gt;mine&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;mine&lt;/td&gt;&lt;td&gt;mine&lt;/td&gt;&lt;td&gt;mine&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;
The more astute reader knows that this disregards
the first rule of testing: &lt;em&gt;Control The Environment&lt;/em&gt;.  I 
should take control of the placement of mines rather than testing
the system with random mine placement.&lt;/p&gt;

&lt;p&gt;Sure enough, I find that the routine
that moves the mine to a new spot is not working quite as expected.
Now I have a failing test which allows me to make changes to the 
randomizer. I fix it, and then decide to pull the random chooser into 
a separate class. It will behave according to the &lt;code&gt;iterator&lt;/code&gt; 
protocol in Python so I can pass a simple array of (col,row] pairs to 
the constructor as a testing stub/mock/thing.&lt;/p&gt;

  &lt;p&gt;Now the top of my file has the random cell chooser.  There are some named constants, the 
  still-busy &lt;code&gt;__init__&lt;/code&gt; method, the now-tested &lt;code&gt;_validate_parameters&lt;/code&gt;
  method, and then other code we&amp;#8217;ve either talked about already or haven&amp;#8217;t touched yet.&lt;/p&gt;  
&lt;pre&gt;

random.seed()

class random_cell_chooser(object):
    def __init__(self, columns, rows):
        self.choices = [ (x,y) for x in range(columns) for y in range(rows) ]
    def next(self):
        if len(self.choices) == 0:
            raise StopIteration, "Out of free cells" 
        item = random.choice(self.choices)
        self.choices.remove(item)
        return item

INITIALLY_EMPTY = ( -2, 0 )
INITIALLY_MINED = ( -1, 0 )

class Field:
    """Provide a playing field for a Minesweeper game.

    This class internally represents a Minesweeper playing field, and provides
    all functions necessary for the basic manipulations used in the game.
    """ 
    def __init__( self, rows = 16, columns = 16, numberOfMines = 40, chooser=None):
        """Initialize the playing field.
        """ 

        self._validate_parameters( rows, columns, numberOfMines )

        self.rows = rows
        self.cols = columns
        self.mines = numberOfMines
        self.selector = chooser or random_cell_chooser(columns,rows)
        self.cleared = 0
        self.flags = 0
        self.start_time = None

        self.board = []
        for col in range( columns ):
            self.board.append( [INITIALLY_EMPTY] * rows )

        for counter in range(numberOfMines):
            (col,row) = self.selector.next()
            self.board[col][row] = INITIALLY_MINED

    @classmethod
    def _validate_parameters( self, rows, cols, mines ):
        for var in ( rows, cols, mines ):
            if var &amp;lt; 0:
                raise ValueError, "all arguments must be &amp;gt; 0" 
        if mines &amp;gt;= ( rows * cols ):
            raise ValueError, "mines must be &amp;lt; (rows * cols)" 

  &lt;/pre&gt;

&lt;p&gt;An engineer friend of mine is fond of saying &amp;#8220;suspenders 
and belt&amp;#8221;, referring to having redundant safety mechanisms.
I&amp;#8217;m appreciating the value of both a good version control system 
and a growing set of tests.  I am aware of the quality of the code
I&amp;#8217;m modifying (whether it passes all the tests) and that I can 
always abandon a change I&amp;#8217;m making if it gets messy. 
&lt;/p&gt;

&lt;p&gt;My little test is building that three-by-three grid and playing
 a very simple game to completion by opening the middle square. 
 I am testing setup and a bit of gameplay. I&amp;#8217;ll put a better test
 in tomorrow.&lt;/p&gt;

&lt;p&gt;It is a simple thing to extract the method for placing the mines
and pass it to the constructor for &lt;code&gt;Field&lt;/code&gt;.  Now I can 
set the mines just where I want them, giving me total control of the
field constructor.  I make it act like an iterator so my tests can 
pass a list iterator to the constructor:
&lt;pre&gt;

class random_cell_chooser(object):
    def __init__(self, columns, rows):
        self.choices = [ (x,y) for x in range(columns) for y in range(rows) ]
    def next(self):
        if len(self.choices) == 0:
            raise StopIteration, "Out of free cells" 
        item = random.choice(self.choices)
        self.choices.remove(item)
        return item

  &lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;Because the tests are new and few, I&amp;#8217;m also playing (losing) a game 
of minesweeper every so often so that I know that it still beats me in
reasonable ways.  I can&amp;#8217;t wait until I can depend entirely on automated
tests. Maybe in this way, it would have been better to have some basic
acceptance tests before working in unit tests. It&amp;#8217;s a thought.&lt;/p&gt;&lt;/p&gt;</description>
      <pubDate>Sat, 17 Feb 2007 13:02:00 -0600</pubDate>
      <guid isPermaLink="false">urn:uuid:ab049910-92f7-40e5-9825-ab1c4fa5ee0f</guid>
      <author>tottinger</author>
      <link>http://blog.objectmentor.com/articles/2007/02/17/un-refactoring-tuple-madness-and-injection</link>
      <category>Tim's Tepid Torrent</category>
      <trackback:ping>http://blog.objectmentor.com/articles/trackback/187</trackback:ping>
    </item>
    <item>
      <title>"PySweeper: Un-refactoring, Tuple Madness, and Injection" by bagsupplyer</title>
      <description>&lt;p&gt;&lt;a href="http://www.bagsupplyer.com/G-star-n218/" rel="nofollow"&gt;Brand fashion Men G-star Jackets from China for wholesale on line store&lt;/a&gt;&lt;/p&gt;</description>
      <pubDate>Fri, 09 Sep 2011 23:14:04 -0500</pubDate>
      <guid isPermaLink="false">urn:uuid:c86b2e44-ebc8-47e1-baf2-03bfad8ad385</guid>
      <link>http://blog.objectmentor.com/articles/2007/02/17/un-refactoring-tuple-madness-and-injection#comment-138973</link>
    </item>
    <item>
      <title>"PySweeper: Un-refactoring, Tuple Madness, and Injection" by okey oyunu oyna </title>
      <description>&lt;p&gt;this code will be very useful for me. Thanks&lt;/p&gt;


	&lt;p&gt;T&#252;m dunyadaki okey oyunculari ile ayni platform i&#231;erisinde sohbet ederek canli &lt;a href="http://www.okeyoyunu-oyna.com" rel="nofollow"&gt;okey oyunu oyna&lt;/a&gt; ve ve internette online oyun oynamanin zevkini &#231;ikar.&lt;/p&gt;</description>
      <pubDate>Mon, 25 Apr 2011 10:22:43 -0500</pubDate>
      <guid isPermaLink="false">urn:uuid:744476e2-e26e-45b8-92b5-0a1cc5b8c887</guid>
      <link>http://blog.objectmentor.com/articles/2007/02/17/un-refactoring-tuple-madness-and-injection#comment-90516</link>
    </item>
    <item>
      <title>"PySweeper: Un-refactoring, Tuple Madness, and Injection" by Robert Schlee</title>
      <description>&lt;p&gt;Thanks for your good publish. I enjoy your composing.&lt;/p&gt;</description>
      <pubDate>Tue, 19 Apr 2011 04:55:43 -0500</pubDate>
      <guid isPermaLink="false">urn:uuid:0130ad9b-a023-44a5-bb8b-0be5fe4e87a8</guid>
      <link>http://blog.objectmentor.com/articles/2007/02/17/un-refactoring-tuple-madness-and-injection#comment-87184</link>
    </item>
    <item>
      <title>"PySweeper: Un-refactoring, Tuple Madness, and Injection" by Tenant Screening</title>
      <description>&lt;p&gt;My little test is building that three-by-three grid and playing a very simple game to completion by opening the middle square. I am testing setup and a bit of gameplay. I&#8217;ll put a better test in tomorrow.&lt;/p&gt;</description>
      <pubDate>Wed, 23 Feb 2011 09:36:10 -0600</pubDate>
      <guid isPermaLink="false">urn:uuid:debd19f8-19d4-4ef7-a7d6-3da6bace2e1f</guid>
      <link>http://blog.objectmentor.com/articles/2007/02/17/un-refactoring-tuple-madness-and-injection#comment-66149</link>
    </item>
    <item>
      <title>"PySweeper: Un-refactoring, Tuple Madness, and Injection" by Criminal Records</title>
      <description>&lt;p&gt;I can&#8217;t wait until I can depend entirely on automated tests. Maybe in this way, it would have been better to have some basic acceptance tests before working in unit tests. It&#8217;s a thought.&lt;/p&gt;</description>
      <pubDate>Mon, 21 Feb 2011 14:20:10 -0600</pubDate>
      <guid isPermaLink="false">urn:uuid:67147f82-33b0-4021-9652-57221f04541d</guid>
      <link>http://blog.objectmentor.com/articles/2007/02/17/un-refactoring-tuple-madness-and-injection#comment-65263</link>
    </item>
    <item>
      <title>"PySweeper: Un-refactoring, Tuple Madness, and Injection" by coach factory outlet store</title>
      <description>&lt;p&gt;This is very interesting. I actually enjoy your writing style and your word choice more than anything.&lt;/p&gt;</description>
      <pubDate>Tue, 28 Dec 2010 00:45:58 -0600</pubDate>
      <guid isPermaLink="false">urn:uuid:fae45351-074c-4cbc-8b08-17658204ed4e</guid>
      <link>http://blog.objectmentor.com/articles/2007/02/17/un-refactoring-tuple-madness-and-injection#comment-52441</link>
    </item>
    <item>
      <title>"PySweeper: Un-refactoring, Tuple Madness, and Injection" by Pandora</title>
      <description>&lt;p&gt;I don&#8217;t recommend that on any project that needs to work, but I also don&#8217;t recommend using a real product to learn your tools.&lt;/p&gt;</description>
      <pubDate>Thu, 02 Dec 2010 05:03:19 -0600</pubDate>
      <guid isPermaLink="false">urn:uuid:0f50a419-e1df-4966-bc35-7f2d70f683a6</guid>
      <link>http://blog.objectmentor.com/articles/2007/02/17/un-refactoring-tuple-madness-and-injection#comment-45404</link>
    </item>
    <item>
      <title>"PySweeper: Un-refactoring, Tuple Madness, and Injection" by Tim.</title>
      <description>&lt;p&gt;I&amp;#8217;m sure it would have worked. :-)&lt;/p&gt;</description>
      <pubDate>Fri, 23 Feb 2007 22:49:18 -0600</pubDate>
      <guid isPermaLink="false">urn:uuid:2d458b16-547b-4540-8110-36b4cf12dd7d</guid>
      <link>http://blog.objectmentor.com/articles/2007/02/17/un-refactoring-tuple-madness-and-injection#comment-127</link>
    </item>
    <item>
      <title>"PySweeper: Un-refactoring, Tuple Madness, and Injection" by Michael Feathers</title>
      <description>&lt;p&gt;Yeah, sometimes refactoring in the constructor is unavoidable to get stuff under test.  But, it&amp;#8217;s neat to know that &amp;#8216;Expose Static Method&amp;#8217; works in Python.&lt;/p&gt;


	&lt;p&gt;What would&amp;#8217;ve happened if you tried to instantiate the class and test it before the extractions?&lt;/p&gt;</description>
      <pubDate>Sat, 17 Feb 2007 19:12:05 -0600</pubDate>
      <guid isPermaLink="false">urn:uuid:ad62239c-185c-4a0c-9795-a180a237c4a4</guid>
      <link>http://blog.objectmentor.com/articles/2007/02/17/un-refactoring-tuple-madness-and-injection#comment-116</link>
    </item>
  </channel>
</rss>

