| [ next ] [ prev ] [ contents ] [ up to Some Loose Ends ] | XP-Cinti TDD Workshop |
Another problem I noticed in the review is that the code to create the 5x5 matrix was unnecessarily verbose. There was also duplication in that the initial value for the matrix cells was repeated 25 times. We can do better than this.
An Array object responds to a * operator by duplicating itself that many times. We can replace the clumsy [false, false ...] notation with simply [false] * 5.
file: net.rb
...
def initialize
@tied = [
[false] * 5,
[false] * 5,
[false] * 5,
[false] * 5,
[false] * 5,
]
end
...
|
We run the tests and everything still passes. Can we use the same trick to reduce the repitition in the outer array as well? Let's try it.
file: net.rb
...
def initialize
@tied = [ [false] * 5 ] * 5
end
...
|
Cool! That looks great. We run our tests and get ...
$ ruby testnet.rb Loaded suite testnet Started... ... Failure occurred in test_multiple_positions(TestNet) [testnet.rb:23]: Expected <false> but was <true> ... Finished in 0.085576 seconds. 5 runs, 18 assertions, 1 failures, 0 errors |
Oops. Something is wrong here. The test for multiple positions is failing. Tying the knot at location (1,1) has effected some of the other positions.
We have fallen into the old trap of having object aliases. Each element of the outer array points to the same 5 element inner array object. Instead of having one outer and five inner arrays, we got one outer and one inner array where the inner array was shared by all five rows of the outer array.
Fortunately, this is an easy problem to work around. Arrays respond to the collect message by iterating through each element of the array and running a bit of code (called a block) on each element of that array. A block looks like this ...
{ |arg| code_body }
|
It is actually a miniture subroutine (but without a name). Arguments are passed to arg and the code body is executed. As always, the last expression in a block is the value of the block.
For example, the following code will add 2 to each element of the array and return the results in a new array.
[1, 2, 3, 4, 5].collect { |n| n+2 }
returns => [3, 4, 5, 6, 7]
|
Collect also works with Range objects. A range (1..5) represents a range of numbers from 1 to 5. Using a range lets us avoid explicitly listing each number.
(1..5).collect { |n| n+2 }
returns => [3, 4, 5, 6, 7]
|
Now we can write a working version of initialize. In fact, I'm going to use the collect notation for the inner arrays as well (just for consistancy).
file: net.rb
...
def initialize
@tied = (1..5).collect {
(1..5).collect { false }
}
end
...
|
And our tests verify that everything is working again.
Hmmm ... There is one small duplication left. We specify the number 5 in two locations. Let's factor that out into a constant, and then use the constant.
file: net.rb
...
MAXSIZE=5
def initialize
@tied = (1..MAXSIZE).collect {
(1..MAXSIZE).collect { false }
}
end
...
|
Do we run the tests? Of course we did! And they passed.
| [ next ] [ prev ] [ contents ] [ up to Some Loose Ends ] | Copyright 2003 by Jim Weirich.![]() |