Quantcast
Channel: Planeta Código
Viewing all articles
Browse latest Browse all 2715

Koalite: Resistiendo el hedor de las clases grandes

$
0
0

Entre las novedades que trajo la versión 2.0 de .NET, allá por el año 2005, estaban las clases y los métodos parciales.

En aquella época Microsoft todavía estaba muy enfocado al wizard driven development y las clases y métodos parciales surgieron, fundamentalmente, para facilitar la integración de código generado por herramientas con código escrito por usuarios. Jamás en mi vida he usado un método parcial, tal vez porque nunca llegué a utilizar los infames DataSets tipados de la época, pero las clases parciales estaban a la orden del día si desarrollabas con Windows Forms, por lo que estaba más que acostumbrado a tratar con ellas.

En el fondo, ese uso de las clases parciales era equivalente al uso que se le había dado antes a las regiones: esconder la basura. Habíamos pasado de tener una región con el texto Windows Forms Designer generated code, a llevarnos ese código a un fichero MyForm.Designer.cs, pero no había mucho más cambio.

Lo único que permiten las clases parciales es separar la implementación de una clase en varios ficheros. En general, para que esto tenga sentido, o bien estamos mezclando código autogenerado con código nuestro, cosa que ya de por si es discutible, o bien tenemos una clase demasiado grande, cosa que directamente huele mal.

Si os cuento esto no es por repetiros cosas que seguro que ya sabéis, sino por, como de costumbre, hacer de abogado del diablo y explicar por qué (a veces) me gusta usar clases parciales pese a cómo huelan.

Lo malo es mucho, pero eso ya lo sabéis

No me voy a extender mucho en este punto porque ya lo he mencionado antes y no creo que pueda aportar mucho nuevo.

Si te ves en la necesidad de separar una clase que has escrito tú en varios ficheros, seguramente estés violando unos cuantos principios SOLID, empezando por el de principio de responsabilidad única. Si la clase tuviese menos responsabilidades, sería más pequeña y no tendrías que andar partiéndola en ficheros.

La forma kosher de tratar con una clase grande es partirla en varias clases más pequeñas, no en más ficheros. Esto tiene mucho sentido y presenta claras ventajas, como poder reutilizar potencialmente esas clases más pequeñas en otros contextos.

Qué me aporta una clase grande

Voy a asimilar el usar una clase parcial con tener una clase grande porque, sinceramente, si tengo una clase con 3 métodos, no le veo mucho sentido a partirla en varios ficheros.

Si quiero tener una clase grande es porque considero fundamental garantizar que se cumplen los invariantes de esa clase.

Para mi existe una tensión entre tener flexibilidad, con clases más pequeñas que puedan ser utilizadas (y reutilizadas) de distintas formas y en distintos contextos, y tener un sistema rígido en el que sólo sea posible hacer aquello que es correcto hacer y de la forma en que es correcta hacerlo.

No todas las partes de una misma aplicación son iguales y, en general, tiene sentido que la mayoría de la aplicación esté compuesta por partes pequeñas, manejables y flexibles, pero hay otras en las que garantizar que no se pueden usar de forma incorrecta es más importante.

En el típico sistema de facturación, la parte que es encarga de gestionar productos tal vez no sea tan crítica, pero la que calcula el importe final de una factura en base a sus impuestos sí lo es, y puede que merezcla la pena encapsular toda esa lógica en la factura, aunque pueda llevar a incrementar el tamaño de esa clase y nos acerque a otro antipatrón como es el god object.

Puedes extraer esa lógica a clases de apoyo, pero eso suele implicar exponer hacia el exterior más estado interno de la clase cuyos invariantes quieres mantener. Eso plantea dos problemas. Por una parte, te arriesgas a que alguien toque ese estado interno violando los invariantes sin utilizar la clase de apoyo que con tanto amor has diseñado, y por otra, alguien podría duplicar el código de la clase de apoyo realizando la misma tarea de forma ligeramente diferente, cosa que no es muy deseable. Podrías acabar con dos APIs para hacer lo mismo y sería complicado saber cuál es la correcta.

Al final se reduce un tema de visibilidad que, como bien dice Modesto, está sobrevalorada, pero que no deja de ser útil para determinadas cosas. Se trata de aprovechar las herramientas que te ofrece cada lenguaje, y si un lenguaje te ofrece mecanismos para controlarla, no está de más aprovecharlos.

Lo que acabo haciendo en muchas ocasiones es implementar esas clases de apoyo que encapsulan parte de la lógica de la clase “grande”, pero implementarlas como clases privadas dentro de la clase grande. De esa forma, puedo controlar que accedan a información privada de la clase “principal” sin necesidad de exponer esa información a todo el mundo.

La clase “principal” se convierte en una especie de fachada que almacena estado sobre el que trabajan un conjunto de clases internas más pequeñas. Esta clase expone un API hacia el exterior que, básicamente, se limita a redirigir las operaciones a sus clases internas.

No tiene mucho sentido poner un ejemplo completo porque hablamos de escenarios complejos, pero para que os hagáis una idea estos ficheros están sacado de un proyecto real:

// Clase "principal" que representa una tarea cuyo 
// estado se computa a partir de eventos externos 
// que se van recibiendo. Dependiendo de varios 
// factores (estado de la tarea, eventos recibidos, 
// hora, etc.) los eventos se aceptan o no, provocan 
// unos cambios u otros, etc.
Task.cs

// Clase interna que construye eventos a partir
// de información externa
Task.EventBuilder.cs

// Clase interna que construye la política de 
// aceptación de eventos en base a la información 
// encapsulada actualmente en la tarea
Task.EventPolicyFactory.cs


// Clases internas (hay unas cuantas) para cada 
// política de aceptación concreta de eventos.
// Forman un patrón Strategy de libro ;-)
Task.OpenTaskPolicy.cs
Task.AssignedTaskPolicy.cs
Task.AcceptedTaskPolicy.cs
//....

Ésta era la miga real de un sistema que controlaba una parte importante del funcionamiento de un aeropuerto.

Era poco código con respecto al total (al final siempre pesa más el interfaz de usuario, acceso a datos, comunicaciones, etc.), pero que el sistema funcionara o no, dependía de que los cálculos que se realizaban aquí dentro estuvieran bien. Para nosotros era crítico que se respetasen correctamente los invariantes del objeto Task porque errores en esta parte tenían repercusiones económicas importantes, así que blindar esta parte del sistema tenía sentido en ese contexto determinado.

Supongo que queda claro, pero cuando hablo aquí de proteger invariantes, me refiero sobre todo a protegerlos de nosotros mismos. No se trata de protegerlo de “gente idiota que no sabe programar”, o al menos no de otros idiotas que no seamos nosotros. Por muy bueno que te creas y muy cuidadoso que seas, llega un momento en que te equivocas, y cuanto más te ayude tu código a defenderte de tu yo del futuro, mejor.

Si entrecierras un poco los ojos y lo miras con cariño, es equivalente a crear un closure sobre unas variables (el estado de la clase principal) que son accedidas desde otras funciones (las clases internas). O dicho de otra forma, el patrón revealing module tan frecuente en Javascript.

Se puede conseguir algo parecido jugando con la visibilidad a nivel de assembly y los internal, pero eso obliga a tener que partir el código en assemblies, lo que implica mayor complejidad de despliegue y, sobre todo, mayor lentitud en desarrollo por la forma en que Visual Studio gestiona los proyectos. Hace mucho que no utilizo assemblies para controlar visibilidad y preferiría no tener que volver a pasar por ello.

Esta técnica de tener clases grandes encapsulando de forma opaca mucho comportamiento presenta un problema indudable para los que queremos testearlas, porque nos obligan a escribir tests más complejos.

Llegado el caso, lo primero que me planteo es si necesito testear las partes por separado. A veces realmente prefiero invertir el tiempo en los tests más complicados porque son los que me aportan más valor, pero hay otras que quiero testear una parte concreta de un algoritmo. En esos escenarios, intento llevarme el comportamiento a testear a una función pura y testearlo independientemente del estado de las clases afectadas.

Si no es posible y realmente creo que el test merece la pena, acabo haciendo pública la clase interna. Tampoco sufro demasiado por ello, porque por la forma en que trabajamos “sabemos” que una clase así es raro que sea pública y sospechamos si vemos un uso suyo fuera de un proyecto de test.

Otros usos de clases parciales

A título anecdótico, existió otro uso interesante para las clases parciales: crear fachadas estáticas extensibles.

Si tenías una fachada estática, por ejemplo para generar constraints en los tests, del tipo de Is.EqualTo, Is.SameAs, etc., podías hacer que la clase stática Is de la que cuelgan todos los métodos de factoría para crear constraints fuese parcial, y así otros proyectos podían colgar su propias constraints de ella y mantener un único punto de entrada facilitando el uso para los clientes.

Cuando aparecieron los métodos extensores esta técnica dejó de tener mucho sentido (había formas mejores de conseguir cosas similares) y cayó en desuso, pero hubo una época (alrededor de 2006-2008) en la que se hacían cosas curiosas con ella.

Conclusión

Utilizar clases lo bastante grandes como para que merezca la pena separarlas en varios ficheros es sospechoso. Huele mal. Pero que algo sea sospechoso no quiere decir que necesariamente sea culpable.

A veces compensa encapsular más información y más lógica en una única clase para facilitar el mantenimiento de invariantes que sean importantes en la aplicación.

En ese caso, podemos utilizar clases privadas internas a la clase principal para encapsular parte de ese comportamiento, haciendo que sea más legible y que sea más fácil razonar sobre él.

Cuando llegamos a ese punto, separar cada clase en un fichero independiente haciendo uso de clases parciales es una forma razonable de mantener el código organizado.

Posts relacionados:

  1. Clases, objetos, funciones e Information Hiding

Viewing all articles
Browse latest Browse all 2715

Trending Articles


Girasoles para colorear


mayabang Quotes, Torpe Quotes, tanga Quotes


Tagalog Quotes About Crush – Tagalog Love Quotes


OFW quotes : Pinoy Tagalog Quotes


Long Distance Relationship Tagalog Love Quotes


Tagalog Quotes To Move on and More Love Love Love Quotes


5 Tagalog Relationship Rules


Best Crush Tagalog Quotes And Sayings 2017


Re:Mutton Pies (lleechef)


FORECLOSURE OF REAL ESTATE MORTGAGE


Sapos para colorear


tagalog love Quotes – Tiwala Quotes


Break up Quotes Tagalog Love Quote – Broken Hearted Quotes Tagalog


Patama Quotes : Tagalog Inspirational Quotes


Pamatay na Banat and Mga Patama Love Quotes


Tagalog Long Distance Relationship Love Quotes


BARKADA TAGALOG QUOTES


“BAHAY KUBO HUGOT”


Vimeo 10.7.0 by Vimeo.com, Inc.


Vimeo 10.7.1 by Vimeo.com, Inc.