Los accessors en java

Comencé a trabajar con java hace unos ocho años. Desde el principio aprendí que el uso de accessors era una buena práctica. Consiste en declarar las variables de instancia como privadas y definir unos métodos públicos, llamados getters y setters para acceder a esos atributos. Algo como esto:

private String foo;
public void setFoo(String foo) {
  this.foo = foo;
}
public String getFoo() {
  return foo;
}

Para acceder a foo entonces escribimos el siguiente código:

objeto.getFoo();
objeto.setFoo("mucho código, pocas nueces");

Nunca me acabaron de encajar demasiado, pero hasta ahora no había dedicado algo de tiempo a ello. Creo que los accessors son sencillamente una mala práctica y sería prudente dejarlos atrás. Una de las críticas más socorridas que se hace a java es su “verbosidad”, la crítica no carece de fundamento y los accessors son un ejemplo de ello.

La alternativa es simple. No utilizar accessors y hacer las variables públicas:

public String foo;

Para acceder a esta variable, entonces:

objeto.foo;
objeto.foo = "ahora sí";

La comparación es demoledora 🙂 aunque un poco simple, me recuerda a los argumentos que esgrimen muchas veces de forma poco fundamentada los enemigos de java, así que me esforzaré un poco más.

En realidad el problema (o más bien la ventaja) es que los accessors son una convención, de forma que si tu código está aislado bien puedes decidir tú cómo organizarlo, java no hace uso en ningún caso de los accessors. Pero multitud de frameworks y productos hacen uso de la convención y esperan que existan los accessors para acceder a las variables de instancia. Así ocurre con Spring, Hibernate y Expression Language, por poner tres ejemplos cotidianos para muchos programadores java en entornos internet.

La solución pasa por flexibilizar el estándar. Complicarlo, por desgracia, a cambio de simplificar mucho el código de los usuarios de esos frameworks. La lógica sería la de buscar el accessor para la variable que tratamos de acceder y si no lo encontramos atacar a la variable directamente. Sólo al no encontrar la variable en este segundo paso devolver un error. Esto proporcionaría compatibilidad hacia atrás de todo el código ya escrito para funcionar con accessors. Esta propuesta tiene el peligro de ser una sobrecarga en rendimiento. En todo caso lo que yo quiero es hacer ver que los accessors son una mala práctica, las soluciones pueden ser distintas de esta que propongo y que soy consciente que debería soportar con pruebas.

Algo de historia y una justificación

Si bien los accessors empezaron a utilizarse en C++, en java hicieron su aparición estelar como parte de la especificación de los JavaBeans. En ese contexto no sólo tienen sentido sino que son necesarios. Los JavaBeans como componentes nunca triunfaron por razones que desconozco, pero dejaron la percepción de que era una buena decisión de diseño fomentar el uso de los accessors. De hecho yo también creo que lo es, pero sólo en determinadas circunstancias. En Data Structures and Algorithms with Object-Oriented Design Patterns in Java Bruno Preiss es muy conciso respecto a las virtudes de los accessors:

“By defining suitable accessors, it is possible to hide the implementation of the class from the user of that class”

Esconder la implementación de la clase del cliente de la misma no me parece una ventaja per se, aunque sí puede serlo en el caso de que tu código vaya a ser distribuido y consumido por terceras partes como por ejemplo si haces un framework. Si eres un programador de Spring puede ser una buena idea ser estricto en el uso de los accessors, de esa manera aseguras la compatibilidad hacia atrás incluso aunque cambie la estructura de tus clases. Sin embargo, si es tu propio proyecto el consumidor de tu código, esa ventaja se diluye, dado que igual que cambias el código del productor puedes cambiar el de los consumidores. El hecho de que java sea estáticamente tipado y que existan los IDEs modernos hacen que esa tarea sea trivial. Eso en todo caso siempre que modifiques una clase estructuralmente sin que sus clientes deban modificarse, algo que la experiencia me dice que rara vez va a ocurrir.

Todas las refencias que he encontrado son muy antiguas. Parece que todos hemos dado por buena la necesidad de los accessors y ya no hay debate sobre ellos si alguna vez lo hubo. Scott Ambler escribe un artículo en defensa de los accessors en octubre de 2000.

Sólo 26 días después publica un segundo artículo abundando en el mismo tema. En un análisis más exhaustivo encuentra múltiples virtudes de los accessors y encuentra un solo defecto. Las virtudes se pueden resumir en una frase del primer artículo:

“Getters and setters are useful because they enable you to encapsulate important business rules, transformation logic, and validation logic that are applicable to your data fields”.

El defecto:

“The only time you might not want to use accessors is when execution time is of the utmost importance”

El defecto creo que sinceramente ha quedado superado con el tiempo, no sólo por el abaratamiento del hardware sino sobre todo por las mejoras en la máquina virtual de java, que creo que harán la diferencia de rendimiento entre usar o no accessors imperceptible.

En cuanto a las ventajas creo mete el dedo en la llaga mencionando la lógica de validación. Tiene mucho sentido que el setter valide si el valor que estamos tratando de setear es correcto. Creo que ésta es la gran virtud práctica de los setters, pero no veo necesario renunciar a ella si seguimos un modelo mixto: al escribir una variable de instancia primero busco su setter, si lo encuentro lo ataco, si no voy directamete contra la variable.

El siguiente artículo, escrito por Allen Holub, me pareció esperanzador al leer el título: Why getter and setter methods are evil, pero no podía estar más alejado de mis tesis:

“Getter and setter methods (also known as accessors) are dangerous for the same reason that public fields are dangerous: They provide external access to implementation details”.

Holub piensa que exponemos demasiado al utilizar accessors, aún propone esconder más el código. Considera que esconder el código es una virtud per se. Me parece una perspectiva interesante aunque quizás muy académica y que nos obligaría a repensar cómo estructuramos el código. No sólo el nuestro sino el de los frameworks que usamos.

Otros lenguajes hacen uso de los accessors no ya como convención sino como parte estructural del lenguaje. Así ruby no proporciona visibilidad fuera del propio objeto a las variables de instancia forzando así la existencia de accessors. De forma muy conveniente, eso sí, proporciona una keyword que implementa los accessors en runtime: attr_accessor.

JavaFX es un lenguaje que ha evolucionado a partir de java y prescinde de los accessors, a cambio implementa triggers. La única utilidad entonces que los diseñadores de JavaFX vieron a los accessors es lanzar operaciones sobre la escritura. Por cierto, ¿qué ha sido de JavaFX?.

Resumen

Los accessors proporcionan una serie de ventajas e inconvenientes que enumero a continuación:

Ventajas

  • Permite validar antes de alterar el estado de los objetos.
  • Proporciona una convención. Aunque no mucho mejor la convención setProp(valor) que prop = valor
  • Permite alterar el comportamiento sin modificar los clientes. En los tiempos de los IDEs cañeros y sus refactorizaciones, esto es difícilmente una ventaja.
  • Estándar de facto. Los frameworks confían en que exista y hasta el programador más bisoño entiende su mecanismo.

Inconveniente

  • Mucho más código. Más engorroso de manejar, más difícil de mantener.

Una sóla desventaja, pero de tanto peso que por mí merece la pena replantear la convención.

Anuncios