KnigaRead.com/
KnigaRead.com » Компьютеры и Интернет » Программирование » Хэл Фултон - Программирование на языке Ruby

Хэл Фултон - Программирование на языке Ruby

На нашем сайте KnigaRead.com Вы можете абсолютно бесплатно читать книгу онлайн Хэл Фултон, "Программирование на языке Ruby" бесплатно, без регистрации.
Перейти на страницу:

В качестве примера рассмотрим многоцелевой итератор, который имитирует цикл с проверкой условия в конце (как в конструкции do-while в С или repeat-until в Pascal):

def repeat(condition)

 yield

 retry if not condition

end

В этом примере ключевое слово yield служит для вызова блока, который задается при таком вызове итератора:

j=0

repeat (j >= 10) do

 j += 1

 puts j

end

С помощью yield можно также передать параметры, которые будут подставлены в список параметров блока (между вертикальными черточками). В следующем искусственном примере итератор всего лишь генерирует целые числа от 1 до 10, а вызов итератора порождает кубические степени этих чисел:

def my_sequence

 for i in 1..10 do

  yield i

 end

end


my_sequence {|x| puts x**3 }

Отметим, что вместо фигурных скобок, в которые заключен блок, можно написать ключевые слова do и end. Различия между этими формами есть, но довольно тонкие.

1.2.7. Исключения

Как и многие другие современные языки, Ruby поддерживает исключения.

Исключения — это механизм обработки ошибок, имеющий существенные преимущества по сравнения с прежними подходами. Нам удается избежать возврата кодов ошибок и запутанной логики их анализа, а код, который обнаруживает ошибку, можно отделить от кода, который ее обрабатывает (чаще всего они так или иначе разделены).

Предложение raise возбуждает исключение. Отметим, что raise — не зарезервированное слово, а метод модуля Kernel. (У него есть синоним fail.)

raise                                               # Пример 1

raise "Произошла ошибка"                            # Пример 2

raise ArgumentError                                 # Пример 3

raise ArgumentError, "Неверные данные"              # Пример 4

raise ArgumentError.new("Неверные данные ")         # Пример 5

raise ArgumentError, " Неверные данные ", caller[0] # Пример 6

В первом примере повторно возбуждается последнее встретившееся исключение. В примере 2 создается исключение RuntimeError (подразумеваемый тип), которому передается сообщение "Произошла ошибка".

В примере 3 возбуждается исключение типа ArgumentError, а в примере 4 такое же исключение, но с сообщением "Неверные данные". Пример 5 — просто другая запись примера 4. Наконец, в примере 6 еще добавляется трассировочная информация вида "filename:line" или "filename:line:in 'method'" (хранящаяся в массиве caller).

А как обрабатываются исключения в Ruby? Для этой цели служит блок begin-end. В простейшей форме внутри него нет ничего, кроме кода:

begin

 #Ничего полезного.

 #...

end

Просто перехватывать ошибки не очень осмысленно. Но у блока может быть один или несколько обработчиков rescue. Если произойдет ошибка в любой точке программы между begin и rescue, то управление сразу будет передано в подходящий обработчик rescue.

begin

 x = Math.sqrt(y/z)

 # ...

rescue ArgumentError

 puts "Ошибка при извлечении квадратного корня."

rescue ZeroDivisionError

 puts "Попытка деления на нуль."

end

Того же эффекта можно достичь следующим образом:

begin

 x = Math.sqrt(y/z)

 # ...

rescue => err

 puts err

end

Здесь в переменной err хранится объект-исключение; при выводе ее на печать объект будет преобразован в осмысленную символьную строку. Отметим, что коль скоро тип ошибки не указан, то этот обработчик rescue будет перехватывать все исключения, производные от класса StandardError. В конструкции rescue => variable можно перед символом => дополнительно указать тип ошибки.

Если типы ошибок указаны, то может случиться так, что тип реально возникшего исключения не совпадает ни с одним из них. На этот случай после всех обработчиков rescue разрешается поместить ветвь else.

begin

 # Код, в котором может возникнуть ошибка...

rescue Type1

 # ...

rescue Type2

 # ...

else

 #Прочие исключения...

end

Часто мы хотим каким-то образом восстановиться после ошибки. В этом поможет ключевое слово retry (внутри тела обработчика rescue). Оно позволяет повторно войти в блок begin и попытаться еще раз выполнить операцию:

begin

 # Код, в котором может возникнуть ошибка...

rescue

 # Пытаемся восстановиться...

 retry # Попробуем еще раз.

end

Наконец, иногда необходим код, который «подчищает» что-то после выполнения блока begin-end. В этом случае можно добавить часть ensure:

begin

 # Код, в котором может возникнуть ошибка...

rescue

 # Обработка исключений.

ensure

 # Этот код выполняется в любом случае.

end

Код, помещенный внутрь части ensure, выполняется при любом способе выхода из блока begin-end — вне зависимости от того, произошло исключение или нет.

Исключения можно перехватывать еще двумя способами. Во-первых, существует форма rescue в виде модификатора:

x = a/b rescue puts("Деление на нуль!")

Кроме того, тело определения метода представляет собой неявный блок begin-end; слово begin опущено, а все тело метода подготовлено к обработке исключения и завершается словом end:

def some_method

 # Код...

rescue

 # Восстановление после ошибки...

end

На этом мы завершаем как обсуждение обработки исключений, так и рассмотрение основ синтаксиса и семантики в целом.

У Ruby есть многочисленные аспекты, которых мы не коснулись. Оставшаяся часть главы посвящена более развитым возможностям языка, в том числе рассмотрению ряда практических приемов, которые помогут программисту среднего уровня научиться «думать на Ruby».

1.3. ООП в Ruby

В языке Ruby есть все элементы, которые принято ассоциировать с объектно-ориентированными языками: объекты с инкапсуляцией и сокрытием данных, методы с полиморфизмом и переопределением, классы с иерархией и наследованием. Но Ruby идет дальше, добавляя ограниченные возможности создания метаклассов, синглетные методы, модули и классы-примеси.

Похожие идеи, только под иными именами, встречаются и в других объектно-ориентированных языках, но одно и то же название может скрывать тонкие различия. В этом разделе мы уточним, что в Ruby понимается под каждым из элементов ООП.

1.3.1. Объекты

В Ruby все числа, строки, массивы, регулярные выражения и многие другие сущности фактически являются объектами. Работа программы состоит в вызове методов разных объектов:

3.succ                # 4

"abc".upcase          # "ABC"

[2,1,5,3,4].sort      # [1,2,3,4,5]

someObject.someMethod # какой-то результат

В Ruby каждый объект представляет собой экземпляр какого-то класса. Класс содержит реализацию методов:

"abc".class       # String

"abc".class.class # Class

Помимо инкапсуляции собственных атрибутов и операций объект в Ruby имеет уникальный идентификатор:

"abc".object_id # 53744407

Этот идентификатор объекта обычно не представляет интереса для программиста.

1.3.2. Встроенные классы

Свыше 30 классов уже встроено в Ruby. Как и во многих других объектно-ориентированных языках, в нем не допускается множественное наследование, но это еще не означает, что язык стал менее выразительным. Современные языки часто построены согласно модели одиночного наследования. Ruby поддерживает модули и классы-примеси, которые мы обсудим в следующей главе. Также реализованы идентификаторы объектов, что позволяет строить устойчивые, распределенные и перемещаемые объекты.

Для создания объекта существующего класса обычно используется метод new:

myFile = File.new("textfile.txt","w")

myString = String.new("Это строковый объект")

Однако не всегда его обязательно вызывать явно. В частности, при создании объекта String можно и не упоминать этот метод:

yourString = "Это тоже строковый объект"

aNumber =5 # и здесь метод new не нужен

Ссылки на объекты хранятся в переменных. Выше уже отмечалось, что сами переменные не имеют типа и не являются объектами — они лишь ссылаются на объекты.

x = "abc"

Из этого правила есть исключение: небольшие неизменяемые объекты некоторых встроенных классов, например Fixnum, непосредственно копируются в переменные, которые на них ссылаются. (Размер этих объектов не превышает размера указателя, поэтому хранить их таким образом более эффективно.) В таком случае во время присваивания делается копия объекта, а куча не используется.

Перейти на страницу:
Прокомментировать
Подтвердите что вы не робот:*