Archive for category Programando

Números mágicos

Hace tiempo que llevo pendiente compensar el desequilibrio en artículos técnicos que últimamente tengo en el blog, así que aquí va éste.

El pasado día 22 de diciembre celebramos el segundo aniversario del primer coding dojo que organizamos como AGILISMO.es. Luis Rivera, siempre disponible para echarnos una mano, nos prestó las instalaciones de Okuri, también conocidas como Tetuan Valley porque allí tiene la sede esta iniciativa de incubación y aceleración de startups en Madrid. Echamos de menos a Alejandro y Roberto, de Autentia, a los que tanto tenemos que agradecer durante estos dos años. Pero junto a los habituales también estuvo gente nueva. Eso es buena señal.

Hasta aquí no parece un artículo técnico, ¿verdad? Bueno, pues al grano. Durante el desarrollo del coding dojo, con la PomodoroKata como ejercicio, surgió un debate sobre si dejar el número 25 en los primeros pasos del desarrollo es aceptable o no. Xavi defendía la sustitución del 25 por una constante por tratarse de un número mágico mientras que opinaba que era suficientemente expresivo y que el refactor era prematuro.

Como podeis comprobar en el video de la kata que grabé hace ya dos años, yo también prefiero sustituir el 25 por una constante (minuto 1:40 aprox) por las siguientes razones:

* el método minutesLeft devolviendo 25 es menos expresivo que minutesLeft devolviendo DEFAULT_DURATION
* en el test también hago este refactor aunque el título del test es incorrecto y debería decir "Un pomodoro tiene una duración por defecto", porque realmente me es indiferente cuál sea esa duración
* si realmente hubiera querido probar que la duración por defecto es 25 entonces debería haber tenido un test del tipo: assertEquals("La duración por defecto es 25", 25, Pomodoro.DEFAULT_DURATION), lo que nos habría llevado a pasar esa constante de privada a pública.

Como veis, mis argumentos están basados en la expresividad del código y en que se ajusten a lo que se quiere probar. El trabajo más difícil es siempre elegir bien los tests porque, dependiendo de ellos, nuestro diseño y posterior implementación tomarán un rumbo u otro.

Tags: , ,

Pasito a pasito


Hacía tiempo que quería hacer un ejercicio interesante (al menos para mi) que consiste en hacer una codekata muy sencilla a pequeños pasos o, como dice Kent Beck en su libro “TDD: By Example“, con baby-steps. Me interesa estudiar cuáles son las fuerzas que empujan a refactorizar hacia un mejor diseño y para eso verlo en un ejercicio de laboratorio me parece ideal. En este sentido, también me interesa ver cómo se aplica la “Premisa de la Prioridad de las Transformaciones” expuesta recientemente por UncleBob.

FizzBuzz en Github (pasito a pasito)

Así pues, he creado un proyecto vacío con Jeweler (mi lenguaje de elección ha sido Ruby porque necesito practicarlo más) y he trabajado sólo en el fichero de test por simplicidad. Jeweler crea el esqueleto y te lo deja todo muy fácil para ejecutar los tests sin más que ejecutar:

rake test

De hecho, Jeweler te hace hasta el primer commit en git y lo deja listo para subirlo a github. Bueno, el resultado de este experimento lo he dejado en Github y veréis que se trata de la kata FizzBuzz, que es lo suficientemente sencilla como para no alargar demasiado el ejercicio. Si alguien tiene ganas de empezar con GitHub, también recomiendo este tutorial de primeros pasos de Adictos al Trabajo.

Así que fui haciendo commit con git para cada uno de los pasos, incluso en aquellos en los que estaba en rojo, porque la idea era tener todos los comentarios que explicaban ese paso en la historia de los commits y así, quizás algún día, tener una herramienta que me permita visualizar ese “paso a paso” a modo de slideshow.

De paso, en el camino, he puesto en práctica mi kung-fu con Git y he aprendido a:

Cambiar el comentario del último commit

Como los comentarios en este ejercicio son fundamentales, no podía permitirme que no pusieran exactamente lo que quería poner. Así que busqué y encontré esto:

git commit --amend

Esto lo que hace es proponerte el editor por defecto para modificar el mensaje del commit.

Deshacer los últimos commits

Más adelante me encontré con que había ido haciendo unas refactorizaciones que no me gustaban nada, así que quise volver atrás. Normalmente habría deshecho los cambios en el editor y hecho un nuevo commit, pero eso habría dejado un poco sucio la historia.

git reset <el hash del commit hasta el que queremos volver>

Esto nos pone apuntando justo a ese commit al que hemos hecho referencia. Si no sabéis cómo conocer el hash, “git log”, aunque hay maneras de abreviarlo y también vale. :-)

Hacer push a github sin haber hecho clone antes

Pensaba que ya había hecho creado el repositorio en github y que había hecho clone (que es lo que se suele hacer), pero como había empezado directamente desde el esqueleto que me ofrecía Jeweler, ni me acordé de todo eso. Así que creé el repositorio en github y, claro, ya no se parecía a lo que Jeweler había dejado configurado por defecto. El nombre del proyecto en local era “fizzbuzz” y en Github era “fizzbuzz-babysteps”. Además, al hacer git push me daba un error. Así que de nuevo recurrí a Google y Stackoverflow y encontré esto otro:

git remote set-url origin git@github.com:jmbeas/fizzbuzz-babysteps.git
git push -u origin master
Algunos alias nuevos bastante útiles

La primera vez que usé Git lo hice siguiendo el tutorial GitImmersion. Allí vi que se podían definir alias muy útiles para simplificar muchas de las tareas habituales. Los amigos de Emergya también nos recomiendan algunos. En particular el hacer “commit -a” parece una tontada, pero a mi me da mucha pereza hacer “git add” todas las veces. :)

LA FOTO: La encontré a través de “Google images” y, la verdad, no he pedido permiso. Espero que como es bastante inocentona y no busco lucrarme, no haya ningún abogado americano que me denuncie. Si alguien tiene interés en la fuente original (la que me da Google), es ésta. Ya véis, nada interesante.

Tags: , ,

Empezando con RSpec

Los que me conocen de cerca saben que siempre estoy intentando aprender cosas nuevas. Es la única manera que conozco para no quedar desactualizado en un sector tan exigente como el nuestro, sobre todo si quieres seguir viviendo de él hasta los 67 o más allá. Y la juventud viene con mucha fuerza. Empieza a dar un poco de respeto ver a gente como , , los y otros muchos. Programan en lenguajes que me son completamente desconocidos y están constantemente aprendiendo cosas nuevas. Pero lo peor de todo es que ¡¡tienen mucho tiempo libre!! ;-) Así que no tengo más remedio que ponerme las pilas si no quiero quedar definitivamente arrinconado junto a los libros de “Aprende Java en 15 días” o, peor aún, junto a los de “Guía Práctica de dBase III” (todos enmohecidos y arrugados por el paso de los años). ¡Qué demonios! Hace mucho (muuuuucho) yo programaba en BASIC, en C, en C++, en COBOL, en LISP… así que simplemente porque mi lenguaje para programar en los últimos ¿diez? años haya sido prácticamente sólo uno (Java) no quiere decir que no pueda aprender cualquier otro. Así que vamos a ello. Vamos a aprender otra cosa: por ejemplo, Ruby.

Ya me he hecho las RubyKoans (que, por cierto, las tengo que volver a hacer porque con una sóla vez no me es suficiente). También me he comprado el libro “Programming Ruby 1.9″ (pero sólo con comprarlo no es suficiente). Necesito practicar. Y para practicar necesito un mínimo ecosistema. No, no es una excusa: vamos a ver cómo lo voy aprendiendo y construyendo.

RSpec

Entorno: Ubuntu 10.10 + Ruby 1.9.2

Antes que nada he de reconocer que no sé bien qué hice para tener Ruby 1.9 en Ubuntu. El que viene por defecto es 1.8 y yo me lié bastante intentando poner “rvm”. Al final lo conseguí con la ayuda de , pero lo cierto es que no sé bien qué hice.

Pero si consigues instalar “rvm” el resto es mucho más fácil. Puedes instalar cualquier versión de Ruby y todo el entorno es mucho más controlado.

$ rvm install 1.9.2
$ rvm use 1.9.2
$ rvm gemset create tdd
$ rvm use gemset 1.9.2 --default

Para empezar con RSpec hay que instalar la gema “rspec”.

$ gem install rspec

En ~/.rspec tengo:

--colour
--format documentation

De esta forma se ve mucho mejor el resultado de la ejecución.

Autotest

Con esto ya podríamos empezar con RSpec, pero ya que hay una utilidad llamada autotest que nos permite evitar tener que ejecutar los tests cada vez y, dado que soy muuuuuuuuuuuuuuy vago (como buen programador) ;-)

Escribo un poco de memoria y echando mano de mi .bash_history, así que es posible que se me haya pasado algo.

$ gem install autotest
$ sudo apt-get install libnotify-bin
$ gem install test_notifier

En ~/.autotest tengo lo siguiente:

Autotest.options[:quiet] = true
require "test_notifier/runner/autotest"

De esta manera configuro las notificaciones para que aparezcan como una nota emergente, al estilo “growl”. Desgraciadamente no he conseguido en Ubuntu nada parecido a Growl (sólo en Mac) :-(

Hola Mundo

$ mkdir ejemplo ; cd ejemplo
$ mkdir spec
$ mkdir lib

En spec vamos a poner el test que escribiremos con RSpec

asdf
asp
fds
';document.write(content);
1
En lib vamos a poner el código que escribiremos en Ruby

Y además en ejemplo/autotest/ hay que incluir un fichero discover.rb que diga:

Autotest.add_discovery { "rspec2" }

De esta manera, autotest puede encontrar las specs y ejecutar rspec sin decirle nada explícitamente.

Arrancamos autotest para no tener que andar ejecutando rspec cada vez. Una notificación emergente me dice “1 example, 1 failed”. Bien. Está funcionando correctamente. En la consola donde se está ejecutando autotest veo:

Rspec Greeter
  should say 'Hello RSpec!' when it receives the greet() message (FAILED - 1)

Failures:

  1) Rspec Greeter should say 'Hello RSpec!' when it receives the greet() message
     Failure/Error: greeting = greeter.greet
     NoMethodError:
       undefined method `greet' for #<RSpecGreeter:0x00000001a48e80>
     # ./spec/greeter_spec.rb:6:in `block (2 levels) in <top (required)>'

Finished in 0.00042 seconds
1 example, 1 failure

Editamos greeter.rb para pasar el test.

asdf
asp
fds
';document.write(content);
1
Al guardar la modificación, autotest ejecutará rspec por nosotros y en la ventana donde se ejecuta autotest aparecerá el resultado de la ejecución y una notificación emergente aparecerá en verde indicando que hemos pasado “1 example”, es decir, que se ha ejecutado 1 test correctamente.

Rspec Greeter
  should say 'Hello RSpec!' when it receives the greet() message

Finished in 0.00038 seconds
1 example, 0 failures

De momento sólo estoy conociendo la herramienta. Hay que conocer las herramientas, claro, pero no es lo principal. Tengo que aplicarme con la lectura de “The RSpec Book”, aprender bien cómo usar RSpec y Cucumber y, sobre todo, practicar mucho para escribir buenas especificaciones ejecutables. Ése es mi objetivo. En eso quiero destacar y ser lo mejor que pueda.

Por supuesto, si veis alguna incorrección o tenéis cualquier sugerencia o comentario, estoy deseando leer vuestros comentarios.

FOTO: La imagen que ilustra este artículo es de Enrique Comba y representa el respeto que me dan esos chavales que vienen por detrás pisando fuerte y que, afortunadamente, me estimulan a no relajarme. <ironic_mode>El personaje fotografiado he de reconocer que, afortunadamente, no me suena de nada. Seguro que a tampoco.</ironic_mode>

 

ACTUALIZACIÓN:

La verdad es que ni me acordaba de dónde había sacado la mayoría de la ayuda para configurar. Creía que había sido de Enrique, pero Alberto Rodríguez me lo acaba de recordar. La mayor parte de la culpa de esta configuración tan chula es suya. Lo dicho, ¡¡esta gente dan verdadero miedo!! :-D

Tags: , , ,

Simetría

Lo siento, si alguien espera un post navideño (a pesar de los copitos cayendo por el blog) al estilo retrospectiva o propósitos de nuevo año, que se vaya buscando otro artículo. Creo que soy de esos que no tiene mucho apego por la Navidad. Sin embargo, ¡Feliz paso de año y que el 2011 os trate mejor que el 2010! (A todos, aunque hayamos sido -algo- malos) :-)

Bueno, al tema.

Picado por no haber podido estar ayer en el codingdojo de (la comunidad de Ruby en Madrid a la que, por fin, se acercó mucha gente que conozco), estaba yo practicando la codekata de los números romanos (una parte de ella, realmente, la de pasar a “romano”) y se me ocurrió hacer un pequeño cambio. En vez de usar un java.util.Map (sí, en Java, qué pasa :-) ) pensé en usar un array. Pensé que no debería afectarme mucho porque había escrito mi código haciendo mi mejor TDD (lo cuál no quiere decir que lo haga bien).

Para que tengáis una primera imagen de lo que hablo, mi método “String to_roman(int n)” es como sigue:

asdf
asp
fds
';document.write(content);
1

NUMERALS es un Map<Integer,String> ordenado para que “int find_biggest_numeral_that_fits(int i)” sea más fácil (y de paso eficiente). Pero lo que os quiero contar no es si es mejor usar un Map o un array para guardar esta tabla. De hecho, sobre esto se me ocurre otro artículo muy chulo sobre SRP (aunque no pueda superar éste de Hadi Hariri, claro). El caso es que, al cambiar la estructura de datos que soporta esto, mi algoritmo ha tenido que cambiar incluso hasta el método de más alto nivel de abstracción “to_roman”.

Recordemos el “Implementation Patterns” que, aunque no lo tengo a mano, recuerdo que hace hincapié en cuidar que nuestros métodos mantengan un nivel de abstracción coherente. En este caso es evidente que estamos llamando a “find_biggest_numeral_that_fits” para abstraernos de los detalles técnicos pero luego, en el mismo “to_roman” usamos NUMERALS para obtener el valor correspondiente. Hemos introducido una asimetría que no es muy dañina (en este caso que el problema es pequeño, que por eso es pequeño, para que podamos practicar sin que nos duela tanto como “en la vida real”).

Al refactorizar he cambiado “find_biggest_numeral_that_fits” por una llamada a “find_biggest_numeral_using_an_ordered_map” y ahí, al cambiar mi solución basada en un array y llamar al nuevo “find_biggest_numeral_using_an_ordered_array” me he dado cuenta de que no he cambiado NUMERALS y que sigue siendo un Map. .

He renombrado NUMERALS a NUMERALS_AS_MAP para que sea más evidente y así, he ocultado ese “código feo” en un bonito “String get_numeral(int i)”, lo cuál deja el método “to_roman” mucho más limpio.

asdf
asp
fds
';document.write(content);
1

Por cierto, observad también el leve cambio que he hecho en el orden de la linea 6, donde inicializo el StringBuffer. Me ha parecido que inicializarla “tan lejos” de donde la uso le daba un cierto tufillo que no me gustaba nada. ¿A que se lee mejor ahora? :-)

Espero vuestros comentarios. Seguro que se puede mejorar mucho aún.

Ea, lo dicho, ¡Feliz 2011!

Tags: , ,

Eclipse en colores

Por si queréis probar con los colores en Eclipse. El leer mejor (más fácil) nuestro código nos puede ayudar a escribirlo mejor. :-)

Yo he cambiado el color de fondo y he resaltado algunos elementos sintácticos:

El color de fondo:
Window > Preferences > General > Editors > Text Editors > Appearance color options:

  • Background color = color personalizado R:217, G:217, B: 236 (un gris clarito para distinguir la ventana de edición del resto pero suficientemente clara para no tener que hacer muchos cambios en el resto de colores)

Colores “sintácticos”:
Window > Preferences > Java > Editor > Syntax Coloring > Java:

  • Inherited Method Invocations : Pink (R: 255, G: 0, B: 255). Esto hace que las llamadas a métodos heredados destaquen pero no demasiado.
  • Method Declarations: Orange (R: 255, G: 128, B: 0) y negrita. Esto hace que el inicio de un método destaque sobre el resto del código.
  • Methods: Dark green (R: 0, G: 128, B: 0) y negrita. Esto hace que los verbos destaquen frente a los sustantivos.

En fin, espero que os resulte útil. :-)

Tags: ,

Contenidos recuperados (I)

Por fin he conseguido recuperar mis contenidos del viejo blog en Blogger después de que Google y sus maléficas hordas de robots me lo robaran. Pero no creáis que lo que ha pasado es que Google se ha apiadado de mi y me los ha devuelto. Ni mucho menos. De hecho, sigo sin recibir noticias de Skynet. Creo que fue Leo Antolí el que me puso en la pista cuando me dijo que él podía leer mi blog con el Google Reader. Así que vi que había una API no documentada pero relativamente fácil de ir encontrando cómo extraer todo el contenido de mi blog desde el principio. Y me puse a ello. El resultado es que, a veces a ratos, a veces a sesiones más largas, ha dependido un poco del tiempo libre disponible, he conseguido:

  • extraer el contenido de mi viejo blog usando Google Reader,
  • lo he guardado en un fichero xml (luego explicaré un poco sobre el formato)
  • y ese mismo fichero lo he importado en el WordPress que mi amigo Carlos Blé ha sido tan amable de cederme en iExpertos.com para alojar mi nuevo blog.

He aprovechado que tenía que escribir este programita para poner en práctica lo que aprendí en el curso de Groovy al que asistí no hace mucho. Groovy es un lenguaje lo suficientemente parecido a Java como para que la curva de aprendizaje no sea una barrera, y además incorpora toda una serie de simplificaciones de la vida normal del programador que hace que ponerse a hacer algo y que funcione sea muy atractivo.

Lógicamente he practicado un poco de TDD, pero sinceramente, la experiencia de refactorizar en un lenguaje dinámico como Groovy hace que sea bastante pesado y a veces desmotivador. El IDE no te ayuda tanto (no puede hacerlo en parte por la naturaleza del lenguaje) y eso se nota. Hay que ser mucho más cuidadoso, ir más despacio… buff. Y lo peor es que te tienes que pensar muy bien los nombres de las cosas porque renombrar un método o una variable implica hacerlo a mano, con mucho cuidadito.

En cualquier caso, es estupendo programar usando TDD: te obligas a que tu diseño sea testeable, lo cuál te obliga a pensar muy bien en las responsabilidades de cada objeto. Se hace muy fácil ir explorando el problema simplemente añadiendo consideraciones en los tests.

Mi estrategia ha sido ir escribiendo todo lo que era necesario en el test para conseguir mis objetivos. Inicialmente lo que quise fue obtener el contenido de cada artículo del viejo blog en HTML y guardarlo (por si acaso, ya sabéis) :-) Más tarde, después de pensar un poco en cuál debería ser mi estrategia, decidí que lo mejor era guardar esos contenidos en un fichero para ser importado en el nuevo blog: un WordPress.

El primer objetivo era más bien jugar, practicando Groovy e ir escribiendo lineas de código para ir desentrañando la API de Google Reader. Una vez hecho esto, me olvidé un poco del código escrito (lógicamente aproveché trozos, pero no hice un trabajo de refactorización propiamente dicho). Lo interesante de esta primera parte fue que me ayudó a sacar varias conclusiones y afinar el entorno de trabajo:

  • trabajé con NetBeans y Eclipse y, aunque no está afinada la integración con Groovy en ninguno de los dos casos, me he de quedar con Eclipse. Por varias razones:
    • La más importante (para mi) es la fuerza de la costumbre. Estoy quizás demasiado acostumbrado a Eclipse y todos sus “keystrokes” (y eso que no programo últimamente demasiado)
    • La integración con Maven (uso el plugin m2eclipse -lo siento, Abel, no uso IAM)
    • El IDE de SpringSource, que apuesta decididamente por Grails y Groovy, está basado en Eclipse
  • La integración del plugin de Groovy y el plugin de JUnit es pésima. Hay que tener mucho cuidado porque te puedes encontrar persiguiendo fantasmas: se ejecutan tests que ya no están escritos, no se ejecutan tests que acabas de escribir o cosas por el estilo. Compilas, vuelves a compilar… y nada.
  • Llamar a Google Reader y procesar el JSON que devuelve como resultado usando Groovy es muy, muy, muy sencillo:
    def result = command.toURL().text
    JSON resultAsJSON = new JsonSlurper().parseText(result)
    
  • Extraer el contenido de cada artículo embebido en algún lugar de ese JSON se hace también muy, muy, muy fácilmente con Groovy:
    feed.items.each { item ->
      String title = item.title
      String content = item.content.content
      def out = prepareWriter(item.alternate[0].href - 'http://jmbeas.blogspot.com/')
      out << composeContentAsXhtml(title,content)
      out.close()
    }
    

De momento lo dejo aquí. En un par de días completo este artículo con la segunda parte, donde os explicaré cómo he generado el fichero en formato WXR (un RSS 2.0 extendido con etiquetas propias de WordPress), que tampoco está precisamente documentado.

Tags: , ,

Refactoring en Español (y 6)

Hoy, por fin, termino esta serie de artículos basados en el ejemplo del primer capítulo del libro de Fowler “Refactoring“.

Recapitulemos un poco antes de comenzar.

Desde la primera entrega hasta ésta no hemos cambiado el comportamiento del sistema: sólo hemos cambiado el diseño interno. A eso le llamamos refactorizar. (Por cierto, mi siguiente entrega de “Aprender inglés es fácil… si sabes cómo” irá justamente sobre esta palabra).

Y estamos seguros de que no ha cambiado el comportamiento porque tenemos una batería de pruebas unitarias que así nos lo han ido demostrando.

A lo largo de las cinco entregas anteriores hemos ido moviendo código dentro de las clases hacia métodos privados que, además, mejoraban la comprensión del código. También hemos movido código entre clases, delegando responsabilidades entre objetos. Y finalmente, en la última entrega dejamos unas condiciones múltiples (switch) en los métodos getCharge y getFrequentRenterPoints en la clase Movie. Vamos a ver en este final del ejemplo cómo eliminar esos ifs, lo cuál me sirve de entradilla para comentar la campaña anti-if (originalmente en italiano, sí, en italiano).

Comenzamos.

At last… Inheritance

Para no plagiar, creo que lo mejor es recomendar directamente la lectura de este capítulo 1 del libro. Sin embargo, y aunque la explicación es excelente, hay que reconocer que el original está en inglés… :-)

Aunque no me dedico a la traducción y mi inglés es mejorable, voy a tratar de aportar mi granito de arena a la causa hispana.

AL FIN… HERENCIA

Tenemos varios tipos de películas que tienen diferentes maneras de responder a la misma pregunta. Esto parece un trabajo para subclases. [N.T. El autor se refiere a Movie.getCharge]. Podemos tener 3 subclases de Movie, cada una de las cuáles teniendo su propia versión de getCharge. (Ver Fig. 1.14)

[N.T. La fig. 1.14 muestra un diagrama de clases UML con las subclases RegularMovie, ChildrensMovie y NewReleaseMovie, heredando de Movie. Todas tienen un método llamado getCharge.]

Esto nos permite sustituir la instrucción switch por el uso de polimorfismo. Desgraciadamente esto tiene un pequeño problema: que no funciona. Una película puede cambiar su clasificación a lo largo de su vida. Un objeto no puede cambiar su clase a lo largo de su vida. Sin embargo, hay una solución en el patrón Estado [GoF]. Usando el patrón Estado, nuestro diseño quedaría como en la fig. 1.15

[N.T. La fig. 1.15 muestra otro diagrama de clases UML con la clase Price y sus subclases RegularPrice, ChildrensPrice y NewReleasePrice. Además, también muestra la clase Movie usando una instancia de Price para el cálculo de getCharge.]

Añadiendo la indirección podemos hacer las subclases del objeto Price y cambiar el precio cada vez que lo necesitemos.

Si estáis familiarizados con los patrones del GoF, quizás os preguntéis: “¿Es esto un estado o una estrategia?”. ¿Representa la clase Price a un algoritmo para calcular el precio (en cuyo caso yo preferiría llamarla Pricer o PrincingStrategy) o representa al estado de una película (Star Trek X es una novedad)? En este punto, la elección del patrón (y del nombre) refleja cómo queremos pensar acerca de la estructura. De momento estamos pensando en este objeto como el estado de una película. Si más adelante decidimos que una estragia comunica mejor nuestras intenciones, refactorizaremos cambiando los nombres.

Hasta aquí la traducción. Espero que os ayude a entender el cambio que vamos a introducir en el diseño y el por qué lo hacemos.

Vamos a introducir el patrón Estado. Para ello nos aseguramos de que tenemos bien encapsulados los accesos al atributo Movie._priceCode. (Lo cambiamos incluso en el constructor).

TESTS

Para introducir Price y toda la jerarquía hacemos TDD y, para ello, creamos PriceTest como sigue y lo añadiremos a la suite TestAll.

package step6;

import org.junit.Test;

public class PriceTest {

     public void testChildrensPrice() {
    Price aPrice = new ChildrensPrice();
    assertEquals(Movie.CHILDRENS,aPrice.getPriceCode());
  }

}

Lógicamente, como no compila, tendremos que crear las clases y métodos correspondientes. Al ejecutar los tests, salen en rojo (si hemos implementado getPriceCode con un valor por defecto), pero en cuanto hacemos que getPriceCode devuelva el valor esperado según la subclase que vayamos incorporando, volvemos al verde. Siempre primero el test y luego el código.

A continuación introducimos el uso de Price en Movie tal y como propone Fowler. De hecho, al copipegar su implementación de Movie.setPriceCode incorpora código para el que no teníamos pruebas. Esto lo descubrimos al ver el informe de cobertura de nuestras pruebas y comprobar que no tenemos una prueba para comprobar qué pasa cuando asignamos a una película un tipo de precio desconocido. (Sí, en sentido estricto no estaríamos refactorizando puesto que hemos cambiado los tests, e.d. la funcionalidad original, pero yo más bien diría que hemos encontrado un defecto en el código original puesto que no contemplaba una condición excepcional).

@Test(expected=IllegalArgumentException.class)
public void testMovieIllegalType() {
  new Movie(ANY_TITLE,UNKNOWN_PRICE_CODE);
}

Por lo demás, tras ejecutar nuestra batería de tests, el refactor ha ido bien. (Lo sabemos porque nuestros tests están en verde)

Siguiendo los pasos de Fowler, refactorizamos Movie.getCharge para que use un nuevo Price.getCharge (OJO, sin olvidar que estamos haciendo TDD) :-)

Implementamos Price.getCharge moviendo el código de Movie.getCharge a la clase abstracta (aunque este método no será abstracto).

No eliminamos el viejo test puesto que es lo que nos indicará que funciona o no nuestro refactor; pero implementamos los tests como el siguiente. (En realidad es fácil porque son idénticos a los de Movie)

 public void testChargeForChildrens() {
  Price aPrice = new ChildrensPrice();
  double charges[] = { 1.5, 1.5, 1.5, 3.0, 4.5, 6.0, 7.5 };
  for (int daysRented = 1; daysRented &amp;amp;lt;= charges.length; daysRented++) {
    assertEquals(charges[daysRented-1], aPrice.getCharge(daysRented),0);
  }
}

TESTS

Declaramos el método ahora como abstracto e implementamos el primero que nos venga en gana (p.ej. ChildrensPrice.getCharge). Ya tenemos los tests, por lo que iremos refactorizando con seguridad. Recordad el lema de TDD: “RED GREEN REFACTOR“.

TESTS

A continuación, vamos haciendo los cambios correspondientes al resto de tipos de precio. Siempre con pasos seguros gracias a nuestra batería de tests.

TESTS

Y finalmente hacemos lo mismo con getFrequentRenterPoints:

  • movemos el código de Movie.getFrequentRenterPoints a Price.getFrequentRenterPoints (incluyendo primero los nuevos tests porque hacemos TDD)
  • no olvidamos mover los javadocs
  • implementamos el caso particular de NewRelease.getFrequentRenterPoints (dejando el caso general en la clase abstracta Price)

Pues bien, llegados a este punto ya hemos terminado con el ejemplo de Fowler. Os recomiendo encarecidamente el libro (en él se detallan las técnicas de refactor y los “tufillos” (smells) que nos indican cuándo es posible que nuestro código requiera ser refactorizado) y un par de secuelas muy interesantes:

  • Refactoring Workbook, con el que podréis ejercitar todo lo que hayáis aprendido con el de Fowler.
  • Refactoring to Patterns, con el que podréis orientar vuestros refactorajes hacia patrones de diseño, mejorando así la calidad de vuestros diseños.

Finalmente, una última recomendación: http://sourcemaking.com/refactoring

Desgraciadamente todas estas referencias están en inglés. :-(
¿Cuándo nos decidiremos a crear contenidos en español ahora que estamos reivindicando la profesión aquí en España?

Tags: ,

Refactoring en Español (5)

Por fin he conseguido sacar tiempo para continuar con la serie basada en el ejemplo del primer capítulo del libro “Refactoring” de Martin Fowler.

En esta entrega vamos a ver cómo nos llevamos la lógica condicional que hay en los métodos Rental.getCharge y Rental.getFrequentRenterPoints a la clase Movie.

Replacing the Conditional Logic on Price Code with Polymorphism

Fowler afirma que es una mala idea basar un switch en un atributo que corresponde a otro objeto. Dado que se basa en un atributo de Movie, el método debería estar en Movie (aunque tengamos que pasar el número de días de alquiler como parámetro).

Los pasos a seguir son los siguientes (suponiendo que tenemos Eclipse):
1. Alt+Shift+V sobre el método Rental.getCharge

  • Seleccionamos Movie
  • Marcamos la opción de crear un método delegado (pero sin marcarlo como @deprecated)
  • Revisamos el javadoc porque ahora el método está en otro objeto y puede tener otras responsabilidades.

Con esto, habremos dejado el código como sigue:

 /**
  *  Movie#getCharge(int)
  */
 double getCharge() {
  return _movie.getCharge(getDaysRented());
 }
 /**
  * Determine the corresponding amount depending on the type of movie
  * (the price code).
  *
  *  rental TODO
  *    */
 double getCharge(Rental rental) {
  double thisAmount = 0;
  switch (rental.getMovie().getPriceCode()) {
    case Movie.REGULAR:
      thisAmount += 2;
      if (rental.getDaysRented() > 2)
        thisAmount += (rental.getDaysRented() - 2) * 1.5;
      break;
    case Movie.NEW_RELEASE:
      thisAmount += rental.getDaysRented() * 3;
      break;
    case Movie.CHILDRENS:
      thisAmount += 1.5;
      if (rental.getDaysRented() > 3)
        thisAmount += (rental.getDaysRented() - 3) * 1.5;
      break;
   }
   return thisAmount;
 }

TESTS

2. El atributo sobre el que se hace el switch es equivalente a getPriceCode() puesto que se trata del mismo objeto Movie; así que esa linea pasa de:

  switch (rental.getMovie().getPriceCode()) {

a:

  switch (getPriceCode()) {

TESTS

3. Cambiamos el atributo que pasamos para que, en vez de pasar el objeto Rental, pasar el número de días que se alquila.

  • Hay que cambiar todas las rental.getDaysRented por el parámetro daysRented
  • Cambiamos el javadoc porque ahora el método recibe parámetros distintos.

El código de este método queda como sigue:

 /**
  * Determine the corresponding amount depending on the type of movie
  * (the price code) and the number or days rented.
  *
  *  daysRented
  *    */
 double getCharge(int daysRented) {
  double thisAmount = 0;
  switch (getPriceCode()) {
    case Movie.REGULAR:
      thisAmount += 2;
      if (daysRented > 2)
        thisAmount += (daysRented - 2) * 1.5;
      break;
    case Movie.NEW_RELEASE:
      thisAmount += daysRented * 3;
      break;
    case Movie.CHILDRENS:
      thisAmount += 1.5;
      if (daysRented > 3)
        thisAmount += (daysRented - 3) * 1.5;
      break;
    }
    return thisAmount;
 }

TESTS

Por simetría (un importante patrón, ver “Implementation Patterns” de Beck), implementamos el método Rental.getFrequentRenterPoints en Movie (pasando el número de días de alquiler como parámetro).

Lo dejo como ejercicio porque es simplemente aplicar la misma receta sobre el método Rental.getFrequentRenterPoints llevando el código al nuevo Rental.getFrequentRenterPoints(days:int).

TESTS

Llegados a este punto… me doy cuenta de que no estamos usando TDD. Es decir, que los tests que tengo no están probando el código que estoy escribiendo. Me refiero a que hemos movido código entre clases y no hemos escrito tests para estas clases.

He escrito MovieTest.

package step5;

import static org.junit.Assert.assertEquals;
import org.junit.Ignore;
import org.junit.Test;

public class MovieTest {
 private static final String ANY_TITLE = "Any Movie Title";
 private static final int UNKNOWN_PRICE_CODE = 99;
   public void testMovieRegular() {
  Movie aMovie = new Movie(ANY_TITLE,Movie.REGULAR);
  assertEquals(Movie.REGULAR, aMovie.getPriceCode());
 }

   public void testMovieChildren() {
  Movie aMovie = new Movie(ANY_TITLE,Movie.CHILDRENS);
  assertEquals(Movie.CHILDRENS, aMovie.getPriceCode());
 }

   public void testMovieNewRelease() {
  Movie aMovie = new Movie(ANY_TITLE,Movie.NEW_RELEASE);
  assertEquals(Movie.NEW_RELEASE, aMovie.getPriceCode());
 }

 @Test(expected=IllegalArgumentException.class)
   public void testMovieIllegalType() {
  new Movie(ANY_TITLE,UNKNOWN_PRICE_CODE);
 }

   public void testFrequentRenterPointsRegular() {
  int points[] = { 1, 1, 1 };
  Movie aMovie = new Movie(ANY_TITLE,Movie.REGULAR);
  for (int daysRented = 1; daysRented <= points.length; daysRented++) {
   assertEquals(points[daysRented-1], aMovie.getFrequentRenterPoints(daysRented));
  }
 }

   public void testFrequentRenterPointsChildrens() {
  int points[] = { 1, 1, 1 };
  Movie aMovie = new Movie(ANY_TITLE,Movie.CHILDRENS);
  for (int daysRented = 1; daysRented <= points.length; daysRented++) {
   assertEquals(points[daysRented-1], aMovie.getFrequentRenterPoints(daysRented));
  }
 }

   public void testFrequentRenterPointsNewRelease() {
  int points[] = { 1, 2, 2, 2 };
  Movie aMovie = new Movie(ANY_TITLE,Movie.NEW_RELEASE);
  for (int daysRented = 1; daysRented <= points.length; daysRented++) {
   assertEquals(points[daysRented-1], aMovie.getFrequentRenterPoints(daysRented));
  }
 }

   public void testChargeForRegular() {
  double charges[] = { 2.0, 2.0, 3.5, 5.0, 6.5, 8.0 };
  Movie aMovie = new Movie(ANY_TITLE,Movie.REGULAR);
  for (int daysRented = 1; daysRented <= charges.length; daysRented++) {
   assertEquals(charges[daysRented-1], aMovie.getCharge(daysRented),0);
  }
 }

   public void testChargeForChildrens() {
  double charges[] = { 1.5, 1.5, 1.5, 3.0, 4.5, 6.0, 7.5 };
  Movie aMovie = new Movie(ANY_TITLE,Movie.CHILDRENS);
  for (int daysRented = 1; daysRented <= charges.length; daysRented++) {
   assertEquals(charges[daysRented-1], aMovie.getCharge(daysRented),0);
  }
 }

   public void testChargeNewRelease() {
  double charges[] = { 3.0, 6.0, 9.0, 12.0 };
  Movie aMovie = new Movie(ANY_TITLE,Movie.NEW_RELEASE);
  for (int daysRented = 1; daysRented <= charges.length; daysRented++) {
   assertEquals(charges[daysRented-1], aMovie.getCharge(daysRented),0);
  }
 }
}

Añadimos este test a la suite y volvemos a lanzarlos todos.

En resumen, el cambio que hemos hecho ha consistido en crear los métodos Movie.getCharge(days:int) y Movie.getFrequentRenterPoints(days:int) para alojar ahí la lógica de los métodos homónimos en la clase Rental y que dependían de una propiedad de la clase Movie.

En la siguiente entrega (la última), donde haremos un cambio muy serio al diseño puesto que introduciremos nuevas clases (entre ellas Price), podremos ver cómo recuperaremos la inversión que acabamos de hacer en los nuevos tests.

Tags:

Refactoring en Español (4)

Hace ya un par de semanas que publiqué la última entrega de “Refactoring en Español”. Voy a seguir con el ejemplo de Fowler en el libro “Refactoring” por el punto donde lo dejamos.

Removing Temps

Antes de comenzar hacemos el siguiente refactor “sintáctico”.

Reordenamos en Customer.statement para poner juntas estas lineas y poder cambiar
lo siguiente:

  Enumeration<Rental> rentals = _rentals.elements();
  while (rentals.hasMoreElements()) {
   Rental each = (Rental) rentals.nextElement();
   ...
  }

por:

  for (Rental each: _rentals ) {
    ...
  }

TESTS. Sí, aunque sea un cambio trivial, refactorizar consiste en hacer pequeños cambios pero siempre muy seguros; por eso lanzamos las pruebas unitarias cada vez que realizamos un cambio en nuestro código.

A partir de aquí podemos ver más claramente la necesidad/posibilidad de
trabajar sobre las variables totalAmount y frequentRenterPoints.

Primero trabajamos con totalAmount y creamos el método getTotalCharge como sigue:

 /**
  * Rolls over the list of rentals calculating
  * the total amount to be charged.
  *
  *    */
 private double getTotalCharge() {
  double totalAmount = 0;
  for (Rental each: _rentals ) {
   totalAmount += each.getCharge();
  }
  return totalAmount;
 }

Obsérvese que lo hacemos replicando el bucle que recorre la lista de Rentals y
moviendo el código que suma la cantidad correspondiente del bucle original al
del método (que se ejecuta al final del bucle original y fuera de éste).

Este cambio es conceptualmente difícil de concebir e implementar (este ejemplo es
un ejercicio de laboratorio y en la práctica nos podemos encontrar con casos mucho
más difíciles de ver).

El método statement queda como sigue después de este cambio:

 public String statement() {
  int frequentRenterPoints = 0;
  String result = "Rental Record for " + getName() + "\n";
  for (Rental each: _rentals ) {
   frequentRenterPoints += each.getFrequentRenterPoints();
   // show figures for this rental
   result += "\t" + each.getMovie().getTitle() + "\t"
     + String.valueOf(each.getCharge()) + "\n";
   totalAmount += each.getCharge();
  }
  // add footer lines
  result += "Amount owed is " + String.valueOf(getTotalCharge()) + "\n";
  result += "You earned " + String.valueOf(frequentRenterPoints)
    + " frequent renter points";
  return result;
 }

TESTS. En este caso está más justificado que nunca puesto que no se trata de un cambio nada trivial.

A continuación aplicamos la misma receta a frequentRenterPoints

El método statement queda finalmente como sigue:

 public String statement() {
  String result = "Rental Record for " + getName() + "\n";
  for (Rental each: _rentals ) {
   // show figures for this rental
   result += "\t" + each.getMovie().getTitle() + "\t"
     + String.valueOf(each.getCharge()) + "\n";
  }
  // add footer lines
  result += "Amount owed is " + String.valueOf(getTotalCharge()) + "\n";
  result += "You earned " + String.valueOf(getTotalFrequentRenterPoints())
    + " frequent renter points";
  return result;
 }

TESTS. No olvidemos ejecutarlos después de cada cambio.

Finalmente, incidir en el comentario que hace Fowler sobre el hecho de que, tras este
cambio, tenemos tres bucles que recorren tres veces la lista de Rentals. Esto lo
tendremos en cuenta cuando estemos mejorando el rendimiento, pero no ahora porque
lo que nos preocupa es mejorar el diseño (sin incumplir con los requisitos), y con
los cambios efectuados hemos mejorado el diseño y la legibilidad del código, lo
que nos llevará a reducir los errores y aumentará la reusabilidad. (El ejemplo de
Fowler es muy bueno: ahora podemos escribir un método htmlStatement que imprime
el recibo en formato HTML, pero reutilizando todo el código que no tiene que ver
con la estética del mismo).

Tags:

Refactoring en Español (3)

Seguimos con el ejemplo de refactor del ejemplo del “videoclub”.

En esta entrega vamos a hacer un cambio parecido al de la entrega anterior (donde transformamos el método Customer.amountFor a Rental.getCharge), pero en este caso moveremos crearemos el método Customer.getFrequentRenterPoints y luego lo moveremos a Rental.

Extracting Frequent Renter Points

Decíamos al final de la entrega anterior que Fowler proponía un último refactor, pero que lo posponíamos. Ahora sí lo hacemos porque veréis que, en mi modesta opinión, es más afín a los siguientes cambios.

1. En Customer.statement sustituimos las ocurrencias de thisAmount por each.getRental(). (En Eclipse, si hacemos doble-click sobre thisAmount veremos que se iluminan todos los puntos donde se usa la variable).
2. Eliminamos la declaración de thisAmount

El código queda como:

        while (rentals.hasMoreElements()) {
            Rental each = (Rental) rentals.nextElement();
            // add frequent renter points
            frequentRenterPoints++;
            // add bonus for a two day new release rental
            if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE)
                    && each.getDaysRented() > 1)
                frequentRenterPoints++;
            // show figures for this rental
            result += "\t" + each.getMovie().getTitle() + "\t"
                    + String.valueOf(each.getCharge()) + "\n";
            totalAmount += each.getCharge();
        }

Hemos hecho este cambio para así eliminar el uso de thisAmount y ver más claro
el uso que se está haciendo de las demás variables locales. ¡No olvidemos pasar los tests!

Ahora nos fijamos en frequentRenterPoints.

Extraemos el método getFrequentRenterPoints (marcando desde el comentario “add frequent renter points” hasta el comentario “show figures for this rental” y haciendo Alt+Shift+M), pero nos quedará “un poco raro” porque estaremos incrementando la variable y luego asignando su valor en Customer.statement. Así que seguiremos refactorizando hasta tener en Customer.statement la siguiente linea:

            frequentRenterPoints += getFrequentRenterPoints(each);

Obsérvese que:
a) quitamos el parámetro frequentRenterPoints de la firma del método getFrequentRenterPoints que nos ha generado Eclipse. Hay que cambiarlo a mano, ahí ya no nos puede ayudar Eclipse. :-)
b) acumulamos el valor fuera del método (de ahí el “+=”); hay que cambiar el código del método para mantener la integridad semántica. Customer.getFrequentRenterPoints devuelve los valores 1 o 2 (dependiendo de si le corresponde bonus o no a la película que se está alquilando).
c) dado que, no sólo creamos un nuevo método sino que además cambiamos la semántica de las líneas de código originales, es muy importante que pongamos atención al javadoc del método

    /**
     * Returns the renter points corresponding to each rental. A two day new
     * release rental comes with a bonus.
     *
     *  each
     *       */
    private int getFrequentRenterPoints(Rental each) {
        // add bonus for a two day new release rental
        if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE)
                && each.getDaysRented() > 1) {
            return 2;
        }
        return 1;
    }

TESTS

Después de este cambio, vemos que Customer.getFrequentRenterPoints no tiene nada
que ver con Customer, por lo que hacemos lo mismo que hicimos con Rental.getCharge
y nos llevamos este método a Rental.

Para ello usamos Alt+Shift+V (pero esta vez desmarcamos la opción de crear el método
delegado), con lo que directamente Eclipse nos cambia la linea anterior a:

            frequentRenterPoints += each.getFrequentRenterPoints();

El resultado final de Rental.getFrequentRenterPoints es:

    /**
     * Returns the renter points corresponding to the rental.
     * A two day new release rental comes with a bonus.
     *
     *  frequentRenterPoints
     *       */
    int getFrequentRenterPoints() {
        if ((getMovie().getPriceCode() == Movie.NEW_RELEASE)
                && getDaysRented() > 1) {
            return 2;
        } else {
            return 1;
        }
    }

Observad el matiz del cambio en el javadoc.

TESTS y listo.

Tags: ,