14 de octubre de 2011

TDD is not a testing technique, it's a design technique

Esta es una pequeña entrada para comentar sobre la reciente publicación de Mark Seemann "The TDD Apostate".

En realidad, no es que quiera debatir lo expresado por Seeman en su blog. En realidad, estoy de acuerdo en la gran mayoría de lo que ahí expresa. Sin embargo, un sentimiento de incomodidad me recorría mientras leía su artículo: "claro, ¡eso es obvio!" decía para mis adentros una y otra vez. Y la razón de ello es bastante clara ahora: TDD es una herramienta valiosísima para un desarrollador. Es quizás la técnica más importante que ha llegado al mainstream del desarrollo de software en los últimos 10 años—si tan solo más personas la usáramos—pero ¡no es una bala de plata!

Parece ser que algunas personas siguen con la obsesión de buscar la nueva técnica o tecnología o invento o aceite de víbora que cure todos los males del desarrollo de software–seguramente que los gerentes y directivos de las grandes empresas tienen puestas sus esperanzas en ello. Otras por el contrario, desean que estos nunca se resuelvan y por el contrario, hacer mucho dinero vendiendo aceite de víbora. Ambos son intereses legítimos. Sin embargo, también habemos muchos programadores que vivimos soñando con estos remedios mágicos. Pensamos en lo difícil que es nuestro trabajo y en cómo nos gustaría descubrir un truco nuevo que nos permitiera resolver nuestros problemas y dedicar nuestro hermoso tiempo a navegar por internet y a jugar con nuevos gadgets.

Sin embargo, es preciso no perder de vista que el desarrollo de software es una actividad inherentemente humana y por consiguiente, difícil de automatizar y simplificar. Es ante todo una actividad creativa, que de algún modo se encuentra en algún punto entre escribir una novela de misterio y elaborar los planos de un rascacielos.

El punto del sr. Seeman es que la Programación Dirigida por Pruebas por sí misma no es una buena forma de diseñar software: de acuerdo. Recuerdo hace años haber leído en algún lugar que los impulsores de XP predicaban el no-diseño, que todos los males se podían arreglar mediante YAGNI y refactoring y no-sé-qué otras estupideces por el estilo. Cualquiera que haya leído el libro de Martin Fowler sabrá que eso no es verdad ni de lejos.

Sin embargo, muchos pseudo-programadores y otros perezosos fuimos seducidos por el llamado de la sirena: nos dejamos embaucar por las falsas promesas de aquellos que ni entendían ni les interesaba la verdadera intención tras el movimiento ágil y hallamos en estas frases propagandísticas una justificación para nuestro descuido. Otros solo entendimos el mensaje a medias, lo suficiente para poder hablar con grandilocuencia de ello, pero sin jamás llegar a aplicarlo. Algunos más escuchamos los cantos de júbilo de aquellos que abrazaron con entusiasmo el movimiento y fueron los primeros en superar los problemas iniciales y recoger sus primeros frutos. Aquello parecía ser demasiado bueno para ser cierto. Y así era.

Si bien es cierto que la TDD nos aporta un sinnúmero de ventajas, no se trata de ninguna panacea. Kent Beck nos dá una pista de la importancia de la TDD al comentar que "si pudiera convencer a los equipos de desarrollo de adoptar solo dos prácticas de XP, estas serían TDD y la programación en parejas".

El maestro Ward Cunningham dijo hace años:

"Programar las pruebas primero no es una técnica de verificación, es una técnica de diseño"

Fijémonos que nunca menciono que fuera "la única técnica de diseño" ni "la técnica definitiva de diseño". Y la razón es clara: El escribir primero la prueba y después el código nos permite expresar de forma concreta las ideas que tenemos acerca del código que estamos a punto de escribir. Es como dibujar un pequeño boceto en una servilleta, excepto que este boceto es ejecutable y nos indica inmediatamente si al poner en marcha la idea que expresamos lo hemos hecho de forma correcta de acuerdo al entendimiento que tenemos del problema hasta ese momento.

Pero por otro lado, ¿qué arquitecto en sus cabales construiría un edificio basado únicamente en un boceto en una servilleta? Al hacer TDD de forma natural nos enfocamos de forma intensa en la clase/función/historia que estamos implementando en el momento. Eso significa que si es lo único que hacemos para diseñar nuestro software, inevitablemente terminaremos con un montón de bocetos en servilletas que no necesariamente forman un plano completo de nuestro edificio. O por lo menos, tal vez no uno en el cual nos gustaría vivir.

Aunque los ingenieros, arquitectos, médicos, etc. tengan a su disposición una gran cantidad de técnicas y tecnologías que faciliten e incluso automaticen las partes más mecánicas de su trabajo, ninguno de ellos puede olvidarse de toda la base teórica que durante tantos años y con tanto esfuerzo adquirieron. No es casualidad que algunas de figuras más notables impulsores de la TDD como Kent Beck, Ward Cunningham, Martin Fowler o Robert C. Martin sean también figuras destacadas en el ámbito de los principios y patrones de diseño.

Si la TDD fuera suficiente, no haría falta conocer los principios SOLID, los patrones de diseño de los GoF o los patrones GRASP, entre muchos otros.

Si, como desarrolladores debemos abrazar la TDD o como mínimo, alguna otra disciplina equivalente como por ejemplo el Diseño por Contrato (DbC). Hoy en día decir que practicamos TDD debería ser tan obvio como decir "si, cuando programo escribo código", debe ser una práctica core de nuestro oficio. A nadie debería sorprender, e incluso me atrevo a decir que a nadie debería enorgullecer el decir "yo practico TDD", pero si debería ser motivo de vergüenza lo contrario.

Sin embargo, TDD es solo el inicio. Recordemos que XP consta de 12 prácticas (o 13, dependiendo de la edición del libro que tengan):

La programación en parejas, el diseño simple, la metáfora, el refactoring, la propiedad colectiva del código, todas son prácticas que contribuyen a mantener la visión a gran escala del sistema en perspectiva, aún cuando nos concentremos en una sola unidad de software a la vez. Si no estamos en un proyecto/equipo que practique XP, esto hace aún más apremiante que debamos hacer nuestra tarea y conocer más allá de los meros detalles sintácticos de mi código, si no su participación y sus consecuencias como parte integral de un todo. Como se menciona en GoF:

"Los Patrones de Diseño... proveen puntos de llegada para nuestros refactorings."

¿Cómo sabremos cuando hemos arribado a nuestro destino si no sabemos cual es este destino?

En resumen: no, TDD no es una técnica completa ni infalible ni mucho menos a prueba de tontos para el diseño de software; es eso sí, una técnica valiosísima para diseñar unidades de código, mantenerlas limpias, cohesivas y débilmente acopladas con el resto del mundo. ¿Acaso no son estas propiedades deseables del todo?, ¿Acaso no son estas retribución más que suficiente a la disciplina que aplicamos todos los días al practicar TDD?

Citando a Michael Feathers:

He encontrado [también] que al tratar de escribir Pruebas Unitarias en proyectos existentes que no son de XP, para proporcionar un andamiaje para el cambio, se deshierban las dependencias, los problemas de instanciamiento, etc. Sólo puedo creer que si cada clase se desarrolla primero en un arnés de prueba, donde todas las dependencias explícitas, las cosas irán mejor. Ha sido mi experiencia en las cosas más bien pequeñas que he hecho, siguiendo el ejemplo de XP.

Saludos

30 de agosto de 2011

Pepe Grillo y TDD

Hace varias semanas tuve una conversación franca y abierta con mi amigo Alcides: el único tipo de conversación que es posible tener con él. En ella, discutimos las dificultades de su actual proyecto y de cómo hace falta una implementación más formal de algunas mejores prácticas, en especial las pruebas unitarias. Durante nuestra charla yo argumentaba "pero, ¿porqué no usas TDD?" a lo que el me respondía con varias razones:

  • Presiones de tiempo
  • La curva de aprendizaje del equipo de desarrollo
  • El estado actual del código
  • etc

Todas razones válidas, pero sin embargo insuficientes para justificar el no-uso de lo que ambos sabemos ayudará al proyecto. Es como ir al doctor, recibir la receta, surtirla y después negarse a tomar la medicina diciendo que estamos demasiado ocupados para hacerlo.

Ante esto, le recordé una frase del Robert "Tio Bob" Martin:

"Es durante las crisis que más debemos aferrarnos aún a nuestras prácticas y nuestras disciplinas"

Está claro: si el ingeniero civil que está construyendo nuestra casa comienza a tener problemas de tiempo, lo que menos queremos que haga es que empiece a dejar de lado los códigos de construcción, los cálculos, las medidas de seguridad, las mejores prácticas, etc., para ahorrar tiempo, dinero o porque son muy complicadas. Después de todo, si lo hace corremos el riesgo de que la casa se nos venga encima.

Tiempo después, yo mismo entré en modo de emergencia en mi propio proyecto y mientras estaba en ello, Alcides pasó a visitarme. Inmediatamente le mostré el código que acababa de escribir para que me diera su opinión, a lo que él replico:

- "bien, veamos... ¿dónde están las pruebas unitarias?" a lo que por supuesto respondí - "No hay. Este proyecto no me pertenece y mi líder me ha comentado que no tenemos tiempo para eso y debemos concentrarnos en escribir el código tan rápido como podamos: ¡Esto es una crisis!"

Por toda respuesta, recibí una mirada significativa de parte de mi amigo y segundos después la misma frase que le había dicho yo tiempo antes:

"Es durante las crisis que más debemos aferrarnos aún a nuestras prácticas y nuestras disciplinas"

Como dicen en mi pueblo: me mató el gallo en la mano. En ese momento Alcides fue como Pepe Grillo diciéndome que estaba faltando a mi propia conciencia, que estaba dejando que circunstancias externas me detuvieran de hacer mi trabajo de la mejor forma que se y la única que considero ética.

Siempre es valioso tener a alguien que pueda hacer eso por nosotros en momentos que por estrés, flojera o hastío dejemos de hacer caso a la propia conciencia.

Saludos

P.D. Me complace informar que tanto Alcides como un servidor hemos vuelto al buen camino y nos sentimos mucho mejor...

19 de julio de 2011

Kata: Búsqueda Binaria

El propósito de la Kata es sencillo: practicar.

  • Los músicos practican escalas, arpegios, acordes, estudios, piezas completas, etc.

  • Los artistas marciales practican los movimientos individuales una y otra vez y después los combinan en secuencias de ataque-defensa-contra.

  • Los programadores... esperamos hasta la hora de la verdad... hmmm... tal vez no es la mejor estrategia.

El propósito del Kata no es buscar la solución a un problema. De hecho, la mayoría están basados en problemas simples, que han sido resueltos y documentados ampliamente en los libros de introducción a la ciencia de la computación.

Más bien, el valor de practicar un kata es practicar y afinar nuestras habilidades y disciplinas.

¿Cuantas veces nos hemos dicho a nosotros mismos que ahora si escribiré pruebas para el código de mi próximo proyecto?

¿O cuantas veces nos hemos prometido que ahora si vamos a aprender a usar correctamente los atajos de teclado del nuevo IDE que estamos comenzando a usar?

Dado que los kata son ejercicios controlados, sobre problemas cuya solución ya conocemos, nos dan la oportunidad perfecta para concentrarnos no tanto en la parte de descubrir o aprender el algoritmo, sino las partes mecánicas del proceso.

A continuación, procedo a desarrollar un kata muy básico: la búsqueda binaria.

Mi propósito para esta sección es hacerlo paso a paso siguiendo estrictamente el método de Desarrollo Dirigido por Pruebas (TDD por sus siglas en ingles)

El ritmo del TDD:

  • Fallo
  • Éxito
  • Refactorizar

También conocido como "Red, Green, Refactor!".

Las tres leyes del TDD:

  1. No se permite escribir nada de código de producción hasta no tener una prueba que falle (y no compilar es fallar).
  2. No se permite escribir una prueba más allá de lo necesario para que falle.
  3. No se permite escribir más código de producción que el necesario para pasar la prueba.

Escribimos nuestra primera prueba: El caso degenerado.

def test_bsearch():
   assert_equal(-1, bsearch(3, []))

if __name__ == '__main__':
   test_bsearch()

puesto que no existe la función bsearch, obtenemos un error:

Traceback (most recent call last):
 File "chop.py", line 64, in <module>
   test_bsearch()
 File "chop.py", line 61, in test_bsearch
   assert_equal(-1, bsearch(3, []))
NameError: global name 'bsearch' is not defined

A continuación hacemos lo mínimo necesario para corregir el error:

def bsearch(what, seq):
   pass

Ahora tenemos:

Traceback (most recent call last):
 File "chop.py", line 67, in <module>
   test_bsearch()
 File "chop.py", line 64, in test_bsearch
   assert_equal(-1, bsearch(3, []))
 File "chop.py", line 47, in assert_equal
   assert expected == actual, message
AssertionError: -1 was expected, but got None

Ahora hacemos lo más simple que pueda funcionar para lograr que pase la prueba:

def bsearch(what, seq):
   return -1

Con esto es bastante (si, algo bobo será, pero tengamos paciencia. Apenas estamos comenzando). Ahora escribamos la siguiente prueba:

def test_bsearch():
   assert_equal(-1, bsearch(3, []))
   assert_equal(-1, bsearch(3, [1]))

Pasa sin hacer nada. Sigamos:

def test_bsearch():
   assert_equal(-1, bsearch(3, []))
   assert_equal(-1, bsearch(3, [1]))
   assert_equal(0,  bsearch(1, [1]))

Falla. ¿Qué es lo más simple que podemos hacer para que pase, junto con las dos anteriores?

def bsearch(what, seq):
   if len(seq):
       if seq[0] == what:
           return 0
   return -1

La prueba pasa. Sigamos:

def test_bsearch():
   assert_equal(-1, bsearch(3, []))
   assert_equal(-1, bsearch(3, [1]))
   assert_equal(0,  bsearch(1, [1]))

   assert_equal(-1,  bsearch(0, [1, 3]))
   assert_equal(0,  bsearch(1, [1, 3]))
   assert_equal(1,  bsearch(3, [1, 3]))

Las pruebas 4 y 5 pasan sin modificación. La 6 falla, sin embargo.

def bsearch(what, seq):
   if len(seq):
       if seq[0] == what:
           return 0
       if seq[1] == what:
           return 1
   return -1

¡¡Woops!!

Traceback (most recent call last):
 File "chop.py", line 79, in <module>
   test_bsearch()
 File "chop.py", line 71, in test_bsearch
   assert_equal(-1, bsearch(3, [1]))
 File "chop.py", line 65, in bsearch
   if seq[1] == what:
IndexError: list index out of range

Claro, debemos tomar en consideración las dimensiones de la lista:

def bsearch(what, seq):
   start, end = 0, len(seq) - 1
   if start <= end:
       if seq[start] == what:
           return start
       elif seq[end] == what:
           return end
   return -1

Listo. Continuamos:

def test_bsearch():
   assert_equal(-1, bsearch(3, []))
   assert_equal(-1, bsearch(3, [1]))
   assert_equal(0,  bsearch(1, [1]))

   assert_equal(-1,  bsearch(0, [1, 3]))
   assert_equal(-1,  bsearch(2, [1, 3]))
   assert_equal(-1,  bsearch(4, [1, 3]))
   assert_equal(0,  bsearch(1, [1, 3]))
   assert_equal(1,  bsearch(3, [1, 3]))

   assert_equal(0,  bsearch(1, [1, 3, 5]))
   assert_equal(1,  bsearch(3, [1, 3, 5]))

Ahora el problema es que el elemento que buscamos se encuentra justo en medio de la lista. Podríamos hacer algo bobo como if seq[1] == what: return 1, pero eso no nos lleva a ningún lado. Recordemos que...

"Conforme las pruebas se vuelven más específicas, el código se vuelve más genérico."
def bsearch(what, seq):
   start, end = 0, len(seq) - 1

   if start <= end:
       half = (start + end) / 2
       if seq[half] == what:
           return half
       if seq[start] == what:
           return start
       if seq[end] == what:
           return end
   return -1

Siguiente prueba:

def test_bsearch():
   assert_equal(-1, bsearch(3, []))
   assert_equal(-1, bsearch(3, [1]))
   assert_equal(0,  bsearch(1, [1]))

   assert_equal(-1,  bsearch(0, [1, 3]))
   assert_equal(0,  bsearch(1, [1, 3]))
   assert_equal(1,  bsearch(3, [1, 3]))

   assert_equal(0,  bsearch(1, [1, 3, 5]))
   assert_equal(1,  bsearch(3, [1, 3, 5]))
   assert_equal(2,  bsearch(5, [1, 3, 5]))
   assert_equal(-1, bsearch(0, [1, 3, 5]))
   assert_equal(-1, bsearch(2, [1, 3, 5]))
   assert_equal(-1, bsearch(4, [1, 3, 5]))
   assert_equal(-1, bsearch(6, [1, 3, 5]))

Esto se está comenzando a poner algo redundante... tiempo de refactorizar:

def impares(cuantos):
   limite = 2 * cuantos
   return [indice for indice in xrange(1, limite, 2)]

def pares(cuantos):
   limite = 2 * (cuantos + 1)
   return [indice for indice in xrange(0, limite, 2)]

@trace
def bsearch(what, seq):
   start, end = 0, len(seq) - 1

   if start <= end:
       half = (start + end) / 2
       if seq[half] == what:
           return half
       if seq[start] == what:
           return start
       if seq[end] == what:
           return end
   return -1

def test_bsearch(limite):
   for cuantos in xrange(limite):
       lista = impares(cuantos)
          
       for indice, valor in enumerate(lista):
           assert_equal(indice, bsearch(valor, lista))

       for not_found in pares(cuantos):
           assert_equal(-1, bsearch(not_found, lista))

if __name__ == '__main__':
   test_bsearch(4)

Bien, hasta este punto todas las pruebas pasan:

$ python chop.py
bsearch(0, []) -> -1
bsearch(1, [1]) -> 0
bsearch(0, [1]) -> -1
bsearch(2, [1]) -> -1
bsearch(1, [1, 3]) -> 0
bsearch(3, [1, 3]) -> 1
bsearch(0, [1, 3]) -> -1
bsearch(2, [1, 3]) -> -1
bsearch(4, [1, 3]) -> -1

Siguiente prueba:

if __name__ == '__main__':
   test_bsearch(5)

Falla:

...
bsearch(1, [1, 3, 5, 7]) -> 0
bsearch(3, [1, 3, 5, 7]) -> 1
bsearch(5, [1, 3, 5, 7]) -> -1
AssertionError: 2 was expected, but got -1

Bien, ahora el resultado no está ni al principio, ni al final ni tampoco en el medio (recordemos que estamos usando aritmética entera, por lo que (0 + 3) / 2 = 1).

Eso significa que debemos examinar los elementos intermedios. Pero recordemos que si procedemos a evaluarlos uno por uno como lo hemos venido haciendo, la búsqueda se convierte en una operación con un tiempo de ejecución de O(n). Por ello, solo hemos de examinar la mitad con probabilidad de contener el elemento buscado:

def bsearch(what, seq):
   start, end = 0, len(seq) - 1

   while start <= end:
       half = (start + end) / 2
       if seq[half] == what:
           return half
       if seq[half] < what:
           start = half + 1
       else:
           end = half - 1
   return -1

Todas las pruebas pasan:

...
test_bsearch(14)
...
test_bsearch(19)
...
test_bsearch(25)

¿Qué pasa si en lugar de listas de números impares usaramos listas de números pares?

if __name__ == '__main__':
   test_bsearch(5)
   pares, impares = impares, pares
   test_bsearch(5)

Desarrollo

Como los músicos harían, una vez que tenemos una técnica bajo nuestro comando, habrá que buscar todas las variaciones posibles.

¿Podemos escribir una versión recursiva en lugar de iterativa?, ¿Usar multiples threads?, ¿En cuantos lenguajes podemos desarrollar nuestra kata, logrando que nuestra implementación no solo sea correcta, sino también idiomática del lenguaje seleccionado?

Saludos

Apéndice

A continuación, incluyo el código de las funciones trace y assert_equal, usadas en el desarrollo del kata.

class ArgFormatter(object):

   def __init__(self, args, kwds):
       (self.args, self.kwds) = (args, kwds)
       self.params = []

   def _clear(self):
       self.params = []

   def _addPositional(self):
       self.params.extend(repr(arg) for arg in self.args)

   def _addKeywords(self):
       self.params.extend('%s=%r' % (k, valor) for (k, valor) in self.kwds.iteritems())

   def _assemble(self):
       return ', '.join(self.params)

   def Format(self):
       self._clear()
       self._addPositional()
       self._addKeywords()
       return self._assemble()


def trace(f):
   from functools import wraps

   def formatParams(args, kwds):
       return ArgFormatter(args, kwds).Format()

   @wraps(f)
   def decorator(*args, **kwds):
       result = None
       params = formatParams(args, kwds)
       print '%s(%s) ->' % (f.__name__, params),
       result = f(*args, **kwds)
       print repr(result)
       return result
   return decorator


def assert_equal(expected, actual):
   message = "%s was expected, but got %s" % (expected, actual)
   assert expected == actual, message

28 de mayo de 2011

Interfaz de línea de comandos (CLI) y separación de ocupaciones.

Hace no mucho, me dí a la tarea de comenzar a crear una interfaz de línea de comandos (CLI por sus siglas en inglés) para el sistema al que actualmente estoy dando mantenimiento, además de ser un ejercicio intelectual bastante provechoso, obtuve resultados muy satisfactorios en cuanto al objetivo de lograr una separación del código de interfaz de usuario del código de dominio, el cual es uno de los problemas más críticos (y crónicos) que tiene el sistema en cuestión.


Como sabemos, debido a la filosofía subyacente en la gran mayoría de los entornos gráficos de programación actualmente en uso, los famosos IDE-RAD's (Rapid Application Development Environments), primero se diseña la interfaz de usuario y luego como no hay ningún lugar para poner la lógica del dominio, como no sea en la base de datos (stored procedures), si no se tiene cuidado, éste código acaba por desgracia casi siempre horriblemente mezclado y acoplado con el código de la interfaz de usuario. La base de código fuente así creada, se torna difícilmente mantenible y con el paso del tiempo y al ir cambiando los requerimientos se convierte en un serio problema en el mejor de los casos y la mayoría de las veces termina como una gran "bola de lodo" (del antipatrón big ball of mud).


Ya desde finales de los años 70's del siglo pasado, Smalltalk-80 introducía el paradigma (patrón de diseño propiamente dicho) MVC (Model-View-Controller) para evitar los problemas antes mencionados. Ya tendremos ocasión de discutir sobre el mencionado patrón de diseño posteriormente.


La cuestión es que muchos de nosotros no nos damos cuenta del problema, lo que es peor, ni siquiera lo vemos como tal. En mi caso fué hasta después de muchos años de recurrentes problemas de mantenimiento de código y de encontrarme una y otra vez sumergido en pantanos de código así mezclado (sin mencionar el sistema al que actualmente doy mantenimiento), que definitivamente supe que algo no estaba bien, mas aún: que algo estaba muy, muy mal.


Al principio no sabía exactamente que éra. Andando el tiempo y después de investigar, toparme con la verdadera POO y los patrones de diseño, sufrir un cambio de paradigma (paradigm-shift) y sobre todo, después de conocer, reflexionar e investigar mas a fondo algunos principios fundamentales de la programación tales como la modularidad, la no-redundancia, la claridad, la simplicidad, la alta cohesión, el bajo acoplamiento, la verificabilidad, entre otros, fué que me dí cuenta de la situación real.


Me dí cuenta que la razón, causa y origen de mis problemas era precisamente el estar transgrediendo sistemáticamente un principio fundamental que tiene su origen y aplicación en todas las areas de la vida en general y no solamente en la programación de sistemas informáticos. Me refiero al principio de separación de ocupaciones (y/o preocupaciones), mejor conocido en inglés como separation of concerns.


El origen del problema era que estaba yo mezclando a la fuerza y en un mismo recipiente dos cosas que son totalmente distintas e independientes una de la otra ("ortogonales" dirían algunos). Esto es: la interfaz de usuario y la lógica del dominio.


A continuación quiero compartir con ustedes algunas citas memorables de Martin Fowler sobre el tema.


"Domain objects should be completely self contained and work without reference to the presentation, they should also be able to support multiple presentations, possibly simultaneously. This approach was also an important part of the Unix culture, and continues today allowing many applications to be manipulated through both a graphical and command-line interface." - http://martinfowler.com/eaaDev/uiArchs.html


"So, if you write an application with a WIMP (windows, icons, mouse, and pointer) GUI, you should be able to write a command line interface that does everything that you can do through the WIMP interface—without copying any code from the WIMP into the command line." - http://www.martinfowler.com/ieeeSoftware/separation.pdf


"A good mental test to use to check you are using Separated Presentation is to imagine a completely different user interface. If you are writing a GUI imagine writing a command line interface for the same application. Ask yourself if anything would be duplicated between the GUI and command line presentation code - if it is then it's a good candidate for moving to the domain." - http://martinfowler.com/eaaDev/SeparatedPresentation.html


Así pues, al verme enfrentado por enésima ocasión al mismo problema del alto acoplamiento (o mezcla completa) de la interfaz de usuario con la lógica del dominio. Decidí llevar a la práctica los consejos de Martin Fowler y me puse a escribir una interfaz de línea de comandos para el módulo del sistema al cual estaba dando mantenimiento en ese momento. Al hacerlo obtuve una visión sumamente clara de los objetos de dominio necesarios así como de dónde y de qué manera necesitaba refactorizar el código. Asimismo, obtuve código mas limpio, objetos mejor factorizados, separación completa de la interfaz de usuario en el módulo en desarrollo y además una nueva forma de probar los objetos del dominio sin la interfaz de usuario. ¡Todo desde la línea de comandos!.


La experiencia anterior me ha llevado a recomendar ampliamente el ejercicio de escribir una interfaz de línea de comandos para las partes de los sistemas con interfaz de usuario gráfica (GUI) en los cuales no estemos muy seguros de cuáles sean los objetos de dominio necesarios o bien tengamos los problemas crónicos de acoplamiento anteriormente mencionados.


Esto también me hizo recordar que el proceso de creación de una aplicación GUI en Smalltalk es: 1.- Diseñar el modelo del dominio 2.- Probar el modelo del dominio 3.- Diseñar la interfaz de usuario. (http://www.object-arts.com/downloads/docs/index.html)


O dicho de otra forma: Primero lo primero (First things first). En cuenstiones de programación y en especial en POO: El dominio es primero.


Gracias por leernos, buen fin de semana y como siempre todos sus comentarios son bienvenidos.