Sunday, December 31, 2006

The job is done


Listen to this article.

Job done. Blog closed.

[Pages 163-5] But it's too slow ! - The profiler


Listen to this article.

Value: High

Level: Easy

Summary:

Ruby comes with a code profiler: it shows you the number of times each
method in the program is called and the average and cumulative time
that Ruby spends in those methods. You can add profiling to your code
using the command-line option "-r profile" or from within the code
using "require 'profile'".

The first thing to notice is that the timings shown are a lot slower
than when the program runs without the profiler. Profiling has a
serious overhead, but the assumption is that it applies across the
board, and therefore the relative numbers are still meaningful.

Memo: None

Example:
require 'profile'
count = 0
words = File.open("/usr/share/dict/words")
while word = words.gets
word = word.chomp!
if word.length == 12
count += 1
end
end
puts "#{count} twelve-character words"

# the following code runs more than five times faster
require 'profile'
words = File.read("/usr/share/dict/words")
count = words.scan(PATT= /^............\n/).size
puts "#{count} twelve-character words"

Reported errata (at 10/17/06 14:17:18 PDT): 1
*Erratum #4666 is a minor suggestion.

Errata I found: 0

My suggestions to the author: 0

Doubts: 1
*"Always try to eliminate unneeded code. Remember to check the code
without the profiler afterward, though—sometimes the slowdown the
profiler introduces can mask other problems.". What does it mean ?

Saturday, December 30, 2006

[Pages 162-3] But it's too slow ! - Benchmark


Listen to this article.

Value: High

Level: Easy

Summary:
Typically, slow-running programs have one or two performance graveyards, places where execution time goes to die. The trick is finding them. The Benchmark module and the Ruby profilers can help.
 
You can use the Benchmark module to time sections of code. For example, we may wonder which is faster: a large loop using variables local to the loop's block or using variables from the surrounding scope.

You have to be careful when benchmarking, because oftentimes Ruby programs can run slowly because of the overhead of garbage collection. Because this garbage collection can happen any time during your program's execution, you may find that benchmarking gives misleading results, showing a section of code running slowly when in fact the slowdown was caused because garbage collection happened to trigger while that code was executing. The Benchmark module has the bmbm method that runs the tests twice, once as a rehearsal and once to measure performance, in an attempt to minimize the distortion introduced by garbage collection. The benchmarking process itself is relatively well mannered—it doesn't slow down your program much.

Memo: Garbage collection could give you misleading results when benchmarking.

Example:
require 'benchmark'
include Benchmark
LOOP_COUNT = 1_000_000
bm(12) do |test|
test.report("normal:") do
LOOP_COUNT.times do |x|
y = x + 1
end
end
test.report("predefine:") do
x = y = 0
LOOP_COUNT.times do |x|
y = x + 1
end
end
end

Reported errata (at 10/17/06 14:17:18 PDT): 0

Errata I found: 1
*On page 162 "bmbm" should be "bm".

My suggestions to the author: 0

Doubts: 2
*What is garbage collection ?
*Why is a large loop using variables local to the loop's block slower than a loop using variables from the surrounding scope ?

Thursday, December 28, 2006

[Pages 159-62] But it doesn't work !


Listen to this article.

Value: High

Level: Easy

Summary:

• First and foremost, run your scripts with warnings enabled (the -w
command-line option).
• If you happen to forget a "," in an argument list—especially to
print—you can produce some very odd error messages.
• A parse error at the last line of the source often indicates a
missing end keyword, sometimes quite a bit earlier.
• An attribute setter is not being called. Within a class definition,
Ruby will parse setter= as an assignment to a local variable, not as a
method call. Use the form self.setter= to indicate the method call
(example code 1).
• Objects that don't appear to be properly set up may have been
victims of an incorrectly spelled initialize method (example code 2).
The same kind of thing can happen if you misspell the instance
variable name (example code 3).
• Block parameters are in the same scope as local variables. If an
existing local variable with the same name as a block parameter exists
when the block executes, that variable will be modified by the call to
the block. This may or may not be a Good Thing (example code 4).
• Watch out for precedence issues, especially when using {} instead of
do/end (example code 5).
• If numbers don't come out right, perhaps they're strings. Text read
from a file will be a String and will not be automatically converted
to a number by Ruby. A call to Integer will work wonders (and will
throw an exception if the input isn't a well-formed integer).
• Unintended aliasing—if you are using an object as the key of a hash,
make sure it doesn't change its hash value (or arrange to call
Hash#rehash if it does) (example code 6).
• Make sure the class of the object you are using is what you think it
is. If in doubt, use puts my_obj.class.
• Make sure your method names start with a lowercase letter and class
and constant names start with an uppercase letter.
• If method calls aren't doing what you'd expect, make sure you've put
parentheses around the arguments.
• Make sure the open parenthesis of a method's parameter list butts up
against the end of the method name with no intervening spaces.
• Use irb and the debugger.
• Use Object#freeze. If you suspect that some unknown portion of code
is setting a variable to a bogus value, try freezing the variable. The
culprit will then be caught during the attempt to modify the variable.

Memo: One major technique makes writing Ruby code both easier and more
fun. Develop your applications incrementally. Write a few lines of
code, and then run them. Perhaps use Test::Unit to write some tests.
Write a few more lines of code, and then exercise them. One of the
major benefits of a dynamically typed language is that things don't
have to be complete before you use them.

Example:

class Incorrect
attr_accessor :one, :two
def initialize
one = 1 # incorrect - sets local variable
self.two = 2
end
end
obj = Incorrect.new
obj.one → nil
obj.two → 2

class Incorrect
attr_reader :answer
def initialise # < < < spelling error
@answer = 42
end
end
ultimate = Incorrect.new
ultimate.answer → nil

class Incorrect
attr_reader :answer
def initialize
@anwser = 42 #<« spelling error
end
end
ultimate = Incorrect.new
ultimate.answer → nil

c = "carbon"
i = "iodine"
elements = [ c, i ]
elements.each_with_index do |element, i|
# do some chemistry
end
c → "carbon"
i → 1

def one(arg)
if block_given?
"block given to 'one' returns #{yield}"
else
arg
end
end
def two
if block_given?
"block given to 'two' returns #{yield}"
end
end
result1 = one two {
"three"
}
result2 = one two do
"three"
end
puts "With braces, result = #{result1}"
puts "With do/end, result = #{result2}"

arr = [1, 2]
hash = { arr => "value" }
hash[arr] → "value"
arr[0] = 99
hash[arr] → nil
hash.rehash → {[99, 2]=>"value"}
hash[arr] → "value"

Reported errata (at 10/17/06 14:17:18 PDT): 1
*See below

Errata I found: 1
*Reported erratum #2822 refers to page 133, not to page 161 as stated.

My suggestions to the author: 0

Doubts: 2
*I didn't understand example code 5.
*"Output written to a terminal may be buffered. This means you may not
see a message you write immediately. In addition, if you write
messages to both $stdout and $stderr, the output may not appear in the
order you were expecting. Always use nonbuffered I/O (set sync=true)
for debug messages." What does it mean ?

[Pages 156-9] Interactive Ruby, Editor support


Listen to this article.

Value: Low

Level: Easy

Summary:
IRb also allows you to create subsessions, each one of which may have
its own context. For example, you can create a subsession with the
same (top-level) context as the original session or create a
subsession in the context of a particular class or instance.
You can also run Ruby code from inside an editor: in Textmate try
selecting some code and press Ctrl+Shift+E.

Memo: None

Example:
irb [ irb-options ] [ ruby_script ] [ program-arguments ]

Reported errata (at 10/17/06 14:17:18 PDT): 1
*Erratum #4066 reports a problem using IRb with Windows XP SP 2 / Finnish locale

Errata I found: 0

My suggestions to the author: 0

Doubts: 0

Tuesday, December 26, 2006

[Pages 155-6]: When trouble strikes - Ruby debugger


Listen to this article.

Value: Low

Level: Easy

Summary:

The debugger supports the usual range of features you'd expect,
including the ability to set breakpoints, to step into and step over
method calls, and to display stack frames and variables, list the
instance methods defined for a particular object or class, list and
control separate threads within Ruby. If your Ruby installation has
readline support enabled, you can use cursor keys to move back and
forth in command history and use line-editing commands to amend
previous input.


Memo: None

Example:

# ruby -r debug [ debug-options ] [ programfile ] [ program-arguments ]

% ruby -r debug t.rb
Debug.rb
Emacs support available.
t.rb:1:def fact(n)
(rdb:1) list 1-9
[1, 10] in t.rb
=> 1 def fact(n)
2 if n <= 0
3 1
4 else
5 n * fact(n-1)
6 end
7 end
8
9 p fact(5)
(rdb:1) b 2
Set breakpoint 1 at t.rb:2
(rdb:1) c
breakpoint 1, fact at t.rb:2
t.rb:2: if n <= 0
(rdb:1) disp n
1: n = 5
(rdb:1) del 1
(rdb:1) watch n==1
Set watchpoint 2
(rdb:1) c
watchpoint 2, fact at t.rb:fact
t.rb:1:def fact(n)
1: n = 1
(rdb:1) where
--> #1 t.rb:1:in `fact'
#2 t.rb:5:in `fact'
#3 t.rb:5:in `fact'
#4 t.rb:5:in `fact'
#5 t.rb:5:in `fact'
#6 t.rb:9
(rdb:1) del 2
(rdb:1) c
120

Reported errata (at 10/17/06 14:17:18 PDT): 0

Errata I found: 0

My suggestions to the author: 0

Doubts: 0

[Pages 155-6]: When trouble strikes - Ruby debugger


Listen to this article.

Value: Low

Level: Easy

Summary:

The debugger supports the usual range of features you'd expect,
including the ability to set breakpoints, to step into and step over
method calls, and to display stack frames and variables, list the
instance methods defined for a particular object or class, list and
control separate threads within Ruby. If your Ruby installation has
readline support enabled, you can use cursor keys to move back and
forth in command history and use line-editing commands to amend
previous input.


Memo: None

Example:

# ruby -r debug [ debug-options ] [ programfile ] [ program-arguments ]

% ruby -r debug t.rb
Debug.rb
Emacs support available.
t.rb:1:def fact(n)
(rdb:1) list 1-9
[1, 10] in t.rb
=> 1 def fact(n)
2 if n <= 0
3 1
4 else
5 n * fact(n-1)
6 end
7 end
8
9 p fact(5)
(rdb:1) b 2
Set breakpoint 1 at t.rb:2
(rdb:1) c
breakpoint 1, fact at t.rb:2
t.rb:2: if n <= 0
(rdb:1) disp n
1: n = 5
(rdb:1) del 1
(rdb:1) watch n==1
Set watchpoint 2
(rdb:1) c
watchpoint 2, fact at t.rb:fact
t.rb:1:def fact(n)
1: n = 1
(rdb:1) where
--> #1 t.rb:1:in `fact'
#2 t.rb:5:in `fact'
#3 t.rb:5:in `fact'
#4 t.rb:5:in `fact'
#5 t.rb:5:in `fact'
#6 t.rb:9
(rdb:1) del 2
(rdb:1) c
120

Reported errata (at 10/17/06 14:17:18 PDT): 0

Errata I found: 0

My suggestions to the author: 0

Doubts: 0

Sunday, December 24, 2006

Pages 152-4: Organizing and running tests - Test suites


Listen to this article.

Value: Low

Level: Easy

Summary:
You can group test cases together into test suites, letting you run
them all as a group. This is easy to do in Test::Unit. All you have to
do is create a Ruby file that requires test/unit, and then requires
each of the files holding the test cases you want to group.
This way, you build yourself a hierarchy of test material individual
tests, all the tests in a file, group many file in a test suite and
run them as a unit, group test suites into other test suites.
Conventionally test cases are in files named tc_xxx and test suites
are in files named ts_xxx.

Memo: You should group cases that test different sets of functions in
different tests suites.

Example:
# file ts_dbaccess.rb
require 'test/unit'
require 'tc_connect'
require 'tc_query'
require 'tc_update'
require 'tc_delete'


Reported errata (at 10/17/06 14:17:18 PDT): 2
*Erratum #1946 is a minor typo.
*Erratum #1947, see below.

Errata I found: 1
*Erratum #1947 is a wrong erratum reporting: it's supposed to be
present in P1.0 Oct 4 2004, but it's not.

My suggestions to the author: 0

Doubts: 0

Friday, December 22, 2006

Pages 151-2: Organizing and Running Tests - Where to put tests


Listen to this article.

Value: Average

Level: Normal

Summary:
If you run a test case from the command line, Test::Unit is clever enough to notice that there's no main program, so it collects up all the test case classes and runs each in turn. From the command line you can also run just a particular method. You should create a test/ directory where to place all your test source files. This directory is then placed parallel to the lib/ directory containing the code you're developing. This works well as a way of organizing files but leaves you with a small problem: how do you tell Ruby where to find the library files to test ? At the front of your test code (for
example in test_roman.rb), add the following line:

$:.unshift File.join(File.dirname(__FILE__), "..", "lib")

This magic works because the test code is in a known location relative to the code being
tested. It starts by working out the name of the directory from which the test file is run and then constructing the path to the files under test. This directory is then prepended to the load path (the variable $:). From then on, code such as require 'roman' will search the library being tested first.

There are worse ways to achieve this goal: you could build the path into require statements in the test and run the tests from the test/ subdirectory, but if the source file to be tested requires other source files, they won't be found in Ruby's $LOAD_PATH. You could also run the tests from the directory containing the library being tested (% ruby ../test/test_roman.rb). Because the current directory is in the load path, the test code will be able to find it, but this approach will obviously break down if you run the test from somewhere else in your system.

Memo: Always tell your tests where to find the source code to be tested.

Reported errata (at 10/17/06 14:17:18 PDT): 1
* Erratum #1945 is a minor typo.

Errata I found: 0

My suggestions to the author: 0

Doubts: 1
*I didn't understand this sentence: "A second, less immediate problem is that we won't be able to use these same tests to test our classes once installed on a target system, as then they'll be referenced simply using require 'roman'."

Pages 151-2: Organizing and Running Tests - Where to put tests


Listen to this article.

Value: Average

Level: Normal

Summary:
If you run a test case from the command line, Test::Unit is clever enough to notice that there's no main program, so it collects up all the test case classes and runs each in turn. From the command line you can also run just a particular method. You should create a test/ directory where to place all your test source files. This directory is then placed parallel to the lib/ directory containing the code you're developing. This works well as a way of organizing files but leaves you with a small problem: how do you tell Ruby where to find the library files to test ? At the front of your test code (for
example in test_roman.rb), add the following line:

$:.unshift File.join(File.dirname(__FILE__), "..", "lib")

This magic works because the test code is in a known location relative to the code being
tested. It starts by working out the name of the directory from which the test file is run and then constructing the path to the files under test. This directory is then prepended to the load path (the variable $:). From then on, code such as require 'roman' will search the library being tested first.

There are worse ways to achieve this goal: you could build the path into require statements in the test and run the tests from the test/ subdirectory, but if the source file to be tested requires other source files, they won't be found in Ruby's $LOAD_PATH. You could also run the tests from the directory containing the library being tested (% ruby ../test/test_roman.rb). Because the current directory is in the load path, the test code will be able to find it, but this approach will obviously break down if you run the test from somewhere else in your system.

Memo: Always tell your tests where to find the source code to be tested.

Reported errata (at 10/17/06 14:17:18 PDT): 1
* Erratum #1945 is a minor typo.

Errata I found: 0

My suggestions to the author: 0

Doubts: 1
*I didn't understand this sentence: "A second, less immediate problem is that we won't be able to use these same tests to test our classes once installed on a target system, as then they'll be referenced simply using require 'roman'."

Pages 151-2: Organizing and Running Tests - Where to put tests


Listen to this article.

Value: Average

Level: Normal

Summary:
If you run a test case from the command line, Test::Unit is clever enough to notice that there's no main program, so it collects up all the test case classes and runs each in turn. From the command line you can also run just a particular method. You should create a test/ directory where to place all your test source files. This directory is then placed parallel to the lib/ directory containing the code you're developing. This works well as a way of organizing files but leaves you with a small problem: how do you tell Ruby where to find the library files to test ? At the front of your test code (for
example in test_roman.rb), add the following line:

$:.unshift File.join(File.dirname(__FILE__), "..", "lib")

This magic works because the test code is in a known location relative to the code being
tested. It starts by working out the name of the directory from which the test file is run and then constructing the path to the files under test. This directory is then prepended to the load path (the variable $:). From then on, code such as require 'roman' will search the library being tested first.

There are worse ways to achieve this goal: you could build the path into require statements in the test and run the tests from the test/ subdirectory, but if the source file to be tested requires other source files, they won't be found in Ruby's $LOAD_PATH. You could also run the tests from the directory containing the library being tested (% ruby ../test/test_roman.rb). Because the current directory is in the load path, the test code will be able to find it, but this approach will obviously break down if you run the test from somewhere else in your system.

Memo: Always tell your tests where to find the source code to be tested.

Reported errata (at 10/17/06 14:17:18 PDT): 1
* Erratum #1945 is a minor typo.

Errata I found: 0

My suggestions to the author: 0

Doubts: 1
*I didn't understand this sentence: "A second, less immediate problem is that we won't be able to use these same tests to test our classes once installed on a target system, as then they'll be referenced simply using require 'roman'."

Wednesday, December 20, 2006

[temp] Pages 148-50: Structuring tests


Listen to this article.

Value: Average

Level: Normal

Summary:
You include Test::Unit facilities in your unit test with the "require 'test/unit'" line.
Unit tests seem to fall quite naturally into high-level groupings, called test cases, and
lower level groupings, the test methods themselves. Quite often you'll find all of the test methods within a test case setting up a particular scenario. Each test method then probes some aspect of that scenario. Finally, each method may then tidy up after itself. For example, we could be testing a class that extracts jukebox playlists from a database.

Memo: The classes that represent test cases must be subclasses of Test::Unit::TestCase.
Within a TestCase class, the setup and teardown methods bracket each test, rather than being run once per test case.

Example:

require 'test/unit'
require 'playlist_builder'
require 'dbi'
class TestPlaylistBuilder < Test::Unit::TestCase
def test_empty_playlist
db = DBI.connect('DBI:mysql:playlists')
pb = PlaylistBuilder.new(db)
assert_equal([], pb.playlist())
db.disconnect
end
def test_artist_playlist
db = DBI.connect('DBI:mysql:playlists')
pb = PlaylistBuilder.new(db)
pb.include_artist ("krauss")
assert(pb.playlist.size > 0, "Playlist shouldn't be empty")
pb.playlist.each do |entry|
assert_match(/krauss/i, entry.artist)
end
db.disconnect
end
def test_title_playlist
db = DBI.connect('DBI:mysql:playlists')
pb = PlaylistBuilder.new(db)
pb.include_title("midnight")
assert(pb.playlist.size > 0, "Playlist shouldn't be empty")
pb.playlist.each do |entry|
assert_match(/midnight/i, entry.title)
end
db.disconnect
end
# ...
end

Reported errata (at 10/17/06 14:17:18 PDT): 0

Errata I found: 0

My suggestions to the author: 0

Doubts: 0