Что именно означает "исправление обезьян" в Ruby?
Согласно Википедии, патч обезьяны это:
способ расширения или изменения кода времени выполнения динамических языков [...] без изменения исходного исходного кода.
Следующее утверждение из той же записи меня смутило:
В Ruby термин "исправление обезьяны" неправильно понимался как означающий любое динамическое изменение класса и часто используется как синоним для динамического изменения любого класса во время выполнения.
Я хотел бы знать точное значение исправлений обезьян в Ruby. Это делает что-то вроде следующего, или это что-то еще?
class String
def foo
"foo"
end
end
9 ответов
Краткий ответ: нет никакого "точного" значения, потому что это новый термин, и разные люди используют его по-разному. По крайней мере, это можно заметить из статьи в Википедии. Некоторые настаивают на том, что он применяется только к коду "времени выполнения" (я полагаю, встроенные классы), в то время как некоторые используют его для ссылки на модификацию времени выполнения любого класса.
Лично я предпочитаю более инклюзивное определение. В конце концов, если бы мы использовали термин только для модификации встроенных классов, как бы мы ссылались на модификацию во время выполнения всех других классов? Для меня важно, что есть разница между исходным кодом и действующим классом.
В Ruby термин "исправление обезьяны" неправильно понимался как означающий любое динамическое изменение класса и часто используется как синоним для динамического изменения любого класса во время выполнения.
Вышеприведенное утверждение утверждает, что использование Ruby некорректно, но термины меняются, и это не всегда плохо.
Лучшее объяснение, которое я слышал о патчинге / утином ударе Обезьяны, - это Патрик Юинг в RailsConf 2007
... если он ходит как утка и говорит как утка, это утка, верно? Так что, если эта утка не дает вам шум, который вы хотите, вы должны просто ударить эту утку, пока она не вернет, что вы ожидаете.
Патч обезьяны - это когда вы заменяете методы класса во время выполнения (не добавляя новые методы, как описали другие).
В дополнение к очень неочевидному и трудному для отладки способу изменения кода, он не масштабируется; По мере того, как все больше и больше модулей запускают методы исправления обезьян, вероятность изменений, мешающих друг другу, возрастает.
Одним из наиболее мощных аспектов 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
В этом примере я снова открыл String
основной класс и добавил метод класса Lipsum.
String.lipsum
=> "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
Тем не менее, мы не только можем добавить методы в ядро String
Класс, мы также можем изменить поведение существующих методов!
class String
def upcase
self.reverse
end
end
В этом примере мы угоняем upcase
метод и вызов reverse
метод вместо!
"hello".upcase
=> "olleh"
Итак, как вы можете видеть, невероятно легко добавлять или изменять методы в существующем классе, даже если вы не являетесь владельцем этого класса или он является частью ядра Ruby.
Когда следует использовать Monkey Patching?
Редко.
Ruby предоставляет нам множество мощных инструментов для работы. Однако то, что инструмент является мощным, не делает его подходящим для работы.
В частности, Monkey Patching - чрезвычайно мощный инструмент. Однако мощный инструмент в чужих руках вызовет бесконечное количество боли и страданий.
Всякий раз, когда вы Monkey Patch a class вы потенциально создаете головную боль в какой-то момент в будущем, когда что-то пойдет не так.
Классы, которые были исправлены Monkey Patched, сложнее понять и отладить. Если вы не будете осторожны, сообщение об ошибке, которое вы получите, скорее всего, даст вам очень мало информации о том, в чем проблема на самом деле.
Когда вы Monkey Patch метод, вы потенциально можете взломать код вниз по течению, который полагается на это поведение.
Когда вы добавляете новый метод в существующий класс с помощью Monkey Patching, вы потенциально открываете странные крайние случаи, которые невозможно предвидеть.
Когда все в порядке с Monkey Patch?
При этом, как говорится, нет смысла иметь такие мощные инструменты, как Monkey Patching, если вы на самом деле ими не пользуетесь.
Есть случаи, когда повторное открытие класса имеет смысл.
Например, вы часто видите патчи Monkey, которые просто добавляют удобный метод, который не имеет побочных эффектов. У Ruby очень красивый синтаксис, поэтому Monkey Patch может быть заманчиво превратить классный вызов метода в нечто более читаемое.
Или, возможно, вам нужно Monkey Patch класс, который у вас есть.
Во многих случаях это нормально для Monkey Patch, но это определенно не должно быть вашим первым оружием выбора.
Часто случается так, что Monkey Patching - это просто ленивое предпочтение разработчика перед реорганизацией или реализацией известного шаблона проектирования для конкретной проблемы.
То, что Monkey Patching предлагает вам простое решение, не означает, что вы всегда должны идти по этому пути.
http://culttt.com/2015/06/17/what-is-monkey-patching-in-ruby/
Ты прав; это когда вы изменяете или расширяете существующий класс, а не делите его на подклассы.
Это исправление обезьяны:
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
И он ломается только изредка, когда его вызывают. Тем, кто обращает внимание, вы уже знаете почему, но представьте, что вы не знали о типе поплавка, имеющего .times
класс-метод, и вы автоматически предположили, что my_special_number
является целым числом Каждый раз, когда параметр представляет собой целое число, целое число или число с плавающей запятой, он будет работать нормально (целые числа передаются обратно, кроме случаев, когда есть остаток с плавающей запятой). Но передайте число с чем-либо в десятичной области, и оно обязательно сломается!
Представьте себе, как часто это может происходить с вашими гемами, плагинами Rails и даже вашими коллегами по проектам. Если есть один или два маленьких метода, таких как этот, и это может занять некоторое время, чтобы найти и исправить.
Если вам интересно, почему он ломается, обратите внимание, что sum
является целым числом и остаток с плавающей точкой может быть передан обратно; Кроме того, экспоненциальный знак работает только при одинаковых типах. Таким образом, вы можете подумать, что это исправлено, потому что вы преобразовали надоедливые числа в числа с плавающей запятой... только чтобы обнаружить, что сумма не может принять результат с плавающей запятой.
В Python monkeypatching часто упоминается как признак смущения: "Мне пришлось monkeypatch этот класс, потому что..." (я впервые столкнулся с ним, когда имел дело с Zope, о котором упоминается в статье). Говорят, что необходимо было овладеть вышестоящим классом и исправить его во время выполнения, вместо того, чтобы лоббировать исправление нежелательных поведений в реальном классе или исправление их в подклассе. По моему опыту, люди в Ruby не слишком много говорят об обезьяньей ловушке, потому что она не считается особенно плохой или даже заслуживающей внимания (отсюда и "штамповка утки"). Очевидно, что вы должны быть осторожны с изменением возвращаемых значений метода, который будет использоваться в других зависимостях, но добавление методов в класс так, как это делают active_support и facets, совершенно безопасно.
Обновление 10 лет спустя: я бы изменил последнее предложение, чтобы сказать "относительно безопасно". Расширение класса базовой библиотеки новыми методами может привести к проблемам, если кто-то другой получит ту же идею и добавит тот же метод с другой реализацией или сигнатурой метода, или если люди спутают расширенные методы для функциональности основного языка. Оба случая часто происходят в Ruby (особенно в отношении методов active_support).
Пояснение понятия без кода:
Обсуждение точной концепции является слишком академическим и нюансированным, чем должно быть. Давайте сделаем это просто на следующем примере:
Нормальная эксплуатация автомобиля
Как вы обычно заводите машину? Все просто: вы включаете зажигание, машина запускается, и вуаля, вы отправляетесь в гонки!
Обезьяна, ремонтирующая автомобиль
Но что, если кто-то еще сделал машину, а что, если вы захотите изменить ее работу?
Вам не нужно идти на завод по производству автомобилей, чтобы внести эти изменения: вы можете просто "подлатать обезьяны", забравшись под капот, и тайно и хитро перемонтировать вещи и добавив туда несколько мест. Вы должны действительно знать, что вы делаете, когда вы делаете это, иначе результаты могут быть довольно взрывоопасными - и, возможно, это именно то, что вы хотите?
" Фабрицио, куда ты идешь? "
Boom!
"Держите ваш исходный код близко, но ваши обезьяны исправляются ближе".
Обычно речь идет об особых изменениях с использованием открытых классов Ruby, часто с некачественным кодом.
Хорошее продолжение темы:
http://www.infoq.com/articles/ruby-open-classes-monkeypatching