Review del libro “Elegant Objects” Vol 1
Se acaba el verano y volvemos a la rutina de escribir en el blog. Vamos allá con una de las lecturas veraniegas.
A principios de Julio terminé de leer el libro “Elegant Objects — Vol.1”. Y, durante el mes de Julio y Agosto, el segundo volumen del libro, al que le dedicaré una próxima entrada en el blog. Ambos han sido escritos por el ruso Yegor Bugayenko, un programador, fundador, inversor y filántropo (según él mismo), un tanto peculiar y algo controvertido por algunas de sus afirmaciones, tanto en el fondo como en las formas.
Elegant Objects es un libro sobre programación orientada a objetos. No es un libro de carácter técnico como tal, aunque también tiene muchos ejemplos de código, sino que tiene un trasfondo filosófico en el que el autor nos da su visión sobre cual debería ser el enfoque correcto que tendríamos que tener cuando programamos bajo el paradigma de la programación orientada a objetos. Hay 23 recomendaciones prácticas e independientes para que programar orientado a objetos nos haga desarrollar un código más limpio con clases más sólidas y una arquitectura más visible. Además, varios de los artículos del libro están tratados en el blog de Yegor . El libro está escrito en inglés, pero es bastante sencillo de leer. Tiene un formato muy cómodo de transportar porque son pequeños. Y bueno, cada volumen de Elegant Objects viene a salir por unos 40 euros, que, en mi opinión, están muy bien invertidos.
No voy a hacer un desglose exhaustivo del contenido del libro ni creo que haga falta repasarlo detenidamente punto por punto pero si haré, al menos, mención de forma más o menos resumida a algunas de las ideas que más me han llamado la atención (si quieres profundizar, lee el libro o entra en el blog del autor), aunque, ya te digo, probablemente, no estés de acuerdo con muchas de las ideas expuestas.
Mantenibilidad del software
Es uno de los objetivos a conseguir más repetidos a lo largo de libro. ¿Qué es software mantenible? pues podríamos definir la mantenibilidad, por ejemplo, como una medida de cuánto tiempo nos lleva entender un código. Cuánto más tarde en comprenderse menor mantenibilidad tendrá y peor código es, lo cual, redundará en dificultades técnicas añadidas en su modificación y, por lo tanto, será un software más caro.
Nunca uses nombres de clases terminados en -er
Aquí al principio hace una diferenciación entre clase y objeto. A veces no tenemos muy claro que es cada una y se usan indistintamente. Yegor, define una clase como una fábrica de objetos, es decir, nosotros definimos una clase y a partir de ella se crean, se instancian, muchos objetos de su tipo, por lo tanto, tendríamos que pensar en las clases como un ‘manager’ activo de objetos.
A la hora de elegir un nombre para una clase, suele haber dos enfoques. El malo, el incorrecto, que es mirar lo que hace nuestra clase y pensar un nombre basado en su funcionalidad. Por ejemplo, una clase llamada CashFormatter y un método format() encargado de formatear una cantidad monetaria. Esta forma de pensar es muy corriente y está muy extendida (yo mismo la he usado muchísimas veces, la verdad). Sin embargo, el enfoque correcto es pensar que es realmente el objeto una vez cread, es decir, pensar en lo que es una vez instanciado, no en lo que hace. Pensar en que es, no en que hace. En el caso anterior, tendríamos una clase Cash y dentro un método, por ejemplo, usd() o eur() para formatear como deseemos.
Mantén los constructores libres de código
Tenemos una clase con un constructor y una serie de argumentos que inicializan el estado del nuevo objeto. La pregunta es, ¿qué podemos y que no podemos hacer con esos argumentos? La regla de oro es “no toques los argumentos”, es decir, no los modifiques, no los trates. Si fuera necesario, se deberían ‘enwrapar’, por ejemplo, se recibe un int $id en el constructor y lo que se asigna al estado del objeto es algo así como $this->id = new IntAsId($id); Y, por supuesto, los objetos se inicializan con un estado válido, final e inmutable (luego hablaré de esto), pero nunca, nunca, deberíamos tener código en los constructores.
Encapsular lo menos posible
Vuelve a hablar sobre la mantenibilidad y lo importante que es para comprender el código hacer clases pequeñas, con poco comportamiento y que sea simple y fácil de comprender. Estos objetos nacidos de clases pequeñas, posteriormente, deberán agruparse y asociarse para hacer objetos más complejos o de un nivel de abstracción mayor.
Elegir nombres de métodos con mucho cuidado
Antes hablábamos de los nombres de las clases, ahora sobre los métodos de las clases. En esta ocasión estamos indicando a como vamos a referirnos al comportamiento de un objeto. El autor sugiere una regla simple: “los constructores son nombres, los manipuladores son verbos”. Los constructores siempre devuelven algo, los manipuladores no.
Posteriormente discute sobre porque no se deben usar los “getters” y los “setters” (tratados más adelante en este post). Aquí tenemos un primer motivo. ‘get’ es un verbo, por lo tanto, es un manipulador y por convención sabemos que un ‘getter’ retorna datos, lo cual estaría incumpliendo esta norma propuesta en la que los manipuladores no devuelven datos.
Sin embargo, hay una excepción na esta normal. Son aquellas que pertenecen a métodos con resultados booleanos: isEmpty(), isReadable(); ...etc. En estos casos sugiere eliminar el prefijo ‘is’ y usar el adjetivo directamente empty(), readable().
No uses constantes públicas
Una propiedad public static final, también conocida como constante, es un mecanismo para compartir datos entre objetos y, precisamente, por esto último, es por lo que el autor está en contra de su uso. Los objetos no deberían compartir nada, deberían ser autosuficientes y muy ‘cerrados’. Este mecanismo para compartir información simplemente va contra la idea de encapsulación y con la forma de pensar en orientado a objetos. La propuesta es construir ‘micro-clases’ que envuelvan a esas constantes y los objetos instanciados sean las que usemos.
Yegor discute este tema en su blog y la verdad es que da para otro post aparte.
Inmutabilidad
Un objeto es inmutable si su estado no puede ser modificado después de ser instanciado, por lo tanto, haz las clases inmutables…y te harás un gran favor por la mantenibilidad del software. La inmutabilidad ayuda a tener clases más pequeñas, más cohesionadas, desacopladas y mantenibles. Una clase es más fácil de mantener si es más fácil de comprender y una clase inmutable es mucho más fácil de comprender que una mutable ya que no nos debemos preocupar por los distintos cambios que pueda tener. Además, un objeto inmutable es, y esto es importante, totalmente predecible en sus resultados.
Además, un objeto inmutable encapsula todo lo necesario y no cambiada nada después, si necesitáramos algún cambio en su estado, deberíamos crear otro objeto nuevo, es decir, ni siquiera él mismo debería poder alterar su estado, en todo caso debería devolver otro objeto con las nuevas características. En una clase inmutable, todos los objetos son identificables por el estado que ellos encapsulan y dicho estado debería ser suficiente para identificar a cada uno.
Yegor se pone serio con este tema afirmando que las clases mutables simplemente no tendrían derecho a existir, que su uso debería estar estrictamente prohibidos o incluso no deberían estar presentes en la programación orientada a objetos. Sin excusas.
También discutido en su blog.
No uses getters y setters
No usar getters y setters es una de mis ideas preferidas por todo lo que implica a la hora de programar orientado a objetos.
Los setters prácticamente los sentencia teniendo en cuenta el anterior punto de la inmutabilidad de los objetos. Un setter hace una clase mutable y esto, es malo.
En realidad, para Yegor Bugayenko todo tiene que ver con una guerra: “objetos vs estructuras de datos”. En las estructuras de datos (struct en lenguaje C, por ejemplo) nosotros accedemos directamente a sus miembros y los tratamos fuera de ella, no hacemos nada con la estructura, no nos comunicamos con ella, no tienen comportamiento, son únicamente una ‘bolsa de datos’, sin ‘personalidad’. Una clase es diferente, no nos debería permitir acceder a sus miembros de cualquier forma, no deberíamos conocer si tienen un atributo que se llama de una forma u otra o es de un tipo u otro. Las clases muestran un comportamiento y deberíamos comunicarnos con los objetos de esas clases en base a dicho comportamiento, sin importarnos como están definidos en su interior, es decir, todo esto es lo que viene a llamarse encapsulación, uno de los pilares de la programación orientada a objetos.
¿Significa todo esto que no podemos obtener el estado de un objeto? ¿qué no podemos obtener información de él? NO, no significa eso, significa que debemos dejar de tratar a las clases como estructuras de datos, significa que dejemos de revelar como se organiza la clase por dentro indicando que atributos tiene. Es cuestión de mirar nuestras clases como lo que son, clases…y no estructura de datos planas.
Podría parecer una tontería pero hay mucho detrás. Imaginemos una clase de la que podemos obtener una cantidad de euros, según lo hablado, ¿qué diferencia podría haber entre tener un método llamado getEuros() y otro euros()? Pues la diferencia es fundamental e importante: getEuros() básicamente está diciendo “ve a los datos, encuentra el atributo euros y devuélvelo” mientras que euros() está preguntando por “¿cuántos euros tienes? En este último caso no estamos tratando al objeto como algo que solo almacena datos. Lo único que asumimos es que tiene euros, pero no sabemos si es en un atributo interno, lo busca en una base de datos, lo pide a un API o lo obtiene de un fichero. No estamos asumiendo nada sobre la organización interna de la clase, únicamente usamos el comportamiento que nos ofrece, esto es, volvemos al concepto definido anteriormente…encapsulación.
Hay otros ejemplos de la ‘vida real’ que ayudan a comprender las diferencia entre un enfoque y otro. Por ejemplo, imagina que estás comiendo en un restaurante y que tú mismo eres un objeto. Has comido y tienes sed. La diferencia entre tener un setDrink() o un drink() es que en el primer caso sería como si el camarero te abriera la boca y te echara en ella el vaso de agua, mientras que en el segundo el camarero estaría llamando a un comportamiento tuyo que sería -beber- que podría tener las validaciones, restricciones o procesos que fueran necesarios. O, por ejemplo, a la hora de pagar. La diferencia entre tener un getMoney() o un pay(). El primero sería como si el camarero te metiera la mano en el bolsillo y sacara el mismo tu dinero, mientras que el segundo estaría usando un comportamiento tuyo para pagar con las restricciones, validaciones o procesos igualmente necesarios.
Este tema, por supuesto, también discutido en su blog.
Bueno, y hasta aquí un resumen (muy resumido) de lo que sería el primer volumen de ‘Elegant Objects’ de Yegor Bugayenko. Hay bastantes más temas en el libro y los que he mencionado mucho más explicados. Como digo, para mi un libro muy recomendable que me ha hecho pensar de forma diferente a la hora de programar orientado a objetos y que también me ha dado muchas ideas y recomendaciones que me parecen acertadas y pienso seguir cuando esté programando.
Chimpún.