Aplicación didáctica: Swing y JDBC
Estimados compañeros de JavaHispano.
Por fin, he terminado una aplicación (llevaba cogiendo polvo en el dico duro desde hace casi tres años) que pretende mostrar algunas buenas prácticas sobre Swing y JDBC.
Muestra un buen puñado de buenas prácticas, especialmente en lo que se refiere a escritura de código, aunque también contiene algunas malas prácticas: entono el mea culpa por anticipado.
Por favor, léase el "leeme.txt" completamente antes de comenzar con la aplicación.
El código está publicado aquí: http://code.google.com/p/tapas-tpv/
La licencia es GPL v3.
Espero que sea de ayuda a quellos que empiezan con Swing y JDBC (a los programadores avanzados les parecerá una pérdida de tiempo estudiarla). Todos los comentarios son bienvenidos (por favor, hazedlos en la página del proyecto).
Un saludo y "happy coding".
Reader Comments (40)
Gracias por compartir.
Mirando... Gracias por compartirla. Siempre se aprende algo nuevo.
tan mal no esta para los que estén aprendiendo les va a ir genial
existen aspectos cuestionables pero no es para morirse
hay cosas que no me cuadran
por que diseñaste DataProvider de esa manera no entiendo tu objetivo podrias explicar por que diseñaste esa clase como la insiste parece que usan un proxy pero no entiendo porque
Hola Luis.
Bueno, el interface "DataProviderable" contiene la funcionalidad de persistencia.
Así que implementé esta funcionalidad en "DataProvider", además, esta clase oculta la implementación concreta que se haga de la DB sobre la que se va a persistir (este es el princpio de JPA aunque JPA lo hace con instrucciones de QL en lugar de hacer con la funcionalidad concreta que una aplicación concreta necesita). Así se pueden hacer implementaciones para Derby, DB2, MS-SQL Server, Oracle DB, etc.
Además, se encarga de hacer algo del trabajo que es común para todas las implementaciones (cortar las cadenas a su tamaño máximo p.ej.)
Como ya he dicho, hay diez mil otras formas de hacerlo, esta es mejor que muchas otras y peor que algunas otras, pero es limpia y sigue muchos de los buenos principios de programación.
Espero hacer resuelto tu duda.
Saludos.
una pregunta eres uno de esos no iniciados que no creen en las pruebas unitarias
necesito que entres en razón para que conozcas la única verdad que es que el tdd y código testeable mañana te busco de madrugada y te toco en la puerta con mi biblia (el libro se llama growing object oriented software guided by tests) para que veas la luz no estoy hablando en broma XD
Buenas Peyrona, le he estado echando un vistazo por encima y me parece que esta genial! Que buena aportacion!
Respecto al DataProvider, me parece una solucion valida, aunque desde mi punto de vista esta fachada-factory solapa un poco a la interfaz y rompe un poco el principio de programar contra interfaces para evitar redundancias y facilitar el desacoplamiento.
Yo utilizaria un esquema mas DAO, con un factory una interfaz y sus implementaciones. Para la funcionalidad comun simplemente utilizaria una clase base abstracta que implemente la intefaz que hereden las implementaciones.
En cualquier caso la tuya me parece tambien una buena solucion.
Por otro lado el DataProvider implementa singleton pero no oculta su constructor haciendolo privado, creo que se te ha olvidado. Ademas, yo haria un doble chequeo en el getInstance para evitar la penalizacion de la sincronizacion del modo:
if( instance == null ) {
synchronized( DataProvider.class )
{
if( instance == null )
[...]
No he tenido tiempo de mirar mucho mas. En cualquier caso muchas gracias, he aprendido muchas cosas con tu codigo.
Un saludo
@Luis
En las aplicaciones de escritorio, el test es siempre la propia aplicación.
A diferencia del diseño de APIs o de Frameworks, las aplicaciones de escritorio son directamente ejecutables, por lo que todo se pone a prueba durante su ejecución.
Eso no impide que durante el diseño, por conveniencia del desarrollador, se puedan crear tests específicos, antes de incorporar novedades a la base de código. Pero no es buena idea extrapolar técnicas de diseño a todo, solo porque en algunos casos puedan ser útiles.
Sobre el uso de singleton, yo siempre prefiero usar un enum:
public enum EsUnSingleton{
INSTANCE;
// métodos públicos static
}
De otro modo, los singleton se pueden construir así, con toda seguridad:
public class UnSingleton {
private static class LazyHolder {
private static final UnSingleton INSTANCE = new UnSingleton();
private LazyHolder() {
}
}
public static UnSingleton getInstance() {
return UnSingleton.LazyHolder.INSTANCE;
}
private UnSingleton() {
}
}
En DataProvider, yo refactorizaría dos métodos, de la forma que sigue:
@Override
public List<Product> getCategoriesAndProducts() {
List<Product> products = null;
try {
products = new ArrayList<Product>(provider.getCategoriesAndProducts());
} catch (Exception ex) {
onFatalError(ex);
}
return products == null ? new ArrayList<Product>(0) : products;
}
@Override
public Map<String, String> getConfiguration4Plugin(String sPluginName) throws Exception {
Map<String, String> ret = null;
try {
ret = new HashMap<String, String>(provider.getConfiguration4Plugin(sPluginName));
} catch (Exception ex) {
onFatalError(ex);
}
return ret == null ? new HashMap<String, String>(0) : ret;
}
No se devuelve un null en ningún caso, sino colecciones vacías. Por otro lado, se aísla la colección que devuelve el método, de su propiedad fuente.
chocess
si pero sabes lo divertido que es volver a abrir un flujo de ventanas en serie para que luego te acuerdes que te equivocaste en la 3 ventana cuarto campo (cuando te equivocaste por tercera ves)
eso de probar toda la aplicación me hace perder el tiempo luego hablas con el colega diciendo que el bug esta en la quinta ventana cuando abres y cierras la cuarta ventana cuanto esta activa do el check y es día feriado y estas autenticado como admin
@Luis
Las aplicaciones, al menos las de escritorio, no se ponen a prueba "al final" ;)
Se van probando, poco a poco, mientras se desarrolla. Es muy difícil, cuando no es un serio inconveniente, hacer tests parciales de Swing. Aparte del hecho de que de las pruebas que se diseñen a la implementación final puede haber "más de un abismo".
Yo suelo hacer tests de componentes extendidos, y de modelos, de Swing, con cierta frecuencia. Y el paso al código base donde se van a utilizar no es ni sencillo ni inmediato.
@choces
Totalmente de acuerdo con el enum para implementar singleton: sencillo de implementar y thread safe, tal y como explica Joshua Bloch en el imprescindible effective Java
Se nota que estoy acostumbrado a trabajar con legacy code y a dar siempre soluciones compatibles con la 1.4 :-S
La solución con el LazyHolder no la conocía y la verdad es que es genial porque evita realizar el bloque sincronizado. Ahora ya la conozco y la utilizare siempre que pueda. ¡Muchas gracias!
Un saludo
Recuerdo una frase sobre los singleton "¿porque haces un singleton?, ¿no sería mejor instanciar el objeto una sola vez?"
Un singleton es equivalente a acoplamiento y estado global, dos de las cosas que más dolores de cabeza te pueden traer en cualquier software OO. Cuando crees que necesitas un singleton lo que suele ocurrir es que en realidad necesitas pensar más en el problema y buscar otra solución.
@choces
TDD se puede aplicar perfectamente al desarrollo con SWING, precisamente en el GOOS (http://www.amazon.com/Growing-Object-Oriented-Software-Guided-Tests/dp/0321503627) durante todo el libro desarrollan como ejemplo una aplicación SWING. SI Effective java es un libro imprescindible para cualquier programador java, en mi opinión el GOOS es imprescindible para cualquier programador OO. Después de leer este libro (y algunos otros) y probar la técnica durante un tiempo (un tiempo largo porque no es una técnica facil) se pueden empezar ha hacer juicios de valor sobre si TDD es aplicable o no a un tipo de desarrollo concreto, pero afirmar como tu haces "que el test es la propia aplicación" y que tal técnica no se puede aplicar sólo porque no tu no la apliques es, como comprenderás, una afirmación totalmente carente de fundamento.
@alfredocasado
No entiendo tu critica a singleton. Estado global?? precisamente lo que caracteriza a los componentes singleton es su condicion stateless.
Que no son necesarios? como solucionarias el problema de la multiinstancia en un entorno concurrente? como harias para asegurar que, como comentas, "se instancia el objeto una sola vez"?
Todas las herramientas (servidores de aplicaciones, el mismo JDK, etc) y frameworks modernos implementan de una forma u otra el patron, incluso Spring con su singleton/no singleton. Quizas el problema es que sus creadores son unos programadores mediocres no han pensado bien la implementacion?
Un saludo,
@luis.ernesto.morales.cordova@gmail.com
Hola Luis, suscribo lo que te ha contestado Choce. De todos modos, claro que hago test unitarios, me gustan y soy firme defensor de ellos (aunque no soy partidario de la Programación Dirigida por Tests), es sólo que no los he subido porque no quiero abrumar con demasiado código.
--------------------------------
@UnoPorAhi
¿No he puesto le constructor del singleton private? Mierda, qué despistado soy. Lo corrijo ahora mismo: mil gracias.
¿Tampoco hago el doble chequeo en el getInstance? Es raro, porque tengo un trozo de código (desde hace años) que siempre copio y pego para hacer los Singleton. Joder, ese día estaba yo en otra cosa... Lo corrijo ahora mismo, muchas gracias.
En cuanto al diseño de DataProvider, llevas razón, es más elegante como tú sugieres y es más recomendable, pero creo que para un ejemplo simple el mio ofrece lo que debe con un menor precio (es más simple). No quería abrumar a quien lo mirase: como dije es para principiantes, no para intermedios o avanzados.
--------------------------
@choces
En cuanto al Singleton, el Enum lo conozco de siempre pero no me gusta, yo qué sé, manías de viejo... :-)
La nueva forma de hacer el Singleton (que es cómo lo genera ahora NetBeans cuando se lo pedimos) no se me había ocurrido hasta que se la vi a NetBeans, pero este código lleva años cogiendo polvo en el HD.
En cuanto a la refactorización: muchísimas gracias por la sugerencia, lo voy a mirar.
Gracias a todos, de verdad, muchas gracias.
@choces
Disculpa, acabo de revisar el código de DataProvider y el constructor es private.
El check para evitar el symchronize estaba mal, gracias: corregido.
@peyrona
No digas eso de "manías de viejo", porque igual soy yo más viejo que tu ;D
Usar un enum como singleton es lo más simple y seguro que existe, aunque es verdad que tampoco es "obligatorio" ;) y no funciona con versiones de JavaSE anteriores a la 1.5, dicho sea de paso.
Por eso, para los "maniáticos" :P creo que el método del lazy loader es más de su "estilo", tan seguro como el enum, y sin sincronizaciones ni dobles chequeos.
@peyrona
Era yo el equivocado. Es cierto, el constructor es privado
Una pregunta a @todos, si en el post pongo links a otras paginas/blogs con referencias o tutoriales utilizando en anchor HTML se descarta el mensaje? Me ha pasado ya varias veces estos dias que al intentar responder y comentar cosas y tras darle al Create Post no veo nunca los mensajes publicados :-S Luego ya me da pereza volver a escribirlos...
@alfredocasado
Lo que he dicho, y mantengo, porque no creo que sea una afirmación sin fundamento es:
"Pero no es buena idea extrapolar técnicas de diseño a todo, solo porque en algunos casos puedan ser útiles".
Ésto: "... que tal técnica no se puede aplicar sólo porque no tu no la apliques es, como comprenderás, una afirmación totalmente carente de fundamento." lo has dicho tu, no yo.
Los singleton existen en Java por buenas razones, y tienen aplicaciones muy sensatas, como el DataProvider de este caso. Lo malo de los singleton, como de cualquier otra técnica, es usarlo cuando no es necesario.
Vuelvo a recordar el dicho. "Cuando se tiene en la mano un martillo, todos los problemas parecen clavos".
@choces
Hombre no me saques frases de contexto, estabas diciendo que en SWING no hace falta hacer TDD o no se puede usar porque la aplicación es el propio test y porque es muy dificil. Yo lo único que te digo es que eso no es cierto, TDD se puede aplicar en aplicaciones SWING, yo lo he echo en el pasado y en el GOOS tienes un ejemplo sensacional si quieres profundizar en el uso de esta técnica de diseño. Si no quieres pues no lo hagas, cada uno a lo suyo, pero no digas que no se puede o no hace falta. ¿No tienes test de regresión?, ¿cuando cambias algo pruebas todo a mano?, ¿te parece la forma más correcta de enfocar un desarrollo?
Sobre el singleton, los singleton no "existen en java", no son parte del lenguaje (ni de ningún otro lenguaje OO), es un patron que puedes aplicar o no. Personalmente siempre que he visto un singleton he encontrado una solución mejor, por ejemplo para el DataProvider se me ocurren dos enfoques:
1 - Evitar el acoplamiento, hacer una instancia del DataProvider en el main y pasarsela a todos los objetos que la necesiten. En general este es el esquema que suelo usar, toda app OO tienen una parte de wiring de objetos inicial, en este punto puedes hacer lo que comento con facilidad y evitas un acoplamiento tan fuerte.
2 - Aceptar el acoplamiento, si sólo quieres una instancia de algo, en realidad no quieres un objeto, puedes hacer que todos los metodos de DataProvider sean estaticos (y las propiedades que ahora son de instancia estaticas tb claro esta), y basicamente tienes lo mismo y no necesitas andar haciendo piruetas para tener un singleton. Esta es igual de mala pero en mi opinión más honrada.
Te dejo 3 articulos de misko hevery que son un poco con los que empece a cambiar mi forma de ver los singleton (en tiempos yo creo que era mi patron favorito)
http://misko.hevery.com/2008/08/17/singletons-are-pathological-liars/
http://misko.hevery.com/2008/08/21/where-have-all-the-singletons-gone/
http://misko.hevery.com/2008/08/25/root-cause-of-singletons/
(leerlos en orden)
Los otros dos no los he leido, pero el primero me ha servidor para enterarme que mezcla churras con merinas. Estoy de acuerdo en que ocultar las dependecias no es nada bueno, y en el ejemplo lo muestra con Singleton, pero de ahí a que los Singleton son para eso y solo eso y siempre lo hacen... hay un graan salto.
Soy el primero que evita los singleton siempre que puede, trabajando en web con distintos classloaders... mejor curarse en salud, pero eso de que simplemente lo solucionas con métodos estáticos no es cierto. Los métodos estáticos no se pueden sobreescribir ni "implementar interfaces", así que no sirven igual. Así que sí es una solución pero solo para algunos casos, en los otros, pasar la instancia es la solución para marcar las dependecias explicitamente, pero ya sabemos lo que le gusta eso a la gente :).
Komorr:
No se me ocurren muchos casos en los que quieras que tus singleton puedan tener un comportamiento polimorfico, no digo que alguna vez no te surja esa necesidad, esta claro que en ese caso la solución de los estáticos no es suficiente, si lo es para este ejemplo en cuestión. Pon un ejemplo con lo que dices y miramos como eliminar el singleton que seguro que encontramos un mejor camino. De todos modos, la segunda opción era por dar varias alternativas, en general la primera, pasar las dependencias de forma explicita en el constructor, me parece la mejor solución. No entiendo que quieres decir con lo de "ya sabemos lo que le gusta eso a la gente", perdona que lo mismo ando algo espeso jeje.
No sigo muchas reglas en mis diseños, sólo unas pocas y muy simples: cero duplicación, test primero y refactor continuo, buscar buenos nombres, minimo acoplamiento posible, maxima cohesión posible y simple responsabilidad. Nada nuevo en realidad, son reglas de diseño simple más alguna norma básica de diseño OO de toda la vida. Un singleton me dificulta cumplir con dos de mis reglas: es acoplamiento puro y dificulta el testing, por tanto si veo uno trato de encontrar alternativas para quitarlo de en medio, y de verdad, no se me ocurre ninguna circunstancia donde no se pueda resolver de otro modo.
PD: que antes no lo mencione, imaginar que después de esta discusión alguien decide probar a rediseñar el código para eliminar el singleton, ¿como comprueba que la app después de un rediseño sigue funcionando?. Imaginar lo mismo en una aplicación de cientos de miles de lineas, ¿alguien se atrevería a rediseñar un componente tan core y con tantas dependencias? (la fachada con la persistencia nada menos).
Llego tarde al hilo pero parece que todavía está caliente :)
Coincido en muchas cosas con Alfredo Casado, aunque Alfredo, cuando se habla de "singleton" en la práctica se habla de una clase con una sola instancia da igual como se crea, vale la definición correcta habla de asegurar esta unicidad, en la práctica usas todos los días "singletons", lo que estás en contra realmente es de la lazy-initialization via métodos estáticos de singletons y la verdad es que yo también :)
"la primera, pasar las dependencias de forma explicita en el constructor, me parece la mejor solución"
Un montón de parafernalia que tenemos en el software de hoy es precisamente la obsesión por evitar algo tan sencillo como eso ¿tú objeto necesita el DataProvider? pues se lo pasas en el constructor o en el método concreto que lo necesita, y ya está, lo de los métodos estáticos que cargan crean objetos CON ESTADO GLOBAL (ahí está la clave) que tiene que ir a buscarlo en su inicialización a su vez a otros objetos singleton que a su vez se obtienen por métodos estáticos por lazy-initialization que a su vez necesitan otros y otros... al final hemos acabado convirtiendo en singletons objetos que no tendría ningún sentido que lo fueran por pura viralidad de este patrón.
A mi personalmente me parece un antipatrón como una casa y que ciertamente lo dificulta TODO, la alternativa de pasarlo en el constructor es mucho más flexible, el objeto sabe que necesita un DataProvider pero nosotros en el constructor le podemos pasar un MockDataProvider, y dicho MockDataProvider lo hemos creado en el bootstrap de la aplicación.
Esta es una de las razones por la que siempre he sido MUY MUY crítico con Spring, Spring y en general la IoC, porque ese atributo con un @Inject no es más que una forma "mu mona y mu moderna" de hacer MiClase.getGlobalInstance() y todo para no tener que pasarla en el constructor, con el precio de hacer el bootstrap de la aplicación un verdadero infierno injustificado de XMLs y una proliferación de singletons acojonante, cuando verdaderamente en el mundo real se necesita flexibilidad en el URL de la conexión de la base de datos o como mucho en los poquitos objetos que podemos mockear sin hacer retrasar el proyecto años.
Llamadme antiguo pero yo prefiero pasar 100 veces o las que sean un objeto singleton creado en el bootstrap de la aplicación a los objetos que lo utilicen/necesiten, antes que el horror de la "moderna" programación de lazy-initialization de singletons aunque estén camuflados en IoCs.
Últimamente trabajo en Android y es muy agradable encontrar un toolkit que prácticamente no tiene métodos estáticos.
Ahora bien factorías de objetos vía métodos estáticos lo uso mucho pero los objetos devueltos no son singleton (el objeto devuelto es nuevo) y suelen ser polimórficos tal que el objeto devuelto puede ser diferente según los parámetros pasados (parámetros que casi siempre son las dependencias del objeto).
También utilizo, a veces, lazy-initialization para singletons pero para objetos "funcionales" (palabrita tan de moda últimamente), sin estado en donde el método decide cual devolver según algún parámetro selector ("este te conviene") pero que ni siquiera lo guarda el singleton.
Pero incluso en esos dos usos de factoría via métodos estáticos, que digamos son "light", pues la verdad, antes de que Alfredo me de cojllejas, es que son un estorbo para el testeo si se necesitan devolver algún tipo de "mocks", para ello es mejor usar algún tipo de factoría instanciada en el bootstrap. Pero vamos tampoco hay que cogérsela con papel de fumar, añades un flag global de "modo test" y en vez de devolver el objeto normal devuelves un mock, mientras sea poco código (estás metiendo código de test en código funcional) no es para tanto, los pecaditos (pero pocos) son los que hacen esta vida más vivible.
Ahora bien si entramos en el terreno del TDD voy a ser muy radical y controvertido para "la academia", si yo hicera TDD, bien hecho, de verdad, con tests unitarios de verdad (no tests funcionales o de integración que por llamarlos con JUnit ya decimos que son unitarios) y a TODA la aplicación, también al UI, no sólo al modelo de datos con un par de métodos de negocio, pues resultaría que mi trabajo tendría RETRASOS BRUTALES y me habrían DESPEDIDO de todos. Ahora bien el software sería COJONUDO XD
Creo en el testing pero no en el TDD (en el first test) y sinceramente, creo que lo único a lo que aceptablemente podemos testear sin que nos pongan la soga al cuello... es a test funcionales y de integración, además creo firmemente que es en la integración donde está el infierno, es la interacción de los modulitos triviales de testear los que dan lugar a un conjunto más complicado de interacciones que ya no son tan triviales a los que si además les añades hilos y concurrencia por medio ya ni te cuento.
A lo mejor me equivoco, ojala, pero se habla mucho mucho más de test y de TDD que lo que realmente se hace.
Un par de notas finales sobre el libro Growing Object Oriented Software Guided y Swing:
1) Los tests de Swing son test puramente funcionales, hablar de test de unidad en UI es casi hablar de ciencia ficción, aunque sólo testees un botón tienes que crear toda la parafernalia de la aplicación al menos hasta llegar a esa ventana.
2) El código de la aplicación de ejemplo son 25,2 KB y el de test 41,9 KB y eso que la parte Swing es meramente funcional y apenas testea un par de text boxes y el click de un botón.
Llego tarde al hilo pero parece que todavía está caliente :)
Coincido en muchas cosas con Alfredo Casado, aunque Alfredo, cuando se habla de "singleton" en la práctica se habla de una clase con una sola instancia da igual como se crea, vale la definición correcta habla de asegurar esta unicidad, en la práctica usas todos los días "singletons", lo que estás en contra realmente es de la lazy-initialization via métodos estáticos de singletons y la verdad es que yo también :)
"la primera, pasar las dependencias de forma explicita en el constructor, me parece la mejor solución"
Un montón de parafernalia que tenemos en el software de hoy es precisamente la obsesión por evitar algo tan sencillo como eso ¿tú objeto necesita el DataProvider? pues se lo pasas en el constructor o en el método concreto que lo necesita, y ya está, lo de los métodos estáticos que cargan crean objetos CON ESTADO GLOBAL (ahí está la clave) que tiene que ir a buscarlo en su inicialización a su vez a otros objetos singleton que a su vez se obtienen por métodos estáticos por lazy-initialization que a su vez necesitan otros y otros... al final hemos acabado convirtiendo en singletons objetos que no tendría ningún sentido que lo fueran por pura viralidad de este patrón.
A mi personalmente me parece un antipatrón como una casa y que ciertamente lo dificulta TODO, la alternativa de pasarlo en el constructor es mucho más flexible, el objeto sabe que necesita un DataProvider pero nosotros en el constructor le podemos pasar un MockDataProvider, y dicho MockDataProvider lo hemos creado en el bootstrap de la aplicación o si me apuras un MockDataProvider adecuado para el objeto en cuestión.
Esta es una de las razones por la que siempre he sido MUY MUY crítico con Spring, Spring y en general la IoC, porque ese atributo con un @Inject no es más que una forma "mu mona y mu moderna" de hacer MiClase.getGlobalInstance() y todo para no tener que pasarla en el constructor, con el precio de hacer el bootstrap de la aplicación un verdadero infierno injustificado de XMLs y una proliferación de singletons acojonante, cuando verdaderamente en el mundo real se necesita flexibilidad en el URL de la conexión de la base de datos o como mucho en los poquitos objetos que podemos mockear sin hacer retrasar el proyecto años.
Llamadme antiguo pero yo prefiero pasar 100 veces o las que sean un objeto singleton creado en el bootstrap de la aplicación a los objetos que lo utilicen/necesiten, antes que el horror de la "moderna" programación de lazy-initialization de singletons aunque estén camuflados en IoCs.
Últimamente trabajo en Android y es muy agradable encontrar un toolkit que prácticamente no tiene métodos estáticos.
Ahora bien factorías de objetos vía métodos estáticos lo uso mucho pero los objetos devueltos no son singleton (el objeto devuelto es nuevo) y suelen ser polimórficos tal que el objeto devuelto puede ser diferente según los parámetros pasados (parámetros que casi siempre son las dependencias del objeto).
También utilizo, a veces, lazy-initialization para singletons pero para objetos "funcionales" (palabrita tan de moda últimamente), sin estado en donde el método decide cual devolver según algún parámetro selector ("este te conviene") pero que ni siquiera lo guarda el singleton.
Pero incluso en esos dos usos de factoría via métodos estáticos, que digamos son "light", pues la verdad, antes de que Alfredo me de cojllejas, es que son un estorbo para el testeo si se necesitan devolver algún tipo de "mocks", para ello es mejor usar algún tipo de factoría instanciada en el bootstrap. Pero vamos tampoco hay que cogérsela con papel de fumar, añades un flag global de "modo test" y en vez de devolver el objeto normal devuelves un mock, mientras sea poco código (estás metiendo código de test en código funcional) no es para tanto, los pecaditos (pero pocos) son los que hacen esta vida más vivible.
Ahora bien si entramos en el terreno del TDD voy a ser muy radical y controvertido para "la academia", si yo hicera TDD, bien hecho, de verdad, con tests unitarios de verdad (no tests funcionales o de integración que por llamarlos con JUnit ya decimos que son unitarios) y a TODA la aplicación, también al UI, no sólo al modelo de datos con un par de métodos de negocio, pues resultaría que mi trabajo tendría RETRASOS BRUTALES y me habrían DESPEDIDO de TODOS. Ahora bien el software sería COJONUDO XD por eso, porque puedo testear automáticamente muy poco, necesito confiar en una arquitectura lo más perfecta posible y en todos los chequeos posibles que me pueda dar el compilador (que sinceramente son MUCHISIMOS), esa y otras (la navegación por el código, el análisis de dependencias, la capacidad de entender rápidamente cualquier trozo de código) son las que me invitan un día y otro también a huir de los lenguajes no estáticamente tipados.
Creo en el testing pero no en el TDD (en el first test) y sinceramente, creo que lo único a lo que aceptablemente podemos aspirar a testear sin que nos pongan la soga al cuello... es test funcionales y de integración de alto nivel, además creo firmemente que es en la integración donde está el infierno, es la interacción de los modulitos triviales de testear unitariamente los que dan lugar a un conjunto mucho más complicado de interacciones que ya no son tan triviales a los que si además les añades hilos y concurrencia por medio ya ni te cuento.
A lo mejor me equivoco, ojala, pero se habla mucho mucho más de testing y de TDD que lo que realmente se hace, no siempre es por falta de consciencia, el time-to-market es muy duro con esto.
Un par de notas finales sobre el libro Growing Object Oriented Software Guided y Swing:
1) Los tests de Swing son test puramente funcionales, hablar de test de unidad en UI es casi hablar de ciencia ficción, aunque sólo testees un botón tienes que crear toda la parafernalia de la aplicación para llegar a esa ventana que contiene ese botón.
2) El código de la aplicación de ejemplo son 25,2 KB y el de test 41,9 KB y eso que la parte Swing es meramente funcional y apenas testea un par de text boxes y el click de un botón.
Con lo de "ya sabemos lo que le gusta eso a la gente", me refería precisamente a lo que menciona jmarranz de la obsesión que tienen muchos de que pasar parámetros es como... "cansino" :).
Hola
Soy nuevo en esto me interesa descargar el Proyecto, direcciono en el link q dejaste pero no hay donde descragarlo..???
Salu2
@komorr, bueno también hay que equilibrar eso un poco, pasar muchos parametros en un constructor suele ser un indicador de que te estas cargando la responsabilidad simple. Eso es diseño en el fondo, tratar de equilibrar muchos factores sin perder de vista los objetivos (robustez, mantenibilidad, extensibilidad si es el caso, etc,etc).
@jmarranz, me refería al singleton singleton, vamos al (anti)patron, en este tema creo que coincidimos totalmente entonces (alguna vez tenia que ocurrir xd).
En lo que ya supones que no coincidimos en lo de TDD, una demostración de que lo que dices no es cierto, o al menos no es aplicable en todos los casos, es que yo sigo teniendo trabajo :P, y hago TDD siempre, todos los días, en todos mis proyectos, para mi TDD no es una tecnica de pruebas, es una tecnica de diseño, en este punto es donde radican probablemente todas los malentendidos acerca de este estilo de programación. (la T de TDD es desafortunada, Example Driven Development define mucho mejor la tecnica).
Tengo que volver a poner mi blog en marcha para escribir más en profundidad de esto, pero bueno, algunos motivos por los que TDD no es una tecnica que consuma más tiempo de desarrollo:
- Hay gente que hace diseño previo y luego programación, con diagramas y cosas de estas, incluso a veces el diseño y la programación lo hacen personas distintas. Cuando se hace TDD esto carece totalmente de sentido, en este punto nos ahorramos tiempo. (aunque no creo que este sea tu caso).
- Feedback rapido, con TDD estoy probando constantemente lo que hago con herramientas que me permiten hacerlo muy rapido. No necesito arrancar ningún runtime pesado para ver los efectos de cada linea que cambio. (esto es un verdadero agujero negro de tiempo para muchos proyectos).
- Sólo usas el debuger en casos muy extremos. Yo llevo más de un año sin usar un IDE y sin necesitar un debuger. (luego resulta que en los casos dificiles de verdad, por ejemplo problemas de concurrencia, un debuger tampoco ayuda tanto y tienes que recurrir a las viejas trazas de toda la vida).
- TDD no es hacer test unitarios, TDD es guiar tu desarrollo por ejemplos, esto quiere decir que no es necesario en absoluto si estas haciendo la clase B probar que pasa si recibe como parametro cualquier combinación imaginable. La clase B la usas tu desde tu clase A y ya has testeado que le envías los parametros correctos, en otras palabras, yo no escribo código defensivo. (salvo en los extremos del sistema, datos que vengan del exterior o clases que quiera que usen otras personas, equipos o empresas). Por supuesto se hacen test de integración o de sistema, no se trata de testear de forma obsesiva siempre de manera unitaria, se trata de hacer los test que necesitas para guiar tus pasos.
- Como tengo test y puedo ejercitar mi código en ejecución en un tiempo minimo, no necesito un lenguaje con tipado, no me aporta nada a parte de ruido, puedo programar tranquilamente en lenguajes como ruby o groovy que son mucho (pero mucho) más productivos que java (evidentemente si el entorno de ejecución te obliga pues se usa java, objective-C o ensamblador si tocase).
- TDD forma parte de una filosofía más amplia, una filosofía como lean/agile donde precisamente el time-to-market es una prioridad absoluta. Si algo he descubierto en que en la inmensa mayoría de los casos lo que te permite llegar a tiempo es ser capaz de descubrir cual es tu MVP (producto minimo viable) y no escribir código que realmente no necesitas. Hacer TDD no supone una perdida de tiempo significativa para llegar a una fecha dada.
- Hacer test no lleva tanto tiempo en realidad cuando tienes soltura haciendolos, al principio claro que se tarda más, cuando llevas algunos años haciendo esto te puedo garantizar que no hay diferencias de tiempo. Yo he trabajado sin hacer TDD muchos años y haciendolo llevaré cuatro o cinco, no siento que ahora más "lento" la verdad.
De todos modos, no tengo claro que TDD sea para todo el mundo, precisamente por ser una técnica de diseño, no a todo el mundo le gusta diseñar del mismo modo. Yo siempre lo digo, a mi esta técnica es la que más me ha cambiado como programador, la que más me ha echo "crecer", por eso los que defendemos TDD parecemos a veces tan talibanes haciendolo, porque realmente hemos descubierto un beneficio que no hemos descubierto en ninguna otra tecnica. Dicho esto, cada cual que pruebe y descubra sus métodos, yo tengo claro cual es el mio.
Yo considero el modelado de un sistema de clases como el trabajo de un alfarero, sí queda poco industrial y poco ingenieril pero así lo creo, en ese proceso hay una idea intuitiva de lo que se quiere hacer (con cajitas o sin cajitas previamente diseñadas), en ese camino de modelado llegar a plasmar algo que me convenza (que si minimización de dependencias, máxima cohexión y esos rollos que tanto nos interesan aunque yo soy un poco menos formalista) lleva su tiempo y su proceso en el que hay bocetos y bocetos y bocetos, refactorizaciones, tras refactorizaciones tras refactorizaciones, andar testeando esos bocetos todo el rato y tener que andar refactorizando una tonelada de tests auto-impuestos por no se que regla divina de que tras cada línea de código debe existir un test ANTES... pues se que al final acabaría mandando a tomar por saco esa regla divina.
Al final lo que testeo es el resultado, básicamente son test funcionales (no, JUnit no convierte tu test mágicamente en unitario).
Respecto a los lenguajes dinámicos... hace un tiempo me tocó refactorizar profundamente una aplicación Java, fue un fascinante ejercicio de equilibrismo, sabía que cada línea que cambiaba rompía algo, todas las roturas estaban controladas por el tipado estático, todas las dependencias estaban controladas con mi maravilloso Find Usages de NetBeans, pero había un problema, un 5% de código estaba codificado en un lenguaje dinámico, adivina cual era el trozo de código que nunca me alertaba de las roturas, que nunca me hablaba de sus dependencias, que nunca chequeaba la signatura de las llamadas en tiempo de desarrollo, el único que me daba errores de ejecución por asuntos triviales como el nombre de un método que fue cambiado en otro lugar, el código que me costaba 10 veces más entender lo que hacía aunque fuera trivial (partiendo del hecho de que no era código mio)... Al final acabé mandando a tomar por saco ese 5% de código y convirtíendolo a Java.
Debe ser que practico mucho el A Tomar Por Saco Development (ATPSD) XD
Quizás en esto del TDD soy más de la escuela de Cedric Beust aunque no comparta todo lo que se dice en ese artículo.
La parte final de ese artículo es lo mejor y merece copiarse:
----------
When it comes to testing, I live by the following rules of thumb:
“Tests first” or “tests last” is unimportant as long as there are tests.
Try to think about testing as early as possible in your development process.
Don’t let one liners contradict your experience. For example, don’t listen to people who tell you to write “the simplest possible thing that could possibly work”, also known as YAGNI. If your experience tells you you’re going to need this extra class in the future even if it’s not needed right now, follow your judgment and add it now.
Keep in mind that functional tests are the only tests that really matter to your users. Unit tests are just a convenience for you, the developer. A luxury. If you have time to write unit tests, great: they will save you time down the road when you need to track bugs. But if you don’t, make sure that your functional tests cover what your users expect from your product.
Don’t feel bad if you are not doing Test-Driven Development. There are a lot of factors that make this practice a bad fit for a lot of projects and developer personalities (aspects which are very often never mentioned by TDD extremists).
------------
De todas formas Alfredo ya sabes que en el fondo quiero seguir tu religión aunque intuyo que algunos dogmas nunca serán los míos pero otros intuyo que son más mediocridades mías ;)
Hola horny3,
Puedes hacer checkout del respositorio subversion del proyecto. http://code.google.com/p/tapas-tpv/source/checkout
Un saludo,
En la visión del desarrollo como algo artesanal coincidimos plenamente, un modelo de clases es algo que se va refinando poco a poco. Hace poco estaba releyendo el famoso articulo de Jack Reeves the design is the code (http://www.developerdotstar.com/mag/articles/reeves_design_main.html) y me parece increíble como algo escrito en el año 92! nada menos refleja de forma tan fantastica esta forma de entender el diseño, te recomiendo leerlo aunque lo hayas leido antes, es de esos articulo raros en nuestra profesión que mejora con los años en lugar de quedarse obsoleto.
Precisamente es en este refactoring continuo donde TDD es más útil, normalmente no cambias tu modelo de clases de arriba a abajo sino que los vas refinando en pequeños pasos. El ciclo de TDD es red-green-refactor, lo mejor ocurre al final, el red-green es un tramite, en el refactor es donde pasan las cosas interesantes jeje.
Otro malentendido con TDD es que se trata de test unitarios, esto no es cierto, los test pueden ser unitarios, de integración o incluso end-to-end abarcando todo el sistema. Es habitual empezar con un test de sistema o integración y luego ir bajando, haces test unitarios cuando hace falta, a veces con el de integración es suficiente a veces es preferible hacer también los unitarios porque quieres tener feedback rapido de una pieza pequeña. En esto hay diferentes estilos, el original de kent beck es de este estilo, con test de micro-integración (varias clases a la vez en cada test) más que unitarios, mientras que por ejemplo en el GOOS se hacen siempre unitarios mockeando toda dependencia, pero si uno lee el GOOS descubre que esto es una decisión de diseño, tienen un par de capitulos antes de escribir un sólo test donde explican su visión de la OO desde la perspectiva del paso de mensajes y roles en lugar de la perspectiva más clasica, y con esta forma de diseño encaja más hacer TDD con mocks porque lo que quieres testear son precisamente los mensajes. Vamos, que cada maestrillo tienen su librillo, yo no estoy en ningún extremo, a veces uso mocks a veces hago test de integración que incluyen N clases, depende del problema, de la tecnología etc,etc.
Los lenguajes dinámicos los carga el diablo claro, si no tienes compilador y tampoco tienes test que dios te pille confesado. La idea es que si haces testing construyes tu "compilador" a medida, uno que no sólo analiza sintaxis sino que además analiza que tu código cumple tus reglas de negocio. Teniendo este "compilador" a medida no necesito uno que sólo me evalue sintaxis. Pero claro, si tienes que tratar con código legacy un lenguaje dinamico puede ser terrorífico, eso lo entiendo perfectamente. Yo prometo que el código que deje escrito en lenguajes dinámicos no será una pesadilla para el que venga después jeje.
El articulo de Cedric lo conozco, tiene unos cuantos en esta linea, y aunque entiendo mucho de lo que dice y aplaudo su trabajo con testNG no dejo de ver bastante resentimiento en cada articulo que escribe, sera cosa mia. Por cierto, este articulo inicia con una charla de Jim Coplien, que es el autor entre otros de Lean software architecture, un libro fantastico, la idea de DCI me gusta mucho, sin embargo su visión de TDD no la comparto. Hay una discusión bastante entretenida de jim y robert martin sobre tdd (http://www.infoq.com/interviews/coplien-martin-tdd) y jim como alternativa al testing plantea cosas como la programación por contrato, esto si que me parece totalmente irreal, ¿alguien ha probado a definir precondiciones/postcondiciones/invariantes para todos sus métodos y todas sus clases?, muy bonito sobre el papel pero yo no he visto a nadie hacerlo y las veces que lo he intentado la verdad es que no he sido capaz de sacar nada útil.
Esta fase por cierto:
"When it comes to testing, I live by the following rules of thumb:"
refleja que cedric habla de testing, no de diseño, es lo mismo que te decía antes. Lo mejor de estas discusiones es que probablemente todo el mundo tenga razón, a todos les habrá funcionado una tecnica u otra, todos son gente inteligente que tiene sus buenas razones para hacer lo que hace. Estas discusiones precisamente enriquecen porque no hay un pensamiento único, hacer sofware es una cosa muy difícil, en un proyecto cualquiera hay montones de factores tanto tecnológicos como humanos (más importantes si cabe) y tenemos que tener una buena caja de herramientas con técnicas para cada situación. A mi TDD me esta funcionando ahora, no descarto que en un futuro proyecto no se den las circunstancias adecuadas para usarlo y haga otra cosa.
Yo soy más de Design By Contract y que el código hable sólo, que el código defina que es lo posible que puede ocurrir en él y que la mayoría de las reglas se verifiquen en tiempo de compilación, por ejemplo el @Override es una anotación CAIDA DEL CIELO, la de veces que he cambiado la signatura de un método base que se redefine totalmente en una clase derivada y no me he enterado (porque no he prestado suficiente atención al Find Usages de NetBeans) antes de existir el @Override.
Me gustaría poder definir algo así como: void funcion(@unsigned int i) y que cascara a nivel de compilador una llamada funcion(-1) o que me lanzara una excepción de las que dan miedo con un funcion(i) con una i con un valor negativo. El hacer un assert o similar dentro del body o if throw me parece triste y patético en el 2013 pero parece que en los lenguajes hay más interés en avanzar en otras g......s de dudosa utilidad
Otra idea es un
@Restrict(pkg=[com.mystuff.ui.*, com.mystuff.dao.*]) que restringiera el uso de una clase a las clases especificadas en la anotación y que se detectara un uso ilegítimo por el compilador.
Y que esas anotaciones fueran fácilmente programables sin la brutal intrusividad que exigen las herramientas que ya existen con cosas parecidas y que apenas funcionan en runtime (estoy pensando en las validaciones de JavaEE y en alguna herramienta que he visto por encima).
Se me ocurrirían muchas más ideas pero seguro que hay otros con este tema mucho más pensado que yo.
Evitaría miles de tests triviales que insultan a la inteligencia.
Lo malo es que los que decís aquello de "soy más de DBC" lo hacéis pensando en teoría (si no es el caso usted disculpe), por que encaja más con vuestra forma de pensar el "como deberían ser las cosas" pero en realidad no lo habéis usado nunca en una aplicación real de esas gordas, y en desarrollo de software ya se sabe que en teoría la teoría es igual a la practica pero en la practica no suele serlo (que me gustan los trabalenguas :P).
Yo soy muy pragmatico, y mis intentos con DBC siempre me han salido regular (con JContract creo que se llamaba probe algo, pero no consegui llegar a ningún sitio útil). Eiffel creo recordar que tenia soporte para DBC como parte del lenguaje, y con Ada y su tipado extra-fuerte se podría conseguir lo que quieres con la anotación "unsigned" más o menos. Por otro lado, con DBC puedo controlar las precondiciones (parametros de entrada) y las postcondiciones (algo sobre la forma de la salida) el tema de los invariantes me parece complejisimo y no veo como a través de DBC puedo definir la logica de negocio que tiene que implementar una clase o un método (salvo ejemplos chorras de una lista o una pila con DBC).
En el fondo queremos lo mismo, por otro camino pero lo mismo, conseguir una herramienta que dado nuestro código fuente o una modificación sobre el mismo, cuando escribamos un comando (igual me da que se llame "compile" o que se llame "test") nos diga si hemos metido la gamba antes de que le explote a nuestro cliente y nos pongan la cara colorada. Y por supuesto también queremos que nuestro código sea manejable cuando a otro programador le toque lidiar con el, que lo pueda modificar con tranquilidad y en un tiempo razonable. Yo de momento con TDD estoy mucho más cerca de estos dos objetivos que a través de DBC, por tanto, me tengo que inclinar por la tecnica que mas beneficios me reporta en la practica.
Las reglas de chequeo de tipos y valores de un lenguaje OO de tipado estático en cierto modo no dejan de ser puro DBC, por lo que es algo más que teoría :)
Ahora bien si me dices que hay que adoptar el DBC en plan lote porque sí pues va a ser que no, y en general a mi me interesa el DBC fundamentalmente en tiempo de compilación y lo que hay por ahí no es más que inyección de código via bytecode, que también me interesa y mucho pero más bien lo meto en el tema de AOP y reutilización de código.
@jmarranz
Creo que por primera vez en todos los años de JH no estoy del todo de acuerdo con mi amigo jmarranz.
Cuando dices: "Llamadme antiguo pero yo prefiero pasar 100 veces o las que sean un objeto singleton creado en el bootstrap de la aplicación a los objetos que lo utilicen/necesiten, antes que el horror de la "moderna" programación de lazy-initialization de singletons aunque estén camuflados en IoCs."
Vale, no soy muy defensor de Spring (me parece un embrollo) pero prefiero un Singleton a tener que pasar una instancia 100 veces a cien clases y/o métodos. El Singleton me parece más limpio y más OOP. Ahora podéis decir lo que queráis, pero para gusto están los colores... :-)
Abrazos.
Amigo peyrona, en este tema hay que reconocer que Alfredo y yo vamos "contra corriente" pero no estamos solos y tenemos amigos muy fuertes :)
Por ejemplo el objeto Application en Android es un objeto que en la práctica es un singleton, los programadores de Google se cuidan mucho de que no se obtenga via un método estático, si miras el javadoc verás que hay una clase derivada MockApplication que sirve para lo que el nombre sugiere.
Ciertamente se puede devolver dicho mock via método estático pero eso supone que el propio código de selección del objeto en modo test está en el propio código funcional salvo que se use algún sistema de bootstrap externo pensado para instanciar singletons de esa manera tal y como es Spring o Guice, pero los de Google no están por la labor de meter una infraestructura de este tipo con escasas ventajas para un entorno de programación en donde lo que hay menos son singletons y los ciclos de vida de los objetos son mucho más complicados que las típicas reglas del singleton, session o request del mundo web.
Amigo peyrona, en este tema hay que reconocer que Alfredo y yo vamos "contra corriente" pero no estamos solos y tenemos amigos muy fuertes :)
Por ejemplo el objeto Application en Android es un objeto que en la práctica es un singleton, los programadores de Google se cuidan mucho de que no se obtenga via un método estático, si miras el javadoc verás que hay una clase derivada MockApplication que sirve para lo que el nombre sugiere.
Ciertamente se puede devolver dicho mock via método estático pero eso supone que el propio código de selección del objeto en modo test está en el propio código funcional salvo que se use algún sistema de bootstrap externo pensado para instanciar singletons de esa manera tal y como es Spring o Guice, pero los de Google no están por la labor de meter una infraestructura de este tipo con escasas ventajas para un entorno de programación en donde lo que hay menos son singletons y los ciclos de vida de los objetos son mucho más complicados que las típicas reglas del singleton, session o request del mundo web.
Respecto al singleton, un MUY buen articulo de dzone:
http://java.dzone.com/articles/singleton-design-pattern-%E2%80%93
Saludos,
Buenas peyrona, siento darte la tabarra de nuevo con un par de cositas:
-Has cambiado el chequeo de nulo en el
getInstance
, pero en lugar de hacer el double checking simplemente has cambiado de posicion el chequeo del null y yo creo que asi ha quedado peor, ya que ahora facilmente se podrian crear multiples instancias de forma accidental, especialmente si el contructorDataProvider()
es muy pesado.-He tratado de hacer checkout del codigo para poder verlo mas en detalle y poder aportar algo, pero me da un error con cualquier cliente svn. Has activado el acceso anonimo read only? Alguien se lo ha podido descargar?
Unable to connect to a repository at URL
'http://tapas-tpv.googlecode.com/svn/trunk'
OPTIONS of 'http://tapas-tpv.googlecode.com/svn/trunk': Could not read status
(http://tapas-tpv.googlecode.com)
Muchas gracias y un saludo
Yo sí he hecho un checkout, tanto desde NetBeans, como desde TortoiseSVN, pero a la siguiente url: http://tapas-tpv.googlecode.com/svn
He leído ese artículo esta mañana, y es muy interesante. Tiene más usos de los que describe hacia el final, como el servir de API público de un módulo, encapsulando mediante factorías las implementaciones privadas contra los interfaces. En arquitectura modular no tienen ningún sentido ni las inyecciones de dependencia, ni los bootstrap, como medios de "comunicación" entre módulos.
Ok, gracias choces, como me temía eran problemas con el proxy.
Ya esta bajado y lo he lanzado en eclipse...y la verdad es que está muy chulo!
Un saludo