viernes, 18 de julio de 2014

Origami sui generis en entorno no agile

Sin poder exponer mucho las circunstancias exactas, menos por consideración a la seguridad que a mis compañeritos, les comparto esta experiencia de la que participé, unos juegos parecidos a los que usamos en nuestros encuentros ágiles.

La persona que trajo los juegos no tenía experiencia en dirigirlos así que cuando se empantanó le ofrecí el juego del origami colaborativo[1]. Como no lo tenía preparado ni sé ningún origami, la consigna que inventé fue que en cada pareja una persona le explicara a la otra como hacer un avión de papel según su propio modelo.

Formé tres parejas, una enfrentada, una de espaldas y otra en trencito, siendo la persona de atrás quien dirigía.

Repartí hojas sin excluir a cada director de pareja tambien, como para facilitarle que hiciera su avión mientras explicaba. Esto, según pude descubrir luego, indujo en la consigna que había que hacer dos aviones iguales, que no es necesariamente lo mismo que hacer el avión del que daba las instrucciones.

Las variaciones al juego fueron menos resultado de mi afán de innovar que de no recordar la forma original.

Los resultados fueron absolutamente hilarantes:

Los que estaban de frente y se podían comunicar y ver libremente, aunque no meter las manos, hicieron el avión más antiestético que quepa imaginar, pero volaba.

Los que estaban de espaldas, hicieron aviones distintos, ambos correctos. Al parecer obraron de buena fé por el desconcierto mostrado al darse vuelta y ver los resultados.

Los que estaban en trencito, que supuse debieron haber sido los segundos y no los terceros en terminar, se rindieron. El que dirigía acusó al dirigido y éste se defendió alegando que nunca había hecho un avión de papel antes. Entonces tomé el teléfono y llamé a la producción de The Big Bang Theory y pregunté si tenían lugar para otro personaje.

Luego me enteré de que habían intentado hacer trampa: el que dirigía le había dicho "hacé cualquier avión y yo te copio, ya que te puedo ver".

Me pregunto si alguien habrá estado en una situación así en la vida real, más allá de documentar después de diseñar.

Se puede estar descontento en el trabajo por muchos motivos, pero no por estas situaciones. Pocas veces me he divertido tanto, son unos genios.




[1] http://tastycupcakes.org/2009/06/collaborative-origami/

domingo, 6 de julio de 2014

Ejemplo de TDD, uso de tags para reportes de ocurrencias de eventos




En esta ocasión relataré un ejemplo de refactorización de clases, acompañado por la práctica de TDD en una aplicación de seguridad informática. No proveeré código y las entidades y atributos estarán parcialmente ofuscados, en parte por discreción y en parte como recurso didáctico de simplificar el modelo presentado.

El objetivo de la transformación tiene que ver con la generación de reportes estadísticos sobre la Ocurrencia de Eventos.

El modelo desarrollado y usado durante varios meses es bastante simple y razonable:

              Área
                |
Ocurrencia - Evento - Tag - Grupo de Tags 
por ejemplo Plataformas(windows,linux,mac
                |
           Provincia


El tag es... eso, un tag. Lo que ocurre es que sabemos que Áreas van a haber y que Provincias hay, pero más importante es que sabemos que nos interesan las Áreas y las Provincias. Otras categorías como Plataforma (windows, linux, mac) van apareciendo, pero no son tan interesantes como para convertirse en entidades. Para no estar cambiando el modelo correteando tras los caprichitos de las necesidades del negocio, existen los Grupos de Tags (Plataforma) con sus tags (windows, linux, mac). Tambien podría ser grupo BBDD con mysql, postqresql, mssql, oracle.

Hasta ahi vamos bárbaro, la operatoria como seda, hasta que vamos a generar reportes.

Quiero estadísticas de Ocurrencias de Eventos por Área, por Provincia, por Área y Provincia y para el otro lado. Ah, y las quiero por Plataformas, Color y Sabor.

Lo primero que uno hace es entonces cada combinación y el sistema nunca está terminado.

No me da la cabeza ni conozco tanto de reporting y encima no puedo estar incorporando módulos extra.

Tuve entonces una serie de ideas reveladoras:

Aunque es más sencillo hacer el reporte según una dimensión tipo entidad como Área o  Empresa que la de Grupo de Tags, está última luego es más reutilizable

El anidamiento es más fácil con los Grupos que con las entidades.

Si a esto le sumamos que la interfaz de selección de dimensiones es generable automáticamente a partir de los Grupos, todo indicaría que en términos de reporte estadístico fue un error no poner toda la información en Tags y Grupos. De todos modos, las entidades son más fáciles de manejar en términos de nulidad y cardinalidad. Si no me entendiste, pensá en como implementar un ABM de Provincias donde estas estan representadas mediante Tags y Grupos de Tags en comparación con app/console.php doctrine:generate:crud (en symfony o equivalente en tu framework habitual).

Habiendo decidido mantener esta suerte de modelo dual, procedí a implementar el reporte, primero con una dimensión, luego dos y finalmente tres.

El algoritmo consiste en iterar sobre todas las Ocurrencias de una fecha e ir acumulando en una estructura tipo árbol, que expresa a recursividad del problema.

Aquí se puede apreciar parte del código de testeo en php:

private function buildExpectedAreas($t1,$t2,$t3,$t4) {
    return array(
        'total'=>$t1,
        'agrupacion'=>array(
            'areas' => array(
                'entidades'=>array(
                    'contable'=> array(
                        'total'=>$t2,
                        'agrupacion'=>array()
                    ),
                    'rrhh'=> array(
                        'total'=>$t3,
                        'agrupacion'=>array()
                    ),
                    'logistica'=>array(
                        'total'=>$t4,
                        'agrupacion'=>array()
                    ),
                )
            )
        )
    );
}


Para una consulta de dos dimensiones, por ejemplo Areas y Plataformas, reemplazá cada una de las tres lineas que dice 'agrupacion'=>array() por

'agrupacion'=>array(
    'plataformas' => array(
        'entidades'=>array(
            'windows'=> array(
                'total'=>$$$,
                'agrupacion'=>array()
            ),
            'unix'=> array(
                'total'=>$$$,
                'agrupacion'=>array()
            ),
            'mainframe'=>array(
                'total'=>$$$,
                'agrupacion'=>array()
            ),
        )
    )
);


Si quisieras una tercera dimensión hay que repetir con la nueva agrupación nueve veces.

Primero implementé el reporte para una y dos dimensiones sobre Grupos.

Ahora bien, ¿qué hacer con Área y Provincia, que ya existen como entidades?

Mi primera idea fue transformarlas en Grupos con sus Tags en el modelo, pero por algo las queríamos como entidades, va a pegar fuerte en la interfaz y en las validaciones, como por ejemplo que Provincia sólo puede haber una y Áreas varias asociadas a un mismo Evento.

La segunda fue, en el momento de generar el reporte, inicio una transacción y genero los Grupos y Tags a partir de Área y Provincias. Tras el reporte va un rollback y no pasó nada.

Al final opté por una variación de la segunda: a cada Evento que el ORM me ha traido, le agrego los Tags necesarios tras haber creado los Grupos necesarios, sin persistir luego.

Problemas de performance no hay, ya que la generación de reportes es de baja frecuencia y concurrencia.

TDD acompañó todo el proceso, aunque no soy muy ortodoxo, sobre todo con el Timely de FIRST[1], Por lo general codeo primero y cuando alcanza una masa crítica hago los test y recién ahí entro en el ciclo test->fail->code->pass->refactor.

Asi que escribí unas pocas lineas y enganché con TDD. Con el siguiente awk se puede obtener las lineas que tenían en cada commit los archivos de código y test. Los AJUSTES se deben a que estas clases ya existían. CORTE es el primer commit. git lola es git log --graph --decorate --pretty=oneline --abbrev-commit --all

#!/bin/bash
CORTE=42ed122
ARCHIVO1=Lib/Codigo.php
ARCHIVO2=Tests/Lib/CodigoTest.php

git lola | cut -b 3-9 | awk -e '
    BEGIN {
       ARCHIVO1="'$ARCHIVO1'"
       ARCHIVO2="'$ARCHIVO2'"

       AJUSTE_CODIGO=346
       AJUSTE_TEST=368
       set +o posix
     }
    /.*/ {
            cmd1 ="git show "$0":"ARCHIVO1" | wc -l "
            cmd2 ="git show "$0":"ARCHIVO2" | wc -l "

            command cmd1  | getline LINEAS_CODIGO
            command cmd2  | getline LINEAS_TEST
         
            LINEAS_CODIGO -= AJUSTE_CODIGO
            LINEAS_TEST -= AJUSTE_TEST
            print LINEAS_CODIGO "\t" LINEAS_TEST          

    }
    /.*'$CORTE'.*/ {
            exit 0;
    }
'


La salida de esto va a tu hoja de cálculo favorita y se vé así:



Finalmente, apareció un requisito nuevo que me produjo un momento de iluminación y me llevó a la solución definitiva.

El requisito tiene que ver con algo que no había mencionado antes, que es el factor multiplicativo. Si un Evento tiene múltiples Empresas, Provincias o Plataformas, las Ocurrencias deben multiplicarse, por ejemplo:

Evento: detección de ataque xss
Áreas: contabilidad
Provicia: Tucumán
Plataformas: mac, windows, linux


En este caso cada Ocurrencia del Evento vale por tres. Si no te cierra bien por qué contabilidad tiene xss en Tucumán no te preocupes, tiene que ver con el enmascaramiento.

El requisito consiste en que si AHORA hago la estadística de Enero, me tiene que dar lo mismo que me dió en ENERO. Si mientras han cambiado las relaciones de "detección de ataque xss", como que en Área se agregue RRHH, la cuenta me daría 6 en Enero. Hay entonces que desnormalizar y en el momento en que se crea la Ocurrencia calcular los factores y guardarlos dentro de la Ocurrencia.

Cuando lo implemente, probablemente tire buena parte del código, pero los test me quedan aprovechables al cien por ciento. Sólo hay que agregar unos pocos que creen unos datos y calculen la estadística para ese momento. Luego, avacen en el tiempo, modifiquen algunas relaciones y vuelvan a calcular para ese momento y los resultados deberán ser los mismos.


Mi conclusión de esta experiencia es que de no haber contado con los tests no habría podido implementar nada, pues la verdad es que la complejidad del código resultante es un poco más grande que lo que mi mente puede abarcar a la vez. Además, ante ese nuevo requisito, no pierdo tanto trabajo pues la inversión está tanto en el código que pueda perder como en los tests que conservaré.



[1] FIRST:
F fast
I independient
R repeatable
S self
T timely