catch
и throw
SUGAR TIME!
Какви са съответните стойности тук и защо?
'text'.object_id == 'text'.object_id # => ???
'text'.freeze.object_id == 'text'.object_id # => ???
'text'.freeze.object_id == 'text'.freeze.object_id # => ???
(foo = 'text').freeze.object_id == 'text'.freeze.object_id # => ???
Какви са съответните стойности тук и защо?
'text'.object_id == 'text'.object_id # => false
'text'.freeze.object_id == 'text'.object_id # => false
'text'.freeze.object_id == 'text'.freeze.object_id # => true
(foo = 'text').freeze.object_id == 'text'.freeze.object_id # => false
'text'.freeze
се оптимизира от Ruby и връща един и същи обект
Какво ще се случи при изпълнение на следния код (и защо):
case 42
when String then 'This is a string'
when :odd?.to_proc then 'This is an odd number'
when :even?.to_proc then 'This is an even number'
else 'I have no f*cking idea what this is.'
end
case
използва "оператора" ===
, за да оценява различните случаи.
when
се оценява така: condition === testable
.
Proc
имплементира ===
като извиква себе си с подадения на ===
аргумент.Как създаваме анонимни класове? А модули?
anonymous_class = Class.new
anonymous_module = Module.new
Избройте поне 3 начина за дефиниране на класови методи.
Какви са конвенциите при използване на тези начини?
def ClassName.method_name(); end
- не се използва
def self.method_name(); end
- при дефиниране на един, максимум два класови метода
class << self
- при дефиниране на повече от един класов методКакво правят `send` и `public_send`? Каква е разликата между тях?
send
може да извиква произволни методи на даден обект (дори private
или protected
)
public_send
може да извиква само публични методи на даден обект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
Сега е моментът да ги зададете :-)
Представете си, че пишете на C:
char source[1000000];
FILE* file = fopen("text-file.txt", "r");
if (file != NULL) {
while ((symbol = getc(file)) != EOF) {
strcat(source, &symbol);
}
fclose(file);
} else {
// ERROR
}
fopen
връща указател към FILE
или NULL
ако има грешка
getc
връща прочетен символ или EOF
(специална константа)
Представете си, че пишете качествен код на C:
char *source = NULL;
FILE *fp = fopen("foo.txt", "r");
if (fp != NULL) {
// Go to the end of the file
if (fseek(fp, 0L, SEEK_END) == 0) {
// Get the size of the file
long bufsize = ftell(fp);
if (bufsize == -1) { /* ERROR */ }
// Allocate our buffer to that size
source = malloc(sizeof(char) * (bufsize + 1));
if (source == NULL) { /* ERROR */ }
// Go back to the start of the file
if (fseek(fp, 0L, SEEK_SET) != 0) { /* ERROR */ }
// Read the entire file into memory
size_t newLen = fread(source, sizeof(char), bufsize, fp);
if (ferror(fp) != 0) { /* ERROR */ }
else { source[newLen++] = '\0'; }
} else { /* ERROR */ }
} else { /* ERROR */ }
fopen
връща NULL
при грешка
fseek
връща 0
при успех, нещо различно от 0
при грешка
ftell
връща -1
при грешка
malloc
връща NULL
при грешка
fread
не сигнализира за грешки
ferror
ви казва дали е имало грешка (в случая във fread
). Връща 0 ако нямало грешки
Представете си, че пишете на NodeJS:
function liveADay(callback) {
getUpInTheMorning(function(time) {
brushYourTeeth(function() {
goToFMI(function() {
sleepInClass(function() {
goHome(function() {
liveADay(callback);
});
});
});
});
});
}
function liveADay(callback) {
getUpInTheMorning(function(err, time) {
if (err) {
callback(err); return; // err = 'too early in the morning'
}
goToFMI(function() {
if (err) {
callback(err); return; // err = {error: 'It is Saturday'}
}
sleepInClass(function(err) {
if (err) {
callback(err); return; // err = TeacherGivesSnickersError
}
goHome(function() {
if (err) {
callback(err); return; // err = {code: ENOENT, errno: -2}
}
liveADay(callback);
});
});
});
});
}
Връщане на nil
понякога е напълно приемливо. Например:
def find(collection, expected_element)
collection.each do |element|
return element if element == expected_element
end
nil
end
Изключенията в Ruby са обекти като всичко останало – инстанции на клас Exception
или негов наследник.
Имат три основни атрибута:
KeyError
, RuntimeError
, NoMethodError
– за автоматична обработка
Нека имаме инстанция на изключение в променлива 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]
Object +-- Exception +-- NoMemoryError +-- ScriptError | +-- SyntaxError | +-- LoadError +-- SecurityError +-- StandardError +-- ArgumentError +-- IndexError | +-- KeyError | +-- StopIteration +-- NameError | +-- NoMethodError +-- RuntimeError +-- TypeError
За да си направим клас-изключение, обикновено наследяваме от RuntimeError
или StandardError
:
class NoFriendsError < StandardError; end
Exception
# Предизвиква RuntimeError
raise "'Prophet!' said I, 'Thing of evil!" # => error: RuntimeError
# Като горното, но с различен текст
raise RuntimeError, 'prophet still, if bird or devil!' # => error: RuntimeError
# Друг начин да предизвикаме RuntimeError
raise RuntimeError.new('Whether tempter sent, or whether...') # => error: RuntimeError
begin
puts '...tempest tossed thee here ashore'
raise NameError, 'Desolate yet all undaunted'
rescue => ex
ex.message # => "Desolate yet all undaunted"
ex.class # => NameError
end
begin
raise KeyError, 'on this desert land enchanted'
rescue ArgumentError => ex
puts 'on this home by horror haunted'
rescue KeyError, TypeError => ex
ex.message # => "on this desert land enchanted"
ex.class # => KeyError
end
rescue
хваща "само" наследници на StandardError
, ако не сме указали друго:
Object +-- Exception +-- NoMemoryError +-- ScriptError +-- StandardError +-- ArgumentError +-- NameError | +-- NoMethodError +-- RuntimeError
begin
raise KeyError, 'tell me truly, I implore'
rescue IndexError => ex
puts 'IndexError'
rescue KeyError => ex
puts 'KeyError'
end
Ще се изведе 'IndexError'
Object +-- Exception +-- NoMemoryError +-- ScriptError | +-- SyntaxError | +-- LoadError +-- SecurityError +-- StandardError +-- ArgumentError +-- IndexError | +-- KeyError | +-- StopIteration +-- NameError | +-- NoMethodError +-- RuntimeError +-- TypeError
Припомняне KeyError < IndexError
$eh = 'foo'
begin
raise KeyError, 'Is there - is there balm in Gilead?'
rescue IndexError => ex
$eh = 'index'
rescue KeyError => ex
$eh = 'key'
end
$eh # => "index"
Изпълнява се първия rescue
, за който изключението е kind_of?
типа.
Динамичните езици обикновено ползват прости правила
Кодът в ensure
клаузата се изпълнява винаги.
begin
raise 'tell me - tell me, I implore!' if rand(2).zero?
ensure
puts '????? ??? ?????, "?????????"'
end
rand(2).zero?
връща true
или false
, 50 на 50.else
клаузата се изпълнява когато няма възникнало изключение.
begin
launch_nukes
rescue
puts 'Uh-oh! Something went wrong :('
else
puts 'War... War never changes'
end
launch_nukes
няма удивителна.
begin
get_a_life
rescue NoFriendsError => ex
puts 'Goodbye cruel world'
rescue InsufficientVespeneGasError, NotEnoughMineralsError
puts 'show me the money'
rescue
puts ';('
else
puts 'Woohoo!'
ensure
puts 'rm -rf ~/.history'
end
Сега е моментът да ги зададете :-)
В случай, че ползвате rescue
в метод така:
def execute
begin
potentially_dangerous
rescue SomeException => e
# Handle the error
ensure
# Ensure something always happens
end
end
По-добре е да го запишете без begin
/end
, което е еквивалентно на предното:
def execute
potentially_dangerous
rescue SomeException => e
# Handle the error
ensure
# Ensure something always happens
end
Ако възникне изключение при обработка на друго, старото се игнорира и се "вдига" новото.
begin
raise KeyError
rescue
raise TypeError
puts "I'm a line of code, that's never executed ;("
end
raise
в rescue
клауза "вдига" същото
изключение, което се обработва.
begin
raise KeyError, 'But high she shoots through air and light'
rescue
puts 'Whoops'
raise
end
result = begin
raise KeyError if rand(3).zero?
raise NameError if rand(3).zero?
rescue KeyError
'nyckel'
rescue NameError
'namn'
else
'ingenting'
end
result # => "ingenting"
rescue
може да се ползва като модификатор.StandardError
.[].fetch(1) rescue 'baba' # => "baba"
raise type, message
всъщност извиква type.exception(message)
,
за да конструира изключение.
class Thing
def exception(message)
NameError.new(message)
end
end
thing = Thing.new
raise thing, 'whoops' # => error: NameError
Сега е моментът да ги зададете :-)
Може да разделим изключенията на два основни вида.
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
(или StandardError
)
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