====== RDoc ======
* RDoc - narzedzie, ktore przeglada pliki zrodlowe Rubiego, i na ich podstawie generuje dokumentacje HTML. Podobnie jak w przypadku phpdoc, konieczne jest stosowanie sie do pewnych form komentarzy.
# 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
{{ :inf:rdoc.jpg?nolink& |}}
Bardzo ladnie to wyglada. :-)
* Zeby RDoc cos zignorowal, korzystamy z modyfikatora '':nodoc:'' po definicji modulu, klasy czy metody:
def secret_method #:nodoc:
end
* Jesli chcemy, aby '':nodoc:'' obowiazywal od wystapienia do wszystkiego ponizej (ale wewnatrz), na przyklad aby wszystkie metody ponizej byly ukrywane, korzystamy z '':nodoc: all''. Jesli inna klasa bedzie zdefiniowana ponizej, bedzie ona dolaczona do dokumentacji.
* Jesli nie chcemy, by czesc komentarzy byla przetwarzana przez RDoca, korzystamy z ''%%#++%%'' i ''%%#--%%'':
# I think that John is pretty
#--
# stupid fella.
#++
# and I really like him.
* RDoc nie przetwarza komentarzy wewnatrz metod.
* Za pomoca opcji linii komend, mozna wyeksportowac dokumentacje do roznych formatow (xml, yaml, chm, pdf): ''--fmt ''
====== 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 `'
* Mozna tworzyc wlasne wyjatki. Robi sie to gdyz, dzieki temu mozna roznie radzic sobie z roznymi wyjatkami.
* Wyjatki obsluguje sie za pomoca slowa ''rescue'' (oraz ''begin'' i ''end''):
# exceptions_02.rb
begin
puts 10 / 0
rescue
puts "You caused an error!"
end
* Podobnie obsluguje sie kilka wyjatkow:
# 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
* Poza obslugiwaniem, mozna tez otrzymywac wyjatki i ich uzywac:
# 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 `'
===== Catch i Throw =====
* Czasem fajnie by bylo moc przerwac wykonywanie jakiejs, na przyklad, petli podczas normalnej pracy w podobny sposob jak dzieje sie to z wyjatkami, ale bez faktycznego generowania bledu. Do tego wlasnie przydaja sie ''catch'' i ''throw''.
* Dzialaja z symbolami a nie z wyjatkami.
# 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 ======
* debuggera uruchamiamy za pomoca polecenia ''ruby -r debug debugfile.rb''. To przeniesie nas do konsoli podobnej do ''irb'':
Debug.rb
Emacs support available.
debugtest.rb:3:i = 1
(rdb:1)
* Za pomoca specjalnych polecen, mozna wykonywac program linia po linii, lub ustawic szereg breakpointow.
* Polecenie ''list'' wyswietla linie programu, nad ktorymi aktualnie pracuje:
(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
* ''step'' przechodzi do nastepnej linii programu,
* ''cont'' wykonuje caly program bez krokow,
* ''break'' ustawia breakpoint w pewnej linii: ''break 3'' ustawia breakpoint na linii 3,
* ''watch'' ustawia warunkowy breakpoint, zamiast linii wykorzystujemy tu jakis warunek: ''watch x > 10'',
* ''quit'' wychodzi z debuggera,
* wpisujac nazwe zmiennej, wyswietlamy jej zawartosc.
====== Testowanie ======
* Test-driven development - najpierw testy, pozniej kod, iteracyjnie.
# 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 `': Fail 3 (RuntimeError)
===== Testy jednostkowe =====
* Mozna testowac jak wczesniej, ale kiedy liczba testow rosnie, brak osobnego miejsca na testy i robi sie balagan.
* ''Test::Unit'' to biblioteka Rubego do testow jednostkowych. Polegaja one na testowaniu kazdej indywidualnej jednostki funkcjonalnosci w programie lub systemie.
* ''Test::Unit'' udostepnia ustandaryzowany framework dla pisania i wykonywania testow.
# 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()'' | |
| ''assert_equal(expected, actual)'' | |
| ''assert_not_equal(expected, actual)'' | |
| ''assert_raise(exception_type, ..) { }'' | |
| ''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.
* ''measure'' przyjac moze rowniez blok kodu.
* gdy pomiarow jest wiecej, poradzic sobie mozemy za pomoca metody ''bm'':
# 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)
* ''bmbm(10)'' przeprowadza test dziesięciokrotnie (w pewnych sytuacjach, CPU caching, memory caching i inne moga przeklamac wyniki).
===== 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
* W odroznieniu do benchmarkow, profiling powaznie obciaza szybkosc dzialania programu. Pomimo tego, spowolnienie jest spojne, a wyniki pewne.
* Pierwsza kolumna to procent czasu spedzonego w danej metodzie. Druga kolumna pokazuje ten sam parametr jako czas w sekundach. Trzecia to liczba wywolan danej metody.
* Aby profilowac tylko pewna czesc kodu, mozna skorzystac z biblioteki ''profiler''. Sekcje profilowana ustawiamy wtedy za pomoca polecen ''%%Profiler__.start_profile%%'' i ''%%Profiler__.stop_profile%%''. ''%%Profiler__.print_profile($stdout)%%'' w odpowiednich miejscach.
==== 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 ====
* Przy konstruowaniu złożonych warunków, na początku warto dać najszybszą metodę,
* Algorytmy iteracyjne szybsze są od rekurencyjnych.