08. Изключения. Замразени обекти. Класови методи

08. Изключения. Замразени обекти. Класови методи

08. Изключения. Замразени обекти. Класови методи

4 ноември 2013

Днес

Ново предизвикателство

Класови методи

Класови методи

преговор

class TodoList
  def TodoList.parse(todos_string)
    # Solution lost.
  end
end

Класови методи

втори начин

Този начин е еквивалентен на предишния слайд:

class TodoList
  def self.parse(todos_string)
    # Solution lost.
  end
end

Това работи, понеже:

Класови методи

трети начин

class TodoList
  class << self
    def parse(todos_string)
      # Solution lost.
    end
  end
end

Класови методи

конвенции

Въпроси дотук?

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

Замразяване на обекти в Ruby

Замразяване на обекти

module Entities
  ENTITIES = {
    '&' => '&amp;',
    '"' => '&quot;',
    '<' => '&lt;',
    '>' => '&gt;',
  }.freeze

  ENTITY_PATTERN = /#{ENTITIES.keys.join('|')}/.freeze

  def escape(text)
    text.gsub ENTITY_PATTERN, ENTITIES
  end
end

Замразяване на низове

Тъй като низовете в Ruby са mutable, всяко срещане на низ = нов обект:

''.object_id                 # 548088440
''.object_id                 # 548088190
''.object_id == ''.object_id # false

Замразяване на низове

пример

class HTTP
  attr_reader :method

  def post?
    method == 'POST' # New string object on each call
  end
end

Замразяване на низове

пример за workaround

class HTTP
  attr_reader :method
  METHOD_POST = 'POST'.freeze

  def post?
    method == METHOD_POST
  end
end

Това е досадно. Затова в Ruby 2.1 има и по-добро решение.

Синтаксис за замразяване на низове

Въпроси дотук?

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

Изключения

Изключенията в 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

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

приоритет на 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

begin/end in all its glory!

begin
  get_a_life
rescue NoFriendsError => ex
  puts 'Goodbye cruel world'
rescue InsufficientVespeneGasError, NotEnoughMineralsError => ex
  puts 'I think I play too much StarCraft'
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    # "ingenting"

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.

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

Разсъждения

catch и throw

catch и throw

def iterate_pairs(hash)
  hash.values.each { |array| iterate_values array }
end

def iterate_values(array)
  array.each do |item|
    if item == 'Nemo'
      puts 'Found Nemo!'
      throw :done
    end
  end
end

animals = {cats: %w[Simba], fish: %w[Crispy Nemo], boars: %w[Pumba]}
catch(:done) { iterate_pairs(animals) }

Този пример е доста синтетичен.

catch и throw

накратко

Въпроси