Несколько паттернов для написания надёжного ruby кода

Illustration of a young person sitting on the floor, leaning against a wall, reading a book, with a focus on their orange sneakers. Illustration of a young person sitting on the floor, leaning against a wall, reading a book, with a focus on their orange sneakers.

В этом посте я решил собрать некоторые паттерны и сниппеты из книги Confident Ruby, которые мне понравились и пригодились. В книге их около 30, я ограничусь всего четырьмя, которыми вы можете воспользоваться уже сегодня.

Array()

Array() превращает любой аргумент в массив. Отличается от to_a тем, что действительно превращает в массив что угодно. Например:

'firefly'.to_a #=> NoMethodError: undefined method `to_a' for "firefly":String
Array('firefly') #=> ['firefly']

Hash#fetch для проверки наличия ключей

Лучший способ проверить существует ли ключ в хэше и получить этот ключ — это вызвать метод fetch. Если ключа в хэше нет, то fetch вызовет исключение KeyError: key not found.

hash = {marko: 'polo'}
hash.fetch(:marko) #=> 'polo'
hash.fetch(:john) #=> KeyError: key not found: :john

Альтернативный синтаксис обработки исключений

Что бы не засорять код метода begin/rescue/end блоками ruby позволяет перенести rescue в конец метода, тем самым отделив рабочую часть кода от обработки исключений.

Сравните:

def kill_mockingbird
    begin
          # выполняем работу
    rescue
          # обрабатываем исключения
    end
end
def kill_mockingbird
     # выполняем работу
rescue
     # обрабатываем исключения
end

На две строчки короче и метод разбит на логические составляющие.

Использование собственных методов для преобразования данных

Допустим, у нас есть библиотека для некоего AuthorAPI. Допустим, API ожидает что поля name и email присутствуют обязательно.

def post_author(author)
     api.send {
          name: author.name
          email: author.email
     }
end

Сразу проблема: у author должны быть определены методы name и email. Мы можем ограничить доступные аргументы и позволить передавать только хэш с атрибутами. А можем сделать использование нашей библиотеки более удобным и позволить помимо хэша использовать свои классы, при условии что для них определён метод to_author. Рефакторим:


def post_author(properties_or_obj = {})
    # Теперь пользователь может передать свой объект или просто хэш с параметрами
    author = properties_or_obj.is_a?(Hash) ? properties_or_obj : properties_or_obj.to_author

    # fetch позволяет передать блок, который вызывается если ключ не найден
    name = author.fetch(:name) { raise KeyError, "Name must be supplied for Author API requests" } 
    email = author.fetch(:email) { raise KeyError, "Email must be supplied for Author API requests" }

    api.send {
      name: name,
      email: email
    }
end 
class User
   def to_author
     {
          name: [first_name, last_name].join(","),
          email: email
     }
   end
end
     post_author(current_user) #=> Yay!

Что думаете об этих паттернах? Как сами боретесь с duck typing и conditional hell?