КулЛиб - Скачать fb2 - Читать онлайн - Отзывы  

Head First. Изучаем Ruby (pdf)

Книга в формате pdf! Изображения и текст могут не отображаться!


Настройки текста:



Head First
Ruby
Wouldn’t it be dreamy if
there were a book on Ruby that
didn’t throw blocks, modules, and
exceptions at you all at once? I
guess it’s just a fantasy…

Jay McGavren

Boston

Head First
Изучаем Ruby
Хорошо бы найти книгу по Ruby,
которая бы не обрушивала на
читателя все подряд — блоки,
модули, исключения... Как жаль,
что это всего лишь мечты…

Джей Макгаврен

2016

Дж. Макгаврен
Head First. Изучаем Ruby
Серия «Head First O’Reilly»
Перевел с английского Е. Матвеев
Заведующая редакцией
Ведущий редактор
Художник
Корректор
Верстка

Ю. Сергиенко
Н. Римицан
С. Заматевская
С. Беляева
Н. Лукьянова

ББК 32.988.0-018
УДК 004.738.5

Макгаврен Дж.
М15

Head First. Изучаем Ruby. — СПб.: Питер, 2016. — 528 с.: ил. — (Серия «Head First O’Reilly»).
ISBN 978-5-496-02278-1

Вам интересно, почему буквально все вокруг заговорили о языке Ruby? Спросите себя прямо: вам нравится работать эффективно? Неужели
многочисленные компиляторы, библиотеки, классы, которыми грузят вас другие языки программирования, приближают вас к решению конкретной
задачи, восхищению коллег и толпе счастливых заказчиков? Вы хотите, чтобы язык программирования занимался техническими подробностями
вместо вас? Тогда бросайте рутинную работу и приступайте к решению конкретных задач, а язык Ruby сделает за вас все остальное.
Как и все книги серии Head First, книга «Изучаем Ruby» использует активный подход к обучению, выходя за рамки сухих, абстрактных
объяснений и справочников. Вас не только научат языку Ruby, но и помогут вашей программистской звезде ярко воссиять на небосклоне. Вы
освоите основы языка и продвинутые возможности Ruby, такие как блоки, объекты, методы, классы и регулярные выражения. С улучшением
ваших навыков задачи будут усложняться, и вы перейдете к таким темам, как обработка исключений, модули, подмешанные классы и метапрограммирование.

12+ (В соответствии с Федеральным законом от 29 декабря 2010 г. № 436-ФЗ.)
ISBN 978-1449372651 англ.
ISBN 978-5-496-02278-1

© 2016 Piter
Authorized Russian translation of the English edition of Head First Ruby,
ISBN 9781449372651 © 2016 Jay McGavren
This translation is published and sold by permission of O’Reilly Media, Inc., which owns
or controls all rights to publish and sell the same.
© Перевод на русский язык ООО Издательство «Питер», 2016
© Издание на русском языке, оформление ООО Издательство «Питер», 2016
© Серия «Head First O’Reilly», 2016

Права на издание получены по соглашению с O’Reilly. Все права защищены. Никакая часть данной книги не может быть воспроизведена в какой
бы то ни было форме без письменного разрешения владельцев авторских прав.
Информация, содержащаяся в данной книге, получена из источников, рассматриваемых издательством как надежные. Тем не менее, имея в виду
возможные человеческие или технические ошибки, издательство не может гарантировать абсолютную точность и полноту приводимых сведений
и не несет ответственности за возможные ошибки, связанные с использованием книги.

ООО «Питер Пресс», 192102, Санкт-Петербург, ул. Андреевская (д. Волкова), 3, литер А, пом. 7Н.
Налоговая льгота — общероссийский классификатор продукции ОК 034-2014, 58.11.12.000 —
Книги печатные профессиональные, технические и научные.
Подписано в печать 20.05.16. Формат 84×108/16. Бумага писчая. Усл. п. л. 55,440. Тираж 1000. Заказ 0000.
Отпечатано в соответствии с предоставленными материалами в ООО «ИПК Парето-Принт».
170546, Тверская область, Промышленная зона Боровлево-1, комплекс № 3А, www.pareto-print.

об авторе

Автор

Джей Макгаврен

Джей Макгаврен занимался автоматизацией деятельности ком-

пании, работающей в области гостиничного обслуживания, когда
коллега показал ему книгу Programming Perl (так называемая «книга
с верблюдом»). Джей мгновенно стал фанатом Perl, потому что ему
понравилось писать код, не дожидаясь, пока группа разработчиков из
10 человек настроит систему сборки. Заодно у него родилась безумная
идея когда-нибудь самому написать техническую книгу.

В 2007 году, когда развитие Perl зашло в тупик, Джей стал искать новый
интерпретируемый язык. Ruby победил — благодаря своей сильной
объектной ориентации, превосходной поддержке и невероятной гибкости. С тех пор он использовал Ruby в работе над двумя игровыми
библиотеками, в проекте в области искусства, а также занимался независимой разработкой с использованием Ruby on Rails. С 2011 года
он работал в области интернет-обучения разработчиков.
Вы можете читать Джея в Твиттере по адресу https://twitter.com/
jaymcgavren или посетить его персональный сайт http://jay.mcgavren.com.

дальше 4  5

содержание

Содержание (сводка)
1

Введение

23

Как сделать больше меньшими усилиями.
Программируйте так, как вам удобно

31

2

Методы и классы. Наводим порядок

3

Наследование. С родительской помощью

65
105

4

Инициализация экземпляров. Хороший старт — половина дела

137

5

Массивы и блоки. Лучше, цем цикл

185

6

Возвращаемые значения блоков. Как будем обрабатывать?

223

7

Хеши. Пометка данных

255

8

Ссылки. Путаница c сообщениями

287

9

Примеси. Аккуратный выбор

315

10

Comparable и enumerable. Готовые примеси

341

11

Документация. Читайте документацию

363

12

Исключения. Непредвиденное препятствие

389

13

Модульное тестирование. Контроль качества кода

419

14

Веб-приложения. На раздаче HTML

451

15

Сохранение и загрузка данных. Сбережения пользователя

485

Приложение. Десять основных тем (не рассмотренных в книге)

529

Содержание (настоящее)
Введение
Ваш мозг думает о Ruby. Вы сидите за книгой и пытаетесь чтонибудь выучить, но ваш мозг считает, что вся эта писанина не нужна.
Ваш мозг говорит: «Выгляни в окно! На свете есть более важные вещи,
например сноуборд». Как заставить мозг думать, что ваша жизнь действительно зависит от Ruby?

6

Для кого написана эта книга?

24

Мы знаем, о чем вы думаете

25

И мы знаем, о чем думает ваш мозг

25

Метапознание: наука о мышлении

27

Вот что сделали МЫ

28

Примите к сведению

30

содержание

1

Как сделать больше меньшими усилиями
Программируйте так, как вам удобно
Вас интересует, почему вокруг столько говорят о языке Ruby
и подойдет ли он вам? Ответьте на простой вопрос: вам нравится работать эффективно? Кажется ли вам, что с многочисленными компиляторами, библиотеками,
файлами классов и комбинациями клавиш других языков вы действительно становитесь
ближе к готовому продукту, восхищению коллег и толпе счастливых заказчиков?
Вы хотите, чтобы язык программирования занимался техническими подробностями
за вас? Если вам хочется перестать писать рутинный код и работать над задачей,
то язык Ruby — для вас. Ruby позволит вам сделать больше с меньшим объемом кода.

Исходный код

Философия Ruby

32

Интерактивное использование Ruby

35

Ваши первые выражения Ruby

36

Математические операторы и сравнения

36

Строки

36

Переменные

37

Вызов метода для объекта

38

Ввод, сохранение и вывод

42

Запуск сценариев

43

Комментарии

44

Аргументы методов

45

Интерполяция строк

46

Анализ объектов методами «inspect» и «p»

48

Служебные последовательности в строках

49

Вызов «chomp» для объекта строки

50

Генерирование случайного числа

52

Преобразование числа в строку

53

Преобразование строк в числа

55

Условные команды

56

«unless» как противоположность «if»

59

Циклы

60

Попробуем запустить игру!

63

Ваш инструментарий Ruby

my_program.rb

Интерпретатор Ruby

Компь
ютер
выполн
яет ваш
у
програм
му.

64

7

содержание

2

Методы и классы
Наводим порядок
В вашей работе кое-чего не хватало.Да, вы вызывали методы и создавали
объекты, как настоящий профессионал. Но при этом вы могли вызывать только те методы
и создавать только те виды объектов, которые были определены за вас в Ruby. Теперь ваша
очередь. В этой главе вы научитесь создавать свои методы, а также свои классы — своего
рода «шаблоны» для создания новых объектов. Вы сами решаете, как будут выглядеть
объекты, созданные на базе вашего класса. Переменные экземпляра определяют, какая
информация хранится в ваших объектах, а методы экземпляра определяют, что эти
объекты делают. А самое главное — вы узнаете, как определение собственных классов
упрощает чтение и сопровождение вашего кода.

Объекты

Класс

8

Определение методов

66

Вызов методов

67

Имена методов

68

Параметры

68

Возвращаемые значения

72

Раннее возвращение из метода

73

Беспорядок в методах

74

Слишком много аргументов

75

Слишком много команд «if»

75

Проектирование класса

76

Чем класс отличается от объекта

77

Первый класс

78

Создание новых экземпляров (объектов)

78

Разбиение больших методов на классы

79

Создание экземпляров новых классов животных

80

Диаграмма классов с методами экземпляра

81

Локальные переменные существуют до завершения метода

85

Инкапсуляция

88

Методы доступа

89

Использование методов доступа

91

Методы чтения и записи атрибутов

92

Ошибки и «аварийная остановка»

100

Использование «raise» в методах записи атрибутов

101

Ваш инструментарий Ruby

103

содержание

3

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

Animal

Переопределение

Копировать, вставлять... Столько проблем!

106

Код Майка для приложения виртуального тест-драйва

107

На помощь приходит наследование!

108

Определение суперкласса (в общем-то ничего особенного!)

110

Определение субкласса (совсем просто)

111

name
age

Добавление методов в субклассы

112

Субклассы содержат как новые, так и унаследованные методы

113

Переменные экземпляра принадлежат объекту, а не классу!

114

talk
move
report_age

Переопределение методов

116

Включение наследования в иерархию классов животных

121

Проектирование иерархий классов животных

122

Код класса Animal и его субклассов

123

Переопределение метода в субклассах Animal

124

Как добраться до переопределяемого метода?

125

Ключевое слово «super»

126

Armadillo
move

Субкласс обретает суперсилу

128

Трудности с выводом Dog

131

Класс Object

132

Почему все классы наследуют от Object

133

Переопределение унаследованного метода

134

Ваш инструментарий Ruby

135

9

содержание

4

Инициализация экземпляров
Хороший старт — половина дела
Пока что ваш класс напоминает бомбу с часовым механизмом. К аждый экземпляр, созданный вами, начинается «с пустого места».
Если вы вызовете методы экземпляра до того, как в нем появятся данные, то может произойти ошибка, приводящая к аварийному завершению программы.
В этой главе мы представим пару способов создания объектов, которые
можно сразу безопасно использовать. Начнем с метода initialize, который позволяет передать набор аргументов для заполнения данных объекта

на момент его создания. Затем мы покажем, как написать методы класса,
которые позволяют еще проще создавать и инициализировать объекты.

Значит, больше никаких
проблем с пустыми
именами и отрицательными
окладами у новых работников?
И проект будет сдан вовремя?
Отличная работа!

10

Зарплата в Chargemore

138

Класс Employee

139

Создание новых экземпляров Employee

140

Деление с использованием класса Ruby Fixnum

142

Деление с классом Ruby Float

143

Исправление ошибки округления в Employee

144

Форматирование чисел для вывода

145

Форматные последовательности

146

Применение метода «format» для исправления вывода

149

Если атрибуты объекта не заданы

150

«nil» означает «ничто»

151

«/» — это метод

152

Метод «initialize»

153

Безопасность использования Employee и «initialize»

154

Аргументы «initialize»

155

Необязательные параметры и «initialize»

156

«initialize» и проверка данных

160

«self» и вызов других методов для того же экземпляра

161

Когда ключевое слово «self» не обязательно

165

Реализация почасовой оплаты на основе наследования

167

Восстановление методов «initialize»

170

Наследование и метод «initialize»

171

«super» и «initialize»

172

Методы класса

179

Полный исходный код класса

182

Ваш инструментарий Ruby

184

содержание

5

Массивы и блоки
Лучше, чем цикл
Очень многие задачи из области программирования связаны с обработкой списков.Списки адресов. Списки телефонных номеров. Списки продуктов.
Мац, создатель языка Ruby, знал об этом. Поэтому он основательно потрудился над тем,
чтобы работать со списками в Ruby было действительно просто. Сначала он поработал
над тем, чтобы массивы, используемые для работы со списками в Ruby, обладали множеством мощных методов для выполнения практически любых операций. Затем он осознал, что написание кода для перебора всех элементов списка для выполнения некоторой
операции с каждым элементом — утомительная рутинная работа, которую программистам
приходится выполнять очень часто. Поэтому он добавил в язык блоки, благодаря которым
код перебора стал лишним. Что же это такое — блок? Сейчас узнаете...

Код в методе остается неизменным.

Массивы

186

Работа с массивами

187

Перебор элементов массива

191

Устранение дубликатов — НЕПРАВИЛЬНЫЙ способ

195

Блоки

197

Определение метода, получающего блок

198

Ваш первый блок

199

Передача управления между методом и блоком

200

Вызов одного метода с разными блоками

201

Многократный вызов блока

202

Параметры блоков

203

Ключевое слово «yield»

204

Форматы блоков

205

Метод «each»

209

Устранение повторений из кода при помощи «each» и блоков

211

Блоки и область видимости переменных

214

Использование «each» с методом «refund»

216

Использование «each» в последнем методе

217

Полный код методов

218

Ваш инструментарий Ruby

222

puts "We're in the method, about to invoke your block!"
puts "Wooooo!"
puts "We're back in the method!"

Код в блоке
изменяется!

11

содержание

6

Возвращаемые значения блоков
Как будем обрабатывать?
Вы видели лишь малую часть возможностей блоков.До настоящего момента методы передавали данные блоку и ожидали, что блок
сделает все необходимое. Однако блок также может возвращать данные методу. Эта возможность позволяет методу получать команды от блока, позволяя
ему выполнять более содержательную работу. Методы, рассмотренные в этой
главе, получают большие и сложные коллекции и используют возвращаемые
значения блоков для сокращения их размера.

[2, 3, 4]

number ** 2
[4, 9, 16]

Поиск в больших коллекциях слов

224

Открытие файла

226

Безопасное закрытие файла

226

Безопасное закрытие файла в блоке

227

Не забывайте про область видимости!

228

Поиск элементов в блоке

231

Долгий способ поиска с использованием «each»

232

А теперь короткий способ...

233

Блоки тоже возвращают значение

234

Как метод использует возвращаемое значение блока

239

Все вместе

240

Возвращаемые значения блоков: присмотримся повнимательнее

241

Исключение лишних элементов с использованием блока

242

Возвращаемые значения для «reject»

243

Преобразование строки в массив слов

244

Определение индекса элемента массива

245

Создание массива на базе другого массива (сложный способ)

246

Создание массива на базе другого массива с использованием «map»

247

Дополнительная логика в теле блока «map»

249

Работа закончена

250

Ваш инструментарий Ruby
["...Trunc
ated is am
az
ing...",
"...Trunca
ted is funn
y...",
"...Trunca
ted is asto
unding..."
]
find_adjective(review)
["amazing"
,
"funny",
"astoundin
g"]

12

253

содержание

7

Хеши
Пометка данных
Хранить данные в одной большой куче удобно... До тех пор,
пока вам не потребуется в них что-нибудь найти.Вы уже видели,
как создать коллекцию объектов с использованием массива. Вы уже видели, как обработать каждый элемент массива и как найти нужные элементы. В обоих случаях
мы начинаем от начала массива и проверяем каждый отдельный объект. Вы уже
видели методы, получающие большие коллекции в параметрах. Вам уже известно,
какие проблемы это создаст: вызовы методов требуют передачи большой, запутанной коллекции аргументов, для которой вам нужно помнить точный порядок.
А не существует ли коллекции, у которой все данные уже снабжены метками? Тогда
вы могли бы быстро найти нужные элементы! В этой главе рассматриваются хеши
Ruby, предназначенные именно для этого.
Подсчет голосов

Массив

AMBER GRAHAM

Хеш

256

Массив массивов — не идеальное решение

257

Хеши

258

Хеши — тоже объекты

260

Хеши возвращают «nil» по умолчанию

263

Значение nil (и только nil) интерпретируется как ложное

264

Возвращение по умолчанию другого значения вместо «nil»

266

Нормализация ключей хеша

268

Хеши и «each»

270

Путаница с аргументами

272

Передача хешей в параметрах методов

273

Параметр-хеш в классе Candidate

274

Фигурные скобки не нужны!

275

И стрелки не нужны!

275

Сам хеш тоже может быть необязательным

276

Опасные опечатки в аргументах-хешах

278

Ключевые слова в аргументах

279

Использование ключевых слов в аргументах в классе Candidate

280

Обязательные ключевые слова в аргументах

281

Ваш инструментарий Ruby

285

13

содержание

8

Ссылки
Путаница c сообщениями
Вы когда-нибудь отправляли сообщения не тому контакту?
Наверное, вам пришлось изрядно потрудиться, чтобы разобраться с возникшей
путаницей. Объекты Ruby очень похожи на контакты в адресной книге, и вызов методов для них напоминает отправку сообщений. Если содержимое вашей
адресной книги будет перепутано, вы рискуете отправить сообщение не тому
объекту. В этой главе вы научитесь распознавать такие ситуации и узнаете, как
снова наладить работу ваших программ.
Загадочные ошибки
Куча

289

Ссылки

290

Путаница со ссылками

291

Наложение имен

292

Исправление ошибки в программе астронома

294

Быстрая идентификация объектов методом «inspect»

296

Проблемы с объектом по умолчанию для хеша

297

Объект по умолчанию для хеша: близкое знакомство

300

Возвращаемся к хешу с планетами и спутниками

301

Чего мы хотим от объектов по умолчанию для хеша

302

Блоки по умолчанию для хешей

303

Блоки по умолчанию для хеша: присваивание

304

Блоки по умолчанию для хеша: возвращаемое значение блока

305

Блоки по умолчанию для хешей: сокращенная запись

306

Хеш астронома: окончательная версия кода

309

Безопасное использование объектов по умолчанию

310

Объекты по умолчанию для хешей: простое правило

313

Ваш инструментарий Ruby

314

Бетти Белл
Кэнди Кэмден
Западная ули- Западная улица, 2106
ца, 2110
Куча, 90210 Куча, 90210

Западная улица в реальности

14

288

Бетти Белл
Западная улица, 2106
Куча, 90210

Кэнди Кэмден
Западная улица, 2106
Куча, 90210

Западная улица по адресной книге Артура

содержание

9

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

В этой главе рассматриваются модули и примеси — мощный механизм группировки методов и их последующего включения только в те классы, для которых
они актуальны.

MyModule
adds:

first_method
second_method

...to any class!

Примесь 1

Примесь 2

Мультимедийное приложение

316

Приложение с наследованием

317

Один из этих классов не похож на другие

318

Вариант 1: Определение Photo как субкласса Clip

318

Вариант 2: Копирование нужных методов в класс Photo

319

Не вариант: Множественное наследование

320

Модули как примеси

321

Примеси: как это работает

323

Создание примеси

327

Использование примеси

328

Об изменениях в методе «comments»

329

Почему не следует добавлять «initialize» в примесь

330

Примеси и переопределение методов

332

Старайтесь не использовать методы «initialize» в модулях

333

Логический оператор «или» при присваивании

335

Оператор условного присваивания

336

Полный код

339

Ваш инструментарий Ruby

340

Класс

15

содержание

10

Сomparable и enumerable
Готовые примеси
Из предыдущей главы вы уже знаете, что примеси полезны.
Но вы еще не видели их истинную мощь. В стандартную библиотеку Ruby входят
две совершенно потрясающие примеси. Первая, Comparable, используется для

сравнения объектов. Мы уже использовали такие операторы, как и ==, с числами и строками, а с Comparable сможете использовать их с вашими классами.

Вторая примесь, Enumerable, применяется при работе с коллекциями. Помни-

те замечательные методы find_all, reject и map, которые мы использовали

с массивами? Так вот, они были предоставлены Enumerable. Но это лишь
малая часть возможностей Enumerable! И разумеется, вы можете включать

Я возьму
этот!

эту примесь в свои классы. Хотите узнать, как это делается? Читайте дальше!

Prime
Choice

16

Select

Встроенные примеси в Ruby

342

Знакомство с примесью Comparable

343

Выбор стейка

344

Реализация метода «больше» в классе Steak

345

Константы

346

Работа только начинается...

347

Примесь Comparable

348

Оператор

349

Реализация оператора в классе Steak

350

Включение Comparable в Steak

351

Как работают методы Comparable

352

Следующая примесь

355

Модуль Enumerable

356

Класс для включения Enumerable

357

Включение Enumerable в класс

358

Внутри модуля Enumerable

359

Ваш инструментарий Ruby

362

содержание

11

Документация
Читайте документацию
В книге не хватит места, чтобы рассказать все о Ruby. Есть
старая поговорка: «Дайте человеку рыбу, и он будет сыт один день. Научите
его ловить рыбу, и он будет сыт всю жизнь». До сих пор мы давали вам рыбу:
показали, как использовать некоторые классы и модули Ruby. Однако существуют десятки других классов и модулей, которые могут пригодиться в вашей
работе, но нам не хватит места, чтобы описать их в книге. Пришло время научить вас ловить рыбу. Существует превосходная бесплатная документация
по всем классам, модулям и методам Ruby. Вам просто нужно знать, где ее
найти и как ее интерпретировать. Именно об этом пойдет речь в этой главе.
Как узнать больше?

Потрясающе! Похоже,
из этой документации я
смогу узнать все о классах и
модулях Ruby. Но как насчет моего
кода? Как другие люди научатся
пользоваться им?

Документация метода
класса «today»

364

Базовые классы и модули Ruby

365

Документация

365

Документация в формате HTML

366

Список доступных классов и модулей

367

Поиск информации о методах экземпляра

368

Методы экземпляра обозначены в документации символом #

369

Документация по методам экземпляров

370

Аргументы в сигнатурах вызова

371

Блоки в сигнатурах вызова

372

Описания методов класса

376

Документация методов класса

378

Документация несуществующих классов?!

379

Стандартная библиотека Ruby

380

Поиск классов и модулей стандартной библиотеки

382

Откуда берется документация Ruby: rdoc

383

Какую информацию может вывести rdoc

385

Добавление комментариев для создания документации

386

Ваш инструментарий Ruby

388

Документация метода экземпляра «year».

17

содержание

12

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

Эта глава посвящена средствам обработки исключений в Ruby, которые
позволяют писать код для обработки аварийных ситуаций и размещать
его отдельно от нормального кода.

RuntimeError

rr
eE
im
nt
Ru

Ой...

Не используйте возвращаемые значения методов
для передачи информации об ошибках

390

Использование «raise» для передачи информации об ошибках

392

Использование «raise» создает новые проблемы

393

Исключения: когда происходит что-то не то

394

Условия rescue: пытаемся исправить проблему

395

Как Ruby ищет условие rescue

396

Использование условия rescue с классом SmallOven

398

Описание проблемы от источника

399

Сообщения об исключениях

400

Разная логика rescue для разных исключений

405

Классы исключений

407

Назначение класса исключения для условия rescue

409

Несколько условий rescue в одном блоке begin/end

410

Переход на пользовательские классы исключений в классе SmallOven 411
Перезапуск после исключения

412

Обновление кода с «retry»

413
415
416

Гарантированное выключение духовки

417

Ваш инструментарий Ruby
Фух!
Спасибо!

18

or
rr
eE
im
nt
Ru

Код, который должен выполняться в любом случае
Условие ensure

418

содержание

13

Модульное тестирование
Контроль качества кода
А вы уверены, что ваша программа работает правильно? Действительно уверены?Прежде чем передавать новую версию пользователям, вы,
вероятно, опробовали новые функции и убедились в том, что они работают. Но проверили
ли вы старые функции, чтобы убедиться, что они не перестали работать? Все старые
функции? Если этот вопрос не дает вам покоя, значит, вашей программе необходимо автоматизированное тестирование. Автоматизированные тесты гарантируют, что основные
компоненты программы продолжают правильно работать даже после изменения кода.
Модульные тесты — самая распространенная, самая важная разновидность автоматизированных тестов. В поставку Ruby входит библиотека MiniTest, предназначенная для
модульного тестирования. В этой главе вы узнаете все, что действительно необходимо

ListWithCommas
items
join

знать об этой библиотеке!
Автоматизированные тесты помогают найти ошибки до того,
как их найдут другие

420

Программа, для которой необходимы автоматизированные тесты

421

Типы автоматизированных тестов

423

MiniTest: стандартная библиотека модульного тестирования Ruby

424

Выполнение теста

425

Тестирование класса

426

Подробнее о тестовом коде

428

Красный, зеленый, рефакторинг

430

Тесты для класса ListWithCommas

431

Исправление ошибки

434

Еще одна ошибка

436

Сообщения об ошибках

437

Другой способ проверки равенства двух значений

438

Другие методы проверки условий

440

Устранение дублирования кода из тестов

443

Метод «setup»

444

Метод «teardown»

445

Обновление кода для использования метода «setup»

446

Ваш инструментарий Ruby

449

19

содержание

14

Веб-приложения
На раздаче HTML
На дворе XXI век. Пользователи требуют веб-приложения.
И Ruby поможет вам в этом! Существуют библиотеки, которые помогут вам
развернуть собственные веб-приложения и обеспечить доступ к ним из любого
браузера. Итак, в двух последних главах этой книги мы покажем вам, как построить полноценное веб-приложение.

Прежде всего вам понадобится Sinatra — независимая библиотека для создания веб-приложений. Но не беспокойтесь, мы научим вас использовать
программу RubyGems (включенную в поставку Ruby) для автоматизации
загрузки и установки библиотек! Затем будет рассмотрена разметка HTML —
ровно столько, сколько необходимо для создания собственных веб-страниц.
И конечно, мы покажем, как предоставить эти страницы браузеру!

20

Написание веб-приложений на Ruby

452

Список задач

453

Структура каталогов проекта

454

Браузеры, запросы, серверы и ответы

455

Загрузка и установка библиотек с использованием RubyGems

456

Установка библиотеки Sinatra

457

Простое приложение Sinatra

458

Ваш компьютер разговаривает сам с собой

459

Тип запроса

460

Маршруты Sinatra

462

Создание списка фильмов в формате HTML

465

Обращение к разметке HTML из Sinatra

466

Класс для хранения данных фильмов

468

Создание объекта Movie в приложении Sinatra

469

Теги внедрения в ERB

470

Тег внедрения вывода в ERB

471

Внедрение названия фильма в разметку HTML

472

Нормальный тег внедрения

475

Перебор названий фильмов в разметке HTML

476

Ввод данных на форме HTML

479

Получение формы HTML для добавления фильма

480

Таблицы HTML

481

Размещение компонентов формы в таблице HTML

482

Ваш инструментарий Ruby

484

содержание

15

Сохранение и загрузка данных
Сбережения пользователя
Сейчас наше веб-приожение просто выбрасывает данные,
введенные пользователем.Мы создали форму, на которой пользователь вводит данные. Пользователь ожидает. что данные будут сохранены, чтобы
их можно было прочитать и вывести позднее. Но сейчас ничего подобного не
происходит! Все введенные данные просто пропадают.

movies
app.rb
movies.yml

В последней главе книги приложение будет подготовлено к сохранению данных, введенных пользователем. Мы покажем, как настроить приложение для
получения данных формы, как преобразовать эти данные в объекты Ruby,
как сохранить их в файле и как загрузить нужный объект, когда пользователь
захочет увидеть его. Готовы? Так доделаем наше приложение!

lib
movie.rb
movie_store.rb
views
index.erb
new.erb
show.erb

Сохранение и загрузка данных формы

486

Настройка формы HTML для отправки запроса POST

490

Создание маршрута Sinatra для запроса POST

491

Преобразование объектов в строки и YAML

495

YAML::Store и сохранение объектов в файле

496

YAML::Store и сохранение описаний фильмов в файле

497

Поиск объектов Movie в YAML::Store

501

Числовые идентификаторы

502

Класс для управления YAML::Store

505

Использование класса MovieStore в приложении Sinatra

506

Тестирование MovieStore

507

Загрузка всех фильмов из MovieStore

508

Загрузка всех фильмов в приложении Sinatra

510

Построение ссылок HTML на фильмы

511

Именованные параметры в маршрутах Sinatra

514

Поиск объекта Movie в YAML::Store

519

Шаблон ERB для отдельного фильма

520

Полный код приложения

523

Ваш инструментарий Ruby

527

21

содержание

Приложение. Оставшиеся темы
Десять основных тем (не рассмотренных в книге)
Мы прошли долгий путь, и книга почти закончена.Мы будем скучать по вам,
но было бы неправильно расставаться и отпускать вас в самостоятельное путешествие без
еще нескольких напутственных слов. Нам при всем желании не удалось бы уместить все,
что вам еще нужно знать о Ruby, на этих страницах… (Вообще-то сначала мы уместили
все необходимое, уменьшив размер шрифта в 25 000 раз. Все поместилось, но текст было
не прочитать без микроскопа, поэтому материал пришлось изрядно сократить.) Но мы
оставили все самое лучшее для этого приложения.

И это уже действительно конец книги!

Файл в формате CSV

1. Другие полезные библиотеки

530

2. Компактные версии if и unless

532

3. Приватные методы

533

4. Аргументы командной строки

535

5. Регулярные выражения

536

6. Синглетные методы

538

7. Вызов любых (даже неопределенных) методов

539

8. Rake и автоматизация задач

541

9. Bundler

542

10. Литература

543

Associate,Sale Count,Sales Total
"Boone, Agnes",127,1710.26
"Howell, Marvin",196,2245.19
"Rodgers, Tonya",400,3032.48
sales.csv

p ARGV[0]
p ARGV[1]
args_test.rb

22

File Edit Window Help

$ ruby args_test.rb hello terminal
"hello"
"terminal"

как пользоваться этой книгой

Введение
Не могу поверить, что
они включили такое
в книгу о Ruby!

вопрос:
ответим на важный
В этом разделе мы
Ruby?»
и ТАКОЕ в книгу о
«Зачем они включил

как пользоваться этой книгой

Для кого написана эта книга?
Если вы ответите «да» на все следующие вопросы...
1

В вашем распоряжении имеется компьютер с текстовым редактором?

2

Вы хотите изучить язык программирования, с которым процесс
разработки становится простым и производительным?

3

Вы предпочитаете оживленную беседу сухим, скучным
академическим лекциям?

...то эта книга для вас.

Кому эта книга не подойдет?
Если вы ответите «да» на любой из следующих вопросов:
1

Вы абсолютно не разбираетесь в компьютерах?
(Быть специалистом не обязательно, но вы должны понимать,
что такое файлы и папки, уметь запустить терминальное приложение и пользоваться простым текстовым редактором.)

2

Вы супер-пупер-разработчик, которому нужен справочник?

3

Вы боитесь попробовать что-нибудь новое? Скорее пойдете
к зубному врачу, чем наденете полосатое с клетчатым? Считаете, что техническая книга, в которой наследование объясняется на примере броненосцев, серьезной быть не может?

...эта книга не для вас.

[Заметка от отдел
а продаж:
вообще-то эта книга
для любого,
у кого есть деньги.]

24 введение

введение

Мы знаем, о чем вы думаете
«Разве серьезная книга по программированию на Ruby может быть такой?»
«И почему здесь столько рисунков?»
«Можно ли так чему-нибудь научиться?»

И мы знаем, о чем думает ваш мозг

Ваш м
озг счи
тает
что Э
,
ТО ва
жно.

Мозг жаждет новых впечатлений. Он постоянно ищет, анализирует, ожидает чего-то необычного. Он так устроен, и это помогает
нам выжить.
Как наш мозг поступает со всеми обычными, повседневными
вещами? Он всеми силами пытается оградиться от них, чтобы
они не мешали его настоящей работе — сохранению того, что действительно важно. Мозг не считает нужным сохранять скучную
информацию. Она не проходит фильтр, отсекающий «очевидно
несущественное».
Но как же мозг узнает, что важно? Представьте, что вы выехали
на прогулку и вдруг прямо перед вами появляется тигр. Что происходит в вашей голове и теле?
Активизируются нейроны. Вспыхивают эмоции. Происходят
химические реакции. И тогда ваш мозг понимает...

поламозг
ш
а
В
ЭТО
, что
гает
Конечно, это важно! Не забывать!
апоз
о не
н
ж
о
м
А3теперь представьте, что вы находитесь дома или в библиотеке
ть.
мина

Замечательно.
Еще 519 сухих,
скучных страниц.

в теплом, уютном месте, где тигры не водятся. Вы учитесь — готовитесь к экзамену. Или пытаетесь освоить сложную техническую
тему, на которую вам выделили неделю... максимум десять дней.
И тут возникает проблема: ваш мозг пытается оказать вам
услугу. Он старается сделать так, чтобы на эту очевидно несущественную информацию не тратились драгоценные ресурсы.
Их лучше потратить на что-нибудь важное. На тигров, например. Или на то, что к огню лучше не прикасаться. Или что на
лыжах не стоит кататься в футболке и шортах.
Нет простого способа сказать своему мозгу: «Послушай, мозг,
я тебе, конечно, благодарен, но какой бы скучной ни была
эта книга и пусть мой датчик эмоций сейчас на нуле, я хочу
запомнить то, что здесь написано».

дальше 4  25

как пользоваться этой книгой

т учиться

Эта книга для тех, кто хоче

том не забыть.
то-то» понять, а по
«ч
о
эт
о
жн
ну
а
ал
ем? Снач
новейшим исслеКак мы что-то узна
аточно. Согласно
ст
до
не
ов
кт
фа
учения, для
побольше
и и психологии об
ги
Затолкать в голову
ло
ио
об
йр
не
,
ки
нице.
и когнитивисти
остой текст на стра
дованиям в област
о большее, чем пр
о-т
чт
я
тс
уе
еб
тр
а
ал
усвоения матери
ботать.
авить ваш мозг ра
ст
за
к
ка
Мы знаем,

rst:
пы серии Head Fi
Основные принци

ет эффективи значительно повыша
, чем обычный текст,
ше
луч
ся
ает
ин
становится
ом
иал
фика зап
Кроме того, матер
Наглядность. Гра
ным исс ледований).
дан
по
%
89
ми или на
(до
и
ни
д
формаци
осится, а не по
ность восприятия ин
ках, к которым он отн
сун
ри
на
ся
териалом,
ает
ма
ещ
ст разм
занных с тек ущим
более понятным. Тек
го решения задач, свя
но
еш
усп
сть
но
оят
— и вер
соседней странице
ое.
вдв
я
етс
повыша
орном стиле
казали, что при разгов
ние исс ледования по
дав
Не
я.
ни
тес тироваже
ом
ло
ь из
ьтатов на итогов
Разговорный стил
ций) улучшение резул
лек
х
ны
аль
ь к себе
рм
тес
фо
о
оси
а (вмест
тать лекцию. Не отн
изложения материал
, вместо того чтобы чи
ию
ор
ист
лекция?
те
или
вай
зы
Расска
ая беседа за столом
нии достигает 40 %.
внимание: занимательн
е
ваш
т
ече
ивл
пр
скорее
слишком серьезно. Что
голове ничего
ь извилины, в вашей
не начнете напрягат
вы
ка
По
.
ля
ачи, формуте
зад
та
е чи
он должен решать
Активное участи
ресован в результате;
нте
заи
ть
бы
каверзные
ен
и
лж
ия
ель до
бходимы упражнен
не произойдет. Читат
ниями. А для этого нео
зна
ми
вы
но
ь
ват
аде
разные чувства.
лировать выводы и овл
а полушария мозга и
ых задейс твованы об
ор
кот
и
ни
ше
ре
в
,
вопросы
дому: «Я очень
туация, знакомая каж
ания читателя. Си
им
вн
)
ие
ен
есное, странан
тер
сохр
ает внимание на ин
Привлечение (и
анице». Мозг обращ
стр
вой
пер
ть скучным.
на
бы
аю
засып
темы не обязано
хочу изучить это, но
сложной технической
е
ни
уче
Из
е.
но
дан
неожи
ное, притягательное,
намного быстрее.
ся
ает
узн
ое
Интересн
ьной мере
оминать в значител
наша способность зап
что
,
тно
вес
Мы запоИз
о.
чн
м.
оц ия
что нам не безразли
Об ра ще ни е к эм
я. Мы запоминаем то,
ани
ив
еж
иях, как
ер
оц
эм
соп
их
го
ьно
: речь идет о так
зависит от эмоционал
енты здесь ни при чем
тим
сан
т,
ающие
Не
уж
м.
окр
вуе
ую
чувст
шении задачи, котор
минаем, когда что-то
о «Да я кру т!» при ре
ств
а Бо б
чув
и
йк
ес
зна
тер
все
ин
во,
в тем е лучше , чем
удивление, любопытст
, что ра зби ра ете сь
ете
ма
ни
по
вы
да
ког
ил и
считают сложн ой —
.
ела
отд
о
ког
чес
ни
из тех

26 введение

введение

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

Как бы теперь
заставить мой мозг
все это запомнить...

Но раз вы читаете эту книгу, то, вероятно, вы хотите научиться программировать на языке Ruby, и по возможности быстрее. Вы хотите
запомнить прочитанное, а для этого абсолютно необходимо сначала
понять прочитанное.
Чтобы извлечь максимум пользы из учебного процесса, нужно заставить ваш мозг воспринимать новый материал как Нечто Важное.
Критичное для вашего существования. Такое же важное, как
тигр. Иначе вам предостоит бесконечная борьба с вашим мозгом, который всеми силами уклоняется от запоминания новой
информации.

Как же УБЕДИТЬ мозг, что программирование
на языке Ruby не менее важно, чем тигр?
Есть способ медленный и скучный, а есть быстрый и эффективный. Первый основан на тупом повторении. Всем известно, что
даже самую скучную информацию можно запомнить, если повторять
ее снова и снова. При достаточном количестве повторений ваш мозг
прикидывает: «Вроде бы несущественно, но раз одно и то же повторяется
столько раз... Ладно, уговорил».
Быстрый способ основан на повышении активности мозга и особенно
на сочетании разных ее видов. Доказано, что все факторы, перечисленные на предыдущей странице, помогают вашему мозгу работать на
вас. Например, исследования показали, что размещение слов внутри
рисунков (а не в подписях, в основном тексте и т. д.) заставляет мозг
анализировать связи между текстом и графикой, а это приводит к активизации большего количества нейронов. Больше нейронов — выше
вероятность того, что информация будет сочтена важной и достойной
запоминания.
Разговорный стиль тоже важен: обычно люди проявляют больше внимания, когда они участвуют в разговоре, так как им приходится следить
за ходом беседы и высказывать свое мнение. Причем мозг совершенно
не интересует, что вы «разговариваете» с книгой! С другой стороны,
если текст сух и формален, то мозг чувствует то же, что чувствуете
вы на скучной лекции в роли пассивного участника. Его клонит в сон.
Но рисунки и разговорный стиль — это только начало.
дальше 4  27

как пользоваться этой книгой

Вот что сделали МЫ:
Мы использовали рисунки, потому что мозг лучше приспособлен для восприятия
графики, чем текста. С точки зрения мозга рисунок стоит тысячи слов. А когда текст
комбинируется с графикой, мы внедряем текст прямо в рисунки, потому что мозг при
этом работает эффективнее.
Мы используем избыточность: повторяем одно и то же несколько раз, применяя разные средства передачи информации, обращаемся к разным чувствам — и все для повышения вероятности того, что материал будет закодирован в нескольких областях
вашего мозга.
Мы используем концепции и рисунки несколько неожиданным образом, потому что
мозг лучше воспринимает новую информацию. Кроме того, рисунки и идеи обычно
имеют эмоциональное содержание, потому что мозг обращает внимание на биохимию
эмоций. То, что заставляет нас чувствовать, лучше запоминается — будь то шутка,
удивление или интерес.
Мы используем разговорный стиль, потому что мозг лучше воспринимает информацию,
когда вы участвуете в разговоре, а не пассивно слушаете лекцию. Это происходит
и при чтении.
В книгу включены многочисленные упражнения, потому что мозг лучше запоминает,
когда вы что-то делаете. Мы постарались сделать их непростыми, но интересными —
то, что предпочитает большинство читателей.
Мы совместили несколько стилей обучения, потому что одни читатели предпочитают
пошаговые описания, другие стремятся сначала представить «общую картину», а третьим хватает фрагмента кода. Независимо от ваших личных предпочтений полезно
видеть несколько вариантов представления одного материала.
Мы постарались задействовать оба полушария вашего мозга; это повышает вероятность
усвоения материала. Пока одна сторона мозга работает, другая часто имеет возможность отдохнуть; это повышает эффективность обучения в течение продолжительного
времени.
А еще в книгу включены истории и упражнения, отражающие другие точки зрения. Мозг
глубже усваивает информацию, когда ему приходится оценивать и выносить суждения.
В книге часто встречаются вопросы, на которые не всегда можно дать простой ответ,
потому что мозг быстрее учится и запоминает, когда ему приходится что-то делать.
Невозможно накачать мышцы, наблюдая за тем, как занимаются другие. Однако мы
позаботились о том, чтобы усилия читателей были приложены в верном направлении.
Вам не придется ломать голову над невразумительными примерами или разбираться
в сложном, перенасыщенном техническим жаргоном или слишком лаконичном тексте.
В историях, примерах, на картинках используются люди — потому что вы тоже человек.
И ваш мозг обращает на людей больше внимания, чем на неодушевленные предметы.

28 введение

введение

Что можете сделать ВЫ, чтобы
заставить свой мозг повиноваться
Мы свое дело сделали. Остальное за вами. Эти советы станут
отправной точкой; прислушайтесь к своему мозгу и определите, что вам подходит, а что не подходит. Пробуйте новое.

Вырежьте и прик
репите
на холодильник.

1

Не торопитесь. Чем больше вы поймете,
тем меньше придется запоминать.

7

Просто читать недостаточно. Когда в книге задается вопрос, не переходите сразу к ответу. Представьте, что кто-то действительно задает вам вопрос.
Чем глубже ваш мозг будет мыслить, тем скорее вы
поймете и запомните материал.
2

Выполняйте упражнения, делайте заметки.

Мы включили упражнения в книгу, но выполнять их
за вас не собираемся. И не разглядывайте упражнения.
Берите карандаш и пишите. Физические действия
во время учения повышают его эффективность.
3 Читайте врезки.
Это значит: читайте всё. Врезки — часть основного
материала! Не пропускайте их.
4

Не читайте другие книги перед сном.

Часть обучения (особенно перенос информации
в долгосрочную память) происходит после того,
как вы откладываете книгу. Ваш мозг не сразу усваивает информацию. Если во время обработки
поступит новая информация, часть того, что вы
узнали ранее, может быть потеряна.
5

Говорите вслух.

Речь активизирует другие участки мозга. Если вы
пытаетесь что-то понять или получше запомнить,
произнесите вслух. А еще лучше, попробуйте объяснить кому-нибудь другому. Вы будете быстрее
усваивать материал и, возможно, откроете для
себя что-то новое.
6

Пейте воду. И побольше.

Мозг лучше всего работает в условиях высокой
влажности. Дегидратация (которая может наступить еще до того, как вы почувствуете жажду) снижает когнитивные функции.

Прислушивайтесь к своему мозгу.

Следите за тем, когда ваш мозг начинает
уставать. Если вы начинаете поверхностно воспринимать материал или забываете
только что прочитанное, пора сделать перерыв. С определенного момента попытки
«загрузить» побольше материала становятся
бесполезными и даже могут повредить процессу.
8 Чувствуйте!
Ваш мозг должен знать, что материал книги действительно важен. Переживайте за
героев наших историй. Придумывайте
собственные подписи к фотографиям. Поморщиться над неудачной шуткой все равно
лучше, чем не почувствовать ничего.
9

Пишите побольше кода!

Освоить разработку приложений Ruby
можно только одним способом: писать
побольше кода. Собственно, именно этим
вы будете заниматься по мере прочтения
книги. Программирование — это квалифицированная работа, и тренировка — единственный путь к мастерству. И мы дадим
вам много возможностей для тренировки:
в каждой главе содержатся упражнения.
Не пропускайте их; работа над упражнениями является важной частью процесса обучения. Мы приводим решения всех
упражнений; не бойтесь заглянуть в решение, если окажетесь в тупике! И все же
сначала постарайтесь решить задачу самостоятельно. И обязательно добейтесь того,
чтобы ваше решение заработало, прежде
чем переходить к следующей части книги.
дальше 4  29

как пользоваться этой книгой

Примите к сведению
Это учебник, а не справочник. Мы намеренно убрали из книги все, что могло бы помешать
изучению материала, над которым вы работаете. И при первом чтении книги начинать
следует с самого начала, потому что книга предполагает наличие у читателя определенных
знаний и опыта.
Небольшой опыт программирования на другом языке не повредит.
Многие разработчики переходят на Ruby после другого языка программирования (часто пытаясь держаться подальше от этого языка). Материал излагается на уровне, понятном даже
для новичка, но мы не рассказываем подробно о том, что такое переменная и как работает
команда if. Вам будет проще, если вы хотя бы в общих чертах представляете эти темы.
Мы не пытаемся подробно описывать каждый класс, библиотеку и метод.
В Ruby существует множество встроенных классов и методов. Конечно, все они представляют интерес, но нам бы не удалось их рассмотреть даже в книге вдвое большего объема. Наше
внимание будет сосредоточено на основных классах и методах, которые важны для вас — начинающего разработчика. Мы позаботимся о том, чтобы вы хорошо понимали их суть и достаточно уверенно чувствовали себя в отношении того, когда и как их следует использовать.
В любом случае после прочтения книги вы сможете взять любой справочник и быстро найти
информацию обо всех классах и методах, которые в книге не рассматриваются.
Упражнения ОБЯЗАТЕЛЬНЫ.
Упражнения являются частью основного материала книги. Одни упражнения способствуют
запоминанию материала, другие помогают лучше понять его, третьи ориентированы на его
практическое применение. Не пропускайте упражнения.
Повторение применяется намеренно.
У книг этой серии есть одна принципиальная особенность: мы хотим, чтобы вы действительно
хорошо усвоили материал. И чтобы вы запомнили все, что узнали. Большинство справочников не ставит своей целью успешное запоминание, но это не справочник, а учебник, поэтому
некоторые концепции излагаются в книге по нескольку раз.
Примеры были сделаны по возможности компактными.
Нашим читателям не нравится просматривать 200 строк кода в примерах, чтобы найти две
действительно важные строки. Большинство примеров книги приводится в минмально возможном контексте, чтобы та часть, которую вы изучаете, была простой и наглядной. Не ждите, что все примеры будут защищенными от ошибок, или хотя бы полными — они написаны
в учебных целях и не всегда обладают полноценной функциональностью.
Все файлы примеров доступны для загрузки в Интернете. Вы найдете их по адресу http://
headfirstruby.com/.

30 введение

1 как сделать больше меньшими усилиями

Программируйте так, как вам удобно
Только посмотрите, какая
замечательная штука Ruby!
В этой главе вы познакомитесь
с переменными, строками,
условными командами и циклами.
А самое замечательное, что мы
напишем игру, и она будет
работать!

Вас интересует, почему вокруг столько говорят о языке Ruby
и подойдет ли он вам? Ответьте на простой вопрос: вам нравится работать
эффективно? Кажется ли вам, что с многочисленными компиляторами,
библиотеками, файлами классов и комбинациями клавиш других языков вы
действительно становитесь ближе к готовому продукту, восхищению коллег
и толпе счастливых заказчиков? Вы хотите, чтобы язык программирования
занимался техническими подробностями за вас? Если вам хочется перестать писать рутинный код и работать над задачей, то язык Ruby — для вас.
Ruby позволит вам сделать больше с меньшим объемом кода.

несколько слов о ruby

Философия Ruby
В 1990-е годы японский программист Юкихиро Мацумото (сокращенно Мац) мечтал
об идеальном языке программирования. Он хотел, чтобы его язык:

Был простым в изучении и использовании.

Был достаточно гибким для решения любых задач из области программирования.

Позволял программисту сосредоточиться на решаемой задаче.

Создавал меньше проблем у программиста.

Был объектно-ориентированным.
Он присмотрелся ко всем языкам того времени, но ни один из этих языков не соответствовал его требованиям в полной мере. И тогда Мац решил создать собственный язык,
который получил название Ruby.
Немного повозившись с Ruby в своей работе, Мац сделал его достоянием широкой публики
в 1995-м. С тех пор в сообществе Ruby произошло немало интересного:

Была построена обширная подборка библиотек Ruby для решения самых разно­
образных задач, от чтения файлов с данными CSV (значения, разделенные запятыми)
до работы с объектами по сети.

Были написаны альтернативные интерпретаторы, способные ускорить выполнение
кода Ruby или интегрировать его с другими языками.

Был создан Ruby on Rails — чрезвычайно популярная инфраструктура для вебприложений.
Причины столь стремительного роста творческого потенциала и продуктивности следует искать в самом языке Ruby. Гибкость и простота использования — основополагающие
принципы языка, а это означает, что Ruby может использоваться для решения любых задач
программирования с меньшим количеством строк программного кода, чем в других языках.
Изучив основы, вы согласитесь: с Ruby приятно иметь дело!

Гибкость и простота использования —
основополагающие принципы языка.

32   глава 1

как сделать больше меньшими усилиями

Где взять Ruby
Сначала о главном: вы можете писать Ruby весь день напролет, только от этого не будет
никакого толку, если вы не сможете этот код выполнить. Прежде всего потребуется работающий интерпретатор Ruby. Вам нужна версия 2.0 или выше. Откройте новое терминальное окно (также называемое окном командной строки) и введите следующую команду:
С ключом «-v»
Ruby выводит
номер версии.
Команда «ruby»
сама по себе запускает интерпретатор Ruby.

ruby -v

File Edit Window Help

$ ruby -v
ruby 2.0.0p0 (2013-02-24 revision 39474) [x86_64-darwin11.4.2]
$ ruby
^Cruby: Interrupt

$
Нажмите Ctrl-C,
ть
рши
чтобы заве
работу интерпретатора и вернуться к подсказке операционной
системы.

Если вы ввели в приглашении команду ruby -v и получили ответ
следующего вида, значит, все хорошо:

Задание!

ruby 2.0.0p0 (2013-02-24 revision 39474) [x86_64-darwin11.4.2]
На остальное не обращайте внимания. Важно, чтобы здесь была
указана версия «ruby 2.0» или выше.

Если в вашей системе
нет версии Ruby 2.0 или
выше, зайдите на сайт
www.ruby-lang.org и загрузите пакет для своей ОС.

Кстати говоря, если вы случайно введете команду ruby (без ключа -v), Ruby будет
ждать, пока вы введете какой-нибудь код. Чтобы выйти из этого режима, просто
нажмите клавишу Control одновременно с клавишей C. Это можно сделать в любой
момент, если вам захочется немедленно завершить работу с Ruby.
дальше 4   33

выполнение сценариев

Использование Ruby
Файлы с исходным кодом Ruby называются сценариями (scripts), но на самом деле
это обычные текстовые файлы. Чтобы запустить сценарий Ruby на выполнение,
просто сохраните код Ruby в файле и запустите этот файл в интерпретаторе Ruby.
Возможно, вы привыкли к другим языкам (таким, как C++, C# или Java), в которых код компилируется в двоичный формат, «понятный» для процессора или
виртуальной машины. В таких языках код не может выполняться без предварительной обработки компилятором.
Компь
ютер
выполн
яет ваш
у
програм
му.

Так это делается в других языках:
Откомпилированный код

Исходный код

MyProgram.java

MyProgram.class

Компилятор

Виртуальная
машина

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

Компь
ютер
выполн
яет ваш
у
програм
му.

Исходный код

my_program.rb

Интерпретатор Ruby

puts "hello world"

Введите исходный код.
Сохраните в файле: hello.rb
34   глава 1

File Edit Window Help

$ ruby hello.rb
hello world

Запустите исходный код
в интерпретаторе Ruby.

А вот и результат!

как сделать больше меньшими усилиями

Интерактивное использование Ruby
У таких языков, как Ruby, есть еще одно большое преимущество. Вам не только не обязательно запускать компилятор каждый раз, когда потребуется опробовать свой код, — его даже
не обязательно сохранять в файле.
В поставку Ruby входит отдельная программа, которая называется irb (Interactive Ruby).
Оболочка irb берет любое выражение Ruby, введенное пользователем, немедленно вычисляет его и выводит результат. Эта возможность особенно полезна при изучении языка,
потому что новичок немедленно получает обратную связь. Впрочем, даже профессионалы
Ruby используют irb для проверки новых идей.
В этой книге мы напишем много сценариев, предназначенных для запуска в интерпретаторе
Ruby. Но каждый раз, когда вы осваиваете новую концепцию, будет полезно запустить irb
и немного поэкспериментировать.
Так чего же мы ждем? Давайте запустим irb и посмотрим, как работают выражения Ruby.

Использование интерактивной оболочки irb
Откройте терминальное окно и введите команду irb. Команда запускает интерактивный
интерпретатор Ruby. (О том, что интерпретатор запустился, вы узнаете по изменению внешнего вида подсказки, хотя в вашей системе она может выглядеть не так, как на иллюстрации.)
После этого введите любое выражение и нажмите клавишу Enter/Return. Ruby мгновенно
вычисляет его и выводит результат.
Завершив работу с irb, введите в приглашении команду exit. На экране снова появляется
приглашение командной строки ОС.
Введите «irb» в приглашении командной строки
и нажмите Return.

irb запускается
и отображает
приглашение.

irb вычисляет выраже-

File Edit Window Help

$ irb

irb(main):001:0> 1 + 2
=> 3

Теперь введите любое выражение Ruby и нажмите
клавишу Return.

irb(main):002:0> "Hello".upcase
=> "HELLO"

ние и выводит результат (с пометкой «=>»). irb(main):003:0> exit

$

Когда вы будете готовы
выйти из irb, введите
команду «exit» и нажмите клавишу Return.

дальше 4   35

выражения ruby

Ваши первые выражения Ruby
Итак, теперь вы знаете, как запустить irb. Введем несколько выражений
и посмотрим, какие результаты будут получены.
Введите следующее выражение в приглашении и нажмите клавишу Return: 1 + 2
Вы увидите следующий результат:

=> 3

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

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

Если ввести:

irb выведет следующий
результат:

5.4 — 2.2

=> 3.2

3 * 4

=> 12

7 / 3.5

=> 2.0

2 ** 3

=> 8

4 < 6

=> true

4 > 6

=> false

2 + 2 == 5

=> false

Строки
Строка (string) представляет собой цепочку символов. Строки могут использоваться
для хранения имен, адресов электронной почты, телефонных номеров... в общем,
чего угодно. У строк языка Ruby есть одна особенность: даже очень большие строки
в Ruby обрабатываются очень эффективно (в отличие от некоторых других языков).
Самый простой способ определить строку — заключить составляющие ее символы
в одинарные (') или двойные кавычки(").
Эти две разновидности ограничителей
слегка отличаются друг от друга; вскоре мы
вернемся к этой теме.

36   глава 1

"Hello"

=> "Hello"

'world'

=> "world"

как сделать больше меньшими усилиями

Переменные
В языке Ruby можно создавать переменные — имена, ссылающиеся на значения.
Объявлять переменные заранее в Ruby не обязательно; они автоматически создаются
в момент присваивания. Присваивание выполняется оператором «=» (один знак равенства).
Если ввести:

irb выведет следующий результат:

small = 8

=> 8

medium = 12

=> 12

Имена переменных начинаются со строчной буквы и могут содержать буквы,
цифры и символы подчеркивания.
После того как переменной будет присвоено значение, вы сможете в любой
момент использовать имя переменной в любом контексте, в котором может использоваться исходное значение.
small + medium

=> 20

У переменных в Ruby нет типа; в них могут храниться любые значения. Переменной можно присвоить строку, потом тут же присвоить ей вещественное число —
это абсолютно нормально.
pie = "Lemon"

=> "Lemon"

pie = 3.14

=> 3.14

Оператор «+=» увеличивает текущее значение
переменной.
number = 3
number += 1
number

=> 3
=> 4
=> 4

string = "ab"
string += "cd"
string

=> "ab"
=> "abcd"
=> "abcd"

Житейская
мудрость
Записывайте имена переменных
в нижнем регистре. Постарайтесь не
включать в имена переменных цифры; обычно без них можно обойтись.
Разделяйте слова символами подчеркивания.
my_rank = 1
Этот стиль записи иногда называется
«змеиным»: с символами подчеркивания имя напоминает змею, ползущую
по земле.

дальше 4   37

ruby — oбъектно-ориентированный язык

Вокруг одни объекты!
Ruby относится к семейству объектно-ориентированных языков. Это означает, что прямо
к данным присоединяются полезные методы — фрагменты кода, выполняемые по мере
надобности.
В современных языках такие данные, как строки, часто представляют собой полноценные
объекты. И конечно, у строк есть свои методы, которые могут вызываться в программах:
Если ввести:

irb выведет следующий результат:

"Hello".upcase

=> HELLO

"Hello".reverse

=> olleH

Язык Ruby идет еще дальше: в нем все данные представляют собой объекты: даже
обычное число является объектом. А это означает, что у чисел тоже могут быть
полезные методы.
42.even?

=> true

-32.abs

=> 32

Вызов метода для объекта
При таком вызове объект, для которого вызывается метод, называется получателем метода.
К этой категории относится все, что находится
слева от оператора «точка». Представьте, что
вызов метода для объекта напоминает передачу
сообщения — что-то вроде записки «Привет, вы не
могли бы передать мне свою версию, записанную
в верхнем регистре?» или «Нельзя ли получить
ваше абсолютное значение (модуль)?»

38   глава 1

Получатели

Операторы
«точка»

Имена мет
одов

"hello".upcase
-32.abs
file.read

как сделать больше меньшими усилиями

Упражнение

Откройте терминальное окно, введите команду irb и нажмите клавишу Enter/Return. Под
каждым из приведенных ниже выражений Ruby запишите, какой результат, по вашему мнению,
будет получен при выполнении его в интерпретаторе. Потом введите выражение в irb и нажмите Enter. Совпадет ли ваше предположение с тем, что выдаст irb?
42 / 6

5 > 4

name = "Zaphod"

number = -32

name.upcase

number.abs

"Zaphod".upcase

-32.abs

name.reverse

number += 10

name.upcase.reverse

rand(25)

name.class

number.class

name * 3

дальше 4   39

выражения в irb

Откройте терминальное окно, введите команду irb и нажмите клавишу Enter/Return. Под
каждым из приведенных ниже выражений Ruby запишите, какой результат, по вашему мнению,
будет получен при выполнении его в интерпретаторе. Потом введите выражение в irb и нажмите Enter. Совпадет ли ваше предположение с тем, что выдаст irb?
Операция
ния
присваива
ой
переменн
возвращает
присвоенное
значение.

42 / 6

5 > 4

7

true

name = "Zaphod"

number = -32

«Zaphod"

е
Вы может
ем
ь
т
ва
вызы
а,
т
ек
ъ
об
ы
тод
я
хранящегос
ой…
н
ен
ем
в пер

name.upcase

«ZAPHOD"

м
Причем ва
язаоб
е
н
даже
ачала
тельно сн
его
сохранять
ной!
в перемен

"Zaphod".upcase

Для значения, возо
вращаемог
,
ом
од
мет
можно вызвать дру
.
од
ет
м
гой

«ZAPHOD"
name.reverse

number.abs

32
-32.abs

32
number += 10

«dohpaZ"

-22

name.upcase.reverse

rand(25)

«DOHPAZ"
name.class

екКласс объ
я- String
ел
ед
р
та оп
й
бо
со
о
ет, чт
т
яе
вл
а
предст
name * 3
ект.
этот объ

Результат
неизвестен
заранее (это
случайное
число).

Оказывается,
строки можно
«умножать»!

«ZaphodZaphodZaphod"

40   глава 1

-32

Случайное число
number.class

Fixnum

ачеТекущее зн
ной
ен
ние перем
ся
ет
увеличива
ер
на 10, а
снова
зультат
ется
присваива
.
ой
переменн
Да, это ТОЖЕ
вызов метода;
просто мы
не указали получателя. Скоро расскажем
подробнее!

Fixnum —
ть
разновиднос
.
л
се
чи
целых

как сделать больше меньшими усилиями

Построим игру
В первой главе книги мы построим простую игру. Если задача покажется слишком сложной, не беспокойтесь: когда вы работаете на Ruby,
все не так страшно!
Для начала разберемся, что же нужно сделать:

ьзовать имя
имя. Испол
о
ег
а
к
о
гр
уи
Запросить
я.
приветстви
а
д
о
в
ы
1 до 100
для в
е число от
о
н
й
а
уч
сл
т угадать
вать
олжен буде
Сгенериро
д
к
о
гр
И
.
ь его
и сохранит
.
о
ал игрок.
это числ
пыток сдел
о
п
о
ьк
л
о
ск
тем,
ь, сколько
Следить за
й сообщит
о
к
т
ы
п
о
п
дой
(из 10).
Перед каж
его осталось
н
у
к
о
т
ы
п
еще по
исло.
ку ввести ч
о
гр
и
ь
т
и
ж
Предло
ь ш е зачисло мен
м
о
к
о
гр
и
s. Your
нное
ение: «Oop
щ
Если введе
б
о
со
и
ст
выве
о больше
енное числ
гаданного,
д
е
в
в
и
сл
Е
W».
ops. Your
guess was LO
бщение: «O
о
со
и
ст
е
в
, вы
загаданного
H».
IG
H
as
дан guess w
адает с зага u
п
в
о
с
о
л
с
и
o
нное ч
job, [имя]! Y
Если введе
ение «Good
щ
s!»
б
se
о
es
со
u
g
и
]
ст
к
попыто
о
ным, выве
сл
и

in
umber
не
guessed my n
и, но так и
все попытк
л
’t
и
n
ат
id
р
d
т
о
u
п
y. Yo
Если игрок
ение: «Sorr
щ
б
е
о
о
н
со
н
и
а
д
ст
е
[зага
угадал, выв
u m b e r wa s
n
y
M
r.
e
b
get my num
он
число]».
сло, пока
водить чи
в
у
к
о
.
гр
и
и
к
ь
т
П р е д л а га т
ит все попы
и не потрат
л
и
т
ае
ад
уг
не

Я составил для вас
этот список из восьми
требований. Справитесь?

Гэри,
разработчик игр

дальше 4   41

сохранение введенных данных в переменных

Ввод, сохранение и вывод
Итак, первое требование — поприветствовать пользователя по имени. Для этого необходимо написать сценарий, который запрашивает
ввод (входные данные) у пользователя, сохраняет этот ввод, а затем
использует сохраненное значение для создания вывода.
Все это можно сделать буквально в нескольких строках кода Ruby:
Комментарии.

# Get My Number Game
# Written by: you!

Вызов метода «puts».

puts "Welcome to 'Get My Number!'"

Вызов метода «print».

print "What's your name? "

Значение присваивается
новой переменной «input».

input = gets

Вызов метода «gets».
Значение интерполируется в строку.

puts "Welcome, #{input}"
Все составляющие этого сценария будут более
подробно рассмотрены на нескольких ближайших
страницах. Но для начала опробуем его в деле!

42   глава 1

Строки.

как сделать больше меньшими усилиями

Запуск сценариев
Мы написали простой сценарий, который выполняет первое
требование: поприветствовать игрока по имени. А теперь вы
узнаете, как выполнить этот сценарий, чтобы вы могли воочию
увидеть результаты своей работы.

Задание!
Шаг 1:
Откройте новый документ в своем любимом текстовом
редакторе и введите следующий фрагмент.
# Get My Number Game
# Written by: you!
puts "Welcome to 'Get My Number!'"
print "What's your name? "
input = gets
puts "Welcome, #{input}"
get_number.rb

Шаг 2:
Сохраните файл под именем get_number.rb.
Шаг 3:
Откройте новое терминальное окно и перейдите
в каталог, в котором была сохранена программа.
Шаг 4:
Запустите программу командой ruby get_number.rb.
Шаг 5:

File Edit Window Help

$ ruby get_number.rb
Welcome to 'Get My Number!'
What's your name? Jay
Welcome, Jay

На экране появляется приветствие и приглашение.
Введите свое имя и нажмите клавишу Enter/Return. Вы
увидите сообщение, в котором программа приветствует
вас по имени.
дальше 4   43

вызов методов

Рассмотрим каждую из составляющих этого кода более подробно.
Комментарии
Файл с исходным кодом начинается с пары комментариев. Ruby
игнорирует все символы от решетки (#) до конца строки, чтобы
вы могли оставлять в программах инструкции или заметки для
себя и своих коллег-разработчиков.
Если вы включили знак # в свой код, то все символы до конца
этой строки будут рассматриваться как комментарий. Этот способ
комментирования работает точно так же, как комментарии //
в Java или JavaScript.

Комментарии.

# Get My Number Game
# Written by: you!

i_am = "executed" # I'm not.
# Me neither.

«puts» и «print»

Вызов метода «puts».

«Настоящий» код начинается с вызова
метода puts («puts» — сокращение от
puts "Welcome
«put string»), который выводит текст
на стандартном устройстве вывода Вызов метода «print».
(обычно на терминале). При вызове
print "What's
метода передается строка с выводимым текстом.

to 'Get My Number!'"
your name? "

Строки.

Другая строка — с предложением ввести имя пользователя — передается ниже методу print. Метод
print работает точно так же, как puts, за одним исключением: puts добавляет в конец выводимого текста символ новой строки (если там еще нет такого символа), а метод print этого не делает.
По эстетическим соображениям мы завершаем строку, передаваемую print, пробелом, чтобы выведенное сообщение немного отстояло от области, в которой пользователь будет вводить свое имя.
Погодите — вы назвали
print и puts методами… Разве
не нужно добавить оператор «точка»
для указания объекта, для которого
они вызываются?

Иногда получателя при вызовах методов
указывать не обязательно.
Методы puts и print настолько важны и так часто используются, что в Ruby они были
включены в среду выполнения верхнего уровня. Методы, определяемые в среде верхнего уровня, могут вызываться где угодно в коде Ruby без указания получателя. О том, как
определяются подобные методы, вы узнаете в начале главы 2.
44   глава 1

как сделать больше меньшими усилиями

Аргументы методов

Так это выглядит
в терминальном окне.

Метод puts получает строку и выводит ее на стандартное
устройство вывода (в терминальное окно).
puts "first line"

File Edit Window Help

first line
second line
third line
fourth line

Строка, передаваемая методу puts, называется аргументом
метода.
Метод puts может получать несколько аргументов: просто
разделите их запятыми. Каждый аргумент выводится в отдельной строке.
puts "second line", "third line", "fourth line"

«gets»
Метод gets (сокращение от «get string»)
читает строку из стандартного ввода (символы, вводимые в терминальном окне).
При вызове gets программа приостанавливается, пока пользователь не введет свое
имя и не нажмет клавишу Enter. Программа
получает введенный пользователем текст
как отдельную строку.

Вывод метода «gets».

input = gets
Присваивание новой
переменной «input».

Как и в случае с puts и print, метод gets может вызываться в любом месте кода без указания получателя.

Круглые скобки не обязательны
В языке Ruby аргументы методов могут заключаться в круглые
скобки:
puts("one", "two")
Однако круглые скобки не обязательны, и при вызове puts многие разработчики предпочитают их опускать.
puts "one", "two"
Как упоминалось выше, метод gets читает строку из стандартного
ввода. Аргументы ему (обычно) не нужны:
gets

Знатоки Ruby непреклонно считают, что если метод вызывается
без аргументов, то и круглых скобок быть не должно. Пожалуйста,
не используйте такие команды (хотя формально они ничего
не нарушают):
gets()

No!

Житейская
мудрость
Если метод не получает
аргументов, не включайте
круглые скобки при вызове. Круглые скобки можно
опустить и в том случае,
если аргументы есть, но
это может немного усложнить чтение кода. Если не
уверены — лучше поставьте
круглые скобки!

дальше 4   45

заполнение строк

Интерполяция строк
В последней строке нашего сценария puts вызывается еще с одной строкой. Этот вызов отличается от других тем, что в нем в строку интерполируется (подставляется) значение переменной.
Каждый раз, когда внутри строки встречается
запись #{...}, Ruby использует значение в фигурных скобках для «заполнения пробелов». Маркеры #{...} могут располагаться в любом месте
строки: в начале, в конце или где-то в середине.

Интерполяция
значения в строку.

puts "Welcome, #{input}"
Вывод

Welcome, Jay

Маркеры #{...} не ограничиваются использованием переменных — в них могут содержаться любые выражения Ruby.
puts "The answer is #{6 * 7}."

Вывод

Обратите внимание: Ruby применяет интерполяцию только
к строкам в двойных кавычках. Если включить маркер #{...}
в строку, заключенную в одинарные кавычки, он будет интерВывод
претирован буквально.
puts 'Welcome, #{input}'

The answer is 42.

Welcome, #{input}

часто

Задаваемые
вопросы

В:
О:

А где завершители «;»?

В Ruby символ «;» может использоваться для разделения
команд, но обычно так делать не рекомендуется (потому что
код сложнее читается).

puts "Hello";
puts "World";

Нет!

В Ruby отдельные строки считаются отдельными командами,
поэтому символы «;» становятся лишними.

puts "Hello"
puts "World"
46   глава 1

В:
О:

Мой другой язык потребовал бы, чтобы сценарий был
помещен в класс с методом «main». В Ruby этого нет?
Нет! И это одна из замечательных особенностей Ruby —
простые программы пишутся без лишних церемоний. Напишите
несколько команд, и работа завершена!

В Ruby простые программы
пишутся без лишних церемоний.

как сделать больше меньшими усилиями

Что там в строке?
File Edit Window Help

$ ruby get_number.rb
Welcome to 'Get My Number!'
What's your name? Jay
Welcome, Jay

Что это за
приветствие? Побольше
энтузиазма! Хотя бы поставьте
восклицательный знак
в конце приветствия!

К счастью, это сделать несложно. Восклицательный знак
добавляется в конец строки приветствия, после интерполируемого значения.
puts "Welcome to 'Get My Number!'"
print "What's your name? "
input = gets
puts "Welcome, #{input}!"
Всего один символ!

Но попытавшись запустить программу, вы увидите, что восклицательный знак не выводится после имени пользователя,
а опускается на следующую строку!
File Edit Window Help

Стоп! Почему он
перешел на следующую строку?

$ ruby get_number.rb
Welcome to 'Get My Number!'
What's your name? Jay
Welcome, Jay
!

Почему это происходит? Может, что-то не так с переменной input…
Но в результате вызова puts ничего особенного не видно. Если
присоединить следующую строку к приведенному выше коду, будет
получен следующий результат:
puts input

Jay
дальше 4   47

что на самом деле хранится в переменной

Анализ объектов методами «inspect» и «p»
Попробуем еще, на этот раз с использованием методов, предназначенных специально для отладки программ Ruby. Метод inspect
поддерживается всеми объектами Ruby. Он преобразует объект
в строковое представление, удобное для отладки. Иначе говоря,
метод раскрывает те стороны объекта, которые обычно не видны
в выходных данных программы.
Результат вызова inspect для нашей строки выглядит так:
puts input.inspect

"Jay\n"

A-HA!

Погодите, что это за \n в конце строки? Мы раскроем эту тайну на
следующей странице…
Вывод результата inspect является настолько частой операцией,
что в Ruby для него существует специальная сокращенная запись:
метод p. Этот метод работает точно так же, как puts, не считая того,
что он вызывает inspect для каждого аргумента перед его выводом.
Этот вызов p почти идентичен предшествующему коду:
p input

"Jay\n"

Запомните метод p; в последующих главах он будет использоваться
для отладки кода Ruby.

Метод «inspect» раскрывает
те стороны объекта, которые
обычно не видны в выходных
данных программы.

48   глава 1

как сделать больше меньшими усилиями

Служебные последовательности в строках
Наш вызов метода p показал, что в конце пользовательского ввода
располагаются какие-то «лишние» данные:

Часто используемые
служебные
последовательности

"Jay\n"

p input

Эти два символа, обратная косая черта (\) и n сразу же после нее, в действительности представляют один символ: символ новой строки. (Такое
название происходит из-за того, что при выводе этого символа курсор
в терминальном окне переходит на новую строку.) Данные, введенные
пользователем, завершаются символом новой строки, потому что пользователь нажимает клавишу Return, чтобы сообщить о завершении ввода,
и это нажатие клавиши сохраняется в виде дополнительного символа.
Этот символ включается в возвращаемое значение метода gets.

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

Если эта
последовательность
входит
в строку
в двойных
кавычках…

На практике чаще всего встречаются служебные последовательности
\n (новая строка, вы уже видели этот символ) и \t (символ табуляции
для создания отступов).
puts "First line\nSecond line\nThird line"
puts "\tIndented line"

First line
Second line
Third line
Indented line

…вы получите
следующий
символ…

\n

новая строка

\t

табуляция

\"

двойные
кавычки

\'

одиночная
кавычка

\\

обратная
косая черта

Обычно попытка включения двойной кавычки (") в строку, заключенную в двойные кавычки, приводит к тому, что внутренняя кавычка интерпретируется как признак завершения строки, что приводит к ошибке:
syntax error, unexpected
Ошибка
puts ""It's okay," he said."
tCONSTANT
Если «экранировать» внутреннюю двойную кавычку, поставив перед ней обратную косую черту,
то ее можно будет включить в строку, заключенную в двойные кавычки, и это не приведет к ошибке.
puts "\"It's okay,\" he said."

"It's okay," he said.

Наконец, раз символ \ отмечает начало служебной последовательности, нам также понадобится
способ представления символа обратной косой черты, который не является частью служебной последовательности. Комбинация \\ обозначает «фактический» символ обратной косой черты.
puts "One backslash: \\"

One backslash: \

Учтите, что большинство служебных последовательностей действует только в строках, заключенных
в двойные кавычки. В строках, заключенных в одинарные кавычки, служебные последовательности
обычно интерпретируются буквально.
puts '\n\t\"'
\n\t\"
дальше 4   49

методы объекта

Вызов «chomp» для объекта строки
File Edit Window Help

Выходит, вывод был
нарушен из-за того, что к строке
пользовательского ввода был
добавлен лишний символ новой
строки. И что теперь с этим делать?

$ ruby get_number.rb
Welcome to 'Get My Number!'
What's your name? Jay
Welcome, Jay
!

Нужно удалить символ новой
строки вызовом метода chomp.
Если текстовые данные завершаются символом новой строки,
то вызов метода chomp удалит его. Этот метод очень удобен для
очистки строк, полученных при вызове gets.
Метод chomp обладает более узкой специализацией, чем методы
print, puts и gets: он доступен только для объектов строк. Это
означает, что строка, на которую ссылается переменная input,
должна быть указана как получатель метода chomp. К переменной
input необходимо применить оператор «точка».
# Get My Number Game
# Written by: you!
Возвращаемое
значение «chomp»
сохраняется в новой
переменной «name».
Строка в «input»
является получателем метода
«chomp».

puts "Welcome to 'Get My Number!'"
print "What's your name? "
input = gets
name = input.chomp

Вызов метода «chomp».

Оператор «точка».
puts "Welcome, #{name}!"
В приветствии используется
«name» вместо «input».

Метод chomp возвращает ту же строку, но без завершающего
символа новой строки. Результат сохраняется в новой переменной name, которая затем выводится в составе приветственного сообщения.
Если снова запустить программу,
вы увидите, что наше эмоциональное приветствие теперь работает
правильно.

50   глава 1

File Edit Window Help

$ ruby get_number.rb
Welcome to 'Get My Number!'
What's your name? Jay
Welcome, Jay!

как сделать больше меньшими усилиями

Какие методы доступны для объекта?
Не стоит полагать, что вы можете вызвать любой метод для любого объекта. Например,
следующая попытка приведет к ошибке:
puts 42.upcase Ошибка

undefined method `upcase' for 42:Fixnum (NoMethodError)

Хотя если подумать, это логично. Попытка перевести число
в верхний регистр не имеет особого смысла, верно?
Но тогда какие методы можно вызывать для числа?
Чтобы получить ответ на этот вопрос, можно воспользоваться еще одним методом с именем methods:

puts 42.methods

Вызвав метод methods для строки,
вы получите другой список: puts "hello".methods

to_i
length
upcase
...

Почему списки различаются? Это связано с классом объекта.
Класс представляет собой «шаблон» для создания новых объектов. Среди прочего он определяет, какие методы могут вызываться для создаваемого объекта.
Также существует другой метод, который сообщает, к какому
классу относится объект. Он называется class (кто бы мог подумать!). Давайте опробуем его на нескольких объектах.
Fixnum
String
TrueClass
В следующей главе мы поговорим о классах более подробно,
так что следите за новостями.
puts 42.class
puts "hello".class
puts true.class

льзова
о имя. Испо
ег
ка
о
гр
и
у
Запросить
ия.
да приветств
имя для выво

ть

to_s
abs
odd?
...

И еще
очень
много!

И еще намного
больше — здесь
не поместятся!

часто

В:
О:

Задаваемые
вопросы
Как узнать, что делают все эти методы?

О том, как найти документацию по методам
класса, вы узнаете в главе 11. А пока многие
из этих методов вам попросту не понадобятся (а возможно, и никогда не понадобятся).
Не беспокойтесь: если метод действительно
важен, мы непременно расскажем, как его использовать!

Собственно, это весь код
первого требования.
Его можно вычеркнуть
из списка!
дальше 4   51

случайные числа

Генерирование случайного числа
С приветствием мы разобрались, можно
переходить к следующему требованию.

ть
я. Использова
грока его им
и
у
ть
си
о
р
Зап
ия.
да приветств
имя для выво
о от 1 до 100
учайное числ
сл
ь
ат
ов
р
и
будет угадать
Сгенер
Игрок должен
о.
ег
ть
и
ан
и сохр
это число.

Метод rand генерирует случайное число в заданном диапазоне. Пожалуй,
мы сможем воспользоваться им для генерирования загаданного числа.
При вызове rand передается аргумент — число, определяющее верхнюю
границу диапазона (100). Посмотрим, как он работает:
puts rand(100)
puts rand(100)

67
25

Вроде бы неплохо, но есть одна проблема: rand генерирует числа в диапазоне от нуля до числа, предшествующего заданному максимуму. А это
означает, что случайные числа будут генерироваться в диапазоне 0–99,
а не 1–100, как нам нужно.
Впрочем, проблема легко решается — достаточно прибавить 1
к значению, полученному при
вызове rand. И мы снова возвращаемся к диапазону 1–100!
rand(100) + 1

Результат сохраняется в новой
переменной с именем target.
д!
Наш новый ко

52   глава 1

# Get My Number Game
# Written by: you!
puts "Welcome to 'Get My Number!'"
# Получение имени игрока и вывод приветствия.
print "What's your name? "
input = gets
name = input.chomp
puts "Welcome, #{name}!"
# Сохранение случайного числа.
puts "I've got a random number between 1 and 100."
puts "Can you guess it?"
target = rand(100) + 1

как сделать больше меньшими усилиями

Преобразование числа в строку
Еще одно требование выполнено! Переходим к следующему...

1 до 100
ное число от
ай
уч
сл
ь
ат
ов
угадать
Сгенерир
должен будет
ок
гр
И
о.
ег
ть
и сохрани
это число.
ал
опыток сдел
м, сколько п
те
ть,
и
за
бщ
ть
о
со
ди
Сле
опыткой
п
й
до
ж
ка
).
талось (из 10
игрок. Перед
ыток у него ос
оп
п
е
ещ
о
ьк
скол

«Следить за тем, сколько попыток сделал игрок…» Похоже, нам понадобится переменная для хранения количества попыток. Разумеется,
в начале игры ни одной попытки еще не сделано, поэтому при создании переменной num_guesses должно присваиваться значение 0.
num_guesses = 0

Первое, что приходит в голову для вывода количества оставшихся
попыток, — конкатенация (сцепление) строк со знаком «плюс» (+),
как это делается во многих других языках. Однако такое решение,
как показано ниже, работать не будет:
remaining_guesses = 10 — num_guesses
puts remaining_guesses + " guesses left."

Ошибка!

Оператор + используется не только для сложения чисел, но и для конкатенации строк, а поскольку remaining_guesses содержит число,
знак «плюс» воспринимается как попытка суммирования чисел.
Что делать? Нужно преобразовать число в строку. Почти у всех объектов Ruby есть метод to_s для выполнения такого преобразования;
попробуем воспользоваться этим методом.
remaining_guesses = 10 — num_guesses
puts remaining_guesses.to_s + " guesses left."

10 guesses left.

Работает! Преобразование числа в строку сначала четко показывает
Ruby, что выполняется конкатенация, а не сложение.
Впрочем, в Ruby существует и более простой способ…

дальше 4   53

преобразование в строку

Упрощенная работа со строками в Ruby
Вместо того чтобы вызывать to_s, мы можем избавиться от лишних
хлопот с явным преобразованием числа в строку, воспользовавшись
интерполяцией. Как вы видели в коде приветствия пользователя,
при включении #{...} в строку, заключенную в двойные кавычки,
Ruby вычисляет результат кода в фигурных скобках, преобразует
его в строку в случае необходимости, после чего подставляет его
в более длинную строку.
Автоматическое преобразование в строку означает, что мы можем
обойтись без вызова to_s.
remaining_guesses = 10 — num_guesses
puts "#{remaining_guesses} guesses left."

Ruby позволяет размещать операторы в фигурных скобках, так
что от переменной remaining_guesses тоже можно избавиться:

10 guesses left.

puts "#{10 — num_guesses} guesses left."

10 guesses left.

puts "You've got #{10 — num_guesses} guesses left."

You've got 10 guesses left.

Так как конструкция #{...} может располагаться в любом месте
строки, мы можем переместить ее в середину, чтобы вывод стал
чуть более понятным для пользователя.

Теперь игрок знает, сколько попыток у него осталось. А значит,
из списка можно вычеркнуть еще один пункт!
# Get My Number Game
# Written by: you!
puts "Welcome to 'Get My Number!'"
# Получение имени игрока и вывод приветствия.
print "What's your name? "
input = gets
name = input.chomp
puts "Welcome, #{name}!"
# Сохранение случайного числа
puts "I've got a random number between 1 and 100."
puts "Can you guess it?"
target = rand(100) + 1
д!
Наш новый ко

54   глава 1

# Отслеживание количества попыток.
num_guesses = 0
puts "You've got #{10 — num_guesses} guesses left."

как сделать больше меньшими усилиями

Преобразование строк в числа

ал
опыток сдел
м, сколько п
те
ть,
и
за
бщ
ть
о
со
ди
Сле
опыткой
п
й
до
ж
ка
(из 10).
игрок. Перед
его осталось
н
у
к
то
ы
оп
п
сколько еще
число.
игроку ввести
Предложить

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

В каждом из этих сравнений
происходит ошибка!

Строку, полученную от метода gets, необходимо преобразовать в число, чтобы сравнить его с загаданным числом. Проще простого! У строк
существует метод to_i, который выполнит преобразование за нас.
Приведенный ниже код вызывает to_i для строки, полученной gets.
Строку даже не нужно сохранять в переменной; мы просто воспользуемся оператором «точка», чтобы вызвать метод прямо для возвращаемого
значения.
guess = gets.to_i

При вызове метода to_i для строки игнорируются все нецифровые
символы, следующие за числом. А это означает, что нам даже не придется
удалять символ новой строки, оставшийся от gets.
Чтобы проверить внесенные изменения, можно вывести результат сравнения.
puts guess < target

преобразования
Вызывая
этот
метод
для объекта…

…вы получаете объект
этого типа.

to_i

целое число

to_s

to_f

строка
вещественное число

...

true

Так гораздо лучше — число, введенное пользователем, сравнивается с загаданным. Еще одно
требование выполнено!
д!
Наш новый ко

# Store a random number for the player to guess.
puts "I've got a random number between 1 and 100."
puts "Can you guess it?"
target = rand(100) + 1
# Track how many guesses the player has made.
num_guesses = 0
puts "You've got #{10 — num_guesses} guesses left."
print "Make a guess: "
guess = gets.to_i
дальше 4   55

что, если...

Условные команды
Еще два требования закрыты, осталось четыре!
Переходим к следующей группе.
число.
игроку ввести
Предложить
о меньше заигроком числ
ое
r
н
ен
ед
вв
Если
е: «Oops. You
сти сообщени
ве
вы
ль
о,
бо
о
ог
н
сл
н
гада
ое чи
Если введенн
».
s.
W
O
op
L
«O
as
w
е:
s
и
gues
общен
о, вывести со
ше загаданног
HIGH».
Your guess was
т с загаданисло совпадае
ч
е
о
н
ен
ед
d job, [имя]!
Если вв
щение: «Goo
об
со
и
ст
ве
сло попыток]
ным, вы
umber in [чи
n
y
m
d
se
es
You gu
guesses!»
и, но так и
ил все попытк
ат
р
от
п
ок
гр
«Sorry. You
Если и
и сообщение:
ст
ве
вы
,
ал
ber was [загане угад
ber. My num
m
u
n
y
m
t
ge
didn’t
».
данное число]
Теперь число, введенное пользователем, необходимо сравнить
с загаданным. Если оно слишком велико, выводится соответствующее сообщение. Иначе, если число слишком мало, выводится
другое сообщение, и так далее… Похоже, нам нужна возможность выполнения отдельных фрагментов кода только при
выполнении определенных условий.
Как и во многих языках, в Ruby существуют условные команды,
выполняемые только при наличии определенных условий. Программа проверяет выражение и, если его результат оказывается
истинным, выполняет код тела условной команды. В противном
случае тело команды пропускается.
Как и многие другие языки, Ruby
позволяет определить несколько
ветвей в условной команде. Такие
команды записываются в форме
if/elsif/else.
Чтобы определить, должен ли выполняться код, условные команды
используют логические (булевские) выражения. В Ruby существуют константы, представляющие два
логических значения: true и false.
56   глава 1

Обратите внимание: в середине «elsif» нет
второй буквы
«e»!

Начало условной
команды.

Логическое
выражение.

if 1 < 2
puts "It's true!"
end
Конец условной
команды.

Тело условной
команды.

if score == 100
puts "Perfect!"
elsif score >= 70
puts "You pass!"
else
puts "Summer school time!"
end

if true
puts "I'll be printed!"
end

if false
puts "I won't!"
end

как сделать больше меньшими усилиями

Условные команды (продолжение)
if 1 == 1
puts "I'll be printed!"
end

if 1 >= 2
puts "I won't!"
end

if 1 > 2
puts "I won't!"
end
if 1 < 2
puts "I'll be printed!"
end

if 2 target
puts "Oops. Your guess was HIGH."
elsif guess == target
puts "Good job, #{name}!"
puts "You guessed my number in #{num_guesses} guesses!"
guessed_it = true
end
# Если попыток не осталось, сообщить загаданное число.
if not guessed_it
puts "Sorry. You didn't get my number. (It was #{target}.)"
end

как сделать больше меньшими усилиями

«unless» как противоположность «if»
Эта команда работает, но читать ее неудобно:
if not guessed_it
puts "Sorry. You didn't get my number. (It was #{target}.)"
end

Во многих отношениях условные команды Ruby похожи на условные команды других языков. Однако в Ruby также существует
дополнительное ключевое слово unless.
Код команды if выполняется только в том случае, если условие
истинно. С другой стороны, код команды unless выполняется
только в том случае, если условние ложно.
unless true
puts "I won't be printed!"
end

unless false
puts "I will!"
end

Ключевое слово unless — один из примеров того, как язык Ruby
упрощает чтение кода. Его можно использовать тогда, когда условие с оператором отрицания выглядит громоздко. Конструкция:
if ! (light == "red")
puts "Go!"
end

записывается в следующем виде:
unless light == "red"
puts "Go!"
end

Ключевое слово unless поможет упростить последнюю условную
команду.

Житейская
мудрость
Ключевые слова else и
elsif могут использоваться с unless в языке Ruby:
unless light == "red"
puts "Go!"
else
Странно!
puts "Stop!"
end

Но такой код читается
очень плохо. Если вам нужна секция else, лучше используйте if в основной
ветви!
if light == "red"
puts "Stop!"
else
puts "Go!" Переместилось в основend
ную ветвь.

unless guessed_it
puts "Sorry. You didn't get my number. (It was #{target}.)"
end

Получилось намного понятнее! При
этом условные команды работают
так же, как прежде!
енарий
Если запустить сц
с, реget_number.rb сейча
ядеть
гл
зультат будет вы
примерно так…

В текущем состоянии программа
предоставляет игроку всего одну
попытку, а должно быть 10. Сейчас
мы исправим этот недостаток…

File Edit Window Help

$ ruby get_number.rb
Welcome to 'Get My Number!'
What's your name? Jay
Welcome, Jay!
I've got a random number between 1 and 100.
Can you guess it?
You've got 10 guesses left.
Make a guess: 50
Oops. Your guess was HIGH.
Sorry. You didn't get my number. (It was 34.)

дальше 4   59

снова и снова

Циклы
Пока все замечательно!
В нашей игре осталось
реализовать всего одно
требование!

ьше заом число мен
ок
гр
и
ое
н
ops. Your
Если введен
общение: «O
со
и
ст
ве
вы
о больгаданного,
еденное числ
вв
и
сл
Е
».
W
O
«Oops.
guess was L
и сообщение:
ст
ве
вы
о,
ог
н
ше загадан
HIGH».
Your guess was
ает с загадан
число совпад
е
о
!
н
я]
ен
м
ед

вв
b,
Если
«Good jo
и сообщение:
сло попыток]
ным, вывест
umber in [чи
n
y
m
d
se
es
You gu
guesses!»
и
ытки, но так
ратил все поп
от
u
п
Yo
ок
y.
гр
rr
и
o
и
«S
Есл
ие:
сти сообщен
ве
ааг
вы
,

ал
as
w
ад
уг
не
y number
M
.
er
b
m
u
n
didn’t get my
».
данное число]
о, пока он не
вводить числ
у
ок
гр
и
ть
га
ытки.
Предла
ратит все поп
от
п
е
н
ли
и
т
угадае

В настоящее время игроку дается всего одна попытка. Угадать одно число из ста возможных непросто, так что такую игру честной не назовешь.
Игрок должен угадывать 10 раз — или до тех пор,
пока не получит правильный ответ (в зависимости от того, что произойдет раньше).
Код, запрашивающий у пользователя число, уже
готов. Просто нам нужно выполнить его многократно. Для повторного выполнения сегмента
кода можно использовать цикл. Возможно, вы
уже встречали циклы в других языках программирования. Если программист хочет, чтобы одна
или несколько команд выполнялись снова и снова, он помещает их в цикл.
Цикл while состоит из ключевого слова while,
логического выражения (как и в командах if
и unless), выполняемого кода и ключевого слова
end. Код в теле цикла продолжает выполняться,
пока условие остается истинным.
В следующем простом примере цикл используется для вывода последовательных значений.
number = 1
while number 5
2
3
puts number
4
number += 1
5
end

как сделать больше меньшими усилиями

Перед вами тот же условный код, измененный для выполнения в цикле while:
Цикл останавливается
после 10-й попытки
или после того, как
число будет угадано
правильно (в зависимости от того, что
произойдет раньше).
Этот код совершенно не изменился;
просто мы поместили его в цикл.
При каждом выполнении цикла
счетчик увеличивается на 1, чтобы цикл не выполнялся бесконечно.

Здесь тоже ничего
не изменилось.

Это ключевое
слово обозначает
конец кода, который будет выполняться в цикле.

# Отслеживание количества попыток
num_guesses = 0
# Признак продолжения игры.
guessed_it = false
while num_guesses < 10 && guessed_it == false
puts "You've got #{10 — num_guesses} guesses left."
print "Make a guess: "
guess = gets.to_i
num_guesses += 1
# Сравнение введенного числа с загаданным
# и вывод соответствующего сообщения.
if guess < target
puts "Oops. Your guess was LOW."
elsif guess > target
puts "Oops. Your guess was HIGH."
elsif guess == target
puts "Good job, #{name}!"
puts "You guessed my number in #{num_guesses} guesses!"
guessed_it = true
end
end
unless guessed_it
puts "Sorry. You didn't get my number. (It was #{target}.)"
end

Осталось еще одно улучшение, которое упростит чтение кода. Как и в случае
с командой if, которая была заменена командой unless, этот цикл while можно сделать более понятным. Для этого его следует преобразовать в цикл until.
До:

while num_guesses < 10 && guessed_it == false
...
end

После:

until num_guesses == 10 || guessed_it
...
end
дальше 4   61

полный код игры

Полный
код нашей
игры.

# Get My Number Game
# Written by: you!
puts "Welcome to 'Get My Number!'"
# Получение имени игрока и вывод приветствия.
print "What's your name? "
input = gets
name = input.chomp
puts "Welcome, #{name}!"
# Сохранение случайного числа.
puts "I've got a random number between 1 and 100."
puts "Can you guess it?"
target = rand(100) + 1
# Отслеживание количества попыток.
num_guesses = 0
# Признак продолжения игры.
guessed_it = false

until num_guesses == 10 || guessed_it
puts "You've got #{10 — num_guesses} guesses left."
print "Make a guess: "
guess = gets.to_i
num_guesses += 1
# Сравнение введенного числа с загаданным
# и вывод соответствующего сообщения.
if guess < target
puts "Oops. Your guess was LOW."
elsif guess > target
puts "Oops. Your guess was HIGH."
elsif guess == target
puts "Good job, #{name}!"
puts "You guessed my number in #{num_guesses} guesses!"
guessed_it = true
end
end
# Если попыток не осталось, сообщить загаданное число.
unless guessed_it
puts "Sorry. You didn't get my number. (It was #{target}.)"
end

62   глава 1

get_number.rb

как сделать больше меньшими усилиями

Попробуем запустить игру!
Цикл готов, а последнее требование выполнено! Давайте откроем
терминальное окно и попробуем
запустить программу.

сло, пока он
у вводить чи
ок
гр
и
ть
га
ытки.
Предла
ратит все поп
от
п
е
н
ли
и
т
не угадае

File Edit Window Help Cheats

$ ruby get_number.rb
Welcome to 'Get My Number!'
What's your name? Gary
Welcome, Gary!
I've got a random number between 1 and 100.
Can you guess it?
You've got 10 guesses left.
Make a guess: 50
Oops. Your guess was LOW.
You've got 9 guesses left.
Make a guess: 75
Oops. Your guess was HIGH.
You've got 8 guesses left.
Make a guess: 62
Oops. Your guess was HIGH.
You've got 7 guesses left.
Make a guess: 56
Oops. Your guess was HIGH.
You've got 6 guesses left.
Make a guess: 53
Good job, Gary!
You guessed my number in 5 guesses!
$

Вы сделали все, что
требовалось, — и притом
быстро! Игрокам это
определенно понравится!

Итак, мы написали полноценную игру на Ruby с использованием переменных, строк, вызовов методов, условных команд
и циклов! А самое замечательное, что программа заняла менее
30 строк кода! Налейте себе чего-нибудь прохладительного,
вы это заслужили.
дальше 4   63

Ваш инструментарий Ruby
Глава 1 осталась позади. В ней
ваш инструментарий Ruby пополнился вызовами методов, условными командами и циклами.

КЛЮЧЕВЫЕ
МОМЕНТЫ
ƒƒ Ruby — интерпретируемый язык. Код Ruby
не нужно компилировать перед выполнением.
ƒƒ Переменные не нужно объявлять перед присваиванием им значений. Также не нужно
указывать тип переменной.
ƒƒ Ruby рассматривает все символы от # до конца строки как комментарий и игнорирует их.
ƒƒ Текст, заключенный в кавычки, рассматривается как строка, то есть последовательность
символов.

ды

Коман

т
полняю
нды вы
а
им
о
к
ые
в завис
Условн
их код
н
в
я
с
овия.
ащий
ого усл
содерж
р
о
т
о
от нек
мости
ащийсодерж
т
ю
я
кл
выполн
но. Ци
Циклы
ократ
г
о
н
м
му
х код
оторо
ся в ни
по нек
я
с
т
е
ва
преры
ю.
и
услов

ƒƒ Если в строку Ruby входит конструкция
#{...}, то выражение в фигурных скобках
интерполируется (подставляется) в строку.
ƒƒ При вызове методов могут передаваться
аргументы, разделенные запятыми.
ƒƒ Заключать список аргументов в круглые скобки
не обязательно. Если аргументы отсутствуют,
не ставьте пустые круглые скобки.
ƒƒ Используйте методы inspect и p для просмотра отладочной информации по объектам
Ruby.
ƒƒ Для включения специальных символов в строки, заключенные в двойные кавычки, используются служебные последовательности (такие,
как \n и \t).
ƒƒ Интерактивный интерпретатор Ruby (или irb)
позволяет быстро проверить результаты выражений Ruby.
ƒƒ Вызов метода to_s для (почти) любого объекта возвращает строковое представление
объекта. Вызов to_i для строки преобразует
ее в целое число.

Далее в программе…

ƒƒ unless — противоположность if ; код
выполняется в том случае, если условие
ложно.

Весь код сейчас свален в одну большую кучу. В следующей главе вы узнаете, как разбить код на простые,
удобные блоки при помощи классов и методов.

ƒƒ until — противоположность while; цикл
выполняется многократно, пока условие
не станет истинным.

64   глава 1

2 методы и классы

Наводим порядок
И как хоть что-то найти
во всем этом коде? Почему
разработчик не разбил его
на методы и классы...

В вашей работе кое-чего не хватало.Да, вы вызывали методы и создавали объекты, как настоящий профессионал. Но при этом вы могли вызывать
только те методы и создавать только те виды объектов, которые были определены
за вас в Ruby. Теперь ваша очередь. В этой главе вы научитесь создавать свои
методы, а также свои классы — своего рода «шаблоны» для создания новых
объектов. Вы сами решаете, как будут выглядеть объекты, созданные на базе
вашего класса. Переменные экземпляра определяют, какая информация хранится в ваших объектах, а методы экземпляра определяют, что эти объекты
делают. А самое главное — вы узнаете, как определение собственных классов
упрощает чтение и сопровождение вашего кода.

ваши методы

Определение методов
Компания Got-A-Motor, Inc. работает над приложением виртуального тест-драйва: клиент опробует новую
машину прямо на своем компьютере, без похода в автосалон. В первой версии приложения компания
хочет видеть методы, с помощью которых пользователь сможет нажать виртуальную педаль газа, подать
виртуальный сигнал и включить виртуальные фары в режиме ближнего или дальнего света.
Определения методов в языке Ruby выглядят примерно так:
я.
Начало определени

Имя метода.

Параметры.

def print_sum(arg1, arg2)
print arg1 + arg2
Тело метода.
end
Конец определения.

Если вы хотите, чтобы при вызове вашего метода передавались аргументы, в определение метода необходимо добавить параметры. Параметры следуют после имени метода и заключаются в круглые скобки.
(При отсутствии параметров круглые скобки можно не указывать.) Каждый аргумент в вызове метода
сохраняется в одном из параметров внутри метода.
Тело метода состоит из одной или нескольких команд Ruby, которые выполняются при вызове метода.
Давайте создадим методы, представляющие действия пользователя в приложении виртуального тестдрайва.
Перед вами два метода для
наращивания скорости и подачи сигнала. Пожалуй, это
едва ли не самые простые
методы, которые можно написать на Ruby: тело каждого метода состоит из пары
команд, которые выводят
строки.

Метод не получает
параметров.
def accelerate
т
ду
puts "Stepping on the gas"
Эти команды бу
puts "Speeding up"
выполняться при
end
вызове метода.
Метод не получает
параметров.
def sound_horn
puts
"Pressing
the
horn
button"
Эти команды будут
puts
"Beep
beep!"
выполняться при
end
вызове метода.

Метод use_headlights ненамного сложнее; он получает один
параметр, который интерполируется в одну из выходных строк.
Один параметр.
def use_headlights(brightness)
puts "Turning on #{brightness} headlights"
puts "Watch out for deer!"
Параметр используется
end
при выводе.

Вот и всё! Определения готовы, теперь методы можно вызывать в программе.
66

глава 2

методы и классы

Вызов методов
Методы, определенные вами, вызываются точно так же, как и любые другие
методы. Давайте попробуем вызвать
новые методы в автомобильном приложении.
Ruby позволяет размещать вызовы
методов где угодно — даже в том исходном файле, в котором эти методы
определяются. Пока наша программа
очень проста, мы именно так и поступим — просто для удобства. Вызовы
методов будут размещены сразу же
после их объявлений.

Запустите исходный файл в терминальном окне — вы увидите
результат вызова новых методов!

def accelerate
puts "Stepping on the gas"
puts "Speeding up"
end
def sound_horn
puts "Pressing the horn button"
puts "Beep beep!"
end
def use_headlights(brightness)
puts "Turning on #{brightness} headlights"
puts "Watch out for deer!"
end
Вызовы без
ументов.
арг
sound_horn
Передается как
accelerate
значение аргуменuse_headlights("high-beam")
та «brightness».

File Edit Window Help

vehicle_methods.rb

$ ruby vehicle_methods.rb
Pressing the horn button
Beep beep!
Stepping on the gas
Speeding up
Turning on high-beam headlights
Watch out for deer!
$

Я заметил, что мы
не использовали оператор
«точка» для определения
получателя при этих вызовах,
как и в случае с методами
puts и print.

Верно. Как и методы puts и print,
эти методы включаются в среду
выполнения верхнего уровня.

Методы, определяемые вне любых классов (как в нашем примере), включаются в среду выполнения верхнего уровня. Как было показано в главе 1, их можно вызывать
в любом месте кода без указания получателя с использованием оператора «точка».
дальше4   67

правила методов

Имена методов
Имя метода может состоять из нескольких слов, записанных
символами нижнего регистра и разделенных символами
подчеркивания (по тем же правилам, что и имена переменных). Цифры в именах методов допустимы, но используются редко.
Также имя метода может завершаться вопросительным (?)
или восклицательным знаком (!). Такие суффиксы не имеют
специального смысла в языке Ruby, но, по общепринятым
соглашениям, именам, возвращающим логическое значение (true/false), присваиваются имена, завершающиеся
знаком «?», а методам с возможными неожиданными побочными эффектами присваиваются имена, завершающиеся
знаком «!».
Наконец, имя метода может завершаться знаком равенства
(=). Методы, имена которых завершаются этим символом,
используются для назначения атрибутов (мы рассмотрим
их позднее, когда будем рассматривать классы). В языке
Ruby этот суффикс имеет специальный смысл, поэтому не
применяйте его в обычных методах — или вы увидите, что
ваш метод ведет себя странно!

Параметры
Если методу должны передаваться данные, укажите после
имени метода один или несколько параметров, разделенных
запятыми. В теле метода к параметрам можно обращаться
точно так же, как к любым другим переменным.
def print_area(length, width)
puts length * width
end

Необязательные параметры
Разработчики из Got-A-Motor довольны вашей работой над
системой виртуального тест-драйва… в основном.

А указывать аргумент при
вызове метода use_headlights
обязательно? Мы почти всегда
используем значение "lowbeam", и эта строка повторяется
в нескольких местах кода!

68  

Житейская
мудрость
Имена методов следует записывать
в «змеином» стиле: одно или несколь­
ко слов в нижнем регистре, разделен­
ных подчеркиваниями (как в именах
переменных).
def bark
end
def wag_tail
end

Как и при вызове методов, не ставьте
круглые скобки в определении метода
при отсутствии параметров. Пожалуй­
ста, не делайте так (притом что эта
запись формально допустима):
def no_args()
puts "Bad Rubyist!"
end

Но если параметры передаются, круг­
лые скобки ставятся всегда. (В гла­
ве 1 упоминались исключения, отно­
сящиеся к вызову методов, но при
объявлении методов исключений не
бывает.) Формально круглые скобки
можно опустить, но мы еще раз гово­
рим: не надо так делать.
def with_args first, second
puts "No! Bad!"
end

use_headlights("low-beam")
stop_engine
buy_coffee
start_engine
use_headlights("low-beam")
accelerate
create_obstacle("deer")
use_headlights("high-beam")

методы и классы

Необязательные параметры (продолжение)
Ситуация достаточно распространенная — один конкретный аргумент используется
в 90% случаев, и вам надоело его повторять. Но и убрать его тоже нельзя, потому что
в 10% случаев используется другое значение.
У проблемы есть простое решение: сделайте параметр необязательным. В объявлении
метода можно задать для него значение по умолчанию.
Пример метода, использующего значения по умолчанию для некоторых параметров:
def order_soda(flavor, size = "medium", quantity = 1)
if quantity == 1
plural = "soda"
Значение
Значение
else
по умолчанию
по умолчанию
plural = "sodas"
для quantity.
size.
для
end
puts "#{quantity} #{size} #{flavor} #{plural}, coming right up!"
end

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

Задается параметр flavor;
для size и quantity использу.
ются значения по умолчанию

order_soda("orange")
order_soda("lemon-lime", "small", 2)
order_soda("grape", "large")

Задаются все
значения.

Задаются параметры
flavor и size; для quantity
используется значение
по умолчанию.

1 medium orange soda, coming right up!
2 small lemon-lime sodas, coming right up!
1 large grape soda, coming right up!
При использовании необязательных параметров необходимо
помнить об одном правиле: они должны следовать после всех
остальных параметров. Если обязательный параметр следует
в списке за необязательным, то опустить необязательный
параметр не удастся:
def order_soda(flavor, size = "medium", quantity)
...
Необязательный параметр
end
не должен находиться перед
обязательным!
order_soda("grape")

Ошибка

wrong number of
arguments (1 for 2..3)

часто

В:
О:

Задаваемые
вопросы

Чем аргументы отличаются от параметров?
Параметры определяются
и используются в определении метода.
Аргументы передаются при вызове
метода.
Параметр

def say_hello(name)
puts "Hello, #{name}!"
end
Параметр
say_hello("Marcy")
Аргумент

Каждый аргумент, передаваемый при
вызове метода, сохраняется на месте
соответствующего параметра.
Эти два термина в основном
используются для того, чтобы понять,
о чем идет речь: об определении
или о вызове метода.
дальше4   69

необязательные параметры

Необязательные параметры (продолжение)
Так давайте сделаем параметр use_headlights необязательным, чтобы упростить жизнь разработчикам, использующим
наши методы.
def use_headlights(brightness = "low-beam")
puts "Turning on #{brightness} headlights"
puts "Watch out for deer!"
end

Да, в таком варианте
сценарии заметно
упрощаются! Спасибо!

Теперь указывать значение не обязательно, если только не
понадобится режим high-beam.
ользовать значение

Исп
use_headlights
по умолчанию «low-beam».
use_headlights("high-beam")
Переопределить значение по умолчанию.

Turning on low-beam headlights
Watch out for deer!
Turning on high-beam headlights
Watch out for deer!
use_headlights
stop_engine
Аргументы
start_engine
не нужны!
use_headlights
accelerate
use_headlights("high-beam")
Наши методы для приложения виртуального тест-драйва готовы.
Попробуем загрузить их в irb и опробовать в деле.

Упражнение
Шаг 1:

Шаг 2:

70

глава 2

Сохраните определения
методов в файле с именем vehicle_methods.rb.
Откройте терминальное окно и перейдите
в каталог, в котором
был сохранен файл.

def accelerate
puts "Stepping on the gas"
puts "Speeding up"
end

vehicle_methods.rb

def sound_horn
puts "Pressing the horn button"
puts "Beep beep!"
end
def use_headlights(brightness = "low-beam")
puts "Turning on #{brightness} headlights"
puts "Watch out for deer!"
end

методы и классы

Упражнение (продолжение)
Шаг 3:

Так как мы собираемся загрузить в irb код из файла, интерпретатор
должен иметь возможность загружать файлы Ruby из текущего
каталога. По этой причине команда запуска irb на этот раз будет
выглядеть немного иначе.
Введите следующую команду в терминальном окне и нажмите Enter:
irb -I .

Это означает: «искать файлы для загрузки в текущем каталоге».

-I — параметр командной строки, который добавляется в команду для
изменения режима ее выполнения. В данном случае -I изменяет
набор каталогов, в которых Ruby ищет загружаемые файлы. Точка
(.) обозначает текущий каталог.
Шаг 4:

К этому моменту интерпретатор irb запущен, а мы можем загрузить файл с нашими методами. Введите следующую строку:
require "vehicle_methods"
Ruby знает, что по умолчанию следует искать файлы с расширением .rb, поэтому расширение в команде не указано. Если
будет выведен результат true, значит, файл загрузился успешно.

Теперь вы сможете ввести
команду вызова любого
из наших методов, и этот
метод будет выполнен!

Пример:

File Edit Window Help

$ irb -I .
irb(main):001:0> require "vehicle_methods"
=> true
irb(main):002:0> sound_horn
Pressing the horn button
Beep beep!
=> nil
irb(main):003:0> use_headlights
Turning on low-beam headlights
Watch out for deer!
=> nil
irb(main):004:0> use_headlights("high-beam")
Turning on high-beam headlights
Watch out for deer!
=> nil
irb(main):005:0> exit
$

дальше4   71

возвращение из методов

Возвращаемые значения
Теперь компания Got-A-Motor хочет, чтобы в приложении выводилась
информация об эффективности использования топлива. Приложение должно выводить расход топлива за последнюю поездку, а также
средний расход топлива за весь срок эксплуатации.
В первом случае расстояние с путевого одометра делится на количество галлонов с последней заправки, а во втором — значение с основного одометра делится на количество галлонов за весь срок эксплуатации. Но в обоих случаях мы берем расстояние в милях и делим
его на количество галлонов. Так нужно ли писать два разных метода?
Нет! Как и в большинстве языков, методы Ruby имеют возвращае­
мое значение, которое передается вызвавшему их коду. Метод Ruby
возвращает значение вызывающей стороне с помощью ключевого
слова return.
Напишем один метод mileage и используем его возвращаемое значение в выводе.
def mileage(miles_driven, gas_used)
return miles_driven / gas_used
end

Методы возвращают
значение в код,
из которого они
были вызваны.

И тогда один метод можно будет использовать для вычисления обоих
видов расхода топлива.
trip_mileage = mileage(400, 12)
puts "You got #{trip_mileage} MPG on this trip."
lifetime_mileage = mileage(11432, 366)
puts "This car averages #{lifetime_mileage} MPG."

You got 33 MPG on this trip.
This car averages 31 MPG.

Неявные возвращаемые значения
Ключевое слово return в этом методе не обязательно.
Значение последнего выражения, вычисленного в методе,
автоматически становится возвращаемым значением этого
метода. Таким образом, метод mileage можно переписать
без явного включения команды return:
def mileage(miles_driven, gas_used)
miles_driven / gas_used
end

Работать он будет точно так же.
puts mileage(400, 12)

72

глава 2

33

Житейская
мудрость
Программисты, работающие на
Ruby, обычно предпочитают
неявные возвращаемые значе­
ния. В коротком методе нет
смысла использовать запись:
def area(length, width)
return length * width
end

…когда можно просто написать:
def area(length, width)
length * width
end

методы и классы

Раннее возвращение из метода
Тогда зачем в Ruby
ключевое слово return,
если обычно без него
можно обойтись?

Существуют обстоятельства,
в которых ключевое слово return
может оказаться полезным.
Ключевое слово return приводит к немедленному выходу из метода
без выполнения оставшегося кода. Это бывает полезно в ситуациях,
когда выполнение этого кода будет бессмысленно или даже вредно.
Например, представьте, что машина совсем новая и пробега у нее
еще нет. И расстояние, и затраты топлива могут быть равны нулю.
Что произойдет, если вызвать метод mileage для такой машины?
Как вы помните, метод mileage делит miles_driven на gas_used…
И как вас учили в других языках программирования, деление чеголибо на нуль является ошибкой!
puts mileage(0, 0)

Ошибка

in `/': divided by 0
(ZeroDivisionError)

Как решить эту проблему? Нужно проверить, равно ли значение
gas_used нулю, и если равно — преждевременно вернуть управление из метода.
def mileage(miles_driven, gas_used)
if gas_used == 0
Если бензин еще не расходовался…
…вернуть 0.
return 0.0
end
Для нулевого значения «gas_used»
miles_driven / gas_used
end
этот код выполняться не будет.

На этот раз код просто возвращает 0.0, не пытаясь делить на нуль.
Проблема решена!
puts mileage(0, 0)

0.0

Методы сильно помогают в упорядочении кода и устранении повторов.
Однако самих методов иногда оказывается недостаточно. Оставим на время друзей из Got-A-Motor и займемся вместо машин живыми существами…
дальше4   73

когда методов недостаточно

Беспорядок в методах
Сотрудники экологической организации Fuzzy Friends Animal Rescue в ходе кампании по привлечению средств решили создать интерактивное приложение. Они
обратились в вашу компанию за помощью. В их приложении есть много видов животных, каждое из которых издает некоторые звуки и выполняет некоторые действия.
Они создали методы для моделирования перемещений и звуков. При вызове этих
методов в первом аргументе передается тип животного, а за ним следуют любые
необходимые дополнительные аргументы.
На данный момент их код выглядит примерно так:
def talk(animal_type, name)
if animal_type == "bird"
puts "#{name} says Chirp! Chirp!"
elsif animal_type == "dog"
puts "#{name} says Bark!"
Выводимая строка
elsif animal_type == "cat"
выбирается в зависиputs "#{name} says Meow!"
мости от параметра
end
animal_type.
end
def move(animal_type, name, destination)
if animal_type == "bird"
puts "#{name} flies to the #{destination}."
elsif animal_type == "dog"
puts "#{name} runs to the #{destination}."
elsif animal_type == "cat"
puts "#{name} runs to the #{destination}."
всех тиend
Этот метод одинаков для
end
аметр
пов животных, поэтому пар
.
animal_type не используется
def report_age(name, age)
puts "#{name} is #{age} years old."
end

Несколько типичных примеров вызовов этих методов:
move("bird", "Whistler", "tree")
talk("dog", "Sadie")
talk("bird", "Whistler")
move("cat", "Smudge", "house")
report_age("Smudge", 6)

Whistler flies to the tree.
Sadie says Bark!
Whistler says Chirp! Chirp!
Smudge runs to the house.
Smudge is 6 years old.

Ребята из Fuzzy Friends хотят совсем немного: добавьте всего 10 типов животных, еще 30 новых действий — и версия 1.0 будет готова!

74

глава 2

методы и классы

Слишком много аргументов
Даже с тремя типами животных и двумя
действиями код получается слишком громоздким.
Команды «if» и «elsif» слишком длинные, а вы
только посмотрите на все эти аргументы! Нельзя ли
как-то упорядочить этот код?

Проблема отчасти связана с тем, что нам приходится передавать слишком много данных. Только взгляните, например,
на эти вызовы метода move:

Аргумент destination
необходим…

move("bird", "Whistler", "tree")
move("cat", "Smudge", "house")

Аргумент destination должен присутствовать, спору нет.
Чтобы перемещаться, нужно знать конечную точку. Но нельзя ли как-то обойтись без передачи значений animal_type
и name? В конце концов, становится трудно понять, что
означает каждый аргумент!

…но так ли необходимо каждый раз передавать эти значения?

Слишком много команд «if»
Кроме того, проблема не ограничивается аргументами методов — внутри самих методов
все тоже неблагополучно. Только представьте, как будет выглядеть метод talk, если
добавить еще 10 типов животных...
Каждый раз, когда от вас потребуют звуки, издаваемые животным (а от вас это потребуют, можете не сомневаться),
вам придется копаться во всех условиях elsif в поисках
нужного типа животного… А что будет, если код talk
усложнится — например, в него добавятся анимации и воспроизведение звуковых файлов? Что произойдет, если все
методы станут такими?
Нам нужен более удобный способ представления типа
животного, с которым работает код. Необходимо как-то
разбить этот код по типу животного, чтобы упростить его
сопровождение. И еще нужен более удобный способ хранения атрибутов каждого отдельного животного (таких,
как имя и возраст), чтобы нам не приходилось передавать
столько аргументов..
Данные животных и весь код, работающий с этими данными, должны храниться в одном месте. Короче, нам нужны
классы и объекты.

def talk(animal_type, name)
if animal_type == "bird"
puts "#{name} says Chirp! Chirp!"
elsif animal_type == "dog"
puts "#{name} says Bark!"
elsif animal_type == "cat"
puts "#{name} says Meow!"
elsif animal_type == "lion"
puts "#{name} says Roar!"
elsif animal_type == "cow"
puts "#{name} says Moo."
elsif animal_type == "bob"
puts "#{name} says Hello."
elsif animal_type == "duck"
puts "#{name} says Quack."
...
Для полного списка
end
не хватит места
end
на странице…
дальше4   75

о классах

Проектирование класса
Основное преимущество объектов заключается в том, что они позволяют хранить набор
данных и методы, работающие с этими данными, в одном месте. И эта возможность очень
пригодится в приложении Fuzzy Friends.
Но чтобы начать создавать собственные объекты, их необходимо сначала определить.
Класс представляет собой «шаблон» для создания объектов. Когда вы используете класс для
создания объекта, класс описывает, что этот объект знает о себе и что этот объект делает.

знает
делает

имя
пароль
зарегистрироваться
войти

знает
делает

Переменные экземпляра:
то, что объект знает о себе
Методы экземпляра:

Видео

Встреча

Пользователь

дата
место
напомнить
отменить

знает

делает

воспроизвести
остановить
перемотать

делает

переменные
экземпляра

имя
возраст

(состояние)

издавать звуки
двигаться
сообщить возраст

методы
экземпляра

Экземпляром класса называется объект, созданный на основе класса. Достаточно написать всего один класс, а потом создать много экземпляров этого
класса.
Считайте, что «экземпляр» и «объект» — одно и то же.
Переменными экземпляра называются переменные, принадлежащие одному объекту. Они составляют всю информацию, которая известна объекту
о нем самом. Переменные экземпляра представляют состояние объекта (его
данные) и могут иметь разные значения для разных экземпляров этого класса.
Методами экземпляра называются методы, вызываемые непосредственно
для этого объекта. Они составляют все то, что объект делает. Методы экземпляра могут обращаться к переменным экземпляра объекта и могут изменять
свое поведение в зависимости от значений этих переменных.
глава 2

кодировка
продолжительность

Кошка

то, что объект делает

76

знает

(поведение)

методы и классы

Чем класс отличается от объекта
Класс — шаблон для создания объекта. Класс
сообщает Ruby, как следует создать объект
этого конкретного типа. У объектов есть
переменные экземпляра и методы экземпляра, но эти переменные и методы включаются
в класс в процессе его проектирования.

Объекты

Класс

Если классы — это формы для
печенья, то объекты — это печенье,
которое делается по форме.
Каждый экземпляр класса может содержать
собственный набор значений для переменных, используемых в методах этого класса.
Например, класс Dog, представляющий собаку, определяется всего один раз. В методах
этого класса Dog достаточно указать всего
один раз, что каждый экземпляр Dog должен
содержать переменные для имени (name)
и возраста (age). При этом каждый объект
Dog содержит собственные значения name
и age, отличающиеся от значений остальных
экземпляров Dog.

Класс для собаки:

Dog
name
age
talk
move
report_age

переменные
экземпляра

(состояние)

методы
экземпляра

(поведение)

Экземпляры Dog:

name: "Lucy"
age: 4

name: "Rex"
age: 2

name: "Bella"
age: 7

name: "Daisy"
age: 5

name: "Killer"
age: 1
дальше4   77

определение классов

Первый класс
Перед вами пример класса, который вполне может использоваться в нашем интерактивном приложении: класс Dog.
класса.
Объявление нового

Определение класса начинается с ключевого слова
class, за которым следует
имя нового класса.
В определение класса
можно включить определения методов. Любой метод, определяемый здесь,
будет доступен в методах
экземпляра у экземпляров
этого класса.
Конец определения класса обозначается ключевым словом end.

class Dog

Имя класса.

def talk
puts "Bark!"
end

Метод
экземпляра.

def move(destination)
puts "Running to the #{destination}."
end
end

Конец
объявления
класса.

Диаграмма этого класса может выглядеть примерно так...

Имя
класса.

Переменные экземпляра (вскоре
мы добавим их).

Создание новых экземпляров (объектов)
Если вызвать для класса метод new, он вернет новый экземпляр этого класса. Полученный экземпляр можно присвоить
переменной или сделать с ним что-нибудь другое.

Методы
экземпляра.

fido = Dog.new
rex = Dog.new

После создания одного или нескольких зкземпляров класса
можно переходить к вызову их методов экземпляра. Эти методы вызываются точно так же, как и все остальные методы
объектов, встречавшиеся до настоящего момента: оператор
«точка» определяет, какой экземпляр является получателем
данного метода.
Bark!
fido.talk
Running
to the food bowl.
rex.move("food bowl")
78

глава 2

Другой метод
экземпляра.

Dog
talk
move

методы и классы

Разбиение больших методов на классы
Для отслеживания типа животного, с которым должен работать метод, в текущей версии приложения используются строки. Кроме
того, вся информация о разных вариантах
реакции животных встроена в нагромождения команд if/else. Такой подход в лучшем
случае неудобен и громоздок.

Объектно-ориентированное решение

def talk(animal_type, name)
if animal_type == "bird"
puts "#{name} says Chirp! Chirp!"
elsif animal_type == "dog"
puts "#{name} says Bark!"
elsif animal_type == "cat"
puts "#{name} says Meow!"
end
end

Теперь, когда вы научились создавать классы, мы можем применить
объектно-ориентированный подход к решению задачи. Для каждого
типа животного создается класс. Тогда вместо одного большого метода, содержащего описание поведения для всех типов животных, мы
включим маленький метод в каждый класс — метод с определением
поведения, относящегося к данному типу животного.
Вы можете вызвать
«talk» или
«move» для
любого созданного вами
экземпляра
Bird!
То же для
экземпляров Dog…

То же для
экземпляров Cat!

class Bird
def talk
puts "Chirp! Chirp!"
Команды if/elsif
end
не нужны!
def move(destination)
puts "Flying to the #{destination}."
end
Примечание: имена
end
животных пока не подclass Dog
держиваются. Скоро
def talk
мы этим займемся!
puts "Bark!"
end
def move(destination)
puts "Running to the #{destination}."
end
end
class Cat
def talk
puts "Meow!"
end
def move(destination)
puts "Running to the #{destination}."
end
end

Житейская
мудрость

Имена классов Ruby долж­
ны начинаться с буквы
верхнего регистра. Все
символы после первого за­
писываются в нижнем ре­
гистре.
class Appointment
...
end

Если имя состоит из не­
скольких слов, каждое
слово тоже должно начи­
наться с буквы верхнего
регистра.
class AddressBook
...
end
class PhoneNumber
...
end

Помните схему записи
имен переменных (с раз­
делением слов символами
подчеркивания), которая
называлась «змеиной»
записью? Стиль записи
имен классов называется
«верблюжьим», потому что
буквы верхнего регистра
напоминают горбы вер­
блюда.
дальше4   79

экземпляры классов

Создание экземпляров новых классов животных
После того как мы определили эти классы, мы сможем создать новые экземпляры этих
классов (новые объекты, созданные по шаблону класса) и вызывать для них методы.
Как и в случае с методами, Ruby позволяет создавать экземпляры классов прямо в том
файле, в котором они были объявлены. Вероятно, для более крупных приложений
такая организация кода не подойдет, но в таком простом приложении, как у нас, можно просто создать несколько экземпляров сразу же после объявлений этих классов.
class Bird
def talk
puts "Chirp! Chirp!"
end
animals.rb
def move(destination)
puts "Flying to the #{destination}."
end
end
class Dog
def talk
puts "Bark!"
end
def move(destination)
puts "Running to the #{destination}."
end
end
class Cat
def talk
puts "Meow!"
end
def move(destination)
puts "Running to the #{destination}."
end
end
bird = Bird.new
dog = Dog.new
cat = Cat.new

Создание новых
экземпляров
наших классов.

bird.move("tree")
dog.talk
bird.talk
cat.move("house")

Вызов методов
для экземпляров.

Сохраните весь этот код в файле с именем animals.rb, выполните команду ruby animals.rb в терминальном окне — и вы
увидите результаты выполнения наших методов экземпляра!
80

глава 2

File Edit Window Help

$ ruby animals.rb
Flying to the tree.
Bark!
Chirp! Chirp!
Running to the house.
$

методы и классы

Диаграмма классов с методами экземпляра
Если бы нас попросили нарисовать
диаграмму классов,
входящих в систему, то диаграмма
выглядела бы примерно так:

Имя класса.

Переменные
экземпляра
(вскоре мы
поговорим
о них!).
Методы
экземпляра.

Имя класса.

Имя класса.

Bird

Dog

talk
move

Cat

talk
move
Методы
экземпляра.

talk
move
Методы
экземпляра.

На данный момент экземпляры наших классов содержат два метода экземпляра (то, что они
могут делать): talk и move. Впрочем, в них пока нет переменных экземпляра (то, что они знают).
Сейчас мы займемся этой темой.

Развлечения с магнитами
На холодильнике разложена программа на языке Ruby. Некоторые фрагменты
находятся на своих местах, другие были перемешаны случайным образом.
Сможете ли вы переставить фрагменты так, чтобы получить работоспособную
программу, которая бы выдавала приведенный ниже результат?
class

Blender

def

close_lid

(speed)
blender

.blend

("high")

blender

.close_lid

puts "Sealed tight!"

end

def

peed} setting."
puts "Spinning on #{s

Вывод

end
blender =

blend
end

File Edit Window Help

Sealed tight!
Spinning on high setting.

Blender .new

дальше4   81

вызов методов экземпляра

Развлечения с магнитами. Рещение
На холодильнике разложена программа на языке Ruby. Некоторые фрагменты
находятся на своих местах, другие были перемешаны случайным образом.
Сможете ли вы переставить фрагменты так, чтобы получить работоспособную
программу, которая бы выдавала приведенный ниже результат?
class

Blender

close_lid

def

puts "Sealed tight!"
end
blend

def

(speed)

puts "Spinning on #{speed} setting."
end

File Edit Window Help

Sealed tight!
Spinning on high setting.

end
blender =

Blender .new

blender

.close_lid

blender

.blend

("high")

часто

Задаваемые
вопросы

В:
О:

Смогу ли я вызывать новые методы move и talk сами
по себе (без объекта)?
За пределами класса — нет. Вспомните, что получатель
в языке Ruby указывает, для какого объекта вызывается метод.
Методы move и talk являются методами экземпляра;
вызывать их без указания экземпляра, к которому они должны
применяться, попросту бессмысленно. Если вы попробуете это
сделать, то получите ошибку:
move("food bowl")
undefined method `move' for
main:Object (NoMethodError)

82

Output

глава 2

В:

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

О:

Разработчикам приходится так часто создавать новые числа
и строки, что для этих операций в языке была предусмотрена
специальная сокращенная запись: строковые и числовые
литералы.
new_string = "Hello!"
new_float = 4.2
Чтобы сделать то же самое для других классов, придется вносить
изменения на уровне самого языка Ruby, поэтому для создания
новых экземпляров большинство классов использует new. (Впрочем,
есть и исключения; вы еще узнаете о них.)

методы и классы

Наши объекты «не знают» свое имя и возраст!
Руководитель проекта из экологической организации напоминает
о паре мелочей, о которых мы забыли в своем решении с классами:

При вызове этих методов
должно выводиться имя
животного! И кстати,
где метод report_age?

Flying to the tree.
Bark!
Chirp! Chirp!
Running to the house.
Верно подмечено: мы забыли реализовать некоторые возможности исходной программы.
Начнем с добавления параметра name в методы
talk и move:
class Bird
def talk(name)
puts "#{name} says Chirp! Chirp!"
пежно
дол
Им я
end
при
ся
ть
ред ава
def move(name, destination)
выз ове мет одо в,
puts "#{name} flies to the #{destination}."
как и прежде.
end
end
Имена используются
в выводе, здесь тоже
class Dog
его не изменилось.
нич
def talk(name)
puts "#{name} says Bark!"
end
def move(name, destination)
puts "#{name} runs to the #{destination}."
end
end
class Cat
def talk(name)
puts "#{name} says Meow!"
end
def move(name, destination)
puts "#{name} runs to the #{destination}."
end
end
дальше4   83

границы локальных переменных

Слишком много аргументов (снова)
После того как мы снова добавили параметр
name к методам talk и move, при вызове метода
можно снова передавать имя животного для вывода сообщения.

dog = Dog.new
dog_name = "Lucy"
dog.talk(dog_name)
dog.move(dog_name, "fence")

cat = Cat.new
cat_name = "Fluffy"
cat.talk(cat_name)
cat.move(cat_name, "litter box")

Lucy says Bark!
Lucy runs to the fence.
Fluffy says Meow!
Fluffy runs to the litter box.

Да ладно. У нас уже есть переменная
для хранения объекта animal. И вы еще
хотите передавать вторую переменную
с именем животного? Так неудобно!

dog = Dog.new
dog_name = "Lucy"
cat = Cat.new
cat_name = "Fluffy"

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

глава 2

методы и классы

Локальные переменные существуют до завершения метода
До сих пор мы работали с локальными переменными — такие переменные локальны по отношению
к текущей области видимости (обычно в границах текущего метода). После выхода из текущей области
видимости локальные переменные перестают существовать, поэтому они не могут использоваться для
хранения имен животных.
Перед вами новая версия класса
Dog с дополнительным методом
make_up­_name. Этот метод сохраняет имя собаки для последующего обращения из метода talk.

Однако при вызове метода talk
появляется сообщение об ошибке:
переменная name не существует.

class Dog

def make_up_name
name = "Sandy"
end

Сохраняем имя.

def talk
puts "#{name} says Bark!"
end
Пытаемся обратиться
end
к сохраненному имени.
dog = Dog.new
dog.make_up_name
dog.talk

Что произошло? Ведь мы определили переменную name
в методе make_up_name!
Дело в том, что мы использовали локальную переменную. Локальные переменные существуют только до
завершения того метода, в котором они были созданы.
В данном случае переменная name перестает существовать сразу же после завершения make_up_name.

Ошибка

in `talk': undefined local
variable or method `name' for
#
class Dog
def make_up_name
name = "Sandy"
При завершении метода
end
«name» выходит из области видимости.
def talk
puts "#{name} says Bark!"
end
В этой точке переменend
ная уже не существует!

Поверьте, недолговечность локальных переменных — это хорошо. Если бы любая переменная была доступна
в любой точке программы, вы бы постоянно путались и обращались не к тем переменным! Как и большинство языков, Ruby ограничивает область видимости переменных для предотвращения подобных ошибок.
Только
представь- def alert_ceo
те, что
message = "Sell your stock."
это локаль- email(ceo, message)
…будет
ная пере- end
доступна
менная…

email(shareholders, message)

здесь…

Ошибка

Уф! Пронесло.

undefined local variable
or method `message'
дальше4   85

переменные экземпляра

Срок жизни переменной экземпляра
Любая локальная переменная, созданная нами, исчезает сразу же при
выходе из области видимости. Но в таком случае как хранить имя
собаки вместе с объектом? Для этого понадобится новая разновидность переменных.
Объект позволяет хранить данные в переменных экземпляра —
то есть переменных, связанных с конкретным экземпляром. Данные,
сохраненные в переменных экземпляра, продолжают существовать
вместе с объектом и удаляются из памяти только при удалении объекта.

Переменные
экземпляра в объекте
существуют, пока
существует сам объект.

Переменная экземпляра выглядит как обычная переменная, а при
выборе ее имени применяются те же соглашения. Единственное отличие: имя переменной экземпляра должно начинаться с символа
@ («собака»).

my_variable

@my_variable

Локальная
переменная

Переменная
экземпляра

Вернемся к классу Dog. Он почти не отличается от предыдущего, если
не считать двух символов @, превращающих две локальные переменные в одну переменную экземпляра.
class Dog

еСохраняем знач
й
но
ен
м
ре
пе
ние в
а.
яр
пл
экзем

def make_up_name
@name = "Sandy"
end
def talk
puts "#{@name} says Bark!"
end
end

Обращаемся к переменной экземпляра.

Теперь точно такой же вызов talk, как в предыдущем случае, прекрасно работает! Переменная экземпляра @name, созданная в методе
make_up_name, остается доступной в методе talk.
dog = Dog.new
dog.make_up_name
dog.talk

86

глава 2

Sandy says Bark!

методы и классы

Срок жизни переменной экземпляра (продолжение)
class Dog

С переменными экземпляра мы также
сможем легко добавить методы move
и report_age…

def make_up_name
@name = "Sandy"
end
def talk
puts "#{@name} says Bark!"
end
def move(destination)
puts "#{@name} runs to the #{destination}."
end
Новый
код!

def make_up_age
@age = 5
end
def report_age
puts "#{@name} is #{@age} years old."
end
end
dog = Dog.new
dog.make_up_name
dog.move("yard")
dog.make_up_age
dog.report_age

И теперь мы наконец-то сможем заполнить
пустые места на диаграмме класса Dog!

Sandy runs to the yard.
Sandy is 5 years old.

Dog
Переменные
экземпляра
Методы
экземпляра

name
age
talk
move

Уже лучше. Но этот
класс позволяет
создавать объекты
только для 5-летних
собак с именем
Сэнди!

Что верно, то верно. Пора разобраться, как присвоить переменным name и age другие значения.
  87

защита данных

Инкапсуляция
Благодаря переменным экземпляра мы теперь можем сохранять
имена и возраст животных. Однако методы make_up_name
и make_up_age methods допускают использование только фиксированных значений (которые не могут изменяться во время
выполнения программы). Нужно сделать так, чтобы программа
могла присвоить переменным любые необходимые значения.

Впрочем, такой код работать
не будет:

fido = Dog.new
fido.@age = 3

Ошибка

class Dog
def make_up_name
@name = "Sandy"
end
def make_up_age
@age = 5
end
...
end

syntax error, unexpected tIVAR

Ruby не позволяет напрямую обращаться к переменным экземпляра за пределами класса. И это вовсе не прихоть; такие ограничения существуют для того,
чтобы другие программы и классы не изменяли ваши переменные экземпляра
как попало.
Представьте, что переменные экземпляра возможно было бы изменять напрямую. Что помешает другим частям программы присвоить им некорректные
значения?

Этот код
некорректен!

fido = Dog.new
fido.@name = ""
fido.@age = -1
fido.report_age

ОЖЕсли бы это М
ь,
ат
ел
сд
НО было
вы
ат
т
ль
то резу

ак
т
глядел бы

is -1 years old.

Что? Простите, кому и сколько лет? Данные объекта явно недействительны, как
хорошо видно из вывода программы!
Пустые имена и отрицательный возраст — это лишь начало. Представьте, что
кто-то по ошибке заменил значение переменной экземпляра @date в объекте
встречи Appointment телефонным номером. Или установил ставку налога
@sales_tax для всех своих объектов счетов Invoice равной нулю. Да здесь
возможен целый миллион разных ошибок!
Чтобы перекрыть доступ к данным объекта злоумышленнику (или просто некомпетентному пользователю), во многих объектно-ориентированных языках
используется концепция инкапсуляции: запрета на прямое обращение или
изменение переменных экземпляра из других частей программы.
88

глава 2

методы и классы

Методы доступа
Чтобы обеспечить инкапсуляцию данных и защитить экземпляры от некорректных изменений, Ruby
не позволяет обращаться к переменным экземпляра или изменять их за пределами класса. Вместо этого можно создать методы доступа (accessor methods), которые записывают значения в переменные
экземпляра и читают их за вас. После того как работа с данными будет вестить исключительно через
методы доступа, вы сможете легко расширить эти методы для проверки данных — с отклонением любых
некорректных значений, переданных при вызове.
В Ruby существуют два вида методов доступа: методы записи атрибутов и методы чтения атрибутов. (Атрибут — другое название для блока данных, относящегося к объекту.) Методы записи атрибутов присваивают
значение переменным экземпляра, а методы чтения атрибутов получают сохраненное ранее значение.
Перед вами простой класс с методами чтения и записи атрибута с именем my_attribute:
Метод записи
атрибута.

class MyClass

Методы
доступа.

def my_attribute=(new_value)
@my_attribute = new_value
end
def my_attribute
@my_attribute
end

Метод чтения
атрибута.

end

Если создать новый экземпляр этого класса…

my_instance = MyClass.new

…мы сможем присвоить значение атрибута…

my_instance.my_attribute = "a value"

…и прочитать его.

puts my_instance.my_attribute

Методы доступа представляют собой обычные методы экземпляра; особое название «методы доступа» связано только с тем, что их основной целью является
обращение к переменной экземпляра.
Для примера рассмотрим метод чтения атрибута: это совершенно обычный метод,
который просто возвращает текущее значение @my_attribute.

def my_attribute
@my_attribute
end

од
Никакого волшебства! Мет
е
уще
тек
ает
ращ
возв
просто
значение.

дальше4   89

работа с данными

Методы доступа (продолжение)
Как и методы чтения атрибута, метод записи атрибута представляет собой совершенно обычный
метод экземпляра. Мы называем его «методом
записи» только потому, что он предназначен для
обновления переменной экземпляра.

class MyClass
def my_attribute=(new_value)
@my_attribute = new_value
end

Метод
записи
атрибута.

...
end

Возможно, метод самый обычный, но его вызовы обрабатываются не совсем обычно.
Помните, ранее в этой главе мы упоминали о том, что имена
методов Ruby могут завершаться знаком равенства (=)? Эта
возможность существует в Ruby именно для методов записи
атрибута.
Когда Ruby встречает в вашем коде конструкцию вида:

def my_attribute=(new_value)
...
Часть
end
имени
метода!

my_instance.my_attribute = "a value"

…он преобразует ее в вызов метода экземпляра my_attribute=.
Значение справа от = передается методу как аргумент:
Аргумент
Вызов
метода.
метода.
my_instance.my_attribute=("a value")

Приведенный выше фрагмент является действительным кодом
Ruby. Если хотите, попробуйте его выполнить самостоятельно:
class MyClass
def my_attribute=(new_value)
@my_attribute = new_value
end
def my_attribute
@my_attribute
Вызов метода «my_
end
attribute=», замаскироend
ванный под присваивание.
my_instance = MyClass.new
my_instance.my_attribute = "assigned via method call"
puts my_instance.my_attribute
Вызов «my_
my_instance.my_attribute=("same here")
attribute=»
puts my_instance.my_attribute
действительно
выглядит так!

90

глава 2

Житейская
мудрость
Альтернативный способ вы­
зова методов записи атрибу­
та приводится только для
того, чтобы вы поняли, что
происходит «за кулисами».
В настоящих программах на
языке Ruby следует исполь­
зовать только синтаксис
с присваиванием!

assigned via method call
same here

методы и классы

Использование методов доступа
Итак, вы готовы использовать новые
знания в приложении Fuzzy Friends. Для
начала дополним класс Dog методами
для чтения переменных экземпляра
@name и @age. Также переменные @name
и @age будут использоваться в методе
report_age. Проверка данных будет
рассмотрена позднее.

class Dog
def name=(new_value)
@name = new_value
end
def name
@name
end

Читаем значение
из @name.

def age=(new_value)
@age = new_value
end
def age
@age
end

Записываем новое
значение в @name.

Записываем новое
значение в @age.

Читаем значение
из @age.

def report_age
puts "#{@name} is #{@age} years old."
end
end

После определения методов доступа мы
можем (косвенно) записывать и использовать переменные экземпляра @name
и @age за пределами класса Dog!

Присвоить @name
значение Fido.

fido = Dog.new
fido.name = "Fido"
fido.age = 2
Задать переменную @age
rex = Dog.new
для объекта с именем Fido.
rex.name = "Rex"
Присвоить @name значение Rex.
rex.age = 3
Задать @age для объfido.report_age екта с именем Rex.
rex.report_age

Fido is 2 years old.
Rex is 3 years old.

Впрочем, вручную определять методы
чтения и записи для каждого атрибута
довольно утомительно. Сейчас мы рассмотрим более простой способ…

Житейская
мудрость
Имя метода чтения атрибута обыч­
но совпадает с именем переменной
экземпляра, из которой читается
значение (конечно, без символа @).
def tail_length
@tail_length
end

То же относится к именам методов
записи атрибута, но в конец имени
добавляется символ =.
def tail_length=(value)
@tail_length = value
end

дальше4   91

сокращенные методы доступа

Методы чтения и записи атрибутов
Создание пары методов доступа для атрибута является задачей настолько распространенной, что в Ruby для нее были определены сокращения — методы с
именами attr_writer, attr_reader и attr_accessor. Вызов этих трех методов в определении класса автоматически определяет эти методы доступа за вас:
Включите в определение класса…

…и Ruby автоматически определит
эти методы:

attr_writer :name

def name=(new_value)
@name = new_value
end

attr_reader :name

def name
@name
end

attr_accessor :name

def name=(new_value)
@name = new_value
end
def name
@name
end

Совсем как
наше старое
определение!
Совсем как
наше старое
определение!

Определяет
два метода
сразу!

Все три метода могут получать несколько аргументов с атрибутами, для которых должны определяться методы доступа.

Символические имена

attr_accessor :name, :age

Определяет

Если вас заинтересовали :name и :age, объясняем:
ЧЕТЫРЕ меэто символические имена. Символическое имя Ruby
тода сразу!
представляет собой последовательность символов (как
и строки). С другой стороны, в отличие от строки зна:hello
чение символического имени не может быть изменено
:over_easy
Символические
позднее. По этой причине символические имена идеRuby
имена
ально подходят для ссылок на любые сущности, имена
:east
которых (обычно) не изменяются, — скажем, методы.
Например, если вы вызовете в irb метод с именем
methods для объекта, то увидите, что он возвра- > Object.new.methods
щает список символических имен.
=> [:class, :singleton_class, :clone, ...]
Ссылки на символические имена в коде Ruby всегда начинаются с двоеточия (:). Символические имена
должны записываться в нижнем регистре с разделением слов подчеркиваниями, как и в именах переменных.
92

глава 2

методы и классы

Методы чтения и записи атрибутов в действии
В классе Dog в настоящее время методы доступа занимают 12 строк кода. С методом attr_accessor можно
обойтись всего... одной строкой!
В результате размер класса Dog сокращается…

с такого…
class Dog
def name=(new_value)
@name = new_value
end
def name
@name
end

Эквивалентно!

Эквивалентно!

def age=(new_value)
@age = new_value
end
def age
@age
end

class Dog

…до
такого!

attr_accessor :name, :age
def report_age
puts "#{@name} is #{@age} years old."
end
def talk
puts "#{@name} says Bark!"
end
def move(destination)
puts "#{@name} runs to the #{destination}."
end
end

def report_age
puts "#{@name} is #{@age} years old."
end
def talk
puts "#{@name} says Bark!"
end
def move(destination)
puts "#{@name} runs to the #{destination}."
end
end

Что теперь скажете об эффективности? Не говоря уже о том, что этот код гораздо проще читается!
Но не будем забывать, для чего вообще создавались методы доступа. Мы хотели защитить переменные экземпляров от некорректных данных. В настоящее время методы никакой защиты
не обеспечивают… Но через несколько страниц проблема будет решена!
дальше4   93

создание экземпляров в irb

Упражнение

До настоящего момента у нас еще не было возможности нормально поэкспериментировать
с классами и объектами. Откройте новый сеанс irb. Мы загрузим простой класс и попробуем
создать его экземпляры в интерактивном режиме.

Шаг 1:
Сохраните это определение класса в файле с именем mage.rb.
class Mage
attr_accessor :name, :spell
def enchant(target)
puts "#{@name} casts #{@spell} on #{target.name}!"
end
end
mage.rb

Шаг 2:
В терминальном окне перейдите в каталог, в котором
был сохранен файл.
Шаг 3:
В этом упражнении, как и в предыдущем, Ruby будет загружать
файлы из текущего каталога, поэтому для запуска irb должна
использоваться команда
irb -I .

Шаг 4:
Как и прежде, необходимо загрузить файл с сохраненным
кодом Ruby. Введите следующую команду:
require "mage"

94

глава 2

методы и классы

Пример
сеанса:

Упражнение

(продолжение)

После загрузки кода класса Mage вы можете свободно экспериментировать —
создайте столько экземпляров, сколько
вам захочется, задайте их атрибуты и
вызывайте методы! Попробуйте для
начала выполнить следующую цепочку
команд:
merlin = Mage.new
merlin.name = "Merlin"
morgana = Mage.new
morgana.name = "Morgana"
morgana.spell = "Shrink"
morgana.enchant(merlin)

Кто я?

File Edit Window Help

$ irb -I .
irb(main):001:0> require 'mage'
=> true
irb(main):002:0> merlin = Mage.new
=> #
irb(main):003:0> merlin.name = "Merlin"
=> "Merlin"
irb(main):004:0> morgana = Mage.new
=> #
irb(main):005:0> morgana.name = "Morgana"
=> "Morgana"
irb(main):006:0> morgana.spell = "Shrink"
=> "Shrink"
irb(main):007:0> morgana.enchant(merlin)
Morgana casts Shrink on Merlin!
=> nil
irb(main):008:0>

Компания концепций Ruby, облаченных в маскарадные костюмы, развлекается
игрой «Кто я?». Игрок дает подсказку, а остальные на основании сказанного
им пытаются угадать, кого он изображает. Будем считать, что игроки всегда
говорят правду о себе. Заполните пропуски справа именами участников. (Мы
уже заполнили одно поле за вас.)
Сегодняшние участники: среди них могут быть любые термины, относящиеся к хранению данных в объекте!

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

часто

подробнее о методах доступа

Кто я?
Решение

В:
О:

Задаваемые
вопросы

Чем методы доступа отличаются
от методов экземпляра?
«Метод доступа» — всего лишь
термин, обозначающий одну конкретную
разновидность методов экземпляра.
Методы этой группы предназначены для
чтения или записи значений переменных
экземпляра. Во всех остальных
отношениях методы доступа ничем
не отличаются от «обычных» методов
экзмпляра.

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

В:

Я создал переменную экземпляра за пределами метода экземпляра,
но когда я пытаюсь обратиться к ней,
у меня ничего не получается. Почему?
class Widget
@size = 'large'
def show_size
puts "Size: #{@size}"
end
Пусто!
end
widget = Widget.new
widget.show_size

О:

Size:

Когда вы используете переменную
экземпляра вне метода экземпляра,
вы в действительности создаете
переменную экземпляра для объекта
класса . (Да, все верно — даже сами
классы в Ruby являются объектами.)
И хотя такие переменные тоже могут
принести пользу, мы не сможем
рассмотреть их в этой книге. На данный
момент результат почти наверняка будет
не тем, на который вы рассчитывали.
Вместо этого создайте переменную
экземпляра в методе экземпляра:
class Widget
def set_size
@size = 'large'
end
...
end

96

глава 2

методы и классы

У бассейна
Выловите из бассейна фрагменты кода и расставьте их в пустых местах в коде. Каждый фрагмент
может использоваться только один раз, причем использовать все фрагменты не обязательно.
Ваша задача — составить код, который будет нормально выполняться и выдавать
приведенный ниже результат.
class Robot

robot = Robot.new

def
@head
end

robot.assemble
robot.arms = "MagGrip Claws"
robot.eyes = "X-Ray Scopes"
robot.feet = "MagGrip Boots"

def
(value)
@arms = value
end
:legs, :body
attr_writer
:feet
def assemble
@legs = "RubyTek Walkers"
@body = "BurlyBot Frame"
= "SuperAI 9000"
end
def diagnostic
puts
puts @eyes
end
Примечание: каждый
объект из бассейна
может использоваться
только один раз.

puts robot.head
puts robot.legs
puts robot.body
puts robot.feet
robot.diagnostic
Результат
File Edit Window Help

SuperAI 9000
RubyTek Walkers
BurlyBot Frame
MagGrip Boots
MagGrip Claws
X-Ray Scopes

end

@arms
arms=
attr_accessor

attr_writer

attr_reader

:eyes

@feet

@head
head

:head

дальше4   97

решение упражнения

У бассейна. Решение
Выловите из бассейна фрагменты кода и расставьте их в пустых местах в коде. Каждый
фрагмент может использоваться только один раз, причем использовать все фрагменты
не обязательно. Ваша задача — составить код, который будет нормально выполняться
и выдавать приведенный ниже результат.
class Robot
def head
@head
end
def arms=(value)
@arms = value
end

attr_reader :legs, :body

attr_writer :eyes

attr_accessor :feet
def assemble
@legs = "RubyTek Walkers"
@body = "BurlyBot Frame"
@head = "SuperAI 9000"
end
def diagnostic
puts @arms
puts @eyes
end
end

98

глава 2

robot = Robot.new
robot.assemble
robot.arms = "MagGrip Claws"
robot.eyes = "X-Ray Scopes"
robot.feet = "MagGrip Boots"
puts robot.head
puts robot.legs
puts robot.body
puts robot.feet
robot.diagnostic
Результат
File
File Edit
Edit Window
Window Help
Help Lasers
Lasers

SuperAI
SuperAI 9000
9000
RubyTek
RubyTek Actuators
Walkers
BurlyBot
BurlyBot Frame
Frame
MagGrip
MagGrip Boots
Boots
MagGrip
Claws
MagGrip Claws
X-Ray
X-Ray Scopes
Scopes

методы и классы

Проверка данных при вызове методов доступа
Помните наш кошмарный сценарий, в котором Ruby позволял
программам напрямую обращаться к переменным экземпляра
и вашим экземплярам Dog были назначены пустые имена с отрицательным возрастом? Плохие новости: теперь, после добавления методов записи атрибутов в класс Dog, это стало возможно!
joey = Dog.new
joey.name = ""
joey.age = -1
joey.report_age

is -1 years old.

Без паники! Те же самые методы доступа помогут избежать
подобных неприятностей в будущем. Мы добавим в методы
простой механизм проверки данных, который будет выдавать
ошибку при любой попытке передачи некорректного значения.

Так как name= и age= — самые
обычные методы Ruby, реализовать проверку будет несложно; мы
просто используем команды if
для выявления пустых строк (для
name=) или отрицательных чисел
(для age=). При обнаружении недопустимого значения выводится
сообщение об ошибке. И только
если значение успешно прошло
проверку, программа действительно присваивает его переменным
экземпляра @name и @age.

class Dog

Автоматически определяются только методы
чтения, потому что методы записи мы будем опреде
лять самостоятельно.

attr_reader :name, :age

Если имя пустое, вывеdef name=(value)
сти сообщение об ошибке.
if value == ""
puts "Name can't be blank!"
else
яЗначение переменной экземпл
@name = value
м
то
в
о
льк
то
я
етс
ра присваива
end
ку.
вер
про
шло
про
имя
случае, если
end
Если возраст отрицателен,
def age=(value)
.
вывести сообщение об ошибке
if value < 0
puts "An age of #{value} isn't valid!"
else
@age = value
Значение переменной экend
земпляра присваивается
end
только в том случае, если
возраст прошел проверку.
def report_age
puts "#{@name} is #{@age} years old."
end

end
дальше4   99

подробнее о raise

Ошибки и «аварийная остановка»
Значит, при присваивании
недопустимого имени или возраста
выводится предупреждение. Прекрасно.
Но ведь потом программа все равно
вызывает report_age, и переменные
name и age оказываются пустыми!

glitch = Dog.new
glitch.name = ""
glitch.age = -256
glitch.report_age

Name can't be blank!
An age of -256 isn't valid!
is years old.
Пусто!

Просто вывести сообщение недостаточно. Необходимо сделать
с недействительными параметрами методов доступа name= и age=
что-то более осмысленное. Изменим код проверки в методах
name= и age= так, чтобы вызов встроенного метода Ruby raise
сообщал пользователю о возникшей ошибке.
raise "Something bad happened!"

Вызов raise привлекает внимание пользователя к проблеме.
Методу raise может передаваться строка с описанием проблемы. Когда в ходе выполнения встречается этот вызов, Ruby
прекращает заниматься текущим делом и выводит сообщение об
ошибке. Так как наша программа не пытается как-то обработать
ошибку, она просто немедленно завершается.

100

глава 2

методы и классы

Использование «raise» в методах записи атрибутов
Перед вами обновленный код класса Dog…
class Dog
Если мы используем raise
в методах записи, включать
attr_reader :name, :age
секцию else в команды if
Если значение
не обязательно. Если новое
«value» недейdef name=(value)
значение недействительно,
ствительно…
if value == ""
то после вызова raise выraise "Name can't be blank!"
рыполнение программы пре- …выполнение пре
end
.
точке
Эта команда не бурывается. Программа просто вается в этой
@name = value
дет выполнена, если
end
не дойдет до команды, приЕсли значение
был вызван метод
сваивающей значение пере«value» недейise».
def
age=(value)
«ra
менной экземпляра.
ствительно…

…выполнение прерывается в этой точке.

Если теперь передать методу name=
пустое имя, Ruby выдаст сообщение об ошибке и выполнение программы прерывается.
При попытке присваивания отрицательного возраста будет выведено другое сообщение об ошибке.

if value < 0
raise "An age of #{value} isn't valid!"
end
Эта команда не бу@age = value
дет выполнена, если
end
был вызван метод
«raise».
def report_age
puts "#{@name} is #{@age} years old."
end

end
anonymous = Dog.new
anonymous.name = ""
joey = Dog.new
joey.age = -1

Ошибка

Ошибка

Как будет показано в главе 12, ошибки также могут обрабатываться в других частях вашей программы, чтобы она могла
продолжить работу. А пока несознательный разработчик,
который попытается передать экземпляру вашего класса Dog
пустое имя или отрицательный возраст, немедленно узнает
о том, что он должен переделать свой код.

in `name=': Name
can't be blank!
(RuntimeError)
in `age=': An age
of -1 isn't valid!
(RuntimeError)

Потрясающе! Если в коде разработчика
допущена ошибка, он узнает об этом
еще до того, как эту ошибку увидит
пользователь. Отличная работа!

дальше4   101

полный код класса

Dog

Код класса Dog
Ниже приведен полный код класса Dog, а также дополнительный
код создания экземпляра Dog.
Определение
методов чтеattr_reader :name, :age
ния атрибутов
«name» и «age».
def name=(value)
Метод записи атриif value == ""
бута для «@name».
raise "Name can't be blank!"
end
Проверка данных.
@name = value
end
Метод записи атрибуdef age=(value)
та для «@age».
if value < 0
raise "An age of #{value} isn't valid!"
end
Проверка данных.
@age = value
end

class Dog

Метод экземпляра.
def move(destination)
puts "#{@name} runs to the #{destination}."
Использование переend
менной экземпляра.
Метод экземпляра.
def talk
puts "#{@name} says Bark!"
end
Использование переменной экземпляра.
def report_age
Метод экземпляра.
puts "#{@name} is #{@age} years old."
end
Использование переменных экземпляра.
end
Создание нового
dog = Dog.new
экземпляра Dog.
dog.name = "Daisy"
Инициализация
dog.age = 3
атрибутов.
dog.report_age
Вызов методов
dog.talk
dog.rb
dog.move("bed")
экземпляра.

Задание!
Сохраните приведенный выше код в файле с именем dog.rb. Попробуйте создать новые экземпляры Dog,
затем выполните команду ruby dog.rb в терминале.
102

глава 2

name
age
move
talk
report_age

переменные
экземпляра

(состояние)

методы
экземпляра

(поведение)

Мы определили методы экземпляра, которые работают как методы доступа к атрибутам и используются для чтения и записи
значений переменных экземпляра.
puts dog.name
dog.age = 3
puts dog.age

Daisy
3
Методы экземпляра позволяют объекту
Dog выполнять разные действия: перемещаться, издавать звуки и сообщать свой
возраст. Методы экземпляра могут использовать данные, хранящиеся в переменных
экземпляра.
dog.report_age
dog.talk
dog.move("bed")

Daisy is 3 years old.
Daisy says Bark!
Daisy runs to the bed.
Также мы изменили методы записи атрибутов, чтобы они проверяли передаваемые
данные и выдавали сообщение об ошибке
в случае их недействительности.
dog.name = ""

Ошибка

in `name=': Name
can't be blank!
(RuntimeError)

методы и классы

Ваш инструментарий Ruby
Глава 2 подошла к концу. В ней ваш
инструментарий Ruby пополнился
методами и классами.

ды

Коман

ют со
ыполня
в
ы
д
о
н
им ма
е код
ь
в завис
вныт
ы
д
о
о
к
бъявит
УслоМ
х
и
е ийся в н
ояж
. но о
м
и
в
в
о
о
щ
д
х
л
а
о
и
с
н
оу
моегт
держ
лив для
тыор
ор
неект
рам
опредаещийся
ат
,
П
о
и
м
и
ы
т
н
с
ж.
рю
ль
еи
затен
т содн
енеобяы
олпояю
молчао
п
у
икл пр аться
в
Ц
.
ы
я
в
и
ч
Цикзлначени ногократн
н
зака овию.
од м
жреот
му усл
оо
м
а
д
т
в них к мет
о
еко
=.
Имяся по н
о
рываетком ?, ! или
е своег
зна
начени
з
т
ю
ния
раща
ыраже
ы возв
нного в
е
л
Метод
с
о заи
н
ч
е мож
его вы
ж
н
к
д
а
е
Т
л
с
по
тода
зова.
ние ме
ону вы
е
р
ч
о
а
н
т
з
с
на
аемое
озвращ
дать в
urn.
ой ret
команд
аблон»
Классы
ой «ш
б
о
с
т
ов.
авляе
бъ ект
предст
яров о
л
п
Класс
м
е
з
эк
одания
о мет
для соз
яет ег
л
е
).
д
е
т
р
оп
бъ ек
ъ екта
АЕТ о
б
Л
о
Е
с
с
Д
а
Кл
а (что
аемпляр
но созд
ды экз
а мож
р
я
то
л
п
зем
(то, ч
одах эк
пляра
м
т
е
е
з
м
к
э
В
ные
еремен
.
вать п
о себе)
ЗНАЕТ
т
к
е
объ

КЛЮЧЕВЫЕ
МОМЕНТЫ
ƒƒ Тело метода состоит из одной или нескольких команд Ruby, выполняемых
при вызове метода.
ƒƒ Круглые скобки не указываются в определении метода в том (и только в том)
случае, если в методе не определяется
ни один параметр.
ƒƒ Если возвращаемое значение не указано явно, то метод возвращает значение
последнего вычисленного выражения.
ƒƒ Определения методов в определении
класса рассматриваются как методы
экземпляра этого класса.
ƒƒ За пределами определения класса
к переменным экземпляра можно обращаться только через методы доступа.
ƒƒ Вызовы методов attr_writer,
attr_reader и attr_
accessor в определении класса
обеспечивают сокращенную запись для
определения методов доступа.
ƒƒ Методы доступа могут использоваться
для проверки действительности данных перед их сохранением в переменных экземпляра.
ƒƒ Метод raise предназначен для вывода сообщений об ошибках в программе.

Далее в программе…
Мы создали полноценный класс Dog. Остается лишь добавить те же возможности в классы Cat и Bird!
Перспектива копирования кода вас не радует? Не беспокойтесь! В следующей главе мы займемся наследованием, и этот механизм упростит задачу!
дальше4   103

3 наследование

С родительской помощью
Наследование? Раньше мы
с родственниками ссорились из-за
него. Но теперь мы поняли, что
у нас много общего, и все идет
прекрасно!

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

опасности дублирования

Копировать, вставлять... Столько проблем!
Разработчики из Got-A-Motor, Inc. решили внедрить принципы объектноориентированного программирования в своей работе. Старое приложение
виртуального тест-драйва было переработано так, чтобы каждый тип машины был представлен отдельным классом. У них есть классы, представляющие легковые машины (Car), грузовики (Truck) и мотоциклы (Motorcycle).
На данный момент структура классов выглядит так:

переменные
экземпляра
методы
экземпляра

Car
odometer
gas_used

переменные
экземпляра

mileage
accelerate
sound_horn

методы
экземпляра

Прислушавшись к пожеланиям клиентов, руководство захотело включить во
все типы машин метод для
управления рулем. Майк,
неопытный разработчик
из Got-A-Motor, считает, что
с этим требованием справиться несложно.

106

глава 3

Truck

Motorcycle

odometer
gas_used

переменные
экземпляра

odometer
gas_used

mileage
accelerate
sound_horn

методы
экземпляра

mileage
accelerate
sound_horn

В чем проблема? Нужно добавить
в класс Car метод steer. После
этого я копирую его и вставляю
во все остальные классы, как мы это
уже делали еще с тремя методами!

наследование

Код Майка для приложения виртуального тест-драйва
class Car

class Truck

attr_accessor :odometer
attr_accessor :gas_used

attr_accessor :odometer
attr_accessor :gas_used

def mileage
@odometer / @gas_used
end

def mileage
@odometer / @gas_used
end

def accelerate
puts "Floor it!"
end

def accelerate
puts "Floor it!"
end

def sound_horn
puts "Beep! Beep!"
end
Копируем!
def steer
puts "Turn front 2 wheels."
end

def sound_horn
puts "Beep! Beep!"
end
Вставляем!
def steer
puts "Turn front 2 wheels."
end

end
class Motorcycle
attr_accessor :odometer
attr_accessor :gas_used
def mileage
@odometer / @gas_used
end
def accelerate
puts "Floor it!"
end
def sound_horn
puts "Beep! Beep!"
end
Вставляем!
def steer
puts "Turn front 2 wheels."
end

end

Но у Марси, опытного объектноориентированного разработчика из этой группы, такая идея
вызывает сомнения.

Копирование кода добром
не кончится. А если метод
потребуется изменить?
Нам придется вносить
изменения в каждом классе!
И присмотритесь к классу
Motorcycle — у мотоциклов
нет двух передних колес!

end

Марси права; сопровождение кода вскоре превратится в сущий кошмар. Для начала разберемся,
как решить проблему с дублированием, а затем исправим метод steer для объектов Motorcycle.
дальше 4   107

совместное использование методов классами

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

2

108

 ы видим, что классы Car,
М
Truck и Motorcycle содержат несколько общих методов экземпляра
и атрибутов.

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

глава 3

Car

Наследование
позволяет нескольким
субклассам наследовать
методы от одного
суперкласса.

Truck

Motorcycle

odometer
gas_used

odometer
gas_used

odometer
gas_used

mileage
accelerate
sound_horn
steer

mileage
accelerate
sound_horn
steer

mileage
accelerate
sound_horn
steer

Vehicle
odometer
gas_used
mileage
accelerate
sound_horn
steer

наследование

На помощь приходит наследование! (продолжение)

Суперкласс

Vehicle
odometer
gas_used
3

 атем вы указываете, что
З
все остальные классы наследуют от класса Vehicle.

Класс Vehicle называется суперклассом для трех других классов.
Car, Truck и Motorcycle называются субклассами по отношению к Vehicle.

Субкласс

Car

Субклассы наследуют все методы и атрибуты суперкласса. Иначе говоря, если суперкласс обладает некоторой
функциональностью, то и его субклассы автоматически
получают эту функциональность. Из классов Car, Truck,
и Motorcycle можно удалить дублирующиеся методы,
потому что они автоматически наследуются от класса
Vehicle. Все классы по-прежнему содержат общие методы, но теперь достаточно сопровождать всего одну
копию каждого метода!
Учтите, что в Ruby субклассы формально не наследуют
переменные экземпляров; они наследуют методы доступа, создающие эти переменные. Это нетривиальное
различие будет рассмотрено через несколько страниц.
Субкласс

Все унаследованные
методы (включая
методы доступа)
можно вызывать дл
я
экземпляров субкла
ссов — так, как есл
и
бы они были объявл
ены непосредственно
в субклассах!

Car

mileage
accelerate
sound_horn
steer
Субкласс

Обозначает
наследование.

Субкласс

Truck

Motorcycle

Суперкласс

Vehicle
odometer
gas_used
mileage
accelerate
sound_horn
steer
Субкласс

Truck

Субкласс

Motorcycle

odometer
gas_used

odometer
gas_used

odometer
gas_used

mileage
accelerate
sound_horn
steer

mileage
accelerate
sound_horn
steer

mileage
accelerate
sound_horn
steer
дальше 4   109

суперклассы и субклассы

Определение суперкласса (в общем-то ничего особенного!)
Суперкласс

Чтобы исключить повторяющиеся методы и атрибуты в классах Car, Truck и Motorcycle,
Марси разработала эту иерархию классов. Общие методы и
атрибуты были вынесены в суперкласс Vehicle. Car, Truck и
Motorcycle являются субклассами Vehicle, и они наследуют все
методы Vehicle.

Vehicle
odometer
gas_used
mileage
accelerate
sound_horn
steer
Субкласс

Субкласс

Car

Truck

Субкласс

Motorcycle

class Vehicle
В Ruby не существует никакого
специального синтаксиса опреатрибуты
деления суперклассов; это самый Все
attr_accessor :odometer
ут унаследоваобычный класс. (Это относится к буд
attr_accessor :gas_used
при объявлении
большинству объектно-ориенти- ны
.
сса
субкла
рованных языков.)
def accelerate
puts "Floor it!"
end

Как и все
методы
экземпляра.

def sound_horn
puts "Beep! Beep!"
end
def steer
puts "Turn front 2 wheels."
end
def mileage
return @odometer / @gas_used
end
end

110

глава 3

наследование

Определение субкласса (совсем просто)
Синтаксис субклассов тоже не отличается сложностью. Определение субкласса
выглядит как определение обычного
класса, не считая того, что вы указываете суперкласс, от которого он наследует.
В Ruby используется знак «меньше» (. Скажем, если вы попытаетесь
использовать оператор , за которыми следует имя нужной переменной. (Обозначение => используется в некоторых литералах хешей, но в данном контексте оно не имеет
никакого отношения к хешам.) Если объект исключения доступен, мы можем
вывести его атрибут message.

оСоздает исключение со стр
sage.
mes
е
ут
риб
ат
в
begin
кой «oops!»
raise "oops!"
rescue => my_exception
Исключение сохраняется в переменной.
puts my_exception.message
end
Выводим сообщение из исключения.

oops!
А теперь обновим код так, чтобы исключение сохранялось
в переменной, а его сообщение
выводилось для пользователя:

dinner = ['turkey', 'casserole', 'pie']
oven = SmallOven.new
oven.turn_off
Духовка выключена.
dinner.each do |item|
begin
oven.contents = item
puts "Serving #{oven.bake}."
rescue => error
Исключение сохраняется в переменной.
puts "Error: #{error.message}"
end
Выводим сообщение,
end
которое хранится
в исключении.

Проблема описана в сообщении из исключения.

Turning oven off.
Error: You need to turn the oven on first!
Error: You need to turn the oven on first!
Error: You need to turn the oven on first!

Проблема решена. Теперь программа выводит то сообщение
из исключения, которое было передано методу raise.
400

глава 12

исключения

Развлечения с магнитами
В программе на языке Ruby, выложенной на холодильнике, часть магнитов
перепуталась. Сможете ли вы расставить фрагменты кода по местам, чтобы
программа выводила указанный результат?
def

end

end

begin

if destination == "Hawaii"
(destination)

drive

end

"You can't drive to Hawaii!"
puts error.message

drive("Hawaii")

=>

error

rescue
raise

Результат:
File Edit Window Help

You can't drive to Hawaii!

дальше 4   401

сообщения об исключениях

Развлечения с магнитами. Решение
В программе на языке Ruby, выложенной на холодильнике, часть магнитов
перепуталась. Сможете ли вы расставить фрагменты кода по местам, чтобы
программа выводила указанный результат?
drive

def

(destination)

if destination == "Hawaii"
raise

"You can't drive to Hawaii!"

end
end

begin
drive("Hawaii")

rescue

=>

error

puts error.message

end

402

глава 12

Результат:
File Edit Window Help

You can't drive to Hawaii!

исключения

Что было сделано...
С того момента, как мы занялись усовершенствованием обработки ошибок в коде
моделирования духовки, мы уже многое сделали! Давайте еще раз припомним,
что же было сделано за это время.
В метод bake класса SmallOven были добавлены команды raise, инициирующие
исключение при обнаружении проблемы. Значение атрибута message объекта задавалось по-разному в зависимости от причины (пустая или выключенная духовка).
class SmallOven
attr_accessor :contents
def turn_on
puts "Turning oven on."
@state = "on"
end
def turn_off
puts "Turning oven off."
@state = "off"
end

Исключение выдается в том
def bake
случае, если пользователь
unless @state == "on"
пытается готовить при
raise "You need to turn the oven on first!"
выключенной духовке.
end
if @contents == nil
raise "There's nothing in the oven!"
Исключение выдается в том
end
случае
, если духовка пуста.
"golden-brown #{contents}"
end

end

В коде, вызывающем метод bake, создается условие rescue с сохранением объекта
исключения в переменной с именем error. Затем выводится содержимое атрибута message объекта исключения, которое сообщает, что же именно пошло не так.
dinner = ['turkey', 'casserole', 'pie']
oven = SmallOven.new
oven.turn_on
dinner.each do |item|
begin
oven.contents = item
puts "Serving #{oven.bake}."
rescue => error
Исключение сохраняется в переменной.
puts "Error: #{error.message}"
end
Выводим сообщение, содерend
жащееся в исключении.
дальше 4   403

анализ кода

Что было сделано... (продолжение)
Если атрибут contents объекта SmallOven содержит nil, выводится
одно сообщение об ошибке:
Атрибуту contents
присваивается «nil»!

dinner = ['turkey', nil, 'pie']
oven = SmallOven.new
oven.turn_on
dinner.each do |item|
begin
oven.contents = item
puts "Serving #{oven.bake}."
rescue => error
puts "Error: #{error.message}"
end
end

Turning oven on.
Serving golden-brown turkey.
Error: There's nothing in the oven!
Serving golden-brown pie.

Сообщение из исключения.

…а если духовка выключена, выводится другое сообщение.
dinner = ['turkey', 'casserole', 'pie']
oven = SmallOven.new
oven.turn_off
Духовка выключена.
dinner.each do |item|
begin
oven.contents = item
puts "Serving #{oven.bake}."
rescue => error
puts "Error: #{error.message}"
end
end
Одно исключение инициируется трижды.

404

глава 12

Turning oven off.
Error: You need to turn the oven on first!
Error: You need to turn the oven on first!
Error: You need to turn the oven on first!

исключения

Разная логика rescue для разных исключений
Как-то странно получается —
если духовка выключена, мы трижды
выдаем одно и то же исключение,
по одному разу для каждого блюда.
А может, просто... включить духовку?

Одно и то же исключение инициируется
три раза.

Turning oven off.
Error: You need to turn the oven on first!
Error: You need to turn the oven on first!
Error: You need to turn the oven on first!

Было бы неплохо, если бы наша программа могла обнаружить проблему, включить духовку и снова попытаться приготовить обед.
Но мы не можем просто включить духовку и повторить попытку для
любого полученного исключения. Если атрибут contents содержит
nil, было бы глупо снова ставить ничто во включенную духовку!
class SmallOven
оВключить духовку и попроб
...
ый
умн
раз
вать снова? Вполне
def bake
план для обработки этого
unless @state == "on"
raise "You need to turn the oven on first!"
исключения…
end
if @contents == nil
raise "There's nothing in the oven!"
…но только не для этого!
end
"golden-brown #{contents}"
end
end

Необходимо как-то различать исключения, которые может выдавать метод bake, чтобы по-разному обрабатывать их. А для
этого можно использовать класс исключения…

дальше 4   405

обработка исключений с учетом класса

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

исклюRuby позволяет распределять
сса.
кла
чения в зависимости от их

OvenEmptyError

or
rr
yE
pt
Em
en
Ov

or
rr
fE
Of
en
Ov

Можно создать одно условие rescue,
которое обрабатывает только
исключения OvenOffError…

…и другое условие rescue, которое обрабатывает только
исключения OvenEmptyError.

Но если мы собираемся по-разному обрабатывать разные классы исключений,
то для начала нужно разобраться в том, как назначить класс исключения.

406

глава 12

исключения

Классы исключений
Когда мы вызываем метод raise, он создает объект исключения… и если это исключение не будет перехвачено, вы увидите
класс объекта исключения при завершении программы.
raise "oops!"

Объ ект исключения
является экземпляром
класса RuntimeError.

myscript.rb:1:in `': oops! (RuntimeError)
По умолчанию raise создает экземпляр класса RuntimeError.
Впрочем, при желании можно выбрать другой класс, который
будет использован методом raise. Просто передайте имя
класса в первом аргументе, перед строкой, которая определяет
сообщение данного исключения.
Укажите
имя класса.
raise ArgumentError, "This method takes a String!"

myscript.rb:1:in `':
This method takes a String! (ArgumentError)
Инициируется
исключение заданного класса!

Укажите класс.

raise ZeroDivisionError, "Can't cut a pie into 0 portions!"

myscript.rb:1:in `':
Can't cut a pie into 0 portions! (ZeroDivisionError)
Происходит исключение
заданного класса!

Вы даже можете создавать и инициировать собственные классы исключений. Впрочем, при попытке использовать такой
класс так, как вы это делали с другими классами, вы получите
ошибку (совершенно не такую, которая вам нужна):
class MyError
end

Не работает!

raise MyError, "oops!"

myscript.rb:4:in `raise': exception
class/object expected (TypeError)
Метод «raise» инициирует
собственное исключение!

дальше 4   407

классы исключений в ruby

Классы исключений (продолжение)
Exception

Для представления исключений может использоваться класс Exception.
Ниже представлена часть иерархии
классов исключений в стандартной
библиотеке Ruby:
NoMemoryError

ScriptError

LoadError

backtrace
message

SecurityError

Все субклассы наследуют атрибуты «backtrace» и «message».

SignalException

StandardError

SystemExit

SyntaxError

Многие программы пытаются
перехватывать только субклассы StandardError.

ArgumentError

EncodingError

FiberError

IOError

IndexError

NameError

RangeError

RegexpError

RuntimeError

ThreadError

TypeError

ZeroDivisionError

Итак, если вы сделаете свой класс исключения субклассом Exception, он будет
работать с raise…
class MyError < Exception
end
raise MyError, "oops!"

Должен быть субклассом Exception.

Наше исключение!

myscript.rb:4:in `': oops! (MyError)
…но учтите, что в Ruby классы исключений обычно являются субклассами StandardError, а не прямыми субклассами Exception. По общепринятым соглашениям StandardError представляет категорию
ошибок, которые могут быть обработаны типичной программой. Другие субклассы Exception представляют проблемы, неподконтрольные вашей программе, — например, нехватку памяти в системе или
завершение ее работы.
Итак, хотя вы можете использовать Exception как суперкласс для своих исключений, обычно вместо
него следует использовать StandardError.
class MyError < StandardError
end

Обычно стоит субклассировать
StandardError, а не Exception.

raise MyError, "oops!"

myscript.rb:4:in `': oops! (MyError)
408

глава 12

исключения

Назначение класса исключения для условия rescue
Теперь, когда вы научились создавать собственные классы исключений, можно заняться перехватом нужных классов. Указывая имя класса за ключевым словом rescue в условии rescue,
вы сообщаете, что условие должно перехватывать только исключения, являющиеся экземплярами этого класса (или одного из его субклассов).
В этом коде инициированное исключение (PorridgeError) не совпадает с типом, указанным
в условии rescue (BeddingError), поэтому исключение не перехватывается:
class PorridgeError < StandardError
end
class BeddingError < StandardError
end

Инициируем
исключение
PorridgeError.

def eat
raise PorridgeError, "too hot"
end
def sleep
raise BeddingError, "too soft"
end
Перехватывает только
исключения BeddingError.
begin
eat
rescue BeddingError => error
puts "This bed is #{error.message}!"
end

Исключение PorridgeError
остается необработанным.

goldilocks.rb:7:in `eat': too hot (PorridgeError)
from goldilocks.rb:14:in `'

…но все исключения, которые соответствуют классу, указанному
в условии rescue, будут обработаны:
Инициирует
исключение
BeddingError.

Перехватывает исклюbegin
sleep
чения BeddingError.
rescue BeddingError => error
puts "This bed is #{error.message}!"
end

Подходящее исключение
успешно перехватывается.

This bed is too soft!

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

дальше 4   409

перехват разных исключений

Несколько условий rescue в одном блоке begin/end
Этот код перехватывает все экземпляры BeddingError,
но игнорирует исключения PorridgeError. А мы хотим, чтобы перехватывались оба типа исключений…

Инициирует исключение PorridgeError.

Исключение PorridgeError
остается необработанным.

class PorridgeError < StandardError
end
class BeddingError < StandardError
end
def eat
raise PorridgeError, "too hot"
end
def sleep
raise BeddingError, "too soft"
end
Перехватывает только
исключения BeddingError.
begin
eat
rescue BeddingError => error
puts "This bed is #{error.message}!"
end

goldilocks.rb:7:in `eat': too hot (PorridgeError)
from goldilocks.rb:14:in `'

В один блок begin/end можно добавить несколько условий rescue,
в каждом из которых указывается отдельный тип исключения.
begin
eat
rescue
puts
rescue
puts
end

ения BeddingError.
Сюда направляются исключ

BeddingError => error
"This bed is #{error.message}!"
PorridgeError => error
Сюда направляются исключения PorridgeError.
"This porridge is #{error.message}!"

This porridge is too hot!

Это позволяет выполнять разный код в зависимости от того,
к какому типу относится перехваченное исключение.
исключение BeddingError.
begin
На этот раз инициируется
sleep
rescue BeddingError => error
Оно обрабатывается здесь.
puts "This bed is #{error.message}!"
rescue PorridgeError => error
puts "This porridge is #{error.message}!"
end

This bed is too soft!

410

глава 12

исключения

Переход на пользовательские классы исключений в классе SmallOven
Итак, вы научились инициировать исключения созданных вами классов и обрабатывать исключения
разных классов в разных местах. Теперь попробуем обновить наш код, моделирующий работу духовки. Если духовка выключена, нужно включить ее, а если пуста — предупредить об этом пользователя.
Мы создадим два новых класса исключений, представляющих две разновидности аномальных
ситуаций, и сделаем их субклассами StandardError. Затем для каждого класса исключения будет
добавлено отдельное условие rescue.
Определяем два class OvenOffError < StandardError
новых класса end
class OvenEmptyError < StandardError
исключений.
end
Инициируем class SmallOven
исключение
...
одного типа,
def bake
если духовка
unless @state == "on"
raise OvenOffError, "You need to turn the oven on first!"
выключена…
end
…и исключение
if @contents == nil
raise OvenEmptyError, "There's nothing in the oven!"
другого типа,
end
если духовка
"golden-brown #{contents}"
пуста.
end
до в духовку!
Снова забыли поставить блю
end

dinner = ['turkey', nil, 'pie']
oven = SmallOven.new
oven.turn_off
А духовка была выключена!
dinner.each do |item|
begin
Перехватывает
oven.contents = item
только исключения
puts "Serving #{oven.bake}."
rescue OvenEmptyError => error
OvenEmptyError.
Выводит сообщение из исключения,
puts "Error: #{error.message}"
как и в предыдущей версии кода.
rescue OvenOffError => error
Перехватывает
oven.turn_on
только исключеРаз духовка выключена,
ния OvenOffErrors. end
мы включаем ее.
end
Условие rescue для OvenOffError
включает духовку.

Условие rescue для OvenEmptyError
выводит предупреждение.

Turning oven off.
Turning oven on.
Error: There's nothing in the oven!
Serving golden-brown pie.

Работает! Когда при выключенной духовке инициируется исключение OvenOffError, вызывается
соответствующее условие rescue, а духовка включается. А когда по значению nil инициируется OvenEmptyError, условие rescue для этого исключения выводит предупреждение.
дальше 4   411

знакомство с retry

Перезапуск после исключения
Впрочем, мы кое-что упустили из вида… Наше условие rescue для OvenOffError снова включило духовку,
и остальные блюда были приготовлены успешно. Но так как исключение OvenOffError произошло в то
время, когда мы пытались приготовить блюдо, эта часть обеда была в итоге пропущена! Нужно вернуться
и снова попытаться приготовить индейку (turkey) после того, как духовка была включена.
Условие rescue для OvenOffError
включает духовку. Но тогда одно
блюдо оказывается пропущенным!

Turning
Turning
There's
Serving

oven off.
oven on.
nothing in the oven!
golden-brown pie.

Ключевое слово retry делает именно то, что нужно. Когда вы включаете retry в условие rescue, выполнение возвращается к началу блока begin/end, и находящиеся там команды выполняются снова.
Например, если исключение возникло из-за попытки деления на 0, можно изменить делитель и по­
пытаться выполнить операцию снова:

После исправления делителя
управления возвращается
к началу блока «begin».

amount_won = 100
portions = 0
Вызовет исключение ZeroDivisionError.
begin
portion_size = amount_won / portions
puts "You get $#{portion_size}."
rescue ZeroDivisionError
puts "Revising portion count from 0 to 1."
portions = 1
Исправляем проблему,
retry
породившую исключение.
end

Revising portion count from 0 to 1.
You get $100.

Будьте внимательны при использовании retry. Если вы не исправите проблему, породившую
исключение (или если в коде rescue допущена ошибка), то исключение будет выдано снова,
управление снова передастся retry... и программа войдет в бесконечный цикл! В таком случае
нажмите клавиши Ctrl-C, чтобы прервать работу Ruby.
Если добавить в приведенный выше код retry без исправления проблемы с делителем, произойдет зацикливание:
amount_won = 100
portions = 0
begin
portion_size = amount_won / portions
Если
puts "You get $#{portion_size}."
пропустить rescue ZeroDivisionError
puts "Revising portion count from 0 to 1."
выражение
…перезапуск произойдет,
«portions = 1»… retry
...
end
а проблема сохранится!
Revising portion count from 0 to 1.
Бесконечный цикл!

412

глава 12

Нажмите Ctrl-C, чтобы прервать
работу программы.

Revising portion count from 0 to 1.
Revising portion count from 0 to 1.
^Cwin.rb:4:in `new': Interrupt

исключения

Обновление кода с «retry»
Добавим retry в условие rescue после включения
духовки и посмотрим, будет ли пропущенное блюдо
обрабатываться на этот раз:

Условие rescue для OvenOffError
включает духовку.
Блок «begin» перезапускается, а индейка
успешно готовится.

dinner = ['turkey', nil, 'pie']
oven = SmallOven.new
oven.turn_off
dinner.each do |item|
begin
oven.contents = item
puts "Serving #{oven.bake}."
rescue OvenEmptyError => error
puts "Error: #{error.message}"
rescue OvenOffError => error
oven.turn_on
retry
Перезапускаем блок
end
«begin» после вклюend
чения духовки.

Turning oven off.
Turning oven on.
Serving golden-brown turkey.
Error: There's nothing in the oven!
Serving golden-brown pie.

Получилось! Мы не только исправили ошибку, приводившую к исключению, но и смогли заново обработать проблемный объект
(на этот раз успешно)!

Упражнение

Заполните пропуски в коде, чтобы он выдавал указанный результат.
class
end

< StandardError

score = 52
begin
if score > 60
puts "passing grade"
else
TestScoreError, "failing grade"
end
rescue
=> error
puts "Received #{error.
}. Taking make-up exam..."
score = 63
Результат:
end

Received failing grade. Taking make-up exam...
passing grade

дальше 4   413

подробнее о retry

Заполните пропуски в коде, чтобы он выдавал указанный результат.
class TestScoreError < StandardError
end
score = 52
begin
if score > 60
puts "passing grade"
else
raise TestScoreError, "failing grade"
end
rescue TestScoreError => error
puts "Received #{error.message }. Taking make-up exam..."
score = 63
retry
Результат:
end

Received failing grade. Taking make-up exam...
passing grade

414

глава 12

исключения

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

Что бы ни случилось,
ОБЯЗАТЕЛЬНО напомни мне
про духовку! В последний раз я
чуть не спалила весь дом.

Инициирует
begin
исключение.
oven.turn_on
oven.contents = nil
puts "Serving #{oven.bake}."
oven.turn_off
Никогда не выполняется!
rescue OvenEmptyError => error
puts "Error: #{error.message}"
end

Turning oven on.
Error: There's nothing in the oven!
Духовка не выключается!

В принципе, можно добавить вызов turn_off и в условие rescue…
begin
oven.turn_on
oven.contents = nil
puts "Serving #{oven.bake}."
oven.turn_off
Код копируется отсюда…
rescue OvenEmptyError => error
Turning oven on.
puts "Error: #{error.message}"
Error: There's nothing in the oven!
oven.turn_off
…в условие rescue.
Turning oven off.
end

…но такое дублирование кода тоже нежелательно.

Духовка выключается,
несмотря на исключение.

дальше 4   415

условие ensure

Условие ensure
Если у вас имеется код, который должен быть выполнен независимо от того, происходило исключение или
нет, его можно разместить в условии ensure. Условие ensure должно находиться в блоке begin/end
после всех условий rescue. Все команды, заключенные между ключевыми словами ensure и end, гарантированно будут выполнены до выхода из блока.
Условие ensure будет выполнено, если в программе происходит исключение:
begin
raise "oops!"
rescue
puts "rescued an exception"
ensure
puts "I run regardless"
end

Выполняется
условие rescue…

rescued an exception
I run regardless

…а за ним выполняется условие ensure.

Условие ensure выполняется и в том случае, если исключение не происходит:
begin
puts "everything's fine"
rescue
puts "rescued an exception"
ensure
puts "I run regardless"
end

Выполняется тело
блока «begin»…

everything's fine
I run regardless

…а за ним выполняется условие ensure.

Даже если исключение не перехватывается, условие ensure все равно будет
выполнено до прерывания работы Ruby!
begin
raise "oops!"
ensure
puts "I run regardless"
Условие ensure
end
выполняется…

I run regardless
script.rb:2:in `': oops! (RuntimeError)

…и только после этого работа Ruby завершается.

Ситуации, в которых требуется выполнить некий завершающий код независимо от того, успешно завершилась операция или нет, достаточно часто встречаются в программировании. Например, файлы
следует закрывать даже в том случае, если их содержимое повреждено. Сетевые подключения должны
закрываться даже в том случае, если вы не получили данные. А духовка должна выключаться даже в том
случае, если обед подгорел. Условие ensure идеально подходит для размещения такого кода.
416

глава 12

исключения

Гарантированное выключение духовки
Попробуем переместить вызов oven_off в условие ensure
и посмотрим, что из этого получится…
begin
oven.turn_on
oven.contents = 'turkey'
puts "Serving #{oven.bake}."
rescue OvenEmptyError => error
puts "Error: #{error.message}"
ensure
Достаточно оставить эту
oven.turn_off
строку в условии ensure.
end
Блок «begin» завершается…
…за ним следует условие ensure.

Turning oven on.
Serving golden-brown turkey.
Turning oven off.

Работает! Условие ensure будет выполнено сразу же после завершения тела begin/end, и духовка выключается.
Даже если происходит исключение, духовка все равно будет выключена. Сначала выполняется условие rescue, а за ним выполняется
условие ensure, в котором вызывается turn_off.
begin
oven.turn_on
oven.contents = nil
Происходит исключение.
puts "Serving #{oven.bake}."
rescue OvenEmptyError => error
puts "Error: #{error.message}"
ensure
Достаточно оставить эту
oven.turn_off
строку в условии ensure.
end
Выполняется условие rescue…

…а за ним следует условие ensure.

Turning oven on.
Error: There's nothing in the oven!
Turning oven off.

В реальном мире не все идет по плану, поэтому-то и существуют
исключения. Когда-то возникающие исключения приводили к
аварийному завершению вашей программы. Но теперь, когда
вы научились обрабатывать исключения, вы убедитесь, что исключения — мощный механизм, обеспечивающий безотказное
выполнение вашего кода!
дальше 4   417

Ваш инструментарий Ruby
Глава 12 осталась позади,
а ваш инструментарий
пополнился обработкой
исключений.

esnts e jects ueste
oerd
taelattsehsm
leecu
SM
s
ncoburenntV
g
i
s neaxb
in
e
t
sasR
CIC
acach
ytk
teeem
od
n
e
s
cea
rhrlelo
h
r
k
t
r
s
s
a
t
o
fio
t
f
st"siom
e
n
A
s
c
e
la
atn
l
n
s
neetsfoth
t
c
s
a
B
o
m
l
elutp
eelem
bellli
la
ne
aio
b
p"a
g
e
B
sbcu
su
H
rau
s
d
ezicceein
ia
f
it
r
ar
n
d
asa
o
n
it
d
e
o
a
r
l
i
a
o
o
o
s
s
g
p
t
in
x
d
R
f
n
if
on
t
я
i
x
o
o
M
a
CM
io
o
a
e
и
h
e
id
r
m
t
olla.
is
k
M
y
e
s
t
f
t
t
h
н
ta
v
c
c
ehlo
e claesrsC
oatnan
se ecaa
oYin
fsle
ld
еctnroalaoollnm
orcu
ou
scto.la
on
euчaespnn
a
llo
.ole
e
it
io
unceкhotm

ccD
sn
ald
esh
ty
ah
b
л
AIn
d
u
h
h
yalo

ftcю
sis
is
d
o r
llrcae
u
rE
bbd
ocl"m
a
tis
mm
d
ea
u
k
in
obafale
co
h
aad
o
d
R
r
t
n
io
f
e
o
lu
a
e
h
t
le
c
A
a
n
s
h
it
p
ix
u
m
is
v
s
r
je
s
d
e
o
p
e
"
d
b
u
w
o
A
m
e
h
e
a
a
c
t
r
>
o
o
le
e
t ad
" m
eeutthoaun aritah erd
em
ev tohrfeeo»
T
er«
Ahh g"hd


. ow
in
syia
llle
fitt e ds
ew
a
tehbp
m
n
rоteaдa
n
inC
yrm
boeо
olubehje
le
ebd
theR
e
hc"otis
v

ah
п
resyed
g"em
TA
aetun
eu
dC
aed
a"drela
it
seвetыrteh
. ow
oh
in
sytia
llle
aeum
p
em
teb
m
tA
afittthe ods овеn
rtm
h
n
in
ah
y
т
m
ais
it
Te
с"sm
otso's
rio
h
reе
crin
le
is
ola
cetb
is
W
ssa.eslo
т
a
cm
. cr
haellap
h
m
n
toom
eg
tw
rm

h
р
su
h
it
tees,g
a
гia
la
m
te
echw
le
s
la
,
e
"
e
o
a
m
s
b
e
c
m
р
s
a
h
d
r
o
's
t
t
е
n
a
n
п
s
id
o
n
t
r
=
c
la
u
e
r
n
a
v
c
u
,
c
5
o
p
c
>
p
r
e
le
a
je
ы
o
t
d
u
s
d
e
s
je
e
,
u
v
d
r
rbn
n tdio
ode
p
r
e
le
.
к
a
u
je
o
t
d
u
s
d
e
s
je
e
,
u
c
v
d
и
r
т
b
n
r
w
s
n
o
nми
s
e
b
n
e
b
n
h
ч
o
=
d
о
t
o
a
p
u
h
tm
a
w
ellр
=ob
io
tvatбm
cexe)
с
h
n
u
o
Y
s
v
е
e
e
d
o
in
u
c
n
a
lu
=
a
o
e
щ
o
id
c
о
р
h
n
m
otoia
т
d
,rn
Y
р ки,
a
m
a
ocsn
sa
кx.n
Eea
e
ю
.m
teh
.daп
p
'tb"оyc< вaсtт
shle
rin
d
sC
аtle
a
rrla
оaм
cбio
's
n
b
,r.оin
d

т
ж
toteи
rscn
o
h
oM
atcm
tn

u
сin
d
h
aеcek
"rd
caalln
er
td
tsru
oлrn
co
afco
h
r
teem
tfdsоsm
oret
csr
m
Vb
t. ionu
к
m
te
b
u
rao
иs,h
esm
d
с
m
n
e
o
h
б
a
ll
R
it
е
.
e
r
m
sau
c
e
"
g
t
c
o
f
y
ч
m
н
.
u
e
c
в
.
n
c
s
m
и
о
w
g
t
u
A in
it
u
n
s
y
a
n
o
.
u
л
e
h
a
m
o
t
r
r
t
т
e
c
in
in
б
la
s
io
т
t
y
d
a
T
Yfш
cn
ь ся
rapv
m
nit
hoвaа
кtro
rsоm
fw
е
gero li
kd
brsa
ad
олт
avи
y
eаcsяd
untm
оtм
пю
бeъ
a
anan
ивса
rorin
а
em
tsrro
a
нио.сcscы
dueaobryоdG
rreeoatw
н
in
vaglu
h
n
seix
hyeaon

it
tыa
tbtg
бзы
n
on
stu
ep
пre
n
зretо
R%
ач
etd
y
>ем
m
hsвvs,oа
e oudee".ндаd
e
witeh
le
u
e
c
"
м
s
r
R
u
%
m
м
a
u
.
<
e
а
o
т
e
.
s
g
s
h
р
а
Bе R
зau
stc.th
th
oг.dE

h
dbdyin ыми ок,ом
п
htooadm
elaeеtрm
гои
аак
о)р
Т
bry
т
ou
о
y
ing
овиня тог к
сл
ferxкoitm
у
н
с
е
я
л
с
е
л.ед
звулю
яsт
руты. польeао.йпр клами
рш
lauф

м. а
и тся M
tore ис
цю
S
::
и
L
л
и
у
з
и оль ляр YA
м
исп
шем:
Экземп ые даалнонгы
иеи. с хе
ь
н
н
д
а
о
ысхя по
ранит
Тзеувевт
ет сох
ж
о
м
р
с оп е
отчик
анное
в
разраб
о
р
и
и
оц
ие, асс
днее об
значен
, а поз
м
о
ч
ю
пользо
ым кл
ю с ис
и
деленн
н
е
ч
а
н
ься к з
а.
ратит
е ключ
ж
о
г
о
т
ванием

Далее в программе…
Это еще не все! Ограниченный объем книги не позволил нам рассмотреть многие важные темы, поэтому мы добавили приложение с краткой сводкой
наиболее важных вопросов, а также перечнем ресурсов, которые помогут вам подготовиться к вашему
следующему проекту на Ruby. Читайте дальше!

КЛЮЧЕВЫЕ
МОМЕНТЫ
ƒƒ Когда методу attribute формы HTML присвоено
значение "post" и пользователь отправляет
данные формы, браузер передает данные формы
серверу в запросе HTTP POST.
ƒƒ У форм также имеется атрибут action, задающий путь к ресурсу. Путь включается в запросы
POST (как и в случае с запросами GET).
ƒƒ В Sinatra определен метод post, который используется для определения маршрутов для запросов POST.
ƒƒ Вызов метода params в блоке маршрута post
возвращает хеш с данными формы запроса.
ƒƒ Метод YAML::Store.new получает строку
с именем файла, с которым выполняются операции чтения и/или записи.
ƒƒ У экземпляров YAML::Store имеется метод transaction, предотвращающий запись
в файл со стороны других программ. Метод
transaction получает блок, в котором
можно вызывать любые необходимые методы
YAML::Store.
ƒƒ М е т о д э к з е м п л я р а r o o t s к л а с с а
YAML::Store возвращает массив со всеми
ключами хранилища.
ƒƒ Sinatra позволяет включить в путь маршрута именованные параметры. Часть пути запроса, находящаяся в позиции именованного параметра, сохраняется и становится доступной в хеше params.
ƒƒ Если один запрос подходит для нескольких маршрутов Sinatra, он будет обработан тем маршрутом,
который был определен ранее других.

ƒƒ Маршруты с именованными параметрами обычно
следует определять последними, чтобы избежать
случайного замещения других маршрутов.

дальше 4   527

А как было бы хорошо, если бы
книга закончилась... Чтобы не было больше
ни упражнений, ни ключевых моментов,
ни фрагментов кода, ничего такого. Как
жаль, что это только мечты…

Поздравляем!
Вы дочитали последнюю главу.
Конечно, еще есть приложение.
И еще веб-сайт…
В общем, никуда вы от нас не денетесь.

528

глава 15

Приложение. Оставшиеся темы

Десять основных тем

(не рассмотренных в книге)
Минутку! Я уж было
подумала, что книга закончится,
а вы ни слова не скажете
о приватных методах! Или
регулярных выражениях!
Не надо меня пугать!

Мы прошли долгий путь, и книга почти закончена.Мы будем
скучать по вам, но было бы неправильно расставаться и отпускать вас в самостоятельное путешествие без еще нескольких напутственных слов. Нам при
всем желании не удалось бы уместить все, что вам еще нужно знать о Ruby,
на этих страницах… (Вообще-то сначала мы уместили все необходимое,
уменьшив размер шрифта в 25 000 раз. Все поместилось, но текст было не
прочитать без микроскопа, поэтому материал пришлось изрядно сократить.)
Но мы оставили все самое лучшее для этого приложения.
И это уже действительно конец книги!

ruby on rails

1. Другие полезные библиотеки
Ruby on Rails
Библиотека Sinatra (рассмотренная в главах 14 и 15) прекрасно подходит для построения простых вебприложений. Но разработчики всегда стараются дополнить свои продукты новыми возможностями, так
что приложения со временем увеличиваются. Со временем одним местом для хранения шаблонов ERB
дело уже не ограничится. Вам также понадобится место для размещения конфигурации базы данных,
место для размещения кода JavaScript и стилей CSS, место для размещения кода, связывающее все это
воедино... и многое другое.
Именно это является сильной стороной Ruby on Rails: стандартизация мест для размещения чего-либо.
Все начинается с фундаментальной архитектуры любого приложения Rails, которое строится на базе
популярного паттерна Модель, Представление, Контроллер (MVC):

Модель используется для размещения данных приложения. Rails умеет автоматически сохранять
объекты модели в базе данных и загружать их позднее. (В этом отношении она напоминает классы
Movie и MovieStore, созданные нами для приложения Sinatra.)

В представлении размещается код отображения данных модели для пользователей. По умолчанию
Rails использует шаблоны ERB для отображения данных в формате HTML (или JSON, или XML).
(Опять же по аналогии с приложением Sinatra.)

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

Получен
запрос GET.
GET "/movies/8"
...Jaws...

Контроллер отвечает полной
разметкой HTML.

Запрос передается методу
соответствующего контроллера.

Модель загружает
запрошенный объ ект
из базы данных.

class MoviesController < ApplicationController
def show
@movie = Movie.find(params[:id])
render :show
end
...
end
movies/show.html.erb

Выбрать подходящее место для этого кода — задача не из простых. Настроить Ruby Представлетак, чтобы он знал, где все это можно найти, будет еще сложнее. Вот почему Rails ние встраивает
активно использует принцип «соглашений по конфигурации». Если код модели, данные объекта
представления и контроллера будет размещаться в стандартных местах, где его будет в шаблон HTML.
искать Rails, то вам не придется заниматься настройкой. Все будет делаться автоматически. Вот почему веб-инфраструктура Rails пользуется такой популярностью!
За дополнительной информацией о Rails обращайтесь по адресуhttp://rubyonrails.org/.
530 приложение

оставшиеся темы

1. Другие полезные библиотеки (продолжение)
dRuby
dRuby (часть стандартной библиотеки Ruby) наглядно демонстрирует мощь Ruby. Название происходит
от сокращения «distributed Ruby» (распределенный Ruby); эта библиотека позволяет организовать доступ по сети к любому объекту Ruby. Разработчик просто создает объект и приказывает dRuby открыть
доступ к нему как к сетевой службе. После этого методы объекта могут вызываться из сценариев Ruby,
работающих на другом компьютере. Вам не придется писать специальный код сетевой службы. Код просто
работает благодаря уникальной особенности Ruby: возможности передачи вызовов методов от одного
объекта к другому (вскоре мы поговорим об этом подробнее).
Перед вами короткий сценарий,
require 'drb/drb'
Загружаем библиотеку dRuby.
который предоставляет доступ к
обычному массиву по сети. Мы
my_object = []
Создаем пустой массив.
передаем dRuby массив и задаем
DRb.start_service("druby://localhost:8787", my_object)
URL-адрес для
20.times do
URL-адрес (включающий номер
Цикл выполняется 20 раз.
дост
упа к массиву.
sleep
10
Ожидаем 10 секунд.
порта), по которому он должен
p
my_object
Выводим
массив.
быть доступен. Мы также доend
бавили код повторного вывода
Ожидаем завершения
DRb.thread.join
массива, чтобы вы могли просервера перед выходом.
следить за тем, как он изменяется клиентскими программами.
server.rb
А теперь мы приведем
Подключаемся к порту,
require 'drb/drb'
отдельный сценарий,
указанному выше.
DRb.start_service
который выполняет
remote_object = DRbObject.new_with_uri("druby://localhost:8787")
функции клиента. Он
remote_object.push "hello", "network"
Вызываем
подключается к серверВызываем другой метод массива.
p remote_object.last
ному сценарию по сети
метод массива.
и получает объект, действующий как заместиclient.rb
тель удаленного объекта.
Любой вызов метода заместителя передается по сети и воспроизводится для удаленного объекта. Возвращаемые значения, полученные при вызове, передаются по сети и возвращаются заместителем.
Чтобы проверить, как работает dRuby, откройте терминальное окно и запустите server.rb.
Сценарий начинает выводить содержимое масси- Сначала массив
пуст…
ва my_object каждые 10 секунд.
Теперь в другом терминальном окне запустите
client.rb. Переключившись в первое терминальное окно, вы увидите, что клиент добавил новые
данные в массив!
Возникают ли при этом проблемы, связанные с безопасностью? Еще бы. Проследите за тем, чтобы при
использовании dRuby программа была защищена
брандмауэром. За дополнительной информацией
обращайтесь к описанию dRuby в документации
стандартной библиотеки Ruby.

Пока клиенты
не начинают добавлять данные!

Это значение
было возвращено
по сети!

File Edit Window Help

$ ruby server.rb
[]
["hello", "network"]
...

File Edit Window Help

$ ruby client.rb
"network"
$

дальше 4   531

обработка файлов csv

1. Другие полезные библиотеки (продолжение)
CSV
Если вам когда-либо доводилось заниматься офисной работой, вы наверняка работали с данными в электронных таблицах — вашими или подготовленными кем-то другим. В электронных таблицах могут определяться
формулы, но их синтаксис весьма примитивен (и плохо запоминается).
Программы для работы с электронными таблицами
обычно могут экспортировать данные в формате CSV
(сокращение от «Comma-Separated Values») — простом
текстовом формате, в котором значения разбиваются по
строкам и столбцам и отделяются друг от друга запятыми.

Файл в формате CSV.

Associate,Sale Count,Sales Total
"Boone, Agnes",127,1710.26
"Howell, Marvin",196,2245.19
"Rodgers, Tonya",400,3032.48

Библиотека CSV, упрощающая обработку файлов CSV, входит в состав стандартной библиотеки Ruby. Следующий короткий сценарий использует CSV для вывода имен и общих продаж
из предыдущего файла. Он пропускает строку заголовка, но использует имена в заголовке
как ключи для обращения к значениям столбцов, словно каждая строка файла CSV представляет собой маленький хеш.
Обрабатываем
каждую строку
файла.

sales.csv

Загружаем
библиотеку.

ируПервая строка интерпрет
ов.
ется как набор имен столбц
require 'csv'
CSV.foreach("sales.csv", headers: true) do |row|
puts "#{row['Associate']}: #{row['Sales Total']}"
end
Обращаемся к данным
Boone, Agnes: 1710.26
столбцов, используя
Howell, Marvin: 2245.19
имена в заголовках
Rodgers, Tonya: 3032.48
как ключи.

К сожалению, здесь мы смогли только упомянуть об этих трех приложениях. Чтобы узнать
о десятках других полезных возможностей стандартной библиотеки, воспользуйтесь поиском
в Интернете. А на сайте http://rubygems.org вы найдете буквально тысячи других полезных пакетов.

2. Компактные версии if и unless
Мы все время говорим, что Ruby помогает выполнять больше полезной работы при меньшем объеме
кода. Этот принцип встроен на уровне самого языка. Одним из примеров такого рода служат встроенные
условные конструкции.
Конечно, вам уже знакомы традиционные формы if и unless:
if true
puts "I'll be printed!"
end

unless true
puts "I won't!"
end

Но если код условной конструкции занимает всего одну строку, условие можно переместить
в конец строки. Следующие выражения работают точно так же, как приведенные выше:
puts "I'll be printed!" if true
puts "I won't!" unless true

532 приложение

оставшиеся темы

3. Приватные методы
Когда вы создаете новый класс, вероятно, какое-то время вы будете его единственным пользователем.
Но так (хочется верить) будет не всегда. Другие разработчики увидят ваш класс и поймут, что он может
пригодиться для решения их задач. Однако их задачи не всегда точно совпадают с вашими; может оказаться, что они будут использовать ваш класс так, как не было предусмотрено исходным планом. И в этом
нет ничего страшного — до тех пор, пока вы не решите внести изменения в свой класс.
Предположим, вам потребовалось увеличить на 15% все суммы в счетах ваших клиентов. Вы создали
класс Invoice с атрибутом subtotal и методом total, вычисляющим общую сумму счета. И чтобы
избежать чрезмерного усложнения метода total, вы выделили вычисления процентов в отдельный
метод fees, вызываемый из total.
class Invoice
attr_accessor :subtotal
Значение subtotal увеdef total
личивается на 15%.
subtotal + fees(subtotal, 0.15)
end
def fees(amount, percentage)
amount * percentage
Значение amount умножается на коэффициент.
end
end
invoice = Invoice.new
invoice.subtotal = 500.00
p invoice.total

575.0

А потом вдруг выясняется, что коммерческий отдел заодно решил добавить ко всем
счетам фиксированную сумму $25. Вы добавляете в метод fees параметр flat_rate…
class Invoice
доплату $25.
Добавляем фиксированную
attr_accessor :subtotal
def total
subtotal + fees(subtotal, 0.15, 25.00)
В метод «fees» включаend
ется новый параметр.
def fees(amount, percentage, flat_rate)
amount * percentage + flat_rate
end
end

И все отлично работает — пока вам не позвонят разгневанные пользователи из другого
отдела, которые хотят знать, почему их код перестал работать из-за вашего класса.
Похоже, они начали использовать метод fees для вычисления своей доплаты, составляющей 8%. Но их код написан в предположении, что метод fees вызывается
с двумя параметрами, тогда как в новой версии он получает три!
fee = Invoice.new.fees(300, 0.08)
p fee

in 'fees': wrong number of arguments (2 for 3)
дальше 4   533

приватные методы

3. Приватные методы (продолжение)
Проблема в том, что другие разработчики вызывают ваш метод fees извне, тогда
как на самом деле он должен был вызываться только внутри вашего класса. Теперь приходится решать: то ли придумывать, как заставить метод fees работать
и для ваших целей, и для целей других разработчиков, которые используют его,
то ли вернуть код к прежнему состоянию и больше никогда не изменять его.
Впрочем, подобные ситуации можно предотвратить. Если вы знаете, что метод
должен использоваться только внутри вашего класса, пометьте его ключевым
словом private. Приватные методы могут вызываться только из кода класса,
в котором они были определены. Ниже приведена обновленная версия класса
Invoice, в которой метод fees помечен ключевым словом private:
class Invoice
вызыattr_accessor :subtotal
Приватные методы могут
ice.
Invo
в
одо
мет
гих
def total
дру
ваться из
subtotal + fees(subtotal, 0.15, 25.00)
Все методы, определяемые после этого ключевого
end
private
слова, будут приватными для класса Invoice.
def fees(amount, percentage, flat_rate)
amount * percentage + flat_rate
end
end

Теперь при попытке вызова метода fees за пределами класса Invoice
происходит ошибка с выдачей сообщения о том, что вызываемый метод
объявлен приватным.
fee = Invoice.new.fees(300, 0.08)

private method `fees' called for #
При этом ваш метод total (метод экземпляра того же класса, к которому
принадлежит fees) все равно может вызывать этот метод.
invoice = Invoice.new
invoice.subtotal = 500.00
p invoice.total

600.0

Вызывает «fees» и включает результат в свое возвращаемое значение.

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

534 приложение

оставшиеся темы

4. Аргументы командной строки
Допустим, вы хотите включить в свое сообщение электронной почты цитату из сообщения, на которое
вы отвечаете. Для этого перед каждой строкой цитируемого текста выводится символ >. По этому признаку получатель легко поймет, о чем идет речь в сообщении.
Открываем файл с заданным именем.

Перед вами простой
сценарий, который
читает содержимое
текстового файла и
вставляет символ «> »
перед каждой строкой:

file = File.open("email.txt") do |file|
file.each do |line|
Метод «each» объекта File
puts "> " + line
последовательно передает
end
каждую строку блоку.
end

Но в этом варианте нам придется
открывать сценарий и изменять имя
файла каждый раз, когда мы захотим
использовать его с новым входным
файлом. Было бы неплохо иметь возможность менять имя файла за пределами сценария.

quote.rb

>
>
>
>
>
>

Jay,
Do you have any idea how far past deadline we are?
What am I supposed to tell the copy editor?
-Meghan

И такая возможность существует! Достаточно использовать аргументы командной строки. Программы,
выполняемые в терминальном окне, часто позволяют указать аргументы после имени программы
(по аналогии с аргументами при вызове метода), и сценарии Ruby не являются исключением. Аргументы, переданные при вызове сценария, доступны в массиве ARGV, заполняемом при каждом
запуске. Первый аргумент хранится в элементе ARGV[0], второй — в элементе ARGV[1], и так далее.
Приведенный ниже короткий сценарий args_test.rb демонстрирует эту возможность. Если запустить ее
в терминальном окне, все данные, следующие за именем сценария, будут выведены в ходе выполнения.
File Edit Window Help

p ARGV[0]
p ARGV[1]
args_test.rb

$ ruby args_test.rb hello terminal
"hello"
"terminal"

Мы можем использовать массив ARGV в quote.rb, чтобы задать любой входной файл при
запуске программы. Нужно лишь заменить жестко запрограммированное имя файла
на ARGV[0]. И с этого момента при запуске quote.rb в терминальном окне мы можем
просто указать имя входного файла после имени сценария!
Первый аргумент командной строки
определяет имя открываемого файла.

file = File.open(ARGV[0]) do |file|
file.each do |line|
puts "> " + line
end
end
quote.rb

В итоге имя обрабатываемого
файла становится аргументом сценария!

File Edit Window Help

$
>
>
>
>

ruby quote.rb reply.txt
Tell them I'm really sorry!
Just ONE more week!
-Jay
дальше 4   535

регулярные выражения

5. Регулярные выражения
Регулярные выражения предназначены для поиска по шаблону в тексте. Поддержка регулярных выражений реализована во многих языках программирования, но в Ruby с ними особенно удобно работать.
Например, если вы ищете адрес электронной почты в тексте, при помощи регулярного выражения
можно сформулировать следующий критерий: «Нужно найти последовательность из какого-то количества букв, за которыми следует знак @, потом еще несколько букв, потом точка и еще сколько-то букв».
Регулярное выражение для поиска адреса электронной почты.

/\w+@\w+\.\w+/

Если вы ищете конец предложения, смысл регулярного выражения будет
таким: «Нужно найти точку, вопросительный или восклицательный знак,
за которыми следует пробел».
Регулярное выражение для поиска конца предложения.

/[.?!]\s/

Регулярные выражения обладают невероятной мощью, но они также бывают сложными и неудобочитаемыми. Впрочем, даже если вы освоите только простейшие возможности регулярных выражений,
они все равно принесут немало пользы. Здесь мы постараемся лишь дать представление о том, на что
способны регулярные выражения, и порекомендуем пару ресурсов для самостоятельного изучения темы.
Предположим, имеется строка, в которой нужно найти телефонный номер:
"Tel: 555-0199"

Создайте регулярное выражение, которое найдет его для вас. Литералы регулярных выражений начинаются и заканчиваются символом косой черты (/).
Литерал регулярного выражения.

/555-0199/

Впрочем, регулярное выражение само по себе еще ничего не делает. При
помощи оператора =~ в условном выражении можно проверить, существует
ли совпадение у регулярного выражения в строке:
Проверяем, существует ли у регулярного выражения (справа) совпадение в строке (слева).
if "Tel: 555-0199" =~ /555-0199/
puts "Found phone number."
end

РАССЛАБЬТЕ

536 приложение

СЬ

Found phone number.

Чтобы программировать на Ruby, не обязательно знать регулярные выражения.
Регулярные выражения чрезвычайно мощны, но при этом они весьма сложны,
а на практике применяются не так уж часто. Но даже если вы ограничитесь
только основными возможностями, они могут принести огромную пользу, если
вы интенсивно работаете со строками!

оставшиеся темы

5. Регулярные выражения (продолжение)
На данный момент наше регулярное выражение может совпасть только с одним телефонным номером:
555–0199. Чтобы оно могло совпадать и с другими номерами, можно воспользоваться символьным классом \d, который совпадает с любой цифрой от 0 до 9. (Также существуют и другие символьные классы —
например, \w для символов в словах или \s для пропусков.)
if "Tel: 555-0148" =~ /\d\d\d-\d\d\d\d/
Совпадает с любой цифрой.
puts "Found phone number."
end
Found phone number.

Вместо того чтобы многократно вводить \d в этом коде, можно указать один экземпляр \d, за которым
следует символ + — признак одного и более вхождений предыдущего совпадения.
if "Tel: 555-0148" =~ /\d+-\d+/
Совпадает с одной или несколькими цифрами.
puts "Found phone number."
end
Found phone number.

Еще удобнее использовать другую конструкцию — число, заключенное в фигурные скобки. Она обозначает предыдущее совпадение, повторяющееся заданное количество раз.
if "Tel: 555-0148" =~ /\d{3}-\d{4}/
Три цифры, затем дефис и еще четыре цифры.
puts "Found phone number."
end
Found phone number.

Конкретный фрагмент текста, с которым совпало регулярное выражение, может быть сохранен с помощью сохраняющей группы. Если заключить часть регулярного выражения в круглые скобки, эта часть
совпадения будет сохранена в специальной переменной с именем $1. Например, можно вывести значение
из $1, чтобы узнать, с чем совпала эта часть выражения.
Совпадающая часть строки сохраняется в переменной «$1».
if "Tel: 555-0148" =~ /(\d{3}-\d{4})/
puts "Found phone number: #{$1}"
end
Found phone number: 555-0148

Регулярные выражения в Ruby также являются объектами, что позволяет передавать их в аргументах
методов. У строк имеется метод sub, который ищет совпадение регулярного выражения в строке и заменяет совпадающий фрагмент новой строкой. Следующий вызов sub удаляет все телефонные номера
в строке:
puts "Tel: 555-0148".sub(/\d{3}-\d{4}/, '***-****')

Tel: ***-****

И это всего лишь крошечная доля того, на что способны регулярные выражения. Функциональности
гораздо больше, чем мы могли бы здесь описать, и вполне может статься, что за всю вашу карьеру программиста вам так и не потребуется изучать все тонкости регулярных выражений. Но даже если вы
ограничитесь хотя бы основными возможностями, это сэкономит вам много времени и усилий!
А если вы захотите узнать больше, обращайтесь к литературе или просмотрите описание класса Regexp
в базовой документации Ruby.
дальше 4   537

синглетные методы

6. Синглетные методы
Многие объектно-ориентированные языки позволяют определять методы экземпляра, доступные для
всех экземпляров класса. Но Ruby принадлежит к числу немногочисленных языков, позволяющих определять методы экземпляра для одного экземпляра. Такие методы называются синглетными.
Имеется класс Person с единственным
методом экземпляра speak. При создании экземпляра Person этот экземпляр
не содержит ничего, кроме метода экземпляра speak.

class Person
def speak
puts "Hello, there!"
end
end
person = Person.new
person.speak

Hello, there!

Но язык Ruby позволяет определить метод экземпляра, доступный только в одном объекте. За ключевым
словом def указывается ссылка на объект, оператор «точка» и имя определяемого синглетного метода.
Следующий код определяет метод fly для объекта в переменной superhero. Метод fly может вызываться точно так же, как любой другой метод экземпляра, но доступен он будет только в superhero.
superhero = Person.new
def superhero.fly
puts "Up we go!"
end
superhero.fly

Для объекта определяется синглетный метод с именем «fly».

Up we go!

Вызываем метод «fly».

Также возможно переопределять методы, определяемые классом, синглетными методами. Этот код
переопределяет метод экземпляра speak из класса Person версией, уникальной для superhero:
eak»
Переопределяет метод «sp
.
son
из класса Per
def superhero.speak
puts "Off to fight crime!"
end
Off to fight crime!
superhero.speak

Вызываем переопределенный метод.

Эта возможность может пригодиться при написании модульных тестов, когда метод объекта
не должен работать так, как обычно. Например, если объект всегда создает файлы для хранения вывода, но вы не хотите, чтобы он загромождал жесткий диск файлами от многочисленных запусков
теста, — переопределите метод создания файла в экземпляре, задействованном в тестировании.
Синглетные методы оказываются неожиданно полезными, хотя это всего лишь один пример
выдающейся гибкости Ruby!
538 приложение

оставшиеся темы

7. Вызов любых (даже неопределенных) методов
При вызове метода экземпляра, не определенного для объекта, Ruby вызывает для
этого объекта метод с именем method_missing. Версия method_missing, наследуемая всеми объектами от класса Object, просто инициирует исключение:
object = Object.new
object.win

undefined method `win' for # (NoMethodError)
Но если переопределить метод method_missing в классе, вы можете создавать экземпляры
этого класса и вызывать для них неопределенные методы без выдачи исключений. Более
того, в этих «фантомных методах» можно делать много всего интересного…
Ruby всегда передает method_missing как минимум один аргумент: имя вызванного метода
в форме символического имени. Кроме того, возвращаемое значение method_missing будет
интерпретироваться как возвращаемое значение фантомного метода. Например, следующий
класс возвращает строку с именем неопределенного метода, вызванного для этого класса.
Параметр содержит имя
вызванного метода.

class AskMeAnything
def method_missing(method_name)
"You called #{method_name.to_s}"
end
end
object = AskMeAnything.new
p object.this_method_is_not_defined
p object.also_undefined

…и еще один.

я из симИмя метода преобразуетс
.
оку
стр
в
ни
волического име

ода…
Вызов неопределенного мет

"You called this_method_is_not_defined"
"You called also_undefined"

Все аргументы, переданные неопределенному методу, передаются method_
missing, так что мы также можем вывести информацию о них…
Первый аргумент сохраняет
ся здесь.

ся здесь.
Второй аргумент сохраняет

class AskMeAnything
def method_missing(method_name, arg1, arg2)
"You called #{method_name.to_s} with #{arg1} and #{arg2}."
end
end
object = AskMeAnything.new
p object.with_args(127.6, "hello")

"You called with_args with 127.6 and hello."
дальше 4   539

method missing

7. Вызов любых (даже неопределенных) методов (продолжение)
Ниже приведен класс Politician, экземпляры которого готовы пообещать вам что угодно. Вызовите
любой неопределенный метод, передайте ему аргумент — и method_missing выведет как имя метода,
так и аргумент.
class Politician
def method_missing(method_name, argument)
puts "I promise to #{method_name.to_s} #{argument}!"
end
Символическое имя преобend
разуется в строку.
politician = Politician.new
politician.lower("taxes")
politician.improve("education")

I promise to lower taxes!
I promise to improve education!

Впрочем, даже это еще не все… Помните код dRuby, приведенный несколько страниц назад? В этом коде
мы создавали объект-заместитель, который вызывал методы другого объекта по сети?
require 'drb/drb' Подключаемся к удаленному серверу
DRb.start_service и получаем заместителя для массива.
remote_object = DRbObject.new_with_uri("druby://localhost:8787")
remote_object.push "hello", "network"
Вызываем метод массива.
p remote_object.last
Вызываем другой метод массива.

Для объектов-заместителей в dRuby можно вызывать любые методы. Так как заместитель почти не определяет собственных методов, эти вызовы перенаправляются методу method_missing заместителя. Здесь
имя вызванного метода и все переданные аргументы пересылаются серверу по сети.
Сервер вызывает метод для реального объекта и отправляет возвращаемое значение по сети, где оно возвращается из метода method_missing объекта-заместителя.
2

Метод «method_missing» пер
есылает имя метода сервер
у.

3 Сервер вызывает ука

занный метод реального
объ екта.

remote_object.method_missing :last

1

Вызывается неопределенный метод
заместителя.

["hello", "network"]
Реальный объект

Объект-заместитель

5

Возвращаемое значение передается
обратно по сети.

my_object.last

Сервер
"network"

"network"

4

Метод возвращает значение.

Процесс на первый взгляд сложный, но благодаря method_missing все сводится к простому
вызову метода заместителя!
540 приложение

оставшиеся темы

8. Rake и автоматизация задач
Вспомните, о чем говорилось в главе 13: чтобы выполнить модульные тесты в подкаталоге lib, нам приходилось включать в командную строку ключ -I lib. А также приходилось указывать файл с тестами,
которые нужно было запустить…
File Edit Window Help

$ ruby -I lib test/test_list_with_commas.rb
Run options: --seed 18716
...
3 runs, 3 assertions, 0 failures, 0 errors, 0 skips

Пока неудобства невелики... А если с ростом проекта появятся десятки тестовых файлов? Выполнять
их по одному станет практически нереально, а ведь тестирование — одна из задач, которые должны
выполняться регулярно. Со временем также возникнет необходимость в построении документации,
упаковки проекта в пакет и т. д.
В поставку Ruby входит программа Rake, которая может упростить все эти операции. Вы можете выполнить команду rake в терминальном окне, она ищет файл с именем «Rakefile» (без расширения) в каталоге
проекта. Этот файл должен содержать код Ruby для создания задач, которые Rake может выполнить за вас.
Ниже приведен файл Rakefile, который создает задачу для автоматизации выполнения всех тестов.
Для этого он использует класс Rake::TestTask, входящий в поставку Rake и предназначенный для
выполнения тестов. Класс TestTask настроен на загрузку файлов из каталога lib (не нужно добавлять
ключ -I lib) и запуск всех тестовых файлов в каталоге test (не нужно перечислять все тестовые файлы
по одному).
require "rake/testtask"

Загружаем специализированную
задачу Rake для запуска тестов.

Создается задача с именем «test».
Rake::TestTask.new(:test) do |t|
t.libs



«Призрачные миры» - интернет-магазин современной литературы в жанре любовного романа, фэнтези, мистики