Что означает "Monkey Patching" точно в Ruby?

Согласно Wikipedia, патч обезьяны:

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

Следующий оператор из той же записи меня смутил:

В Ruby термин "патч обезьяны" был неправильно понимается любая динамика модификация класса и часто используется как синоним динамически изменение любого класса во время выполнения.

Я хотел бы знать точный смысл патчей обезьян в Ruby. Делает ли что-то вроде следующего, или это что-то еще?

class String
  def foo
    "foo"
  end
end

Ответ 1

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

Лично я предпочитаю более инклюзивное определение. В конце концов, если бы мы использовали термин для модификации только встроенных классов, как бы мы ссылались на изменение во времени всех других классов? Для меня важно то, что существует разница между исходным кодом и фактическим запущенным классом.

В Ruby термин "патч обезьяны" был неправильно понимается любая динамика модификация класса и часто используется как синоним динамически изменение любого класса во время выполнения.

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

Ответ 2

Лучшее объяснение, которое я услышал для Патчей обезьяны /Duck -punching, принадлежит Патрику Юину в RailsConf 2007

... если он ходит как утка и говорит, как утка, ее утка, да? Так если эта утка не даст вам шум, который вы хотите, вы должны просто ударите эту утку, пока она не вернет то, что вы ожидаете.

Ответ 3

Патч обезьяны - это когда вы заменяете методы класса во время выполнения (не добавляя новые методы, как описывали другие).

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

Ответ 4

Одним из самых мощных аспектов Ruby является возможность повторно открыть любой класс и изменить его методы.

Да, правильно, вы действительно можете открыть какой-либо класс и изменить способ его работы. Сюда входят стандартные классы Ruby, такие как

String, Array or Hash!

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

Но тем не менее, способность "Monkey Patch" любого класса чрезвычайно эффективна. Рубин похож на острый нож, он может быть чрезвычайно эффективным, но обычно это ваша собственная ошибка, если вы порезаете себя.

Прежде всего, добавьте удобный метод для генерации некоторого текста Lorem Ipsum:

class String
  def self.lipsum
    "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
  end
end

В этом примере Ive снова открыл основной класс String и добавил метод класса губ.

String.lipsum
=> "Lorem ipsum dolor sit amet, consectetur adipiscing elit."

Однако мы не только добавим методы в основной класс String, но также можем изменить поведение существующих методов!

class String
  def upcase
    self.reverse
  end
end

В этом примере был upcase метод upcase и вместо этого upcase к reverse методу!

"hello".upcase
=> "olleh"

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

Когда вы должны использовать Monkey Patching?

Редко.

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

Monkey Patching, в частности, является чрезвычайно мощным инструментом. Однако мощный инструмент в чужих руках вызовет бесконечное количество боли и страданий.

Всякий раз, когда вы Monkey Patch класс, вы потенциально создаете головную боль в какой-то момент в будущем, когда все пойдет не так.

Классы, которые были Monkey Patched, сложнее понять и отладить. Если вы не будете осторожны, сообщение об ошибке, которое вы получите, скорее всего, даст вам очень мало информации о том, какова проблема на самом деле.

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

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

Когда это нормально для Monkey Patch?

Теперь, сказав это, нет смысла создавать мощные инструменты, такие как Monkey Patching, если вы на самом деле их не используете.

Бывают случаи, когда повторное открытие класса имеет смысл.

Например, вы часто видите Monkey Patches, которые просто добавляют метод удобства, который не имеет побочного эффекта. Ruby имеет очень красивый синтаксис, и поэтому может возникнуть соблазн Monkey Patch класс превратить какой-то уродливый вызов метода в нечто более читаемое.

Или, возможно, вам нужно, чтобы Monkey Patch принадлежал вам классу.

Есть много случаев, когда это прекрасно для Monkey Patch, но это определенно не будет вашим первым оружием выбора.

Часто бывает, что Monkey Patching - это просто ленивые предпочтения разработчиков по сравнению с фактическим рефакторингом или внедрением известного шаблона дизайна для конкретной проблемы.

Просто потому, что Monkey Patching предлагает вам легкое решение, это не значит, что вы всегда должны идти по этому пути.

http://culttt.com/2015/06/17/what-is-monkey-patching-in-ruby/

Ответ 5

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

Ответ 6

Это исправление обезьяны:

class Float
  def self.times(&block)
    self.to_i.times { |i| yield(i) }
    remainder = self - self.to_i
    yield(remainder) if remainder > 0.0
  end
end

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

def my_method(my_special_number)
  sum = 0
  my_special_number.times { |num| sum << some_val ** num }
  sum
end

И он ломается только время от времени, когда его вызывают. Тем, кто обращает внимание, вы уже знаете, почему, но представьте, что вы не знали о методе float, имеющем класс .times, и вы автоматически предполагали, что my_special_number является целым числом. Каждый раз, когда параметр является целым числом, integer или float, он будет работать нормально (целые ints передаются обратно, за исключением случаев, когда имеется остаток с плавающей запятой). Но передайте число с чем угодно в десятичной области, и он наверняка сломается!

Представьте, как часто это может происходить с вашими драгоценными камнями, плагинами Rails и даже с вашими коллегами в ваших проектах. Если там есть один или два небольших метода, и это может занять некоторое время, чтобы найти и исправить.


Если вам интересно, почему он ломается, обратите внимание, что sum является целым числом, а остаток с плавающей запятой может быть возвращен обратно; кроме того, экспоненциальный знак работает только тогда, когда типы одинаковы. Таким образом, вы можете подумать, что это исправлено, потому что вы конвертировали номера чисел в float... только чтобы найти, что сумма не может принимать результат с плавающей запятой.

Ответ 7

В Python monkeypatching упоминается как признак смущения: "Мне пришлось обезвредить этот класс, потому что..." (сначала я столкнулся с Zope, о чем упоминается в статье). Раньше говорилось, что нужно было взять верхний класс и исправить его во время выполнения, а не лоббировать, чтобы иметь нежелательные поведения, зафиксированные в фактическом классе, или их фиксацию в подклассе. По моему опыту, люди Ruby не говорят об этом, так как это не считается особенно плохим или даже заслуживающим внимания (отсюда "утка"). Очевидно, что вам нужно быть осторожным в изменении возвращаемых значений метода, который будет использоваться в других зависимостях, но добавление методов в класс так, как это делает active_support и facets, совершенно безопасно.

Обновление через 10 лет: я бы в последнем предложении исправился, сказав: "относительно безопасно". Расширение класса базовой библиотеки новыми методами может привести к проблемам, если кто-то другой получит ту же идею и добавит тот же метод к другой реализации или сигнатуре метода, или если люди путают расширенные методы для функциональности основного языка. Оба случая часто случаются в Ruby (особенно в отношении методов active_support).

Ответ 8

Обычно речь идет о специальных изменениях с использованием открытых классов Ruby, часто с низким качеством кода.

Хорошее продолжение по теме:

http://www.infoq.com/articles/ruby-open-classes-monkeypatching

Ответ 9

Объяснение концепции без кода:

Обсуждение точной концепции слишком академично и детально, чем нужно. Пусть это будет просто на следующем примере:

Нормальная эксплуатация автомобиля

Как вы обычно заводите машину? Все просто: вы включаете зажигание, машина запускается, и вуаля вы отправляетесь в гонки!

Обезьяна, ремонтирующая автомобиль

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

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

"Фабрицио, куда ты идешь?"

Boom!

Monday, Tuesday, Wed-nes-day, Thursday, Friday, Sat-a-day. Image is free and open source from unsplash - https://unsplash.com/photos/dyrehVIidQk

"Держите ваш исходный код близко, но ваши обезьяньи патчи ближе".