7 days of WordPress plugins, themes & templates - for free!* Unlimited asset downloads! Start 7-Day Free Trial
Advertisement
  1. Code
  2. Ruby

Fundamentos de los antipatrones: Modelos Rails

Scroll to top
Read Time: 22 mins

Spanish (Español) translation by Esther (you can also view the original English article)

¿Anti qué? Probablemente suene mucho más complicado de lo que es. A lo largo de las dos últimas décadas, los programadores han sido capaces de identificar una útil selección de patrones de "diseño" que aparecen con frecuencia en sus soluciones de código. Mientras resolvían problemas similares, eran capaces de "clasificar" soluciones que les evitaban reinventar la rueda todo el tiempo. Es importante señalar que estos patrones deben considerarse más como descubrimientos que como inventos de un grupo de desarrolladores avanzados.

Si esto es bastante nuevo para ti y te ves más en el lado de los principiantes en todo lo relacionado con Ruby/Rails, entonces este está escrito exactamente para ti. Creo que lo mejor es que lo veas como una rápida inmersión en un tema mucho más profundo cuyo dominio no se producirá de la noche a la mañana. No obstante, creo firmemente que empezar a adentrarse en esto desde el principio beneficiará enormemente a los principiantes y a sus mentores.

Los antipatrones, como su nombre indica, representan prácticamente lo contrario de los patrones. Son descubrimientos de soluciones a problemas que definitivamente debes evitar. A menudo representan el trabajo de codificadores sin experiencia que no saben lo que no saben todavía. Peor aún, podrían ser el resultado de una persona perezosa que simplemente ignora las mejores prácticas y los marcos de trabajo de las herramientas sin una buena razón, o cree que no los necesita. Lo que pueden esperar ganar en ahorro de tiempo al principio, al elaborar soluciones rápidas, perezosas o sucias, les va a perseguir a ellos o a algún lamentable sucesor más adelante en el ciclo de vida del proyecto.

No subestimes las implicaciones de estas malas decisiones: te van a acosar, pase lo que pase.

Temas

  • Modelos de grasa
  • Falta el conjunto de pruebas
  • Modelos voyeuristas
  • Ley de Deméter
  • Espagueti SQL

Modelos gordos

Estoy seguro de que escuchaste la canción "Modelos gordos, controladores flacos" montones de veces cuando empezaste con Rails. Bien, ¡ahora olvídate de eso! Por supuesto, la lógica de negocio debe resolverse en la capa del modelo, pero no deberías sentirte inclinado a meter todo allí sin sentido sólo para evitar cruzar las líneas hacia el territorio del controlador.

Aquí hay un nuevo objetivo al que deberías apuntar: "Modelos delgados, controladores delgados". Te preguntarás: "Bueno, ¿y cómo deberíamos organizar el código para conseguirlo, después de todo, es un juego de suma cero?". ¡Buena pregunta! El nombre del juego es composición, y Ruby está bien equipado para ofrecerte muchas opciones para evitar la obesidad de los modelos.

En la mayoría de las aplicaciones web (Rails) respaldadas por bases de datos, la mayor parte de tu atención y trabajo se centrará en la capa del modelo, siempre que trabajes con diseñadores competentes que sean capaces de implementar sus propias cosas en la vista, quiero decir. Tus modelos tendrán inherentemente más "gravedad" y atraerán más complejidad.

La cuestión es cómo pretende gestionar esa complejidad. Active Record te ofrece, sin duda, mucha cuerda para ahorcarte al tiempo que te hace la vida increíblemente fácil. Es un enfoque tentador diseñar tu capa del modelo siguiendo simplemente el camino de mayor conveniencia inmediata. Sin embargo, una arquitectura preparada para el futuro requiere mucha más consideración que cultivar clases enormes y meter todo en objetos de Active Record.

El verdadero problema con el que te enfrentas aquí es la complejidad, innecesaria, diría yo. Las clases que acumulan toneladas de código se vuelven complejas solo por su tamaño. Son más difíciles de mantener, difíciles de analizar y entender, y cada vez más difíciles de cambiar porque su composición probablemente carece de desacoplamiento. Estos modelos a menudo superan su capacidad recomendada para manejar una sola responsabilidad y están bastante desbordados. En el peor de los casos, se convierten en camiones de basura, manejando toda la basura que se les arroja con desidia.

Podemos hacerlo mejor. Si crees que la complejidad no es un gran problema, después de todo, eres especial, inteligente y todo, ¡piensa de nuevo! La complejidad es el asesino en serie de proyectos más notorio que existe, y no tu amistoso "Defensor de la oscuridad".

Los "modelos más delgados" consiguen una cosa que la gente avanzada en el negocio de la codificación (probablemente muchas más profesiones que el código y el diseño) aprecia y por la que todos deberíamos esforzarnos absolutamente: la simplicidad. O al menos más de ella, lo cual es un compromiso justo si la complejidad es difícil de erradicar.

¿Qué herramientas ofrece Ruby para facilitarnos la vida en ese sentido y permitirnos recortar la grasa de nuestros modelos? Sencillo, otras clases y módulos. Identificas un código coherente que podrías extraer en otro objeto y así construir una capa de modelo que consiste en agentes de tamaño razonable que tienen sus propias responsabilidades únicas y distintivas.

Piensa en ello en términos de un artista con talento. En la vida real, esa persona podría ser capaz de rapear, romper, escribir letras y producir sus propias melodías. En la programación, prefieres la dinámica de una banda, aquí con al menos cuatro miembros distintos, en la que cada persona se encarga del menor número de cosas posible. Quieres construir una orquesta de clases que pueda manejar la complejidad del compositor, no una clase de maestro genio microgestionador de todos los oficios.

one man bandone man bandone man band

Veamos un ejemplo de un modelo gordo y juguemos con un par de opciones para manejar su obesidad. El ejemplo es ficticio, por supuesto, y al contar esta tonta historia espero que sea más fácil de digerir y seguir para los novatos.

Tenemos una clase de Spectre que tiene demasiadas responsabilidades y, por lo tanto, ha crecido innecesariamente. Además de estos métodos, creo que es fácil imaginar que un espécimen de este tipo ya acumulaba muchas otras cosas como las que representan los tres puntitos. Spectre va camino de convertirse en una clase divina. (¡Las probabilidades de volver a formular una frase así con sensatez son bastante bajas!)

Spectre convierte a varios tipos de agentes enemigos, delega el asesinato de 007, interroga a los miembros del gabinete de Spectre cuando fallan, y también imprime asignaciones operativas. Un caso claro de microgestión y definitivamente una violación del "principio de responsabilidad única". Los métodos privados también se acumulan rápidamente.

Esta clase no necesita conocer la mayoría de las cosas que hay actualmente en ella. Dividiremos esta funcionalidad en un par de clases y veremos si la complejidad de tener un par de clases/objetos más vale la pena la liposucción.

Creo que la parte más importante a la que debes prestar atención es cómo utilizamos una clase Ruby simple como Interrogator para manejar el giro de los agentes de diferentes agencias. Los ejemplos del mundo real podrían representar un convertidor que, por ejemplo, transforme un documento HTML en un pdf y viceversa. Si no necesitas toda la funcionalidad de las clases de Active Record, ¿por qué utilizarlas si una simple clase de Ruby también puede servir? Un poco menos de cuerda para ahorcarnos.

La clase Spectre deja el desagradable asunto de convertir a los agentes a la clase Interrogador y solo delega en ella. Ésta tiene ahora la única responsabilidad de torturar y lavar el cerebro a los agentes capturados.

Hasta aquí todo bien. Pero, ¿por qué hemos creado clases distintas para cada agente? Muy sencillo. En lugar de extraer directamente los diversos métodos de giro como turn_mi6_agent a Interrogator, les dimos un mejor hogar en su propia clase respectiva.

Como resultado, podemos hacer un uso efectivo del polimorfismo y no nos preocupamos por los casos individuales de los agentes de giro. Solo le decimos a estos diferentes objetos agentes que giren, y cada uno de ellos sabe lo que tiene que hacer. El Interrogador no necesita saber los detalles de cómo gira cada agente.

Como todos estos agentes son objetos de Active Record, creamos uno genérico, EnemyAgent, que tiene un sentido general de lo que significa girar un agente, y encapsulamos esa parte para todos los agentes en un solo lugar subclasificándolo. Hacemos uso de esta herencia suministrando los métodos de turn de los diversos agentes con super, y por lo tanto tenemos acceso al negocio de lavado de cerebro y tortura, sin duplicación. Las responsabilidades únicas y la no duplicación son un buen punto de partida para seguir adelante.

Las otras clases de Registro Activo asumen diversas responsabilidades de las que Spectre no necesita preocuparse. El "Número Uno" suele encargarse él mismo de interrogar a los miembros fallidos del gabinete de Spectre, así que ¿por qué no dejar que un objeto dedicado se encargue de la electrocución? Por otro lado, los miembros de Spectre que fracasan saben cómo morir ellos mismos al ser fumados en su silla por NumberOneOperation ahora imprime sus asignaciones por sí misma, sin necesidad de hacer perder el tiempo a Spectre con cacahuetes como ese.

Por último, pero no por ello menos importante, matar a James Bond suele intentarlo un agente sobre el terreno, por lo que kill_james_bond es ahora un método en SpectreAgent. Goldfinger lo habría manejado de forma diferente, por supuesto, supongo que hay que jugar con el láser si se tiene uno.

Como puedes ver claramente, ahora tenemos diez clases donde antes solo teníamos una. ¿No es demasiado? Puede serlo, sin duda. Es una cuestión con la que tendrás que luchar la mayor parte del tiempo cuando repartas esas responsabilidades. Sin duda, puedes pasarte de la raya. Pero verlo desde otro punto de vista puede ayudar:

  • ¿Hemos separado las preocupaciones? ¡Por supuesto!
  • ¿Tenemos clases ligeras y delgadas que se adaptan mejor a las responsabilidades singulares? ¡Seguro que sí!
  • ¿Contamos una "historia", pintamos una imagen más clara de quién está involucrado y es responsable de ciertas acciones? ¡Eso espero!
  • ¿Es más fácil digerir lo que hace cada clase? ¡Por supuesto!
  • ¿Hemos reducido el número de métodos privados? ¡Sí!
  • ¿Representa esto una mejor calidad de la programación orientada a objetos? Dado que hemos utilizado la composición y nos hemos referido a la herencia solo cuando era necesario para configurar estos objetos, ¡por supuesto!
  • ¿Se siente más limpio? ¡Sí!
  • ¿Estamos mejor equipados para cambiar nuestro código sin hacer un desastre? ¡Claro que sí!
  • ¿Mereció la pena? ¿Qué opinas?

No quiero decir que haya que tachar estas preguntas de la lista cada vez, pero estas son las cosas que probablemente deberías empezar a preguntarte mientras adelantas tus modelos.

Diseñar modelos flacos puede ser difícil, pero es una medida esencial para mantener tus aplicaciones sanas y ágiles. Estas tampoco son las únicas formas constructivas de tratar con modelos gordos, pero son un buen comienzo, especialmente para los novatos.

Falta el conjunto de pruebas

Este es probablemente el antipatrón más obvio. Viniendo del lado de las pruebas, tocar una aplicación madura que no tiene cobertura de pruebas puede ser una de las experiencias más dolorosas de encontrar. Si quieres odiar al mundo y a tu propia profesión por encima de todo, sólo tienes que dedicar seis meses a un proyecto de este tipo, y aprenderás cuánto de misántropo hay potencialmente en ti. Es una broma, por supuesto, pero dudo que te haga más feliz y que quieras volver a hacerlo. Tal vez una semana sirva también. Estoy bastante seguro de que la palabra tortura aparecerá en tu mente más a menudo de lo que crees.

Si las pruebas no formaban parte de tu proceso hasta ahora y ese tipo de dolor se siente como algo normal en tu trabajo, tal vez deberías considerar que las pruebas no son tan malas, ni son tu enemigo. Cuando tus niveles de alegría relacionados con el código son más o menos constantes por encima de cero y puedes cambiar sin miedo tu código, entonces la calidad general de tu trabajo será mucho mayor en comparación con el resultado que está contaminado por la ansiedad y el sufrimiento.

¿Estoy sobrestimando? ¡Realmente no lo creo! Quieres tener una cobertura de pruebas muy extensa, no solo porque es una gran herramienta de diseño para escribir solo el código que realmente necesitas, sino también porque necesitarás cambiar tu código en algún momento en el futuro. Estarás mucho mejor equipado para comprometerte con tu código, y mucho más confiado, si tienes un arnés de pruebas que ayude y guíe las refactorizaciones, el mantenimiento y las extensiones. Seguro que se producirán en el futuro, no hay duda de ello.

Este es también el punto en el que un conjunto de pruebas empieza a dar la segunda vuelta de los dividendos, porque la mayor velocidad con la que se pueden hacer de forma segura estos cambios de calidad no se puede conseguir ni de lejos en las aplicaciones que están hechas por personas que piensan que escribir pruebas es una tontería o lleva demasiado tiempo.

Modelos voyeuristas

Se trata de modelos que son súper entrometidos y quieren reunir demasiada información sobre otros objetos o modelos. Esto contrasta con una de las ideas más fundamentales de la programación orientada a objetos: la encapsulación. Más bien queremos esforzarnos por conseguir clases y modelos autocontenidos que gestionen sus asuntos internos por sí mismos en la medida de lo posible. En términos de conceptos de programación, estos modelos voyeuristas básicamente violan el "Principio del Menor Conocimiento", también conocido como la "Ley de Deméter", como se quiera pronunciar.

Ley de Demeter

¿Por qué es un problema? Es una forma de duplicación, muy sutil, y también conduce a un código mucho más frágil de lo previsto.

La Ley de Demeter es prácticamente el olor a código más fiable que siempre puedes atacar sin preocuparte por los posibles inconvenientes.

Supongo que llamar a éste "ley" no es tan pretencioso como podría sonar al principio. Híncale el diente a este olor, porque lo vas a necesitar mucho en tus proyectos. Básicamente establece que en términos de objetos, puedes llamar a los métodos del amigo de tu objeto pero no al amigo de tu amigo.

Esta es una forma común de explicarlo, y todo se reduce a no usar más que un solo punto para tus llamadas a métodos. Por cierto, está totalmente bien usar más puntos o llamadas a métodos cuando se trata de un solo objeto que no intenta llegar más allá. Algo como @weapons.find_by_name('Poison dart').formula está bien. Los buscadores pueden acumular bastantes puntos a veces. Encapsularlos en métodos dedicados es, sin embargo, una buena idea.

Violaciones de la Ley de Demeter

Veamos un par de malos ejemplos de las clases anteriores:

Para que te hagas una idea, aquí tienes otras ficticias:

Plátanos, ¿verdad? No tiene buena pinta, ¿verdad? Como puedes ver, estas llamadas a métodos se meten demasiado en los asuntos de otros objetos. La consecuencia negativa más importante y obvia es el cambio de un montón de estas llamadas a métodos por todas partes si la estructura de estos objetos tiene que cambiar, lo que hará eventualmente, porque la única constante en el desarrollo de software es el cambio. Además, tiene un aspecto realmente desagradable, nada agradable a la vista. Cuando no sabes que es un enfoque problemático, Rails te permite llevarlo muy lejos de todos modos, sin gritar. Mucha cuerda, ¿recuerdas?

Entonces, ¿qué podemos hacer al respecto? Al fin y al cabo, queremos obtener esa información de alguna manera. Por un lado, podemos componer nuestros objetos para que se ajusten a nuestras necesidades y, por otro, podemos hacer un uso inteligente de la delegación para que nuestros modelos sean delgados. Vamos a sumergirnos en algo de código para mostrar lo que quiero decir.

Esto es definitivamente un paso en la dirección correcta. Como puedes ver, hemos empaquetado la información que queríamos adquirir en un montón de métodos de envoltura. En lugar de llegar a muchos objetos directamente, abstraemos estos puentes y dejamos que los respectivos modelos hablen con sus amigos sobre la información que necesitamos.

La desventaja de este enfoque es tener todos estos métodos envolventes adicionales por ahí. A veces está bien, pero realmente queremos evitar mantener estos métodos en un montón de lugares si un objeto cambia.

Si es posible, el lugar dedicado para que cambien es en su objeto, y solo en su objeto. Contaminar los objetos con métodos que tienen poco que ver con su propio modelo también es algo a tener en cuenta, ya que esto siempre es un peligro potencial para diluir las responsabilidades individuales.

Podemos hacerlo mejor. En la medida de lo posible, deleguemos las llamadas a los métodos directamente a sus objetos responsables e intentemos reducir los métodos envolventes todo lo que podamos. Rails sabe lo que necesitamos y nos proporciona el práctico método de la clase delegate para decirle a los amigos de nuestro objeto qué métodos necesitamos que sean llamados.

Vamos a profundizar en algo del ejemplo de código anterior y ver dónde podemos hacer un uso adecuado de la delegación.

Como puedes ver, pudimos simplificar un poco las cosas usando la delegación de métodos. Nos deshicimos de Operation#spectre_member_name y Operation#spectre_member_number por completo, y SpectreAgent ya no necesita llamar a number en spectre_member; number se delega directamente a su clase "origen" SpectreMember.

En caso de que esto sea un poco confuso al principio, ¿cómo funciona exactamente? Le dices a delegate a qué :method_name debe delegar to: qué :class_name (múltiples nombres de métodos también están bien). La parte del prefix: true es opcional.

En nuestro caso, antepuso el nombre de la clase receptora en forma de serpiente antes del nombre del método y nos permitió llamar a operation.spectre_member_name en lugar del potencialmente ambiguo operation.name, si no hubiéramos utilizado la opción del prefijo. Esto funciona muy bien con las asociaciones belongs_to y has_one.

En el lado has_many de las cosas, sin embargo, la música se detendrá y te encontrarás con problemas. Estas asociaciones te proporcionan un proxy de colección que te lanzará NameErrors o NoMethodErrors cuando delegues métodos a estas "colecciones".

Espagueti SQL

Para terminar este capítulo sobre los antipatrones de los modelos en Rails, me gustaría dedicar un poco de tiempo a lo que hay que evitar cuando se trata de SQL. Las asociaciones de Active Record proporcionan opciones que facilitan sustancialmente la vida cuando se es consciente de lo que debes evitar. Los métodos de búsqueda son un tema completo por sí mismos, y no los cubriremos en toda su profundidad, pero quería mencionar algunas técnicas comunes que le ayudarán incluso cuando escriba métodos muy simples.

Las cosas que deberían preocuparnos se hacen eco de la mayor parte de lo que hemos aprendido hasta ahora. Queremos tener métodos que revelen la intención, simples y con nombres razonables para encontrar cosas en nuestros modelos. Vamos a sumergirnos en el código.

Parece inofensivo, ¿no? Solo estamos buscando un grupo de agentes que tengan licencia para matar para nuestra página de operaciones. Piénsalo de nuevo. ¿Por qué debería el OperationsController indagar en el interior del Agent? Además, ¿es esto realmente lo mejor que podemos hacer para encapsular un buscador en el Agent?

Si piensas que podrías añadir un método de clase como Agent.find_licence_to_kill_agents que encapsule la lógica del buscador, definitivamente estás dando un paso en la dirección correcta, aunque no lo suficiente.

Tenemos que comprometernos un poco más que eso. En primer lugar, esto no es utilizar las asociaciones a nuestro favor, y la encapsulación también es subóptima. Las asociaciones como has_many tienen la ventaja de que podemos añadirlas al array de proxies que nos devuelven. Podríamos haber hecho esto en su lugar:

Esto funciona, seguro, pero también es otro pequeño paso en la dirección correcta. Sí, el controlador es un poco mejor, y hacemos un buen uso de las asociaciones de modelos, pero todavía debe sospechar por qué Operation se preocupa de la implementación de encontrar un determinado tipo de Agent. Esta responsabilidad corresponde al propio modelo de Agent.

Los ámbitos con nombre son muy útiles para ello. Los ámbitos definen métodos de clase encadenables, muy importantes, para tus modelos y, por tanto, te permiten especificar consultas útiles que puedes utilizar como llamadas a métodos adicionales sobre tus asociaciones de modelos. Los dos siguientes enfoques para el Agent de alcance son indiferentes.

Eso es mucho mejor. En caso de que la sintaxis de los ámbitos sea nueva para ti, no son más que lambdas (apiladas), no es muy importante que las mires de inmediato, por cierto, y son la forma correcta de llamar a los ámbitos desde Rails 4. Agent se encarga ahora de gestionar sus propios parámetros de búsqueda, y las asociaciones pueden limitarse a arropar lo que necesitan encontrar.

Este enfoque te permite realizar consultas como llamadas SQL individuales. Personalmente, me gusta utilizar el ámbito de aplicación por su explicitud. Los ámbitos también son muy útiles para encadenar dentro de métodos de búsqueda bien nombrados, de esta manera aumentan la posibilidad de reutilizar el código y de DRY-ing. Digamos que tenemos algo un poco más complicado:

Ahora podemos utilizar todos estos ámbitos para construir consultas más complejas.

Claro, eso funciona, pero me gustaría sugerirte que vayas un paso más allá.

Como puedes ver, a través de este enfoque cosechamos los beneficios de la encapsulación adecuada, las asociaciones de modelos, la reutilización del código y la denominación expresiva de los métodos, y todo ello mientras realizamos consultas SQL individuales. No más código espagueti, ¡impresionante!

Si te preocupa violar la Ley de Demeter, te alegrará saber que, como no estamos añadiendo puntos metiéndolos en el modelo asociado, sino encadenándolos solo en su propio objeto, no estamos cometiendo ningún delito de Demeter.

Reflexiones finales

Desde la perspectiva de un principiante, creo que has aprendido mucho sobre el mejor manejo de los modelos Rails y cómo modelarlos de forma más robusta sin llamar a un verdugo.

Pero no te engañes pensando que no hay mucho más que aprender sobre este tema en particular. Te he presentado unos cuantos antipatrones que creo que los novatos pueden entender y manejar fácilmente para protegerse desde el principio. Si no sabes lo que no sabes, hay mucha cuerda disponible para echártela al cuello.

Aunque este fue un sólido comienzo en este tema, no sólo hay más aspectos de los modelos AntiPatterns en Rails sino también más matices que tendrás que explorar también. Estos eran los aspectos básicos, muy esenciales e importantes, y deberías sentirte satisfecho por no haber esperado hasta mucho más tarde en tu carrera para descubrirlos.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.