08. Изключения. require. gems.

08. Изключения. require. gems.

08. Изключения. require. gems.

4 ноември 2015

Днес

OpenFest

Планина

Четвърта задача

Скорост на напредване

Въпрос 1

Какво ще се случи при изпълнение на следния код (и защо):

class Proc
  def ===(testable)
    call testable
  end
end

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.

Въпрос 2

Какъв ще бъде резултатът от изпълнението на следния код:

class Sequence
  def generate
    yield
    yield 'hello'
    yield [1, 2]
  end
end

Sequence.new.to_enum.count # => ?

Грешка – недефиниран метод each.

Object#to_enum е еквивалентно на Object#enum_for(:each).

Правилният код тук би бил Sequence.new.enum_for(:generate).count. Щеше да върне 3.

Въпрос 3

Какъв ще бъде резултатът от изпълнението на следния код:

numbers = {one: 'eins', two: 'zwei'}
numbers.each.with_index.to_a.flatten.size # => ?

6.

След to_a ще имаме списък с два елемента: [[[:one, "eins"], 0], [[:two, "zwei"], 1]].

Въпрос 4

Какъв ще бъде резултатът от изпълнението на следния код:

numbers = {one: 'eins', two: 'zwei'}.freeze
numbers[:one].upcase!

Няма да има грешка.

Хешът numbers реално не се променя. Мутира се само обектът, към който сочи ключа :one.

За да се предпазим от това, би могло да напишем numbers.each { |_, number| number.freeze }.

Въпрос 5

Какъв ще бъде резултатът от изпълнението на следния код:

x = 0
0.upto(100).lazy.map { x += 1 }.map { x += 1 }.take(2)

x # => ?

0. Причината е, че lazy последователността все още не е оценена.

Може да я оценим с to_a след take(). Тогава x щеше да е 4.

Изключения

Изключенията в Ruby

Непълна йерархия

Object
+-- Exception
   +-- NoMemoryError
   +-- ScriptError
   |   +-- SyntaxError
   |   +-- LoadError
   +-- SecurityError
   +-- StandardError
       +-- ArgumentError
       +-- IndexError
       |   +-- KeyError
       |   +-- StopIteration
       +-- NameError
       |   +-- NoMethodError
       +-- RuntimeError
       +-- TypeError 

После ще видим пълната.

Предизвикване на изключения

# Предизвиква 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?

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

Йерархията отново

Object
+-- Exception
   +-- NoMemoryError
   +-- ScriptError
   |   +-- SyntaxError
   |   +-- LoadError
   +-- SecurityError
   +-- StandardError
       +-- ArgumentError
       +-- IndexError
       |   +-- KeyError
       |   +-- StopIteration
       +-- NameError
       |   +-- NoMethodError
       +-- RuntimeError
       +-- TypeError 

Хващане на изключения

приоритет на rescue клаузите

Припомняне 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 клауза

Кодът в ensure клаузата се изпълнява винаги.

begin
  raise 'tell me - tell me, I implore!' if rand(2).zero?
ensure
  puts '????? ??? ?????, "?????????"'
end

Хващане на изключения

else клауза

else клаузата се изпълнява когато няма възникнало изключение.

begin
  launch_nukes
rescue
  puts 'Uh-oh! Something went wrong :('
else
  puts 'War... War never changes'
end

The beginning and the end

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 в метод

В случай, че ползвате rescue в метод така:

def execute
  begin
    potentially_dangerous
  rescue SomeException => e
    # Handle the error
  ensure
    # Ensure something always happens
  end
end

rescue в метод

Предпочитания вариант

По-добре е да го запишете без 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

raise в rescue клауза "вдига" същото изключение, което се обработва.

begin
  raise KeyError, 'But high she shoots through air and light'
rescue
  puts 'Whoops'
  raise
end

begin/end

...е израз, като всичко друго в ruby

result = begin
  raise KeyError if rand(3).zero?
  raise NameError if rand(3).zero?
rescue KeyError
  'nyckel'
rescue NameError
  'namn'
else
  'ingenting'
end

result    # => "nyckel"

rescue като модификатор

[].fetch(1) rescue 'baba' # => "baba"

Exception#exception

raise type, message всъщност извиква type.exception(message) за да конструира изключение.

class Thing
  def exception(message)
    NameError.new(message)
  end
end

thing = Thing.new
raise thing, 'whoops' # => error: NameError

Как да ползваме изключения

Може да разделим изключенията на два вида.

За първите обикновено създаваме клас. За вторите обикновено ползваме raise.

Кога ползваме изключения

Разсъждения

Въпроси за изключенията?

Имате ли въпроси по механизма за изключения в Ruby?

Импортиране на файлове

В Ruby, код от други файлове се импортира с require.

Например:

require 'bigdecimal'
require 'bigdecimal/util'

Какво търси require?

Текуща директория на процес

лирическо отклонение

Всеки процес си има "текуща директория". По подразбиране наследява това от процеса, който го стартира и може да се променя.

Зареждане на файлове от текущата директория

Зареждане на файлове от текущата директория

require_relative

Load path

където Ruby търси файлове за require

Как работи require?

Библиотеките в Ruby

Типичната структура на един gem

skeptic опростен

.
├── README.rdoc
├── Rakefile
├── bin
│   └── skeptic
├── features
├── lib
│   ├── skeptic
│   │   ├── rules.rb
│   │   └── scope.rb
│   └── skeptic.rb
├── skeptic.gemspec
└── spec 

Особеностите

Останалите неща

Kernel#load

Въпроси