martes, 3 de abril de 2018

Objectos y estructuras de datos - Clean Code

Esta publicación se centra en el capítulo de Objetos y estructuras de datos de Clean Code (Robert C. Martin).

Abstracción de datos


La abstracción de datos tiene como finalidad manipular la esencia de los datos de un sistema sin conocer los detalles de implementación del mismo.

Si el sistema presenta un dato que es un punto en un plano, su representación se podría hacer mediante una clase que exponga su implementación o con una abstracción que esconda completamente su implementación.

Clase que expone su implementación.


Clase que esconde su implementación.


La clase «Point» expone la implementación. Los atributos de la instancia se tienen que modificar directa e independientemente. Hacer las variables privadas y manipularlas a través de métodos públicos no oculta la implementación, esta sigue ahí.

La «interface» punto sí que oculta la implementación. Esta representa más que una estructura de datos formada por dos atributos. El método «setCoordinates» está forzando una política de acceso donde la asignación de las dos coordenadas se hace de manera atómica.

La abstracción de datos es más compleja que ocultar la implementación detrás de métodos «get» o «set». Esta se basa en representar los componentes del sistema y su funcionamiento sin detenerse en cómo se va a implementar.

Antisimetría de datos y objetos


Mientras que las estructuras de datos exponen sus datos y no tienen funciones significativas, los objetos ocultan los datos mediante abstracciones. Por definición son justo lo opuesto.

En el siguiente ejemplo se observa un estilo de estructuras de datos para unas figuras geométricas. Se puede apreciar que las estructuras de datos no tienen ningún comportamiento y sólo tienen los datos. Todo el comportamiento aparece en la clase «Geometry».

Si se añade un nuevo comportamiento a la clase «Geometry», ninguna de las clases que son estructuras de datos cambia. Por otra parte, si se añade una nueva figura geométrica, se debe cambiar el comportamiento de la clase «Geometry».

Si se utilizan objetos para la implementación del ejemplo anterior, el comportamiento estaría incluido en cada clase con el método «area». Si se añade una nueva figura geométrica, esto no afectaría a las demás pero, si se añade un nuevo comportamiento, entonces todas las clases tienen que incluir esta modificación. Esto se puede solventar con un «Visitor».




Con estos ejemplos se hace patente la naturaleza de los dos estilos.

Las estructuras de datos facilitan la adición de nuevas funciones sin cambiar las estructuras de datos existentes mientras que los objetos posibilitan la incorporación de nuevas clases sin cambiar las funciones existentes.

Hay situaciones en las cuales es estilo con estructuras de datos será más apropiado y otros en los que los objetos serán la mejor solución.

La ley de Demeter


La ley de Demeter establece que un módulo no debería saber sobre la implementación de los objetos que manipula.

Un método «f» de una clase «C» debería llamar únicamente a métodos de:
  • «C».
  • Un objeto creado por «f».
  • Un objeto pasado como argumento a «f».
  • Un objeto en una variable de instancia de «C».

El método «f» no debería invocar métodos de objetos que se devuelven por cualquiera de las funciones permitidas. En otras palabras, debería limitarse a hablar con amigos y no con extraños.

En algunos lenguajes de programación se usa el punto como identificador y la ley se puede simplificar como «usar sólo un punto». El código a.b.method() vulnera dicha ley mientras que a.method() no lo hace.

Choque de trenes


Este tipo de código se ve como un encadenamiento de funciones y/o variables. Este tipo de cadenas se considera un estilo descuidado y debería evitarse.


Usualmente es mejor no encadenar y almacenar en variables con un nombre semántico.


En principio parece que la función sabe cómo navegar a través de distintos objetos. Si esto incumple o no la ley de Demeter va a depender de si estos objetos son estructuras de datos sin comportamiento, lo cual no vulnera la ley, o si son objetos con un compartimiento lo cual sí incumpliría la ley.

Si el código estuviera escrito como acceso a distintos atributos no cabría duda de que se trata de una estructura de datos y la ley de Demeter no aplicaría aquí.



Híbridos


Hay veces que la confusión viene dada por estructuras híbridas que son en parte objetos y en parte estructuras de datos. Tienen funciones que realizan tareas significativas (comportamientos) y también tienen variables públicas.

Estos Híbridos tienen lo peor de los dos aspectos, por lo que se debe tratar de evitarlos.

Ocultar la estructura


Como no podemos encadenar llamadas a través de los objetos para no romper la Ley de Demeter podríamos crear un método en «$app» que obtenga el resultado:

Esta opción provoca una explosión de método en el objeto «$app». Además, desvela el funcionamiento interno del mismo.

Otra posibilidad sería:



En este caso se está asumiendo que el resultado de «getPathsFromConfig()» es una estructura de datos.

Ninguna de las soluciones parece buena ya que se está preguntando sobre su estructura interna. Al ser un objeto, este debería tener un comportamiento, una abstracción del negocio, que es el que se tendría que utilizar.

Una estrategia es analizar el porqué de obtener esa ruta, ¿dónde se va a utilizar y para qué? Si un lector analizara el código y encontrase lo siguiente:


Se puede inferir que la intención de obtener la ruta de las imágenes es mover una imagen que ha sido previamente subida.

Así que se podría abstraer este comportamiento en el objeto «$app» de la siguiente manera:


Objetos de transferencia de datos


Los objetos de transferencia de datos también conocidos como DTO (Data Transfer Object) o Bean (en Java) son básicamente estructuras de datos que en su forma más pura sólo tienen los atributos. Se pueden añadir «getter» y «setter» para los más puristas aunque no añade beneficios sustanciales.

Si utilizamos la clase «Rectangle» del ejemplo anterior, su DTO sería el siguiente:



Registros activos o Active Record


Los registros activos o active records, en inglés, son una forma sofisticada de DTO. Son estructuras de datos formadas por atributos públicos con funcionalidades adicionales como «save», «delete», «find», etc. Estos sirven para la manipulación directa sobre una base de datos u otra fuente de datos.

Un problema de estas estructuras de datos con dichas funcionalidades es que los desarrolladores las tratan como si fueran objetos añadiendo más funcionalidades de las reglas de negocio. Esto al final genera un híbrido entre una estructura de datos y un objeto.

La solución es tratar a los registros activos o active records como una estructura de datos y crear abstracciones mediante objetos para las reglas de negocio.

Conclusiones


Los objetos exponen comportamientos y ocultan los datos (implementación). Añadir nuevos tipos de objetos sin cambiar el comportamiento es fácil ya que sólo hay que crear una nueva clase y añadir los comportamientos sin tener que modificar los existentes. Por contra, añadir un nuevo comportamiento es más difícil, ya que hay modificar todas las clases existentes para incluir ese comportamiento.

Las estructuras de datos exponen sus datos pero no tienen un comportamiento significativo. Esto facilita añadir nuevos comportamientos en las clases que implementan dichos comportamientos ya que sólo hay que modificar esa clase pero hace que resulte más complejo añadir nuevas estructuras de datos, ya que hay que crear la nueva estructura de datos y modificar todos los comportamientos para que incluyan esa estructura de datos.

Los programadores tienen la responsabilidad de valorar cual es la solución más adecuada para resolver cada tarea concreta.

No hay comentarios:

Publicar un comentario