Estático vs Dinámico
A menudo escribo tutoriales. Realmente, estos tutoriales son artículos de opinión: Alguien puede estar en desacuerdo conmigo. Siempre hay otra manera de hacer las cosas y es más que probable que yo no la conozca. En este caso va a ser más patente.
En este caso vamos a ver las diferencias que hay entre hacer las cosas de manera estática y hacerlas dinámica. Y veremos también cómo hay más cosas estáticas de las que creemos.
Primero vamos a definir las cosas: algo es estático cuando se impide su crecimiento. Algo es dinámico si es permite el crecimiento sin grandes consecuencias para sus colaboradores.
Es obvio pensar que un tipo básico (números y cadenas) es estático. Tenemos el valor de la variable y nada más. No existe la posibilidad de agregar nada a un tipo básico.
Alguien puede decirme que en Ruby sí se pueden añadir métodos a los “tipos básicos”. Por definición, si se pueden añadir métodos, no es un tipo básico. Es… otra cosa.
También es obvio que un array es estático, ya que su tamaño es constante y cambiarlo implica una gestión algo ineficiente de la memoria.
Pero… ¿Qué ocurre con los vectores, tablas hash, sets, …?
Pues a primera vista son dinámicos, ya que permiten modificar su tamaño. Sin embargo, desde otro punto de vista, son completamente estáticos, ya que es una solución genérica a un problema y no admite su especialización.
Consecuencias
Darse cuenta de esto tiene consecuencias. La primera y más obvia es descubrir que no deberíamos usar estos tipos de forma automática. Es mejor encapsularlos en clases específicas que los utilicen. De esta manera sí podremos especializarlos, adaptándolos a cada problema.
Pondremos un ejemplo: en uno de nuestros objetos necesitamos varias tablas hash. A primera vista, estas tablas son todas iguales. Pero si fueran iguales, sería una sola tabla. Es decir: ya existe un elemento diferenciador. Sin embargo, desde nuestra clase estamos manejando las tres. Esto es, claramente, una violación del principio de única responsabilidad.
¿Significa esto que tengo que pasar cada tabla a un objeto que la maneja? Sí, claramente.
Esto tiene su utilidad. Supongamos que esas tres tablas evolucionan de forma diferente. Por ejemplo, su tamaño es diferente. De pronto, descubrimos que una de las tablas se ha vuelto terriblemente grande. En el caso de tener un único objeto con todas las hashes, tenemos un problema. Si hemos aislado su manejo, podemos cambiar la implementación de una tabla hash por el de una caché, guardando a disco los elementos menos utilizados, o incluso cambiarlo por una base de datos.
De esta manera, se demuestra que una lista o una tabla hash es estática, ya que no admite cambios de implementación. No son objetos en sí mismos, sino que debemos considerarlos útiles cuando se encapsulan dentro de objetos que les aportan significado. Son herramientas, no objetos finales.
Sacrificios
A menudo sacrificamos la buena programación por terminar antes. Es lógico: programar bien lleva mucho tiempo. Sin embargo, hay que tratar de llegar a un compromiso entre el tiempo dedicado y la calidad del código. Eso es lo que diferencia a un programador de un profesional.
Un programador resuelve el problema. Un profesional aporta la mejor solución que conoce en cada momento.
Estos sacrificios pueden realizarse esporádicamente cuando los colaboradores de nuestra clase son otras clases privadas. Sin embargo, compartir tipos básicos, una lista o una tabla hash por medio de una API puede ser el mayor de los errores, ya que estamos limitando su utilización. Probablemente sea siempre una mejor solución utilizar un objeto hecho a propósito.
Un ejemplo de esto puede verse en la autenticación. Tenemos nuestra preciosa función “autenticate(String user, String password)”. Sin embargo, ahora resulta que queremos autenticar un OpenId. Nuestra función no ofrece suficiente versatilidad. Sin embargo, si hubiéramos encapsulado los datos en un objeto, tendríamos: “autenticate(Credentials credentials)”, lo que sí es suficientemente versátil.