¿Qué significa exactamente ‘Monkey Patching’ en Ruby?

Según Wikipedia, un parche de mono es:

una forma de ampliar o modificar el código de tiempo de ejecución de lenguajes dynamics […] sin alterar el código fuente original.

La siguiente statement de la misma entrada me confundió:

En Ruby, el término parche de mono no se entendió como cualquier modificación dinámica de una clase y, a menudo, se utiliza como sinónimo de modificación dinámica de cualquier clase en tiempo de ejecución.

Me gustaría saber el significado exacto de parche de monos en Ruby. ¿Está haciendo algo como lo siguiente, o es algo más?

class String def foo "foo" end end 

La respuesta corta es que no hay un significado “exacto”, porque es un término nuevo, y diferentes personas lo usan de manera diferente. Eso al menos se puede discernir del artículo de Wikipedia. Hay algunos que insisten en que solo se aplica al código de “tiempo de ejecución” (supongo que en las clases incorporadas) mientras que otros lo usarían para referirse a la modificación en tiempo de ejecución de cualquier clase.

Personalmente, prefiero la definición más inclusiva. Después de todo, si tuviéramos que usar el término para la modificación de las clases integradas solamente, ¿cómo nos referiríamos a la modificación del tiempo de ejecución de todas las otras clases? Lo importante para mí es que hay una diferencia entre el código fuente y la clase de ejecución real.

En Ruby, el término parche de mono no se entendió como cualquier modificación dinámica de una clase y, a menudo, se utiliza como sinónimo de modificación dinámica de cualquier clase en tiempo de ejecución.

La statement anterior afirma que el uso de Ruby es incorrecto, pero los términos evolucionan, y eso no siempre es malo.

La mejor explicación que escuché sobre Monkey patching / Duck-Punching es la de Patrick Ewing en RailsConf 2007

… si camina como un pato y habla como un pato, es un pato, ¿verdad? Entonces, si este pato no te está dando el sonido que deseas, tienes que golpear ese pato hasta que regrese lo que esperas.

El parche de mono es cuando reemplaza los métodos de una clase en el tiempo de ejecución ( sin agregar nuevos métodos como otros han descrito).

Además de ser una forma muy obvia y difícil de depurar para cambiar el código, no escala; a medida que más y más módulos inician métodos de parche de monos, aumenta la probabilidad de que los cambios se aceleren unos a otros.

Uno de los aspectos más poderosos de Ruby es la posibilidad de volver a abrir cualquier clase y cambiar sus métodos.

Sí, es cierto, puede reabrir cualquier clase y cambiar la forma en que funciona. Esto incluye las clases estándar de Ruby, como

 String, Array or Hash! 

Ahora esto es obviamente tan peligroso como parece. Ser capaz de cambiar el resultado esperado de un método puede causar todo tipo de comportamiento extraño y es difícil rastrear errores.

Pero, no obstante, la capacidad de “Monkey Patch” de cualquier clase es extremadamente poderosa. Ruby es como un cuchillo afilado, puede ser extremadamente efectivo, pero por lo general es tu culpa si te cortas.

Primero, agregaremos un método útil para generar algunos textos de Lorem Ipsum:

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

En este ejemplo, he reabierto la clase de núcleo String y he añadido un método de clase lipsum.

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

Sin embargo, no solo podemos agregar métodos a la clase String central, sino que también podemos modificar el comportamiento de los métodos existentes.

 class String def upcase self.reverse end end 

En este ejemplo, estamos secuestrando el método upcase y llamando al método reverse lugar.

 "hello".upcase => "olleh" 

Como puede ver, es increíblemente fácil agregar o modificar métodos en una clase existente, incluso cuando no es dueño de esa clase o es parte del núcleo de Ruby.

¿Cuándo deberías usar Monkey Patching?

Raramente.

Ruby nos proporciona una gran cantidad de herramientas poderosas para trabajar. Sin embargo, solo porque una herramienta sea poderosa, no la convierte en la herramienta adecuada para el trabajo.

Monkey Patching en particular es una herramienta extremadamente poderosa. Sin embargo, una herramienta poderosa en las manos equivocadas causará cantidades interminables de dolor y sufrimiento.

Cada vez que Monkey Patch una clase potencialmente estás creando un dolor de cabeza en algún momento en el futuro cuando las cosas van mal.

Las clases que han sido parcheadas con mono son más difíciles de comprender y depurar. Si no tiene cuidado, el mensaje de error que recibirá probablemente le dará muy poca pista sobre cuál es realmente el problema.

Cuando Monkey Patch es un método, posiblemente descifrarás código que se basa en ese comportamiento.

Cuando agrega un nuevo método a una clase existente usando Monkey Patching, potencialmente está abriendo casos de bordes extraños que no es posible prever.

¿Cuándo está bien para Monkey Patch?

Ahora bien, dicho esto, no tiene sentido tener herramientas poderosas como Monkey Patching si no las usas realmente.

Hay casos donde la reapertura de una clase tiene sentido.

Por ejemplo, a menudo se ven Parches de mono que simplemente agregan un método de conveniencia que no tiene ningún efecto secundario. Ruby tiene una syntax muy hermosa, por lo que puede ser tentador para Monkey Patch convertir una clase de método feo en algo que sea más legible.

O tal vez necesites Monkey Patch, una clase que posees.

Hay muchos casos en los que está bien para Monkey Patch, pero definitivamente no debería ser tu primera arma de elección.

A menudo será el caso de que Monkey Patching es solo la preferencia del desarrollador perezosa sobre la refactorización o implementación de un patrón de diseño conocido para un problema en particular.

El hecho de que Monkey Patching le ofrezca una solución fácil no significa que deba tomar ese camino.

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

Estás en lo correcto; es cuando modificas o extiendes una clase existente en lugar de subclasificarla.

Esto es parche de monos:

 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 

Ahora me imagino que esto podría ser útil a veces, pero imagine si viera la rutina.

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

Y se rompe solo ocasionalmente cuando se llama. Para aquellos que prestan atención, ya saben por qué, pero imaginen que no sabían que el tipo de flotación tiene un .times clase .times y automáticamente asumieron que my_special_number es un número entero. Cada vez que el parámetro es un número entero, entero o flotante, funcionaría bien (se pasan los enteros enteros excepto cuando hay un rest de punto flotante). ¡Pero pase un número con cualquier cosa en el área decimal y se romperá con seguridad!

Imagínese con qué frecuencia esto podría suceder con sus gems, complementos de Rails e incluso con sus propios compañeros de trabajo en sus proyectos. Si hay uno o dos métodos pequeños como este, podría tomar algún tiempo encontrarlos y corregirlos.

Si se pregunta por qué se rompe, tenga en cuenta que la sum es un número entero y que un rest de punto flotante podría pasarse; Además, el signo exponencial solo funciona cuando los tipos son iguales. Entonces, puede pensar que es fijo, porque convirtió los números molestos en carrozas ... solo para descubrir que la sum no puede tomar el resultado de coma flotante.

En Python, el parche monopatín se menciona mucho como un signo de vergüenza: “Tuve que ponerle parche a esta clase porque …” (Lo encontré primero cuando trato con Zope, que el artículo menciona). Se solía decir que era necesario tomar una clase ascendente y arreglarla en tiempo de ejecución en lugar de cabildear para corregir los comportamientos no deseados en la clase real o arreglarlos en una subclase. En mi experiencia, la gente de Ruby no habla tanto de parchear, porque no se considera especialmente malo o incluso digno de mención (de ahí el “golpe de pato”). Obviamente, debe tener cuidado al cambiar los valores de retorno de un método que se utilizará en otras dependencias, pero agregar métodos a una clase de la forma en que active_support y facets lo hacen es perfectamente seguro.

Actualización 10 años después : Modificaría la última oración para decir “es relativamente seguro”. Ampliar una clase de biblioteca principal con nuevos métodos puede provocar problemas si alguien más obtiene la misma idea y agrega el mismo método con una implementación o firma de método diferente, o si la gente confunde métodos extendidos para la funcionalidad del lenguaje central. Ambos casos ocurren a menudo en Ruby (especialmente con respecto a los métodos active_support).

Por lo general, se refiere a cambios ad-hoc, usando clases abiertas de Ruby, frecuentemente con código de baja calidad.

Buen seguimiento del tema:

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