catch
и throw
Каква е разликата между require './foo'
и require_relative 'foo'
?
require './foo' # Релативно спрямо Dir.pwd
require_relative 'foo' # Релативно спрямо __FILE__
"Текущата директория" на процеса може да се променя както при стартиране, така и по време на изпълнение на програмата (с Dir.chdir
) и е различна от директорията, в която се намира Ruby файла, изпълняващ require метода:
cd /home
ruby /foo/bar/baz.rb # Dir.pwd ще е: /home
Каква е разликата между require
и load
?
require
изпълнява зареждания файл само веднъж, без значение колко пъти сме извикали require
, докато load
го изпълнява при всяко изикване на load
.
Кой е родителският клас на класа StandardError
?
Exception
.
Кой клас стои най-горе в йерархията на всички изключения (като не броим Object
)?
Exception
.
От какъв тип са изключенията, хвърлени така: raise 'foo'
?
RuntimeError
.
Как ще се оцени следният израз?
begin
raise 'oh noes!'
rescue Exception
'A general exception has occurred.'
rescue RuntimeError
'A standard error has occurred.'
end
На низа "A general exception has occurred."
. Изключението се хваща от първия rescue
, който споменава клас exception_class
, за който е вярно, че exception.is_a?(exception_class)
.
puts
с нула или повече аргумента
puts :foo, [:bar, :baz]
ще изведе foo
, bar
и baz
на три отделни редаcaller
връща списък с низове, които показват къде сме в текущия call стек
# inception.rb:
def roll_the_ball() go_deep end
def go_deep() we_need_to_go_deeper end
def we_need_to_go_deeper() even_deeper_than_that end
def even_deeper_than_that() puts(caller) end
roll_the_ball
Изпълняваме го с ruby inception.rb
.
Примерът от предния слайд ще продуцира:
inception.rb:3:in `we_need_to_go_deeper' inception.rb:2:in `go_deep' inception.rb:1:in `roll_the_ball' inception.rb:6:in `<main>'
> puts caller .../irb/workspace.rb:86:in `eval' .../irb/workspace.rb:86:in `evaluate' .../irb/context.rb:380:in `evaluate' .../irb.rb:492:in `block (2 levels) in eval_input' .../irb.rb:624:in `signal_status' .../irb.rb:489:in `block in eval_input' .../irb/ruby-lex.rb:247:in `block (2 levels) in each_top_level_statement' .../irb/ruby-lex.rb:233:in `loop' .../irb/ruby-lex.rb:233:in `block in each_top_level_statement' .../irb/ruby-lex.rb:232:in `catch' .../irb/ruby-lex.rb:232:in `each_top_level_statement' .../irb.rb:488:in `eval_input' .../irb.rb:397:in `block in start' .../irb.rb:396:in `catch' .../irb.rb:396:in `start' .../bin/irb:11:in `' => nil
Изключенията в Ruby са обекти като всичко останало – инстанции на клас Exception
или негов наследник.
Имат три основни атрибута:
KeyError
, RuntimeError
, NoFriendsException
– за автоматична обработка
Нека имаме инстанция на изключение в променлива error
. Тогава:
error.class
(както и всеки друг Ruby обект)
error.message
error.backtrace
Exception
foo # NameError: undefined local variable or method `foo' for main:Object
1 / 0 # ZeroDivisionError: divided by 1
File.open # ArgumentError: wrong number of arguments (0 for 1..3)
File.open('/Ah?') # Errno::ENOENT: No such file or directory @ rb_sysopen - /Ah?
Между другото, Errno::ENOENT
си е нормално изключение:
Errno::ENOENT.ancestors.take_while { |kind| kind != Object }
# => [Errno::ENOENT, SystemCallError, StandardError, Exception]
За да си направим клас-изключение, обикновено наследяваме от RuntimeError
или StandardError
:
class NoFriendsError < StandardError
end
Exception
Може да разделим изключенията на два основни вида.
rescue
от нас или от ползвателите на нашия код
Exception
или да обгръщате огромни части от програмата си с begin ... rescue Exception
It is recommended that a library should have one subclass of StandardError or RuntimeError and have specific exception types inherit from it. This allows the user to rescue a generic exception type to catch all exceptions the library may raise even if future versions of the library add new exception subclasses.
RuntimeError
class Skeptic::Error < RuntimeError; end
ArgumentError
, NotImplementedError
и прочее)
Въпроси дотук?
raise
и rescue
def go_deep
go_deeper
end
def go_deeper
throw :some_label, 'the value'
raise 'This line WILL NOT execute.'
end
return_value = catch :some_label do
go_deep
end
return_value == 'the value' # => true
catch
приема символ и блок.
throw
със същия символ.
catch
.
catch
-а.
throw
взема допълнителен аргумент, който е върнатата стойност на catch
.
throw
, стойността на catch
е последния оценен израз.catch(:baba) do
throw :baba, 'Penka'
'Ginka'
end # => "Penka"
catch(:baba) { 'Ginka' } # => "Ginka"
throw :baba, 'Penka' # error: UncaughtThrowError
Взет от Vagrant:
found = catch(:done) do
[modified, added, removed].each do |changed|
changed.each do |listenpath|
throw :done, true if listenpath.start_with?(hostpath)
end
end
# Make sure to return false if all else fails so that we
# don't sync to this machine.
false
end
Предния пример аз бих го направил така:
found =
[modified, added, removed].any? do |changed_paths|
changed_paths.any? { |path| path.start_with? host_path }
end
Или дори така:
changed_paths = [modified, added, removed].flatten
found = changed_paths.any? { |path| path.start_with? host_path }
Взет от Discourse (и леко модифициран):
def get_excerpt(html)
parser = Nokogiri::HTML::SAX::Parser.new(self)
catch(:found_excerpt) do
parser.parse(html)
end
end
def some_parser_hooks
# Make checks and gather info...
# Found our thing!
excerpt = 'something'
throw :found_excerpt, excerpt
end
Следва кратка вметка за методи и след това Fibers.
def an_example_of_great_method_design(
a,
_,
_,
b,
c = :something,
*splat,
d,
(e, f),
keyword:,
with_default: 'value',
**other_keyword_args,
&some_block
)
end
options = {}
Като споменахме Санди...
I'm the author of Practical Object-Oriented Design in Ruby (POODR). I believe in simple code and straightforward explanations. I want to help you transform your code and make the pain go away.
Кодът по-долу няма да приключи никога:
class FibonacciNumbers
def each
current, previous = 1, 0
while true
yield current
current, previous = current + previous, current
end
end
end
FibonacciNumbers.new.each { |number| puts number }
Кодът по-долу ще работи:
class FibonacciNumbers
def each
current, previous = 1, 0
while true
yield current
current, previous = current + previous, current
end
end
end
FibonacciNumbers.new.to_enum.take(5) # => [1, 1, 2, 3, 5]
Fiber.new
, Fiber.yield
, Fiber#resume
require 'fiber'
: Fiber.current
, #alive?
и #transfer
Най-простият възможен пример:
fiber = Fiber.new { :larodi }
fiber.resume # => :larodi
fiber.resume # => error: FiberError
Обратно на нашия пример от преди малко:
class FibonacciNumbers
def each
current, previous = 1, 0
while true
yield current
current, previous = current + previous, current
end
end
end
Заменяйки yield
с Fiber.yield
, правим безкраен поток:
class FibonacciNumbers
def each
current, previous = 1, 0
while true
Fiber.yield current
current, previous = current + previous, current
end
end
end
fibonacci_stream = Fiber.new { FibonacciNumbers.new.each }
fibonacci_stream.resume # => 1
fibonacci_stream.resume # => 1
fibonacci_stream.resume # => 2
fibonacci_stream.resume # => 3
Enumerator
класът в Ruby се възползва от Fiber
.
Това се случва, когато направите (1..100_000).each
.