Table of Contents

RDoc

# rdoc.rb
 
# This class stores information about people.
class Person
    attr_accessor :name, :age, :gender
 
    # Create the person object and store their name
    def initialize(name)
        @name = name
    end
 
    # Print this person's name to the screen
    def print_name
        puts "Person called #{@name}"
    end
 
end

Uruchamiamy RDoca:

sqbell@sqbell-gentoo ~/ruby/beginning_ruby/ch08_doctestdebug.rb $ rdoc rdoc.rb 
Parsing sources...
100% [ 1/ 1]  rdoc.rb                                                           

Generating Darkfish...

Files:         1
Classes:       1 (    0 undocumented)
Constants:     0 (    0 undocumented)
Modules:       0 (    0 undocumented)
Methods:       2 (    0 undocumented)
100.00% documented

Elapsed: 0.0s
sqbell@sqbell-gentoo ~/ruby/beginning_ruby/ch08_doctestdebug.rb $ ls -la doc/
razem 44
drwxr-xr-x 2 sqbell sqbell  4096 05-05 17:35 .
drwxr-xr-x 3 sqbell sqbell  4096 05-05 17:35 ..
-rw-r--r-- 1 sqbell sqbell  6726 05-05 17:35 Person.html
-rw-r--r-- 1 sqbell sqbell    72 05-05 17:35 created.rid
-rw-r--r-- 1 sqbell sqbell  1459 05-05 17:35 index.html
-rw-r--r-- 1 sqbell sqbell 12560 05-05 17:35 rdoc.css
-rw-r--r-- 1 sqbell sqbell  1240 05-05 17:35 rdoc_rb.html

Bardzo ladnie to wyglada. :-)

def secret_method #:nodoc:
end
# I think that John is pretty
#--
# stupid fella.
#++
# and I really like him.

Obsługa błędów

# exceptions_01.rb
 
class Person
    def initialize(name)
        raise ArgumentError, "No name present!" if name.empty?
    end
end
 
fred = Person.new('')

Output:

exceptions_01.rb:5:in `initialize': No name present! (ArgumentError)
	from exceptions_01.rb:9:in `new'
	from exceptions_01.rb:9:in `<main>'
# exceptions_02.rb
 
begin
    puts 10 / 0
rescue
    puts "You caused an error!"
end
# exceptions_03_ps.rb
 
begin
    #code here
rescue ZeroDivisionError
    #code here
rescue YourOwnException
    #code here
rescue
    #code that rescues all other types of exception
end
# exceptions_04.rb
 
begin
    puts 10 / 0
rescue => e
    puts e.class
    puts e.backtrace
end

Output:

ZeroDivisionError
exceptions_04.rb:4:in `/'
exceptions_04.rb:4:in `<main>'

Catch i Throw

# catch_throw_2.rb
 
def generate_random_number_except_123
    x = rand(1000)
    throw :finish if x == 123
end
 
catch(:finish) do
    1000.times { generate_random_number_except_123 }
    puts "Generated..."
end

Debugger

Debug.rb
Emacs support available.

debugtest.rb:3:i = 1
(rdb:1)
(rdb:1) list
[-2, 7] in debugtest.rb
   1  # debugtest.rb
   2  
=> 3  i = 1
   4  j = 0
   5  
   6  until i > 1000000
   7      i *= 2

Testowanie

# testing.rb
 
class String
    def titleize
        self.gsub(/\b\w/) { |letter| letter.upcase }
    end
end
 
raise "Fail 1" unless "this is a test".titleize == "This Is A Test"
raise "Fail 2" unless "another test 1234".titleize == "Another Test 1234"
raise "Fail 3" unless "We're testing titleize".titleize == "We're Testing Titleize"

Output:

testing.rb:11:in `<main>': Fail 3 (RuntimeError)

Testy jednostkowe

# unit_testing.rb
 
class String
    def titleize
        self.gsub(/(\A|\s)\w/) { |letter| letter.upcase }
    end
end
 
require 'test/unit'
 
class TestTitleize < Test::Unit::TestCase
    def test_basic
        assert_equal("This Is A Test", "this is a test".titleize)
        assert_equal("Another Test 1234", "another test 1234".titleize)
        assert_equal("We're Testing", "We're testing".titleize)
    end
end

Output:

Loaded suite unit_testing
Started
.
Finished in 0.000407 seconds.

1 tests, 3 assertions, 0 failures, 0 errors, 0 skips

Test run options: --seed 26015
asserty co porownuje
assert(<boolean expression>)
assert_equal(expected, actual)
assert_not_equal(expected, actual)
assert_raise(exception_type, ..) { <code block> }
assert_nothing_raised(exception_type, ..) { }
assert_instance_of(class_expected, object)
flunk:flunk

Benchmarki i profilowanie

Benchmarki

# simple_bench.rb
 
require 'benchmark'
 
puts Benchmark.measure { 10000.times { print "." } }

Output:

0.020000   0.010000   0.030000 (  0.026244)

Kolumny to kolejno: user CPU time, system CPU time, total CPU, real time taken.

# multiple_bench.rb
 
require 'benchmark'
iterations = 1000000
 
Benchmark.bm do |bm|
    bm.report("for:") do
        for i in 1 .. iterations do
            x = i
        end
    end
 
    bm.report("times:") do
        iterations.times do |i|
            x = i
        end
    end
end

Output:

        user     system      total        real
for:    0.110000   0.000000   0.110000 (  0.108295)
times:  0.090000   0.000000   0.090000 (  0.096391)

Profilowanie

W Rubym profiler jest wbudowany. Jedyne co trzeba zrobic to umiescic require “profile” w kodzie, lub uruchomic go poleceniem ruby -r profile.

# profiling.rb
 
require 'profile'
 
class Calculator
    def self.count_to_large_number
        x = 0
        100000.times { x += 1 }
    end
 
    def self.count_to_small_number
        x = 0
        1000.times { x += 1 }
    end
end
 
Calculator.count_to_large_number
Calculator.count_to_small_number

Output:

  %   cumulative   self              self     total
 time   seconds   seconds    calls  ms/call  ms/call  name
100.00     0.18      0.18        2    90.00    90.00  Integer#times
  0.00     0.18      0.00        1     0.00     0.00  Class#inherited
  0.00     0.18      0.00        2     0.00     0.00  BasicObject#singleton_method_added
  0.00     0.18      0.00        1     0.00   180.00  Calculator#count_to_large_number
  0.00     0.18      0.00        1     0.00     0.00  Calculator#count_to_small_number
  0.00     0.18      0.00        1     0.00   180.00  #toplevel

Profilowanie Garbage Collectora

GC::Profiler.enable
 
def factorial_iterative(n)
  (2 .. n - 1).each {|i| n *= i}
  n
end
 
m = 30_000
 
factorial_iterative(m)
 
puts GC::Profiler.result

Output:

GC 176 invokes.
Index    Invoke Time(sec)       Use Size(byte)     Total Size(byte)         Total Object                    GC Time(ms)
    1               0.035               179400               703480                17587         1.95800000000000151701
    2               0.054               179400               703480                17587         1.37900000000001909939
    3               0.071               179400               703480                17587         1.40599999999999059064
    4               0.088               179400               703480                17587         1.25100000000000211031
    5               0.104               179400               703480                17587         1.23499999999998610889
    6               0.120               179400               703480                17587         1.21299999999999186251
    7               0.136               179400               703480                17587         1.22200000000000086331
    8               0.152               179400               703480                17587         1.15599999999999036859
    9               0.168               179400               703480                17587         1.21299999999996410693
   10               0.183               179400               703480                17587         1.19400000000002837197
[...]

Kilka przykładów optymalizacji

Hash a Array

#!/usr/bin/env ruby -wKU
require 'benchmark'
 
paragraph = %w[Lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua Ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur Excepteur sint occaecat cupidatat non proident sunt in culpa qui officia deserunt mollit anim id est laborum]*100
words_ary = []
words_hsh = {}
 
Benchmark.bm(10) do |b|
  b.report("array") { paragraph.each {|word| words_ary << word unless words_ary.include?(word)} }
  b.report("uniq") {paragraph.uniq}
  b.report("hash") do 
    paragraph.each {|word| words_hsh[word] = nil}
    words_hsh = words_hsh.keys
  end
end

Output:

                 user     system      total        real
array        0.040000   0.000000   0.040000 (  0.046044)
uniq         0.010000   0.000000   0.010000 (  0.002790)
hash         0.000000   0.000000   0.000000 (  0.005029)
[Finished in 0.1s]

Powodem takich różnic jest fakt, że przy dodawaniu do tablicy, słowo musi być sprawdzone z każdym elementem tablicy. W przypadku słownika, nie jest to konieczne. Wygrywa metoda Array#uniq.

Lazy Instantiation

#!/usr/bin/env ruby -wKU
require 'benchmark'
 
class Employee
  def initialize
    @embezzled = []
  end
 
  def embezzle(amount)
    @embezzled << amount
  end
 
  def embezzled_total
    @embezzled.inject(0) {|sum, amount| sum + amount}
  end
end
 
class FairEmployee  
  def embezzle(amount)
    (@embezzled ||= []) << amount
  end
 
  def embezzled_total
    (@embezzled || []).inject(0) {|sum, amount| sum + amount}
  end
end
 
EMPLOYEES = 1000000
 
Benchmark.bmbm(15) do |b|
  b.report("empl") { EMPLOYEES.times {Employee.new} }
  b.report("fair") { EMPLOYEES.times {FairEmployee.new} }
end

Output:

Rehearsal ---------------------------------------------------
empl              1.490000   0.000000   1.490000 (  1.522579)
fair              0.960000   0.000000   0.960000 (  0.971468)
------------------------------------------ total: 2.450000sec

                      user     system      total        real
empl              1.460000   0.010000   1.470000 (  1.489645)
fair              0.930000   0.000000   0.930000 (  0.944282)
[Finished in 5.0s]

W pierwszym wypadku, dla każdego obiektu tworzymy pustą tablicę, zakładając, że każdy pracownik będzie nieuczciwy. W drugim przypadku, tablica tworzona jest tylko, gdy faktycznie coś zostało zapodziane - oszczędzamy więc pamięć i czas procesora. Gdy jednak wielu pracowników byłoby nieuczciwych, każde wywołanie metod embezzle i embezzled_total będzie wolniejsze z powodu dodatkowych warunków.

Ccccccbooooom!

#!/usr/bin/env ruby -wKU
 
require "benchmark"
require "inline"
 
class Integer
  def count_set_bits(use_c = false)
    return csb(self) if use_c
    return 0 if self.zero?
    bits, x = 1, self
    bits += 1 while (x = x & (x - 1)).nonzero?
    bits
  end
 
  inline do |builder|
    builder.c "int csb(long x) {
      if (x == 0) return (x);
      int bits = 1;
      while ((x = x & (x - 1))) bits ++;
      return (bits);
    }"
 
  end
end
 
Benchmark.bmbm do |b|
  b.report("ruby") { 100_100.times { 152363.count_set_bits } }
  b.report("c") { 100_100.times { 152363.count_set_bits(true) } }
end

Output:

Rehearsal ----------------------------------------
ruby   0.620000   0.000000   0.620000 (  0.638407)
c      0.060000   0.000000   0.060000 (  0.071187)
------------------------------- total: 0.680000sec

           user     system      total        real
ruby   0.610000   0.000000   0.610000 (  0.655447)
c      0.070000   0.010000   0.080000 (  0.079884)
[Finished in 1.9s]

Czasem po prostu jest szybciej. Dzięki RubyInline, możemy umieszczać kod C bezpośrednio w Rubym. Można też wywoływać funkcje C jakby były częścią Rubego.

Ogólnie