Использование send для удобства, от безысходности и ради развлечения

от автора


Один из методов библиотеки Sidekiq. Объяснение смайла

send в Ruby вызывает методы объектов по имени. Вот очевидный способ применения:

# До: явно используем присваивание. Неудобно, если полей много или они определяются в рантайме. user.name = "Иван" user.age = 29  # После: передаём имя атрибута параметром. Решает проблемы первого способа. def set(field, value)   send("#{field}=", value) end user.set(:name, "Иван") user.set(:age, 29)

А ещё вы наверняка видели такие строки:

after_create :send_email

Да-да, коллбэки в рельсах внутри реализованы тоже с помощью send.

Ещё через send при тестировании вызывают приватные методы. О том, нужно ли вообще их тестировать, рассказывает Sandi Metz, автор книги Practical Object-Oriented Design in Ruby (о приватных методах с 10:58).

Краткий перевод-пересказ

Она считает тестирование приватных методов излишним: правильный набор входных данных при тестировании публичных методов обеспечит 100%-е покрытие. Но периодически при активной разработке нестабильного кода Sandi нарушает это правило, чтобы не копаться в трейсах, а ловить ошибку в месте возникновения. Такие тесты она считает временными и с готовностью удаляет, когда код стабилизируется. Она в курсе про подход "Если вам нужно тестировать приватный метод, вынесите его в отдельный класс", но считает, что от такого выделения код не станет более стабильным.

Иногда без send не обойтись:

data_point = OpenStruct.new(:queued? => true) data_point.queued?  # -> true data_point.send("queued?=",false) # иначе такой метод не вызвать data_point.queued?  # -> false

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

В продолжение темы о необычных названиях методов. Часто пытаетесь выйти из irb командой учше? Просто добавьте в ~/.irbrc следующий код:

module Kernel   def учше     exit   end end

Пришло в голову, пока обдумывал примеры для статьи; добавил ради теста себе в .irbrc — работает. Собирался было удалить, потом думаю: "А ведь удобно же". Пока оставил, пускай полежит недельку.

Для отчаянных: зачем останавливаться только на переводе учше-exit?

module Kernel   def method_missing(method_name, *arguments, &block)     return(super) unless contains_russian_letters?(method_name.to_s)     possible_meaning = translit(method_name.to_s)     send(possible_meaning, *arguments)   end    private    def translit(string)     string.chars.map do |char|       russian_to_english_mapping[char] || char     end.join   end    def contains_russian_letters?(string)     !(string.chars & russian_symbols).empty?   end    def russian_symbols     "йцукенгшщзхъфывапролджэячсмитьбю".chars   end    def russian_to_english_mapping     english = "qwertyuiop[]asdfghjkl;'zxcvbnm,.".chars     russian_symbols.zip(english).to_h   end end  # Пробуем: puts(1.inspect) # Выводит 1 згеы(1.штызусе) # Тоже выводит. Работает!

Оставлять это не решился.

Недостатки

Неаккуратное использование send ухудшает читаемость. Сравните:

# Вариант 1. Читаемо, но не масштабируемо if params[:sort_by] == "age"   users.sort_by_age else   users.sort_by_name end  # Вариант 2. Масштабируемо, но нечитаемо sorting_method = "sort_by_#{params[:sort_by]}" users.send(sorting_method)  # Лучше переписать код так, чтобы можно было писать  # и масштабируемо, и читаемо: users.sort_by(params[:sort_by])

Подведём итоги

Используйте send, если вам нужно:

  • Определять имя метода во время исполнения;
  • Писать свои велосипедные коллбэки;
  • Вызывать приватные методы в тестах.

Не используйте send без необходимости.

ссылка на оригинал статьи https://habrahabr.ru/post/318546/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *