Carlos Ble

Carlos Ble

I am a professional software developer, I solve problems.

I also teach and mentor developers to build better software.

Developing software since 2001.

Can I help you?

  • Do you need high quality tailor-made software?
  • Need training on TDD, clean code or refactoring?
  • Do you need a technical consultant?
  • May I pair with you to write better code?

Events

Upcoming training courses:

  1. TDD - [en Español] - 6 y 7 Octubre
    Gran Canaria
  2. TDD - [in English] - October 20, 21 & 22
    London, UK
  3. TDD - [en Español] - 29, 30 y 31 de Octubre.
    Madrid, Spain

Conferences:

  1. I'll be at the Agile Testing Days 2014
  2. I'll be at the London Test Gathering Workshops.

Archive for August, 2012



Update: this post talks only about one form of event handling. Read this other post for a more detailed explanation on event oriented programming with JavaScript.

Back in the old days of desktop development (gtk, qt, swing, delphi, windows.forms...) it was very common to use events as the primary means of communication between artifacts. However, automated testing wasn't very common at that point and there was a lack of tools for ui automation. With the web, we haven't really used events in more than 10 years (asp.net tried to simulate them) but now with javascript we are back to the old techniques. There have been very few changes in software architecture and patterns in the last 20 years. We could have been writing web applications in the browser the way we do today, more than 10 years ago, if the big companies had agreed on standards and all that. It's always about people, not about software :-)

Anyway, for those friends of mine that are already practicing TDD but haven't really used events, it is not clear how to test drive a collaboration between objects, using events.
Let's see the simplest way to publish and subscribe to an event using javascript:

  1.  
  2. function Publisher(){
  3. this.onWeirdData = function(){} // event
  4.  
  5. this.someMethod = function(someData){
  6. if (someData.isWeird)
  7. this.onWeirdData(); // fires the event
  8. }
  9. }
  10.  
  11. function Subscriber(publisher, errorMessageBox){
  12. this.errorMessageBox = errorMessageBox;
  13.  
  14. var handleWeirdData = function(){ // private event handler
  15. errorMessageBox.show("error: weird data");
  16. }
  17.  
  18. publisher.onWeirdData = function(){ // subscribes to event
  19. handleWeirdData();
  20. }
  21. }
  22.  

This is not a proper implementation of the "Observer pattern" as only one object can
subscribe to the event. We basically replace the publisher event implementation which was
intentionally left blank.
In most cases, I don't need the publisher to have a list of observers but just one, so this way of event firing/handling is OK for me.

Now imagine that we haven't written the code above and we start with our test first:

I want my object to fire an event as weird data is received

  1.  
  2. it("fires event when data is weird", function(){
  3. var fired = false;
  4.  
  5. publisher.onWeirdData = function(){ // spy on the event
  6. fired = true;
  7. };
  8.  
  9. publisher.someMethod({isWeirdData: true});
  10.  
  11. expect(fired).toBeTruthy();
  12. });
  13.  

I want my objects to interact through events

  1.  
  2. it("asks the error box to show when the publisher fires weird data event", function(){
  3. spyOn(observer.errorMessageBox, "show");
  4.  
  5. publisher.onWeirdData();
  6.  
  7. expect(observer.errorMessageBox.show).toHaveBeenCalled();
  8. });
  9.  

Notice that I haven't spied on the private method "handleWeirdData". First because it is private, but even if it was public, I don't want to expose that detail in the test. I just want to express the behavior that I expect from my objects.

Nota: Me apetecía escribir este post en inglés pero me surgió la siguiente duda.. ¿cuántos de los visitantes hispano parlantes dejarían de leer el post por estar en inglés? Si lees este post y te resuta de ayuda estaría bien que añadieses un comentario diciendo si lo hubieses leído en inglés tambien. Así puedo tener feedback sobre si escribir más en castellano o con mi inglés chungo.

Aunque las bases de datos No-Sql están a la vanguardia, muchos seguimos usando también Sql y seguirá siendo así porque Sql y No-Sql tienen ventajas y desventajas. Sin embargo habiendo trabajado con ambos paradigmas, me he llevado cosas que he aprendido con No-Sql a Sql y la ventaja es muy "agil" :-)

Normalmente las bases de datos Sql se diseñan normalizadas, es decir, se evita la duplicidad de datos mediante tablas que se relacionan entre sí con claves foráneas y demás restricciones. Unas tablas apuntan a otras mediante los identificadores y estas cosas. La ventaja de la normalización y de las restricciones tipo "unique", etc, es que no se repiten datos y por tanto el motor optimiza el acceso a ellos. Además podemos mantener una cierta integridad de los datos evitando que se inserten datos sin sentido.
La desventaja es que los cambios estructurales en la bd cuestan muchísimo. Hay herramientas de migración automática que funcionan bastante bien cuando los cambios son relativamente sencillos pero hay migraciones muy complicadas.

Ahora bien... ¿con qué frecuencia hay que hacer cambios en la bd cuando estamos en pleno desarrollo? Pues en cada iteración. Cuando la funcionalidad es nueva, practicamente cada dia o cada semana.
¿Y cuánto nos cuesta manejar en el código un diseño de base de datos complejo? Es caro, porque la optimización se paga con tiempo de desarrollo. Una base de datos es compleja porque tiene relaciones.
Cargar con relaciones entre tablas desde el inicio es muy caro. Diseñar una bd normalizada desde el inicio me parece tremendamente ambicioso, demasiado optimista. Me supone crear capas de anticorrupción para que la lógica de negocio no se vea afectada por la extrema complejidad del esquema de datos. Me supone mucho tiempo en las migraciones.

Personalmente prefiero crear bases de datos los más planas posibles, que me permitan
desarrollar muy rápido y hacer cambios sin problemas de migraciones. Una vez que el feedback de los usuarios me dice que ya conozco suficiente del negocio, de verdad, me planteo ir normalizando la bd para optimizar.
Para esto suelo crear un campo en la tabla de tipo texto, donde puedo guardar serializada toda la información que voy necesitando, usando un formato clave-valor. Luego en código cuando cargo los datos me encargo de deserializar e interpretar esos datos. Así cuando necesito quitar o poner un campo no tengo que tocar la estructura de la base de datos. Ejemplo:
Quiero gestionar las tareas de un usuario. Un usuario debe tener un nombre unico, algunos datos de contacto y puede tener muchas tareas. Cada tarea tiene una serie de datos, de momento titulo y descripcion.
Esquema de BD inicial:

Estructura Tabla user:

field type
id Pk, autoincremental...
username varchar(50)
fields text
userdata text

Ejemplo de una fila rellena en la tabla:

id username fields userdata
0 fulanito email=fulanito@gmail.com#age=20#phone=30 {tasks: [{'title': 'un titulo', 'description': 'probando'}]}

En el campo "fields" de la tabla voy poniendo lo que creo que son campos de esta tabla, separados por el simbolo #. Si necesito poner o quitar campos o necesito que unos objetos tengan un campo y otros no, no tengo que tocar la BD.
Para gestionar este tipo de campos tengo una clase que sabe leerlos y transformarlos en un diccionario por ejemplo y luegos los "getters" de mi objeto de negocio los puedo implementar consultando a ese parser. No importa que use algun tipo de ORM, normalmente admiten métodos en los objetos de negocio.
En el campo "userData" he metido informacion en formato "json" (por poner un ejemplo de formato) que me permite guardar cualquier cosa.

La desventaja de esto es que cuando haya varios usuarios, habra datos duplicados. En la versión normalizada tendriamos una tabla User con varios campos, una tabla Task. Si la relacion es de muchos a muchos, ademas necesitamos una tabla UserTasks que una ambas. La otra gran desventaja es que si tengo que hacer ciertas consultas, las tendre que hacer en código en vez de usar SQL.

La ventaja es que puedo desplegar nuevas versiones sin preocuparme por las migraciones estructurales de base de datos. Puedo retrasar la normalización de la base de datos hasta que de verdad conozca lo suficiente del negocio. De esta manera consigo el esquema más simple posible que me permite programar con comodidad y que a la vez está optimizado. No tengo que cargar con sobreingeniería.

Si normalizo primero, igual decido crear la tabla UserTasks para que exista una relación de muchos a muchos. Si mas adelante las estadisticas de uso de nuestra aplicación y el conocimiento que tenemos de nuestros usuarios nos dicen que una tarea no la pueden tener dos usuarios, entonces hemos sobrediseñado.
Tenemos una tabla que no sirve. El problema es el coste que tiene mantener un código que hace frente a un requisito innecesario.

A menudo se piensa en el coste que tiene escribir una funcionalidad. Pero muy pocas veces se mide el coste que tiene mantener código que no se usa. Y en mi experiencia es un coste altísimo que te hace ir lento. Es una bola de nieve.

Para ir del esquema desnormalizado al normalizado, la prioridad es ir creando campos en las tablas para aquellos atributos por los que quiero poder hacer consultas. Por ejemplo, si nunca hay necesidad de buscar por el numero de telefono, este campo no tengo por qué extraerlo a un campo de bd. Incluso puede que nunca.

Por otra parte, para argumentar sobre el rendimiento de la BD, como con muchas otras cosas, conviene tener datos empíricos objetivos. Es decir, no voy a trabajar en un problema de rendimiento hasta que no haya hecho pruebas de estres o de carga y tenga unos datos que vemos que no son buenos y que hay que mejorar.
A menudo, optimización y rendimiento son enemigos de mantenibilidad. Por eso en mi opinión y mi experiencia (a base de equivocarme) sólo hay que optimizar cuando hay algo que ya funciona bien.

Esta forma de evolucionar las bases de datos puede suponer para algunos hacer las cosas al revés. Lo que ya les pasa con algunas prácticas como TDD. Sin embargo cuanto más experiencia tengo más pienso justo lo contrario, que he pasado demasiado tiempo haciendo las cosas al revés.

Hay que poner en perspectiva lo que uno aprende en la universidad porque a veces son conceptos de hace muchos años, que ahora tienen que ser revisados de otra manera.