martes, 23 de junio de 2009

Paradigmas.

Los desarrolladores somos –¿hipótesis?- ante todo humanos, luego usuarios, luego desarrolladores.

Como humanos –entre otras cosas-, tendemos a sobrevalorar la propia experiencia. Por ejemplo, si el sistema que desarrollamos nos es fácil de utilizar solemos atribuirle esta característica obviando el hecho de que hemos aprendido a utilizarlo “desde atrás” (es decir, desde el código, el diseño o los requerimientos) y no “desde adelante” (desde la pantalla, los manuales o la capacitación). Para cuando nos enfrentamos a la pantalla de inicio ya sabemos casi de memoria todo lo que sigue. Abstraerse de este conocimiento es –por más que me juren y recontrajuren- imposible, y es por eso que nuestras pruebas y experiencias personales son bastante objetables como medida de lo que sucederá en el mundo real. Esto se aplica, con variaciones, tanto a desarrolladores como a analistas, testers, líderes de proyecto y a cualquiera implicado en el desarrollo de software (excepto, claro está, al cliente y al usuario).

Además de humanos –si es que damos por válida la hipótesis-, somos usuarios de sistemas de un determinado negocio: entornos de desarrollo, clientes de bases de datos, herramientas de diseño y documentación de requisitos, entre muchos otros, conforman el software de soporte al negocio del desarrollo de software. Negocio en el que estamos inmersos y que, como todos, tiene su paradigma establecido y materializado en la forma de determinados estándares: flujos de trabajo, herramientas visuales, disposición física de los objetos en una oficina… todo lo que nos rodea se deriva de éste en alguna medida.

La importancia de estos elementos –paradigma y estándares- que sobre el papel parecen un conjunto arbitrario de caprichos funcionales -¿por qué Ctrl+U y Ctrl+Shift+U? ¿Por qué un árbol y no una serie de listas encadenadas?- radica en que condicionan toda nuestra visión de la realidad (¡vaya si estamos condicionados por los paradigmas de nuestro negocio!). Los sistemas -si pretenden ser exitosos- deberán respetar –o revolucionar, pero esto es más difícil-, sobre todo en la interacción con sus usuarios, los paradigmas del negocio para el que fueron diseñados.

Como desarrolladores que somos –de eso sí estamos seguros- nos sería imposible, si estuviésemos librados a nuestro criterio, no aplicar el paradigma y los estándares propios del negocio al que pertenecemos –el desarrollo de software- a los sistemas que desarrollamos.

El problema es que, como establecimos más arriba, otros negocios o actividades tienen estándares y paradigmas diferentes. Internalizarlos es prácticamente imposible (deberíamos ser, por ejemplo, tan usuarios de distintos sistemas de gestión como lo somos de distintos entornos de desarrollo, tanto como lo es un empleado administrativo que ha transitado por diferentes empresas y puestos). Sólo podemos pretender, a lo más, conocerlos. Y para ello se requiere de una experiencia… “presencial”. Es la experiencia que trata de transmitir el analista funcional, el área comercial o quienquiera que tenga contacto directo con el cliente.

Pero un modelo mental es algo realmente difícil de describir, los estándares que se deriven de éste serán en su gran mayoría reglas y prácticas comunes y no estarán explicitadas en ningún lado. Es, en definitiva, difícil de transmitir. Por todo esto, cuantos más intermediarios haya entre aquellos que conocen a los clientes y usuarios y los desarrolladores, más probabilidades habrá de que nos apartemos del paradigma y los estándares a los que ellos están acostumbrados.

viernes, 19 de junio de 2009

Charlando con Wolfram.

Empecé con una pequeña introducción de rigor…

Hello.

hello

What is your name?

whatisyourname

Where are you?

whereareyou

Who is your father?

whoisyourfather

Are you alive?

areyoualive

Are you Skynet? (esto me tenía preocupado)

Areyouskynet

Is Wolfram|Alpha better than Google?

betterthangoogle

Decidí desafiarlo. Can you pass the Turing Test?

canyoupassturing

Ok, ahí vamos… Is there a God?

isthereagod

What is the meaning of life?

whatisthemeaningoflife

To be or not to be?

tobeornottobe

Why did the chicken cross the road?

whydidthechicken

How many roads must a man walk down before you can call him a man?

howmanyroads

Mejor que algunas personas que conozco. Se ve que sabe de películas, por lo menos:

What’s the speed of an unladen swallow?

speedunladenswallow

88Mph

88mph

1.21 GW

121GW

¿Y de programación? Hello, World

helloworld

Bueno, hora de preguntar algo realmente importante. Can you help me?

Canyouhelpme

How can I score?

howtoscore

Es una buena respuesta, pero hay formas más fáciles.

martes, 16 de junio de 2009

Mostrar entradas de hace un año en Blogger.

Mostrar las entradas del mismo día del año pasado en la barra lateral del blog parecía una tarea sencilla, y como toda tarea sencilla se volvió un verdadero desafío. Finalmente he llegado a una solución que, aunque con sus contras, funciona. Por eso les recomendaría leer el post hasta el final antes de decidirse a implementarla.

La solución (más bien el hack) utiliza la Javascript Client Library de Google para obtener los títulos y links de las entradas de hace un año mediante mediante la API de datos de Blogger.

Primero vamos a lo que importa:

  1. Ingresamos al escritorio de Blogger y luego seleccionamos “Diseño” y “Edición de Html”. 
  2. Antes que nada es recomendable hacer un backup del template de nuestro blog, haciendo click en el link “Descargar plantilla completa”.
  3. Esa cosa rara e ilegible que vemos ahí es el código de nuestro template de Blogger. Buscar la etiqueta <head> (está en las primeras líneas) e insertar el siguiente código a continuación:
    <script src='http://www.google.com/jsapi?autoload=%7Bmodules%3A%5B%7Bname%3Agdata%2Cversion%3A1.x%2Cpackages%3A%5Bblogger%5D%7D%5D%7D' type='text/javascript'/>
  4. Guardar los cambios.
  5. Luego ir a “Elementos de página”. Noten que la dirección de la página (en la barra de direcciones del navegador) dice “BlogID=xxxx”. Anoten ese número.
  6. Agregar un gadget (donde más nos guste) del tipo “HTML/Javascript”.
  7. Escribir un título (ej.:”Hace un año en…”) y como contenido copiar lo siguiente (por temas de formateo lo tengo en tres partes, pegar una detrás de la otra):
        <div id="haceUnAnio" style="display:none" ></div>
            
        <script type='text/javascript'>        
        <!--
        
    var _blogID = 'xxxx';
       
    function date2RFC3339(dateObject)
    {          	
        var _ = function(text,size){
            var out = text.toString();
            while(out.length < size)
                out = '0' + out;                    	            
            return out;     
        }
        
        var formattedDate = [];
        var date = [_(dateObject.getFullYear(),4), _(dateObject.getMonth()+1,2), _(dateObject.getDate(),2)].join('-');
        formattedDate.push(date);
        
        var time = [_(dateObject.getHours(),2), _(dateObject.getMinutes(),2), _(dateObject.getSeconds(),2)].join(':');
        var timezoneOffset = dateObject.getTimezoneOffset();
        time = time + (timezoneOffset > 0 ? '-' : '+') + 
                                _(Math.floor(Math.abs(timezoneOffset)/60),2) + ':' +
                                _(Math.abs(timezoneOffset)%60,2);
        formattedDate.push(time);	        
        return formattedDate.join('T');
    };        
                                 
    function handleErrorPostsDeHaceUnAnio(){};        
    
    function handleGetPostsDeHaceUnAnio(feedRoot)
    {            
        var feedItems = feedRoot.feed.getEntries();
    
        if(feedItems.length==0)
            return;
            
        var boxHTML = '<ul>';
        
        for(var i=0;i<feedItems.length;i++)
        {
            var feedItem = feedItems[i];
            boxHTML = boxHTML + '<li><a href="'+feedItem.getHtmlLink().getHref()+'">' + feedItem.getTitle().getText() + '</a></li>';
        }
        
        boxHTML = boxHTML + '</ul>';
        
        var placeHolder = document.getElementById("haceUnAnio");
        placeHolder.innerHTML = boxHTML;
        placeHolder.style.display='';
    }
                    
    function getPostsDeHaceUnAnio()
    {
        var minDate = new Date();
        minDate.setFullYear(minDate.getFullYear()-1);        
        minDate.setHours(0,0,0);
        
        var maxDate = new Date();
        maxDate.setFullYear(maxDate.getFullYear()-1);
        maxDate.setHours(23,59,59);
           
        var feedUrl = 'http://www.blogger.com/feeds/'+_blogID+'/posts/default?published-min='+date2RFC3339(minDate)+'&published-max='+date2RFC3339(maxDate);
        var bloggerService = new google.gdata.blogger.BloggerService('ACP-OneYearAgo-v100');                    
        bloggerService.getBlogPostFeed(feedUrl, handleGetPostsDeHaceUnAnio, handleErrorPostsDeHaceUnAnio);     
    }
    
    google.setOnLoadCallback(getPostsDeHaceUnAnio);
    	-->
        </script>
  8. Verán que al principio de todo eso decía “var _blogID = 'xxxxxx';”. Bueno, hay que cambiar las “xxx” por el ID de su blog, que es el número que anotaron en el punto 5 (¿no lo hicieron?, pues retroceden tres casilleros).
  9. Bueno, eso es lo básico.

¿Qué hace el código? Busca los posts y crea una lista (utilizando los elementos UL y LI) de los links correspondientes en el elemento div que está al inicio del código. Pueden agregarse algo de estilo con CSS a gusto de cada quien.

Algunas aclaraciones. Definitivamente no es el método más eficiente para hacerlo, tiene por lo menos tres contras:

La primera es que necesita la librería cliente de Javascript de Google (ese código que pusieron luego del <head>) y eso es… pesado en relación al uso que se le da. Pero bueno, la librería está bastante optimizada, y si no notan que la carga se hace pesada es soportable.

La segunda es que por más que el código muestre sólo el título y el link a los posts correspondientes, por detrás está pidiendo al servidor los posts completos. Esto es, hasta donde yo sé, inevitable (se escuchan sugerencias). Si la cuestión es que en aquel inspiradísimo fin de semana de 2008 escribimos 5 posts larguísimos… bueno, eso se va a notar.

Y la tercera es que está escasamente probado. Por lo menos en mi caso funciona bien con Chrome, Firefox y IE 8. Tomen sus recaudos y prueben por su cuenta. Cualquier problema comenten y vemos de ir actualizando.

Actualización: arreglado un pequeño error en la línea 34 (al generar el html para mostrar los posts no iniciaba la lista con el tag <ul>, quedando huérfano el </ul> que está más abajo, en la 42.

viernes, 12 de junio de 2009

NUTS-QL (Negate/Unite/Test Standard Query Language) [DRAFT].

cucú


Nota para lectores del feed: el código SQL es más legible si lo ven formateado en la entrada original.


¿Qué es NUTS-QL? Para hacer honor a su nombre, comencemos por la negativa: NUTS-QL no es un dialecto SQL. NUTS-QL es una técnica de creación y mantenimiento de consultas SQL, un conjunto de prácticas y principios, y por lo tanto puede aplicarse en conjunción con cualquiera de los dialectos de SQL conocidos.

¿Cuáles son sus orígenes? NUTS-QL es una etiqueta bajo la cual se agrupa un conjunto coherente de técnicas que -por separado- son conocidas y utilizadas por innumerables desarrolladores a lo largo y ancho del globo. Esta compilación y su organización es obra, humildemente, de mi autoría.

¿Cuáles son sus ventajas? La principal ventaja de NUTS-QL es que va más allá de asegurar un código SQL legible: hace que la legibilidad sea irrelevante.

Vamos directamente a un ejemplo. El requerimiento será desarrollar el “Reporte de movimientos diarios”.

El primer paso es escribir lo primero que se nos venga a la cabeza, probar que compile y mandarlo a pruebas:

SELECT C.ID, C.DESCRIPCION, D.DEBE, D.HABER
FROM MOVIMIENTOS_CABECERA AS C 
INNER JOIN MOVIMIENTOS_DETALLE AS D ON C.ID=D.ID
WHERE C.FECHA = @FECHA
ORDER BY C.FECHA

¡Y eso es todo! No hace falta documentar nada. En el improbable caso de que luego de un par de días, semanas o meses, a alguien le toque modificar la consulta, aplicará los principios de NUTS-QL.

Digamos que ahora se necesita que “en los movimientos de emisión de cheques (MOVIMIENTOS_CABECERA.TIPO=1) se presente el número de cheque emitido”.

Lo primero que tenemos que hacer es implementar el requerimiento con una consulta aparte que satisfaga sólo el caso mencionado. Si bien puede utilizarse la consulta anterior como base, usualmente es más fácil no preocuparse por lo que ya está hecho, que probablemente no sirva para nada. Otra vez escribimos lo primero que se nos viene a la cabeza:

SELECT C.ID, C.DESCRIPCION, CHQ.NUMERO, D.DEBE, D.HABER
FROM MOVIMIENTOS_CABECERA AS C 
INNER JOIN MOVIMIENTOS_DETALLE AS D ON C.ID=D.ID
INNER JOIN CHEQUES AS CHQ ON CHQ.MOVIMIENTO_EMISION_ID = C.ID
WHERE C.FECHA = @FECHA AND C.TIPO = 1
ORDER BY C.FECHA

Ahora hay que integrar las dos consultas. Son tres fases. La primera es la de negación (Negative), en la que excluimos de las cláusulas anteriores los registros que incorporamos en la nueva. Esto es fácil porque podemos tomar el WHERE de la nueva y agregarlo en la primera precedido por los operadores AND NOT (ver que la línea 5 del ejemplo siguiente es la negación de la línea 5 del ejemplo anterior). La primera consulta nos queda:

SELECT C.ID, C.DESCRIPCION, D.DEBE, D.HABER
FROM MOVIMIENTOS_CABECERA AS C 
INNER JOIN MOVIMIENTOS_DETALLE AS D ON C.ID=D.ID
WHERE C.FECHA = @FECHA
	AND NOT (C.FECHA = @FECHA AND C.TIPO = 1)
ORDER BY C.FECHA

La segunda es la de unión (Unite), en donde unimos las dos consultas con la cláusula UNION:

SELECT C.ID, C.DESCRIPCION, D.DEBE, D.HABER
FROM MOVIMIENTOS_CABECERA AS C 
INNER JOIN MOVIMIENTOS_DETALLE AS D ON C.ID=D.ID
WHERE C.FECHA = @FECHA
	AND NOT (C.FECHA = @FECHA AND C.TIPO = 1)
ORDER BY C.FECHA

UNION

SELECT C.ID, C.DESCRIPCION, CHQ.NUMERO, D.DEBE, D.HABER
FROM MOVIMIENTOS_CABECERA AS C 
INNER JOIN MOVIMIENTOS_DETALLE AS D ON C.ID=D.ID
INNER JOIN CHEQUES AS CHQ ON CHQ.MOVIMIENTO_EMISION_ID = C.ID
WHERE C.FECHA = @FECHA AND C.TIPO = 1
ORDER BY C.FECHA

La tercera es la de prueba (Test), que consiste en ejecutar y corregir hasta que funcione. En este caso hay dos problemas: el ORDER BY está repetido (va uno sólo al final) y la cantidad de campos en la sección SELECT no coinciden entre las dos partes (rellenamos con nulos donde sea necesario). Necesitaremos al menos dos intentos. El resultado será:

SELECT C.ID, C.DESCRIPCION, NULL AS NUMERO, D.DEBE, D.HABER
FROM MOVIMIENTOS_CABECERA AS C 
INNER JOIN MOVIMIENTOS_DETALLE AS D ON C.ID=D.ID
WHERE C.FECHA = @FECHA
	AND NOT (C.FECHA = @FECHA AND C.TIPO = 1)

UNION

SELECT C.ID, C.DESCRIPCION, CHQ.NUMERO, D.DEBE, D.HABER
FROM MOVIMIENTOS_CABECERA AS C 
INNER JOIN MOVIMIENTOS_DETALLE AS D ON C.ID=D.ID
INNER JOIN CHEQUES AS CHQ 
	ON CHQ.MOVIMIENTO_EMISION_ID = C.ID
WHERE C.FECHA = @FECHA AND C.TIPO = 1

ORDER BY C.FECHA

Es muy, muy improbable que surja una tercera modificación o error, pero agreguemos una a modo de ejemplo. Supongamos que nos enteramos de que “los importes en moneda extranjera (registros en los que MOVIMIENTOS_DETALLE.IDMONEDA <> NULL) no se están convirtiendo a la moneda corriente de acuerdo al tipo de cambio indicado en MOVIMIENTOS_DETALLE.COTIZACION”.

Apliquemos NUTS-QL. La consulta para los registros en moneda extranjera será:

SELECT C.ID, C.DESCRIPCION, 
	D.DEBE * D.COTIZACION, 
	D.HABER * D.COTIZACION
FROM MOVIMIENTOS_CABECERA AS C 
INNER JOIN MOVIMIENTOS_DETALLE AS D ON C.ID=D.ID
WHERE C.FECHA = @FECHA AND NOT D.IDMONEDA IS NULL
ORDER BY C.FECHA

Ahora, en la etapa de negación, debemos modificar la sección WHERE de las consultas anteriores negando la condición WHERE de la nueva sección (ok, son 2, pero es sólo copiar y pegar).

La primera queda:

WHERE C.FECHA = @FECHA
	AND NOT (C.FECHA = @FECHA AND C.TIPO = 1)
	AND NOT (C.FECHA = @FECHA AND NOT D.IDMONEDA IS NULL)

y la segunda:

WHERE C.FECHA = @FECHA AND C.TIPO = 1
	AND NOT (C.FECHA = @FECHA AND NOT D.IDMONEDA IS NULL)

Luego, en la etapa de unión nos queda:

SELECT C.ID, C.DESCRIPCION, NULL AS NUMERO, D.DEBE, D.HABER
FROM MOVIMIENTOS_CABECERA AS C 
INNER JOIN MOVIMIENTOS_DETALLE AS D ON C.ID=D.ID
WHERE C.FECHA = @FECHA
	AND NOT (C.FECHA = @FECHA AND C.TIPO = 1)
	AND NOT (C.FECHA = @FECHA AND NOT D.IDMONEDA IS NULL)

UNION

SELECT C.ID, C.DESCRIPCION, CHQ.NUMERO, D.DEBE, D.HABER
FROM MOVIMIENTOS_CABECERA AS C 
INNER JOIN MOVIMIENTOS_DETALLE AS D ON C.ID=D.ID
INNER JOIN CHEQUES AS CHQ 
	ON CHQ.MOVIMIENTO_EMISION_ID = C.ID
WHERE C.FECHA = @FECHA AND C.TIPO = 1
	AND NOT (C.FECHA = @FECHA AND NOT D.IDMONEDA IS NULL)
ORDER BY C.FECHA

UNION 

SELECT C.ID, C.DESCRIPCION, 
	D.DEBE * D.COTIZACION, 
	D.HABER * D.COTIZACION
FROM MOVIMIENTOS_CABECERA AS C 
INNER JOIN MOVIMIENTOS_DETALLE AS D ON C.ID=D.ID
WHERE C.FECHA = @FECHA AND NOT D.IDMONEDA IS NULL
ORDER BY C.FECHA

Ya en la etapa de test, nos damos cuenta de que (otra vez) el ORDER BY está repetido y de que tenemos que rellenar el SELECT de la nueva consulta con campos nulos para que coincida con el de las anteriores. El resultado final será:

SELECT C.ID, C.DESCRIPCION, NULL AS NUMERO, D.DEBE, D.HABER
FROM MOVIMIENTOS_CABECERA AS C 
INNER JOIN MOVIMIENTOS_DETALLE AS D ON C.ID=D.ID
WHERE C.FECHA = @FECHA
	AND NOT (C.FECHA = @FECHA AND C.TIPO = 1)
	AND NOT (C.FECHA = @FECHA AND NOT D.IDMONEDA IS NULL)

UNION

SELECT C.ID, C.DESCRIPCION, CHQ.NUMERO, D.DEBE, D.HABER
FROM MOVIMIENTOS_CABECERA AS C 
INNER JOIN MOVIMIENTOS_DETALLE AS D ON C.ID=D.ID
INNER JOIN CHEQUES AS CHQ 
	ON CHQ.MOVIMIENTO_EMISION_ID = C.ID
WHERE C.FECHA = @FECHA AND C.TIPO = 1
	AND NOT (C.FECHA = @FECHA AND NOT D.IDMONEDA IS NULL)

UNION 

SELECT C.ID, C.DESCRIPCION, NULL, 
	D.DEBE * D.COTIZACION, 
	D.HABER * D.COTIZACION
FROM MOVIMIENTOS_CABECERA AS C 
INNER JOIN MOVIMIENTOS_DETALLE AS D ON C.ID=D.ID
WHERE C.FECHA = @FECHA AND NOT D.IDMONEDA IS NULL

ORDER BY C.FECHA

Y listo, nuevamente a pruebas y producción.

Los puristas encontrarán que en aquellos registros que corresponden a cheques nominados en moneda extranjera no se está mostrando el número. En el cuasi-imposible caso de que esto se detecte nos llegará el requerimiento correspondiente. Normalmente deberíamos entender toda la consulta y buscar el error. Gracias a NUTS-QL simplemente hacemos lo de siempre.

Para abreviar les dejo sólo el resultado final de aplicar la técnica a este último caso (es un buen ejercicio que el lector lo haga por su cuenta y compare los resultados):

SELECT C.ID, C.DESCRIPCION, NULL AS NUMERO, D.DEBE, D.HABER
FROM MOVIMIENTOS_CABECERA AS C 
INNER JOIN MOVIMIENTOS_DETALLE AS D ON C.ID=D.ID
WHERE C.FECHA = @FECHA
	AND NOT (C.FECHA = @FECHA AND C.TIPO = 1)
	AND NOT (C.FECHA = @FECHA AND NOT D.IDMONEDA IS NULL)
	AND NOT (C.FECHA = @FECHA AND C.TIPO = 1 
		AND NOT D.IDMONEDA IS NULL)

UNION

SELECT C.ID, C.DESCRIPCION, CHQ.NUMERO, D.DEBE, D.HABER
FROM MOVIMIENTOS_CABECERA AS C 
INNER JOIN MOVIMIENTOS_DETALLE AS D ON C.ID=D.ID
INNER JOIN CHEQUES AS CHQ 
	ON CHQ.MOVIMIENTO_EMISION_ID = C.ID
WHERE C.FECHA = @FECHA AND C.TIPO = 1
	AND NOT (C.FECHA = @FECHA AND NOT D.IDMONEDA IS NULL)
	AND NOT (C.FECHA = @FECHA AND C.TIPO = 1 
		AND NOT D.IDMONEDA IS NULL)

UNION 

SELECT C.ID, C.DESCRIPCION, NULL, 
	D.DEBE * D.COTIZACION, 
	D.HABER * D.COTIZACION
FROM MOVIMIENTOS_CABECERA AS C 
INNER JOIN MOVIMIENTOS_DETALLE AS D ON C.ID=D.ID
WHERE C.FECHA = @FECHA AND NOT D.IDMONEDA IS NULL
	AND NOT (C.FECHA = @FECHA AND C.TIPO = 1 
		AND NOT D.IDMONEDA IS NULL)

UNION

SELECT C.ID, C.DESCRIPCION, CHQ.NUMERO, 
	D.DEBE * D.COTIZACION, 
	D.HABER * D.COTIZACION
FROM MOVIMIENTOS_CABECERA AS C 
INNER JOIN MOVIMIENTOS_DETALLE AS D ON C.ID=D.ID
INNER JOIN CHEQUES AS CHQ 
	ON CHQ.MOVIMIENTO_EMISION_ID = C.ID
WHERE C.FECHA = @FECHA AND C.TIPO = 1 
	AND NOT D.IDMONEDA IS NULL

ORDER BY C.FECHA

Comentarios: se le achaca a este método producir sentencias cada vez más ilegibles e ineficientes. Pero hemos visto que no es necesario leerlas, por lo que el primer punto es irrelevante. En cuanto al segundo… bueno, eso es un problema de hardware ¿no?

Lo mejor de todo es que lograr la adopción de NUTS-QL es fácil: una vez que un desarrollador comienza los demás están obligados a seguirlo, basta con respetar el punto de no documentación de requerimientos (si éstos estuviesen documentados, un programador inexperto podría estar tentado a reescribir toda la sentencia). Así, es un camino de ida.


Nota para lectores del feed: el código SQL es más legible si lo ven formateado en la entrada original.

Jueguitos de Viernes: BowMaster Prelude.

BowMaster prelude es un clásico de apuntar-y-tirar con toques de RPG y estrategia. Nuestra misión es defender el castillo ante el avance de las tropas enemigas. A medida que progresamos en el juego aparecen armas y rivales cada vez más poderosos.

bowmaster

jueves, 11 de junio de 2009

Crisis, ajuste, motivación, productividad, oportunidad.

Corren tiempos de crisis. Traducido al argentino esto implica, aunque sea implícitamente (vía inflación, que aquí el que no corre vuela y el margen de beneficio a corto plazo se valora incluso más que la supervivencia), “ajuste” de salarios. Escribo “ajuste” y no ajuste porque con el entrecomillado pretendo denotar que “ajuste” no es ajuste sino un eufemismo para baja que suena feo. Y es baja y no “congelamiento” o “suspensión de aumentos” debido al pequeño detalle de la inflación mencionado más arriba.

Pero bueno, es lo que marca el mercado y si el golpe de la crisis se distribuye justa o injustamente entre capital y trabajo (o la suposición de que justicia y economía están relacionadas de alguna manera) no es el tema de este post.

Por otro lado, en la industria del del software (y supongo que en cualquier otra cuya base esté mayormente conformada por trabajadores del conocimiento) motivación y productividad están fuertemente relacionadas, si es que no son la misma cosa (¿podría decir que está motivado un programador que no produce? ¿por qué otra razón que no sea falta de motivación podría no producir un programador? Es decir, el que está motivado llega a un resultado, que puede no tener un gran nivel o calidad, pero producir produce).

Mi hipótesis es que si el mencionado “ajuste” afecta a la productividad en esta industria es -por lo menos principalmente- a través de esta forma indirecta, por su impacto en la motivación.

Es un buen momento para recordar aquello de que el motivador más caro es el dinero (creo que es una frase célebre pero no puedo encontrarla, tal vez esté formulada de otra manera). Es por ello que me cuidé de escribir que el ajuste “afecta” a la productividad y no que la disminuye o aumenta, ya que la productividad depende de la motivación y la motivación no necesariamente del dinero.

Me parece una explicación bastante sencilla de por qué hay emprendimientos o proyectos en los que la productividad aumenta considerablemente en tiempos de crisis: hay ajustes monetarios pero la motivación, y por tanto la producción (el desarrollo) se mantiene (o incluso aumenta) y la productividad (la relación entre recursos utilizados y avance obtenido) mejora.

Y también de por qué hay otros proyectos que florecen velozmente una vez pasada la tormenta: si el aumento de productividad es en la práctica una mejora en el proceso de desarrollo el proyecto actúa tal cual un resorte que absorbe el impacto primero e impulsa al equipo, empresa, organización o producto cuando la crisis pasa.

Y de por qué otros proyectos, incluso sobreviviendo a la crisis, no logran volver a ser lo que eran. Hay un punto en el que la motivación cae de tal manera que luego (parafraseando a un gran amigo) “a esto no se lo levanta ni con plata”.

Ejemplo un poco burdo: imaginemos tres equipos enfrentados a la misma crisis. En la práctica (aparte del “ajuste”), lo que sucede es que faltan horas/hombre de analistas funcionales para las pruebas, que el recurso de las horas extra está agotado (es decir, los actuales analistas están agotados) y que no hay posibilidades de contratar más.

  • Equipo I: no hay respuesta. El equipo hace lo que siempre hizo: programar y punto. El proyecto se atrasa, el producto no mejora, se codifica pero no se prueba y las modificaciones no salen, etcétera. Para cuando pasa la crisis hay un retraso importante respecto de la tecnología y de los competidores que han sabido aprovechar o por lo menos capear el temporal.

  • Equipo II: el equipo pone el hombro y se pone a probar. No son buenos probando pero hacen lo posible y la productividad sufre pero las cosas salen. Si pasa la crisis se vuelve al ritmo normal, y tal vez con más recursos se logra recuperar el tiempo perdido, aunque con gran esfuerzo extra.

  • Equipo III: no se resignan a producir menos ni a trabajar más, por lo que buscan la forma de trabajar mejor. Así que buscan la forma no de cubrir las horas faltantes sino de requerir menos horas de pruebas. Descubren (imaginemos que no las conocían) las bondades de la programación orientada al test, herramientas para automatizar tests unitarios y funcionales, reducen costos con máquinas virtuales, programan simuladores de componentes y un largo etcétera. Para cuando la crisis pasa están mejor parados que al principio y cuando se recuperan los recursos éstos se utilizan para hacer más que antes logrando una gran expansión sobre un mercado depurado de competidores.

¿Cuál habrá sido el nivel de motivación en cada equipo antes de afrontar la crisis? ¿Y al final?

miércoles, 10 de junio de 2009

El noble arte del baseball.

Que consiste en

“batear las pelotas tan lejos como sea posible y dar vueltas al tema para hacer puntos”,

inspiradísima frase de “Trabajar por proyectos o la subversión organizativa”, el post de hoy de Sueños de la razón, que les recomiendo.

Twitteado por @Yoriento. BTW: ya sé, esto ya casi es microblogging, qué vergüenza.