Modelos Desnudos — Parte I : Setters

Las viejas y confiables estructuras de datos y su polémico acceso (de escritura)

Maximiliano Contieri
Diseño de Software
6 min readJul 17, 2020

--

Utilizar a los objetos como estructuras de datos es una práctica arraigada que genera muchos problemas asociados a la mantenibilidad y evolución del software y desaprovecha brillantes conceptos enunciados hace cinco décadas. En esta primera parte reflexionaremos sobre los accesos de escritura de dichos objetos.

En su paper clásico de 1972, David Parnas definió un concepto novedoso y fundacional para la ingeniería de software moderna: El ocultamiento de la información.

La regla es simple:

Si escondemos nuestra implementación podremos cambiarla cuantas veces sea necesario.

Foto por Dmitry Ratushny en Unsplash

Previo al paper de Parnas, no existían reglas claras sobre el acceso a la información y no estaba visto como una mala práctica inmiscuirse en las estructuras de datos provocando que estas no pudieran ser cambiadas sin generar el temido efecto de onda.

Veamos cómo modelar un punto cartesiano:

Punto Cartesiano

Cualquier componente de software que manipule estos puntos estará acoplándose a la implementación accidental consistente en guardar los valores como las coordenadas cartesianas x e y.

Como se trata únicamente de una estructura de datos sin operaciones, quedará a criterio de cada programador la semántica que se le quiera dar a cada uno de sus atributos.

Por lo tanto, si quisiéramos cambiar la implementación accidental del punto por su equivalente en coordenadas polares:

Punto Polar
El mismo punto se puede representar de dos maneras diferentes
La representación polar (√2, π/8) es equivalente a la cartesiana (1, 1)

Al tratarse del mismo punto en el mundo real deberá necesariamente ser representado por el mismo objeto en nuestra biyección.

La biyección siempre depende la visión subjetiva sobre los aspectos que estemos intentando modelar. Con el objetivo de dibujar un polígono, los puntos cartesianos (1, 1) y polar (√2, π/8) son el mismo punto.

Distinto sería el caso de intentar representar las posibles representaciones matemáticas si estuviéramos programando la semántica del Wolfram.

En ese caso las diferentes representaciones serían parte del problema y, por lo tanto, deberán ser modelados por distintos objetos.

La solución es ocultar la representación interna

Tal como predijo Parnas, muchos de los problemas de mantenibilidad de código se solucionaron encapsulado las decisiones dentro de los módulos que las definían. De esto se trata el magnífico paper.

El siguiente paso evolutivo

Con la llegada de los objetos, los conceptos de encapsulamiento y ocultamiento de la información se llevaron a un extremo mucho más atómico. Ya no hablamos de encapsular dentro de un módulo sino dentro de un mismo objeto.

Retomando el ejemplo anterior pasamos de:

hacia el cambio de representación:

Un buen diseño es aquel en el cual los objetos están acoplados a responsabilidades (interfaces) y no a representaciones.

Por lo tanto si definimos una buena interfaz para los puntos, estos pueden cambiar arbitrariamente de representación (aun en tiempo de ejecución) sin propagar ningún efecto de onda.

y ante un cambio de representación…

… todo sigue funcionando de manera correcta.

Algoritmos y Datos

Si estuviéramos trabajando con la vieja regla:

programas = algoritmos +estructuras de datos

… entonces podríamos construir excelente software con setters y getters.

Este artículo asume que decidimos construir, con objetos declarativos, modelos en los que la implementación esté escondida detrás de las responsabilidades de los objetos.

Estas responsabilidades serán exactamente las que determine la biyección entre estos objetos y el mundo real.

La involución

A pesar de los beneficios enumerados en los ejemplos de arriba, el estado del arte actual nos muestra muchos problemas relacionados al acoplamiento y al efecto de onda. La mayoría son generados por la costumbre arraigada de utilizar setters y getters (o simplemente: accessors).

Foto por Johannes Plenio en Unsplash

Veamos la presencia de setters y getters como problemas separados.

Los setters

Cambiar el estado interno de un objeto viola el principio de inmutabilidad. Eso está desaconsejado ya que, en el mundo real, los objetos no mutan en su esencia.

Los únicos métodos que deben realizar escritura sobre los atributos son los inicialización atómica. A partir de ese momento, las variables deberían ser de solo-lectura.

Si nos mantenemos fieles a la biyección, notaremos que nunca existen mensajes de la forma setAttribute..() en el mundo real. Estos son artificios de implementación,que utilizamos los programadores, y rompen los buenos modelos.

Nunca podremos explicarle a un experto de negocio qué responsabilidad tienen esos métodos a partir del nombre.

Imaginemos un polígono como una estructura de datos.

Asumamos que el polígono tiene por lo menos tres vértices.

Al ser una estructura de datos, no podemos imponer dicha restricción.

Utilizando nuestra increíble IDE con generación de código automático, le agregamos los setters y getters.

Intentemos agregar la restricción sobre la cantidad de vértices en el constructor:

A partir de ahora, será imposible crear un polígono con menos de tres lados, cumpliéndose así la biyección con el mundo real de la geometría euclidiana.

Salvo que utilicemos nuestro setter

Nada nos impide ejecutar este código:

En este punto tenemos dos opciones:

  1. Duplicar la lógica de negocio en el constructor y en el setter.
  2. Eliminar el setter de manera definitiva, favoreciendo la inmutabilidad

En caso de aceptar el código repetido, el efecto de onda comienza a propagarse cuando crezcan nuestras restricciones. Por ejemplo, si hacemos la precondición aún más fuerte:

Asumamos que el polígono tiene por lo menos tres vértices distintos.

La respuesta correcta, según nuestros axiomas de diseño, es la segunda.

Lógica repetida o ausente de verificación de invariantes

Muchos objetos tienen invariantes que garantizan su cohesión y la validez de la representación para mantener la biyección con el mundo real. Permitir hacer el seteo parcial (un atributo) nos obligaría a controlar los invariantes de representación en más de un lugar, generando código repetido, que siempre es propenso a errores al modificar una referencia y pasar por alto las demás reglas.

Código generado sin control

Muchos entornos de desarrollo nos regalan la posibilidad de automatizar la generación de setters y getters. Esto induce a las nuevas generaciones de programadores a pensar que es una buena práctica de diseño, generando vicios difíciles de corregir.

Esta facilidad propaga el problema, tener esta herramienta produce la sensación de que es una práctica aceptada.

Recomendaciones

  • No usar setters. No existen razones bien argumentadas para hacerlo.
  • Tener métodos de la forma setAlgo…() es un code smell.
  • No tener atributos públicos. A fines prácticos es como tener setters y getters.
  • No tener atributos estáticos públicos. Además de lo enumerado más arriba las clases deberían ser stateless y esto es un code smell indicando que se está usando una clase como una variable global.
  • Evitar objetos anémicos (Aquellos que tengan únicamente atributos y sin responsabilidades. Es un code smell indicador de un objeto ausente en la biyección.

Conclusiones

La práctica de utilizar setters genera acoplamiento e imposibilita la evolución incremental de nuestros sistemas informáticos. Por los argumentos enunciados en este artículo, deberíamos restringir su uso lo máximo posible.

Los Getters

Al igual que sucede con los setters, los getters están desaconsejados. Desarrollamos este tema en profundidad en este artículo:

Parte del objetivo de esta serie de artículos es generar espacios de debate y discusión sobre diseño de software.

Esperamos comentarios y sugerencias sobre este artículo.

Este artículo también está disponible en inglés aquí.

--

--

Maximiliano Contieri
Diseño de Software

I’m a senior software engineer specialized in declarative designs. S.O.L.I.D. and agile methodologies fan. Maximilianocontieri.com