Kata FizzBuzz: Javascript, BDD y APIs fluidas
Durante el día de hoy he tenido algo de tiempo libre y he decidido hacer la Kata FizzBuzz. Algunos os preguntareis, ¿y qué es es eso de la Kata FizzBuzz?, es más ¿qué es una Kata?. ¿Eso no es algo de Karate?. Pues sí, en muchas artes marciales se practican Katas para interiorizar una serie de movimientos preestablecidos, es decir que los aprendamos de manera tan automática que salgan solos. En el desarrollo de software se puede hacer lo mismo practicando con algunos problemas sencillos, lo importante no es resolver el problema en si mismo sino interiorizar una serie de buenas prácticas mientras lo resolvemos (TDD, refactorización, etc).
La Kata FizzBuzz en concreto es un problema MUY sencillo, que se puede ver en este enlace. A grandes rasgos lo único que hay que hacer es crear una programa que dado un número se comporte de la siguiente manera.
Hay otras variantes en las que se pide escribir un programa que te devuelva el resultado para los primeros 100 número enteros, pero nos quedaremos con lo que he explicado anteriormente, porque esa variante no añade mucho valor a lo que queremos aprender.
Para resolver el problema yo he seleccionado Javascript como lenguaje de programación, ¿por qué?, pues porque es una herramienta que cada vez tengo que utilizar más y no domino ni lo más mínimo, incluso se puede decir que no tengo ni idea de Javascript, por eso en mi caso es un lenguaje interesante para hacer una Kata. Como framework de test en Javascript he elegido Jasmine porque ya he usado Qunit anteriormente y quería utilizar una aproximación más BDD (no explicaré como funciona ni como instalarlo porque en la web ya lo hacen maravillosamente, si alguien tiene dudas por supuesto puede preguntar).
Para los que no lo sepan, BDD son las siglas de Behaviour Driven Development. Yo soy de esos desarrolladores que piensan que TDD y BDD son el mismo perro con distinto collar, solo cambia la forma en la que enuncias los test, pero en la práctica no cambia mucho los pasos que realizas (siempre depende de cada desarrollador), por no decir que no cambian nada. Jasmine es un framework BDD porque enuncia los tests como Specs, es decir que en vez de llamarse test utiliza la construcción describe-it al estilo de RSpec. Puedes comprobarlo en mi solución, que llegó a las siguientes especificaciones.
Una de las cosas que se pueden observar con estos tests es que he intentado ir muy poco a poco, triangulando en todos los casos. ¿Qué es eso de triangular?, muy sencillo, cuando me encontraba por ejemplo con el caso "should return fizz with input 3", en la implementación hacía que devolviese 3 de forma directa, y luego creaba otro test "should return fizz with input 6" que me llevaba a "descubrir" la función isDivisibleByThree.
Si te fijas, el primer test que realicé fue la comprobación que devuelve cero ante la entrada cero, pero luego seguí por la entrada tres que debe devolver fizz. No fue hasta el cuarto test cuando me di cuenta que si el número no era divisible por 3 ni por 5 debía devolver el propio número. Hasta ese momento solo devolvía cero para todos los casos. He ahí la importancia de tener una libreta con los tests que se deben pasar y anotar todos los nuevos tests que vayan apareciendo. Yo anote dicho test y continué hasta llegar a una solución que me convencía.
Buscando lo que había hecho otra gente encontré un enlace al blog de Javier Acero, recomiendo la lectura porque comenta la importancia del diseño orientado a objetos y se ve como extrae las comprobaciones de la divisibilidad a otra clase. Pues claro!!! se me tenía que haber pasado a mi por la cabeza. Esos son el tipo de detalles que hay que tener en cuenta en una Kata. Se me ocurrió que era buen momento para aprender a hacer APIs fluidas en Javascript y llegue a esta nueva solución, comprobando que pasaba todos los tests que ya pasaban antes.
Quizás la "clase" number no es tan clara como se espera (puede que sea por mi ignorancia en Javascript), pero desde luego de cara a los usuarios de nuestra API tener líneas como
es más claro que el agua :-)
Bueno no he explicado lo que es una API fluida, ¿no lo has averiguado tú solo?, pues se trata de una API en la que se componen las llamadas a los métodos de manera consecutiva, es decir llamamos a number(input) y seguidamente podemos llamar a isDivisibleBy(5) de forma que se puede leer todo junto y se entiende que se quiere comprobar que es input el que tiene que ser divisible por 5 :-D ¿Está más claro ahora? Por ejemplo en Java, JodaTime es un buen ejemplo de API fluida.
Espero que os haya gustado y que hagáis todas las críticas (constructivas) buenas o malas que merezca :-D el debate es lo interesante.
La Kata FizzBuzz en concreto es un problema MUY sencillo, que se puede ver en este enlace. A grandes rasgos lo único que hay que hacer es crear una programa que dado un número se comporte de la siguiente manera.
- Devuelve fizz si el número es divisible por 3.
- Devuelve buzz si el número es divisible por 5.
- Devuelve fizzbuzz si el número es divisible por 3 y por 5.
Hay otras variantes en las que se pide escribir un programa que te devuelva el resultado para los primeros 100 número enteros, pero nos quedaremos con lo que he explicado anteriormente, porque esa variante no añade mucho valor a lo que queremos aprender.
Para resolver el problema yo he seleccionado Javascript como lenguaje de programación, ¿por qué?, pues porque es una herramienta que cada vez tengo que utilizar más y no domino ni lo más mínimo, incluso se puede decir que no tengo ni idea de Javascript, por eso en mi caso es un lenguaje interesante para hacer una Kata. Como framework de test en Javascript he elegido Jasmine porque ya he usado Qunit anteriormente y quería utilizar una aproximación más BDD (no explicaré como funciona ni como instalarlo porque en la web ya lo hacen maravillosamente, si alguien tiene dudas por supuesto puede preguntar).
Para los que no lo sepan, BDD son las siglas de Behaviour Driven Development. Yo soy de esos desarrolladores que piensan que TDD y BDD son el mismo perro con distinto collar, solo cambia la forma en la que enuncias los test, pero en la práctica no cambia mucho los pasos que realizas (siempre depende de cada desarrollador), por no decir que no cambian nada. Jasmine es un framework BDD porque enuncia los tests como Specs, es decir que en vez de llamarse test utiliza la construcción describe-it al estilo de RSpec. Puedes comprobarlo en mi solución, que llegó a las siguientes especificaciones.
describe("FizzBuzzCalculator", function() { it("should return 0 with input 0", function() { expect(FizzBuzzCalculator.calculate(0)).toEqual(0); }); it("should return fizz with input 3", function() { expect(FizzBuzzCalculator.calculate(3)).toEqual('fizz'); }); it("should return buzz with input 5", function() { expect(FizzBuzzCalculator.calculate(5)).toEqual('buzz'); }); it("should return fizz with input 6", function() { expect(FizzBuzzCalculator.calculate(6)).toEqual('fizz'); }); it("should return input when input is not divisible by 3 or 5 (2 == 2)", function() { expect(FizzBuzzCalculator.calculate(2)).toEqual(2); }); it("should return buzz with input 10", function() { expect(FizzBuzzCalculator.calculate(10)).toEqual('buzz'); }); it("should return fizzbuzz with input 15", function() { expect(FizzBuzzCalculator.calculate(15)).toEqual('fizzbuzz'); }); it("should return fizzbuzz with input 30", function() { expect(FizzBuzzCalculator.calculate(30)).toEqual('fizzbuzz'); }); });
Una de las cosas que se pueden observar con estos tests es que he intentado ir muy poco a poco, triangulando en todos los casos. ¿Qué es eso de triangular?, muy sencillo, cuando me encontraba por ejemplo con el caso "should return fizz with input 3", en la implementación hacía que devolviese 3 de forma directa, y luego creaba otro test "should return fizz with input 6" que me llevaba a "descubrir" la función isDivisibleByThree.
Si te fijas, el primer test que realicé fue la comprobación que devuelve cero ante la entrada cero, pero luego seguí por la entrada tres que debe devolver fizz. No fue hasta el cuarto test cuando me di cuenta que si el número no era divisible por 3 ni por 5 debía devolver el propio número. Hasta ese momento solo devolvía cero para todos los casos. He ahí la importancia de tener una libreta con los tests que se deben pasar y anotar todos los nuevos tests que vayan apareciendo. Yo anote dicho test y continué hasta llegar a una solución que me convencía.
FizzBuzzCalculator = { calculate : function(input) { if (input == 0) return 0; if (isDivisibleByTrhee(input) && isDivisibleByFive(input)) return 'fizzbuzz'; if (isDivisibleByThree(input)) return 'fizz'; if (isDivisibleByFive(input)) return 'buzz'; return input; } function isDivisibleByTrhee(input) { return input % 3 == 0; } function isDivisibleByFive(input) { return input % 5 == 0; } }
Buscando lo que había hecho otra gente encontré un enlace al blog de Javier Acero, recomiendo la lectura porque comenta la importancia del diseño orientado a objetos y se ve como extrae las comprobaciones de la divisibilidad a otra clase. Pues claro!!! se me tenía que haber pasado a mi por la cabeza. Esos son el tipo de detalles que hay que tener en cuenta en una Kata. Se me ocurrió que era buen momento para aprender a hacer APIs fluidas en Javascript y llegue a esta nueva solución, comprobando que pasaba todos los tests que ya pasaban antes.
var number = function(val) { var operations = { _val: val, isDivisibleBy: function(divisor) { return this._val % divisor == 0; } } return operations; } FizzBuzzCalculator = { calculate : function(input) { if (input == 0) return 0; if (number(input).isDivisibleBy(3) && number(input).isDivisibleBy(5)) return 'fizzbuzz'; if (number(input).isDivisibleBy(3)) return 'fizz'; if (number(input).isDivisibleBy(5)) return 'buzz'; return input; } }
Quizás la "clase" number no es tan clara como se espera (puede que sea por mi ignorancia en Javascript), pero desde luego de cara a los usuarios de nuestra API tener líneas como
if (number(input).isDivisibleBy(5)) return 'buzz'
es más claro que el agua :-)
Bueno no he explicado lo que es una API fluida, ¿no lo has averiguado tú solo?, pues se trata de una API en la que se componen las llamadas a los métodos de manera consecutiva, es decir llamamos a number(input) y seguidamente podemos llamar a isDivisibleBy(5) de forma que se puede leer todo junto y se entiende que se quiere comprobar que es input el que tiene que ser divisible por 5 :-D ¿Está más claro ahora? Por ejemplo en Java, JodaTime es un buen ejemplo de API fluida.
public boolean isRentalOverdue(DateTime datetimeRented) { Period rentalPeriod = new Period().withDays(2).withHours(12); return datetimeRented.plus(rentalPeriod).isBeforeNow(); }
Espero que os haya gustado y que hagáis todas las críticas (constructivas) buenas o malas que merezca :-D el debate es lo interesante.
Comentarios