Mostrando las entradas con la etiqueta eficacia. Mostrar todas las entradas
Mostrando las entradas con la etiqueta eficacia. Mostrar todas las entradas

sábado, 30 de agosto de 2014

Mi aplicación

Casi todos (los programadores, obvio) tenemos “mi aplicación”. Ésa que, cuando haya tiempo, vamos a hacer “como se debe”.

El “como se debe” varía con cada uno, pero me arriesgo a generalizar: no es una afirmación positiva sobre ciertas herramientas y prácticas, es más bien una negación de aquellas que venimos utilizando, utilizamos o nos obligaron a utilizar, o de las que escuchamos hablar mal por ahí o… un largo etcétera.

En criollo: “mi aplicación” no va a ser como éste proyecto-huérfano-engendro-mutante del que me contaron, caí o armé (“por culpa de…”, seguro).

¿Cómo es “mi aplicación”? La mía de mi propiedad, al día de hoy, es:

-Una aplicación web hosteada en google.

-Server side: en java, pero va a devolver exclusivamente JSON. Pura data. Lo juro.

-Con alguna base de datos no relacional, porque son re-cool.

-Bootstrap.

-Pero no pienso escribir una sola regla de css. Lo juro.

-Ni tampoco mucha imagen ni iconito. Si puedo quedarme sólo con lo de bootstrap, mejor.

-Ni meterle 10.000 plugins (aunque ya empecé… pero lo voy a deshacer, y cuando lo deshaga lo juro).

-Jquery

-Jquery validation, si hace falta (imagino que sí).

-Knockout.

-Require.

-Y visjs, porque necesito algo así.

-Y NADA más.

El problema es… el de más arriba: que es cuasi-simétricamente-opuesto a todo lo que alguna vez hice: no soy diseñador ni mucho menos (estoy aprendiendo que entre “seguir la onda”, y “diseñar” hay un abismo insondable). Nunca programé en java (profesionalmente, proyectitos de prueba hicimos todos). Nunca usé bootrstrap. De íconos y gráfica o imágenes… sólo sé buscar en google, y al photoshop no me lo presentaron (ni siquiera sé usar bien el paint.Net). Visjs es genial, pero todavía estoy leyendo la documentación. Los únicos viejos conocidos son Knockout, require, jquery y compañía. Pero son herramientas, no una forma de trabajar con la que uno viene acostumbrado, y en la que “el camino para hacer tal cosa” aparece casi naturalmente.

Como todo primer intento, es… bueno, como se imaginan que es. Pero hay que seguir haciendo y rehaciendo hasta que salga. Mentalmente, claro, porque en la real realidad, uno va dedicándole horas a cuentagotas entre proyectos más rentables, con la esperanza de que en algún momento éste también lo sea.

Si bien “construir éste sistema lo antes posible” es la madre de todas las cag…, también es la madre de todas las cosas que están ahí afuera en este preciso momento, satisfaciendo necesidades de éste preciso momento, probablemente tan efímeras como la combinación de herramientas que (ahora) “me gusta”.

“Como a mí me gusta” va variando a medida que avanzamos. A veces más, a veces menos, pero siempre, con cada paso hacia adelante, surge el imperioso impulso de pasarle la guadaña a (casi) todo lo que se deja atrás… y uno va y lo hace. Porque ésta es “como a mí me gusta”. Es inevitable: la motivación parte de “armar un proyecto como a mí me gusta”, y no “construir éste sistema lo antes posible”.

Pero bueno, juntar dos o veinte librerías, copypastear un poco de código y salir con algo rápido a ver si pega es, probablemente, eficaz. O por lo menos un fracaso rápido y a otra cosa… pero no es lo que tengo ganas de hacer ahora.

Tengo ganas de seguir jugando.

Y a todo esto… ¿qué hace, exactamente, “mi aplicación”?

Por ahora, nada.

¿Qué debería hacer?

No está muy claro, ya veremos. Lo importante es que “esté como a mí me gusta”.

¿Verá la luz del sol?

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.

miércoles, 27 de mayo de 2009

Calidad y consultoras, software factories y demás.

Hoy leí por un lado Welcome to the machine en Oh My Blog! I Can’t Believe It!, que nos relata la experiencia del desarrollo de software desde las trincheras de los grandes ejércitos:

[…] Pongamos que trabajas en un proyecto en el que participa un consorcio de diecisiete empresas, y estás subcontratado por parte de una de esas empresas […]

[…] Y entonces el alucinómetro estalla cuando te dicen que no hay diseño ni especificaciones hechas. Que tu vayas tirando código, que ya ellos haran ingeniería inversa para sacar la especificación y diagramas de clases y demás en base a lo que tu hagas […]

Más tarde me crucé con Burro (o consultor) grande, ande o no en Diario de un director de sistemas:

[…] Como otras veces, el debate sobre el precio y el valor de las consultoras grandes. […] Yo estuve siete u ocho años en una de esas consultoras. […] No cabe duda de que en el coste hora de un consultor de estas compañías estás pagando una parte de la oficina de Picasso o La Moraleja, la esponsorización de una estrella del golf o un equipo de Fórmula 1, y los salarios "indecentes" (es una forma de hablar, yo no los considero así) de los socios o partner o principals, según como se llamen en cada casa. Todo esto es verdad. No creo que nadie lo discuta.

[…] tampoco ofrece dudas que un documento bien preparado por gente de estas compañías suele significar un cuidado en la presentación y en el contenido bastante mayor del que te presentan otras empresas más pequeñas. Y lo mismo que hablamos del documento podemos aplicarlo al desarrollo y ejecución de los proyectos, siempre y cuando estemos hablando de proyectos de cierta magnitud/dificultad […]

¿Qué imagen es más cercana a la realidad? ¿La de la consultora de renombre y proyectos desastrosos que de alguna manera terminan funcionando o la de la experta en la ejecución de proyectos a gran escala bajo estrictos estándares de calidad? Tal vez no haya una única respuesta, sino dos caras de la misma moneda.

Hablando de imágenes, de percepciones, es deber aclarar primero fuentes y preconceptos. Mi visión es la del programador, del desarrollador que conoce un poco tras bambalinas e intuye otro tanto. Aunque nunca he trabajado en una gran consultora o en una empresa con un área de desarrollo gigantesca como las que se mencionan, conozco y he compartido proyectos con programadores y analistas que sí y con los que he comentado muchas veces los contrapuntos entre éstas y los equipos pequeños o medianos.

A partir de esa muestra de experiencias (y juzgando contra mis propios estándares), yo me formé la idea de que:

  • La irracionalidad de la metodología empleada (no la enunciada, sino la de todos los días), ya sea etiquetada como ágil o tradicional, llega a extremos insospechados transformándose en un conjunto de rituales vacíos.

  • La conveniencia de las herramientas y lenguajes que se utilizan con respecto a los requerimientos de cada proyecto es -por lo menos- cuestionable en la mayoría de los casos. Se utiliza un martillo y todos los proyectos son clavos. Si el cliente pide tornillos se le entregan clavos en cajas de tornillos.

  • La robustez de la arquitectura de los proyectos es similar a la del queso gruyere. De todas maneras su utilización (es decir, cuánto se respeta en la práctica esa estructura enunciada) es prácticamente nula.

  • La calidad del código suele ubicarse entre mediocre y WTF.

  • Los documentos de análisis internos (no los bonitos que se presentan a los clientes sino los que se terminan utilizando, si es que existen) no sirven más que de punto de partida para que los desarrolladores hagan volar su imaginación.

  • Y por supuesto, las modificaciones y el mantenimiento son un infierno para ellos…

  • que de todas maneras es raro que tengan que soportar por más que un par de años (la rotación es vertiginosa).

Ya sé, ya sé, hay proyectos y proyectos. La misma firma de pobre desempeño en uno desenvolverse bien en otro, y mucho tendrá que ver con ello el control y la seriedad del otro lado del mostrador. Pero sigamos.

El cliente de “Pelota Plus Consulting” no es “Megaempresa S.A” que necesita un ERP. El cliente es el gerente-de-lo-que-sea de “Megaempresa” que contrata a “Pelota Plus” sabiendo que toma una decisión irreprochable más allá del resultado del proyecto, y eso es lo que compra: cierta (mas no absoluta) seguridad para sí más allá de los resultados. ¿Para qué arriesgarse? “Megaempresa” tiene mucho dinero para gastar y el precio está consolidado por el mercado.

La atención que brinda la consultora a su cliente (el gerente-de-lo-que-sea) es inigualable: un contrato importante con muchos ceros a la derecha, presentaciones, carpetas, folletos, aulas para capacitación, equipos, gente de traje con una tarjetita a la altura de la solapa y toda la parafernalia necesaria para hacer de él alguien importante dentro de “Megaempresa”.

Todos los puntos oscuros que mencioné al principio son invisibles tanto para el cliente como para “Megaempresa”. Conforman una deuda técnica que la consultora adquiere (resultado inevitable de la industrialización del desarrollo) pero que luego transfiere a “Megaempresa” al facturarle capital e intereses (más su propio margen de beneficio) en forma de horas de mantenimiento. El cliente no sabe -ni le interesa- por qué se tardan 8 horas en correr un botón de un formulario.

¿Que se podría hacer mejor? No tiene que ser perfecto, sólo funcionar. ¿Que podría ser más barato? La pequeña consultora o equipo de desarrollo implica más riesgo y el cliente ya está asumiendo mucho… si la cosa se complica él puede terminar viendo el partido desde la tribuna.

Por lo menos así lo veo yo.
nimo

martes, 24 de febrero de 2009

Siguiendo el rastro del WTF.

perro Una tarea que se perfila como rutinaria puede transformarse inesperadamente en análisis forense funcional.

La asignación -más que rutinaria, aburrida-, era migrar un reporte del sistema viejo al nuevo. Usualmente se resuelve con un par de adaptaciones y listo, pero…

Mi primer descubrimiento en la tarea comenzó consistió en el procedimiento almacenado más enrevesado que vi en mi vida: una sola instrucción SELECT de más de 300 líneas, con tres niveles de subconsultas y un par de UNION’s en cada uno de ellos. Un bonito WTF.

Por supuesto que una maraña de código de ese calibre no es puntual ni tiene un sólo padre. Es el producto más paradigmático (entre muchos otros) de una larga cadena de producción de basura útil. Una cadena de errores que puede recorrerse hacia atrás desde la codificación hasta la gestión del proyecto.

La codificación, ilegible, traicionera y escurridiza, es el producto de “construir un rascacielos con ladrillos”. Es decir, se pretende resolver  todo el problema con una sola herramienta (¿qué necesidad había de hacer todo en una sola consulta?) en vez de separarlo en partes y aplicar a cada una la más adecuada.

No tiene sentido buscar un culpable, obviamente no lo hay. Si bien el código nació medio enrevesado fueron las sucesivas modificaciones las que lo volvieron caótico. Un buen ejemplo de la parábola de la rana hervida: pequeñas modificaciones, todas ellas muy inocentes tomadas en forma individual, generan un bonito quilombo desastre.

Lo que nos lleva a la metodología. Una refactorización a tiempo hubiese cortado este problema de raíz. Es claro, por la estructura de la consulta (bloques SELECT conectados por UNION’s que agregan casos no contemplados, subconsultas que vinieron a reemplazar referencias a tablas, cálculos repetidos que producen pequeños ajustes sobre el  resultado sin alterar la estructura, reutilización de campos para agregar datos “sin tocar nada”, etc.), que su creador sabía mucho más al final del proceso que al principio. La refactorización no sólo es imprescindible para mantener el código legible a través de las sucesivas modificaciones, es la actividad con la que se solidifica el aprendizaje, el momento en que lo aprendido se transforma en código. Aquel primer autor partió tiempo atrás y todo el conocimiento acumulado, por lo menos en lo relativo a esta experiencia en particular, se ha perdido irremediablemente (snif).

Esto es triste, sobre todo combinado con el hecho de que luego de un tiempo este código se ha convertido en la única documentación existente sobre la funcionalidad.

Lo que nos lleva al segundo error metodológico: ¿cómo transmitió el sector de análisis las especificaciones del reporte? No lo sabemos. El sólo hecho de que no lo sepamos hace de esa transmisión un acto fallido. ¿Fue verbalmente? ¿Se elaboró un documento? En todo caso, la falta de un soporte o repositorio confiable hizo humo (o leyenda) ese trabajo de análisis.

El que crea que el conocimiento puede preservarse “solo” en la cabeza de los integrantes del equipo puede acompañarme en la experiencia de consultar a las personas directa o indirectamente involucradas con una funcionalidad en particular. Lo que se obtiene son varios pedazos -ligeramente diferentes y que no encajan del todo- de la misma fotografía. Los detalles… “mirá el reporte en el sistema viejo”, con lo que volvemos a nuestro querido e ilegible procedimiento almacenado.

Entonces el problema es no de una o unas personas sino de todo el equipo. Nadie trabajó aislado. El analista hizo su trabajo, el programador hizo su trabajo. ¿Entonces? Lo que ha fallado es la interacción entreambos. La falta de visión a futuro (o la indiferencia con respecto a él), o un exceso de individualismo (“sólo hago mi trabajo”).

Los problemas de equipo son, por definición, de liderazgo. Cuando las partes no coordinan bien entre sí es necesaria la intervención de un elemento superior que modifique esa interacción: por caso, el líder. Por complicidad, desconocimiento o falta de soluciones (es irrelevante) eso no ha ocurrido.

Esta basura útil, este “cowboy code” incrustado en medio de un “pure chaos environment” funciona. Y bastante bien, por cierto. Entonces, en la mirada miope o distraída de líder, el equipo debe haber superado con creces el desafío, que quedó cerrado y archivado definitivamente.

El siguiente eslabón de la cadena es organizacional. La vara (ya sea para medir o aleccionar) más común en las organizaciones es el costo/beneficio. El costo de no haber documentado el análisis y refactorizado el código ha pasado desapercibido, lo que ha devenido en una medición inflada del resultado final, generando un pernicioso condicionamiento positivo respecto del apuro y la desprolijidad. Tal vez una medición más precisa del costo a futuro de esas desprolijidades hubiese motivado una corrección más temprana.

Esta última observación excede el ámbito de la gestión de un proyecto. Está más relacionada con la estructura de costo de la organización, del sistema que utilice para medirlo y de cómo se utilizan esas mediciones. El costo de este retrabajo, originado en aquél otro proyecto que salió “tan bien” y fue “tan rentable” pesará sobre este nuevo proyecto.

En fin, hemos comenzado hablando de procedimientos almacenados y prolijidad del código para terminar en cálculo y gestión de costos a nivel organizacional. Hemos recorrido, a través de un divertido ejemplo la cadena que une el error con la pérdida (en el mejor de los casos) o el fallo con la catástrofe (en el peor).

lunes, 5 de enero de 2009

Búsqueda de errores y depuración: una historia, un poco de metodología y algunos consejos.

Último día laboral de este 2008. 7.20 de la mañana (¡sí señor!), ya despabilado y mate en mano frente a la pantalla. Mientras inicia Windows, el entorno de desarrollo y el proyecto (y un largo etc.) leo un auto-recordatorio de baja tecnología (un papel suelto sobre el teclado) que dice "ver incidente 1269". Me acuerdo del protagonista de Memento quien, como yo, tiene el Garbage Collector demasiado activo: no recuerdo de qué se trata el maldito 1269.

Resumamos esquemática y velozmente algo que en realidad es un poco más complejo, pero no viene al caso: vendo una combinación de "fichas" de diferentes valores preestablecidos, eligiendo el usuario la combinación deseada. La pantalla pide primero el importe, luego ofrece la lista con las distintas fichas disponibles donde el usuario indicará la cantidad requerida de cada una.

Hay un tipo de ficha que es el más vendido. Por lo tanto cuando el usuario ingresa el importe y lo confirma se presenta completado por defecto el cuadro correspondiente a este tipo con el importe traducido en cantidad.

Como el usuario puede cambiar los valores, al final se comprueba que la entrada (el importe) sea igual a la salida (el valor de las fichas) y si está todo ok, se graba y listo.

Ya tenemos la primera máxima:

Ver el cuadro completo antes de bajar a los detalles: luego de una leída rápida al reporte del incidente hay que abocarse en obtener "the big picture": la mirada del cliente (humano o no), determinar el papel de esta funcionalidad dentro del sistema y, sobre todo: saber cómo debería funcionar.

Una nota: el caso en particular puede variar, pero en general un reporte de error o incidente sólo describe una situación, que no debería aceptarse como un error sin más análisis. Por ello el siguiente paso es reproducir la situación y clasificarla (por ejemplo: "error del sistema", o "error del entorno de ejecución", "nueva funcionalidad", o "error en el reporte de error" -valga la redundancia- o etiquetas similares).

Un poco de manoseo con la pantalla en cuestión no dejó lugar a dudas: si yo ingresaba un importe imposible de vender en fichas (digamos $10,31 y yo tengo fichas de $1 cada una) el sistema redondeaba la cantidad ofrecida por defecto (en mi ejemplo ofrecería vender 10 u 11 fichas). Al confirmar la transacción se leía un bonito mensaje "la transacción no suma 0" (una excepción de negocio medio genérica que se aplica a un sinfín de situaciones). Hasta ahí iba bastante bien aunque pensé que podría mejorar el mensaje. Pero cuando retrocedí y corregí el importe y volví a avanzar... ¡crash bum pang! Un error no controlado rompía la mitad de la pantalla. Ok. Hay un error, no cabe duda.

Esto da para un poco de teoría: el primer mensaje fue una excepción (controlada), el segundo un error. Escribí un poco más sobre eso hace poco: Errores y excepciones parte I, parte II y parte III. Pero citemos así seguimos adelante:

[...] Entiendo que una excepción es una situación inusual en la ejecución del sistema, pero contemplada al momento de diseñar el sistema o de escribir el código. [...] Un error, a diferencia de la excepción, es una respuesta o estado no contemplado en el sistema. [...]

Es decir, que el dinero ingresado no coincidiera con el valor de las fichas que salían era algo contemplado, se estaba chequeando (aunque me molestara el mensaje). Pero el posterior aterrizaje poco sutil de la aplicación, digamos, fue resultado de algo no contemplado por nadie (espero).

Así que me puse a leer ese feo mensaje de error. Completo, con stack trace y todo. Aunque ud. no lo crea, esto es algo que poca gente hace. Y así es como nos llenan de reportes de error del tipo "La conexión a la base de datos se ha caído." o "Disk Full". Es decir, situaciones que están claramente descritas en ese mensaje... que nadie lee.

Pero no son sólo los usuarios. Cierta ansiedad profesional nos lleva a enchufar el depurador, reproducir la situación, concentrarnos en la línea que tira el error y empezar desde ahí (tratando desesperadamente de no ir a ningún otro lado, como si todos los errores estuviesen en la última línea que se ejecutó del código). Mucha de esa información está ahí, delante nuestro.

Un dato importante obtenido al leer el mensaje que explotó la aplicación fue, justamente, "El tipo de ficha ya se encuentra incluido en la transacción". Es decir, era un mensaje codificado por nosotros, no un error descontrolado. En algún lado del código la situación fue tratada como una excepción (es decir contemplada), pero la aplicación explotó igual.

El siguiente punto de análisis es ¿qué debería haber pasado? Para corregir un error primero tenemos que saber cómo nos hubiese gustado que termine la historia. Como todo en sistemas, se empieza por la salida. ¿A dónde quiero llegar?

Pensé un poco en la situación, ordenando los problemas cronológicamente:

  • En principio, me debería haber informado de que no se podía vender por ese importe cuando lo confirmé y no dejarme seguir hasta el final de la operación. Los errores de entrada se informan lo antes posible.

  • Después, al confirmar toda la operación, el mensaje podría haber sido algo así como "No puede conformar el importe indicado con las fichas disponibles" en vez de eso tan extraño de "La transacción no suma 0".

  • Para seguir, al ir hacia atrás y volver a intentar se ve que el sistema intentó ingresar a la operación el mismo tipo de ficha dos veces, o aún más probable, que no haya borrado lo que estaba antes de reintentar.

    Eso se deduce del segundo mensaje, "El tipo de ficha ya se encuentra incluido en la transacción". No puedo decir que vendo 5 fichas de $1 por un lado y otras 5 de $1 por el otro en la misma operación, tiene que detallarse una y sólo una vez cada tipo de ficha: son 10 fichas de $1.

    Una nota importante: mal por el front-end, que primero se olvidó de borrar y después no contempló la posibilidad de una excepción. Bien por el negocio que controló la situación salvando las papas del fuego al cancelar la operación y arrojar una excepción con un mensaje decente. Es como si hubiese gritado dramáticamente "¡no puedo hacer eso, soy demasiado bueno, prefiero morir!"). ¿O hubiesen preferido quedar mejor con el cliente, que no pase nada, ni reportes, ni llamadas desesperadas, ni nada y encontrarse a fin de mes con 10.000 ventas que no cierran?.

  • Pero ese último mensaje debería haberse mostrado prolijamente como el primero, sin embargo en la pantalla apareció como un error no controlado.

    Otra nota: si luego de este bodoque siguen con ganas de leer los artículos citados, verán que esta situación indica que una excepción lanzada en la capa de negocio hizo "explotar" a la capa de presentación porque debido a un error ella no se lo esperaba. El problema no es el mensaje, sino que la capa de presentación no lo esperara y mostrara correctamente.

Me gustaría que los líderes de proyecto que lean esto lo tengan en mente cuando en su próximo ataque de ansiedad (ya sé que uds. ponen la cara) pretendan que arreglemos "esa bobada de los centavos" en 10 o 15 minutos o renieguen de que "el sistema explotó por una boludez tontería (pero nosotros tenemos que aguantarlos, y no tenemos a nadie más para descargar tensiones... lo que me recuerda "Reacciones ante los errores en un sistema informático").

Como podemos ver, que el sistema explote por una bobada es de por sí un error grave que trasciende esa bobada.

Ante este tipo de situaciones tenemos dos alternativas: resolver la bobada, y luego la otra y la otra y la otra (porque bobadas hay muchas) o resolver el problema de fondo logrando que el sistema resista las bobadas dejándonos más tranquilos y con más tiempo para trabajar en lo que nos gusta.

Para ser más explícito y utilizar el ejemplo: hay una larga cadena de errores, pero el último es el peor. Si ese mensaje se hubiese mostrado correctamente en vez de hacer explotar la pantalla,

  • el usuario se habría dado cuenta de que con reiniciar la operación bastaba para seguir trabajando,
  • lo hubiese percibido como una desprolijidad menor y no como un error de la aplicación,
  • y así la prioridad de todo el asunto baja bastante, ¿no?

Por supuesto, es más fácil arreglar el problema puntual de los centavos que solucionar todo eso.

Pero por suerte no le explotó al cliente en producción sino a nuestro equipo de pruebas (bien por ellos) con lo cual en vez de nervioso me sentía contento y aliviado. La misma sensación que debe tener un acróbata cuando tras fallar una pirueta aterriza suavemente en su red de seguridad.

No lo aclaré antes porque le resta dramatismo a todo.

Para finalizar:

  • ¿Programador principiante? Mira la línea en la que saltó el error y no entiende nada. Va hacia atrás y donde puede poner un "if" para evitar la situación lo enchufa, probablemente rompiendo otra cosa.
  • ¿Programador desganado? Arregla lo de los centavos (el primer problema) y todo lo demás desaparece... por un rato.
  • ¿Programador apurado? Arregla el último problema (el del mensaje) y manda el arreglo. Deja los demás registrados detalladamente con una solución propuesta para después.
  • ¿Yo en el último día de trabajo del año? En apenas 9 horitas lo tengo todo resuelto, cuando alguien me pida explicaciones le paso un link a este post, aunque seguramente no llegue al final.

¿Y cómo se corrige eso? Del último al primero:

  • que el mensaje de excepción no explote la pantalla (y lo pruebo),
  • que borre la basura de la operación incompleta evitando que tire ese mensaje (y lo pruebo),
  • cambiando el mensaje al final de la transacción para que diga claramente que no puede vender ese importe en fichas porque no hay forma (y lo pruebo),
  • y por último agregando un control para que controle esto apenas ingresado el importe (y lo pruebo).

Y así, mi catedral no sólo es más bonita, también es más resistente.

Un tip: vean que de esta manera voy probando todos los arreglos a medida que los voy implementando con el mismo caso de prueba. Si empiezan por el final, luego probar todo es un problema.

Espero que hayan llegado hasta aquí leyendo todo, despiertos y con ganas de vivir...

Ah, por cierto, cuando volvía para casa a las 18.00 me dí cuenta de un pequeño detalle que introduje en el último arreglo: si quiero vender $10.50 y tengo fichas de $1 y de $0.50 (operación 100% válida) pero tengo como predeterminada la ficha de $1 el sistema va a tratar de proponer $10.50 en fichas de $1, no va a poder y por obra y gracia de mis controles prematuros va a trabar toda la operación sin darle posibilidad al usuario de modificar las cantidades.

En resumen: corregí un buen par de problemas graves, pero por un pequeño error lo dejé, a ojos del mundo, peor que como estaba. ¡Ah! Y para eso tardé todo el día.

¿Me acordaré de corregirlo cuando vuelva? Seguro que sí, porque ese día es hoy y alguien me va a avisar. Magias de la programación de posts.

martes, 16 de septiembre de 2008

Hasta el máximo nivel de ineficiencia.

El post de Enrique Dans, Departamentos de IT: recortando por arriba, me hizo recordar un ¿chiste? bastante conocido que rezaba:

"Un empleado asciende en la organización hasta alcanzar su máximo nivel de ineficiencia."

A ver: un joven recién salido del secundario ingresa a una gran corporación como cadete. El trabajo le queda chico y se desempeña por encima de la media. A medida que va cursando sus estudios va ascendiendo a través de diferentes funciones administrativas, en las que se desenvuelve maravillosamente.

Se recibe y título en mano se presenta al departamento de personal, y luego de un tiempo su labor es reconocida con un nuevo ascenso.

Y así siguiendo. El ya-no-tan-joven prospecto va subiendo por la pirámide enfrentando retos cada vez más desafiantes, que supera con soltura, digamos de manera brillante, hasta que...

Hasta que llega a un puesto en el que ya no es tan bueno... simplemente porque no estamos hablando de un genio ni nada por el estilo. El trabajo es difícil y su desempeño ya no es espectacular.

Pero mientras se desempeñe "bien" seguirá ascendiendo (probablemente en forma cada vez menos frecuente) pero su trabajo será cada vez menos espectacular, más bien tirando a mediocre o incluso malo. Si no es tan malo como para ser despedido (nadie baja), terminará estancado allí, en ese puesto donde ha alcanzado "el máximo nivel de ineficiencia soportable por la empresa".

Conclusión: coincido con el referenciado artículo de Robert X. Cringely, "Fire Your Boss: The best place to cut IT organizations is generally at the top". Hay que cortar por arriba.

jueves, 29 de mayo de 2008

Eficacia vs. eficiencia del programador.

El mataburros define programador como "Persona que elabora programas de ordenador." (segunda acepción, que es la que viene al caso). Si buscamos programa nos encontramos (entre otras, 12ava acepción) con "Conjunto unitario de instrucciones que permite a un ordenador realizar funciones diversas, como el tratamiento de textos, el diseño de gráficos, la resolución de problemas matemáticos, el manejo de bancos de datos, etc.".

Así que llegamos a la asombrosa conclusión de que un programador es una persona que elabora un conjunto unitario de instrucciones que permite a un ordenador realizar funciones diversas... etc.

Me voy a tomar la libertad de agregar una condición, que no por obvia es menos importante: "...que permite a un ordenador realizar funciones diversas en razonable concordancia con sus deseos..." Es decir, el ordenador tiene que realizar las funciones que el programador quiere y no otras, y esta igualdad debe ser "razonable" porque ya sabemos que todo software tiene errores.

Entramos en terreno escabroso, ya que "razonable" es un adjetivo muy subjetivo. Así que, en un acto de descarado libertinaje, modifico y agrego un tercer elemento: "...en razonable concordancia con los deseos de quien utiliza o requiere tal función...". Ha nacido el usuario o el cliente.

Bien. Llego entonces a la conclusión de que un programador es quien puede hacer que un ordenador haga razonablemente lo que el usuario o cliente desea, según la subjetiva evaluación de este último. Tengo que recordar en este punto que ya no se trata de una definición de diccionario sino de mi personalísima y discutible opinión, ya que la he modificado bastante. Pero es justamente esto lo que quiero expresar.

Decir que un programador es "eficaz" es una tautología (segunda acepción: "Repetición inútil y viciosa.", como estos links que ya me cansé de poner). Si no soy eficaz no soy programador.

Una empresa que desarrolla software contrata personal que se dice programador. Con el paso del tiempo esto queda demostrado: hace su trabajo entregando software que funciona razonablemente (es eficaz) o lo echan con justa causa, ya que la persona ha engañado a su contratista diciendo poder hacer lo que no puede.

Ahora, ¿qué parámetros se tienen en cuenta en la evaluación que toda empresa o equipo de trabajo hace de sus integrantes y que implica premios, ascensos, vacaciones o al menos renombre entre sus pares y respeto por parte de sus superiores? A veces, sólo la eficacia. Por ejemplo: se entrega un premio por proyecto completado (a secas, no "completado en tiempo" ni "completado y certificado por la norma xyz"). Es decir, se premia a una persona simplemente por hacer su trabajo, por hacer lo mínimo indispensable que define su puesto. Pero se supone que un premio o el renombre y el respeto van de la mano de una habilidad que está por encima de la línea base. Es como si en una facultad me dieran un premio o gozara de renombre académico por aprobar con 4 (el mínimo requerido). ¿Me recibo con un 4 en todas las materias? Sí. ¿Soy tan "Licenciado en Sistemas" como cualquier otro? Sí. Pero no es menos cierto que soy el "peor Licenciado en Sistemas" posible, ya que si tuviese menos de 4 no sería ni Licenciado (es sólo una analogía para graficar la cuestión, ya sé que luego en la vida profesional es donde se demuestran las verdaderas aptitudes, etc.).

Esto sucede porque a veces se comete el error de medir la calidad de un software solamente desde el punto de vista del cliente, interno o externo. Y esta falencia tiene que ver con el hecho de que incluso para un Líder de Proyecto es difícil obtener otras medidas sobre la calidad del producto de un programador y de su desempeño.

En resumen: un programador se define por su eficacia. Pero su desempeño debe medirse por su eficiencia. ¿Qué es la eficiencia, dentro del marco de este artículo? Lo uso como sinónimo de la suma de legibilidad, mantenibilidad, corrección, simpleza, ese tipo de cosas.

Es tan dificil para un no-programador (cliente, analista funcional, líder de proyecto) medir la eficiencia de una porción de código o de un sistema como fácil lo es para sus compañeros de equipo. Todos nos damos cuenta cuando "esta parte no te quedó muy bien", o cuando "ese sistema está podrido por dentro".

Hay otra cuestión sutil, que es muy interesante: el miembro más inexperto del equipo es el que tiene, a mi entender, la opinión más relevante. Si éste programador -que puede ser un junior en su primer desarrollo- entiende el código (obviamente con la tutela necesaria), le resulta claro y fácil de modificar o depurar, y muestra un claro progreso y adaptación espontáneos a medida que adquiere experiencia, es motivo de orgullo para el que lo escribió así sea el más experimentado programador del mundo. Mucho más que la opinión de otros compañeros más expertos, quienes a veces ni ven las ambigüedades por conocer bien el sistema, y ni que hablar del cliente, que sólo puede ver que hemos hecho nuestro trabajo, que es lo mínimo que él espera, o del Líder de Proyecto que a veces -remarco "a veces"- sólo evalúa los proyectos mediante "horas ejecutadas vs. horas presupuestadas".

¿Nunca se han sentido frustrados por la reacción de un cliente ante un software del cual el equipo está justificadamente orgulloso? Yo los consolaría diciendo (y me consolaría, porque también me pasa) "¿qué esperábamos?". El fruto de nuestro especial esfuerzo, de nuestro apego a rajatabla por los estándares y el buen diseño ha sido oculto por esa empaquetadora llamada compilador antes de llegar a sus manos.

El esfuerzo tiene su recompensa. El código es mantenible, los errores, muchos o pocos, se corrigen sin grandes problemas, el sistema es escalable y la nueva funcionalidad resulta más sencilla de desarrollar ya que tiene una base estable. Pero... muchas veces se premia más a quien se esfuerza por corregir errores y trabaja a deshoras que al que ha desarrollado código mantenible, y por ello logra resolver cada cuestión en un par de minutos, retirándose temprano. Incluso vemos que el que se fue temprano no ha tenido oportunidad de demostrar su "compromiso", y con suerte nunca se presente.

No soy un experto en medidas de la calidad interna del software, así que no puedo presentar el formulario de evaluación mágico que resuelve el problema del Líder de Proyecto, que a esta altura debe estar pensando "bueno, sé que esta situación existe ¿pero cómo evalúo en concreto el desempeño de cada uno?". Me quedo con esta pregunta, y con el plan de googlear un poco en busca de respuestas, que seguro que las hay.

Resumo, para finalizar, los conceptos centrales de todo este artículo:

  • Todos hacen, no todos hacen bien, y nadie hace bien siempre.
  • Todos los programadores somos eficaces. Es la eficacia (o al menos su apariencia, real o no) ante los superiores, clientes y compañeros lo que nos mantiene dentro del equipo. Nuestro esfuerzo debe concentrarse en ser cada vez más eficientes. Para esto no hay límites.
  • Necesitamos de la buena opinión de nuestros clientes (internos o externos) para sobrevivir. Condición necesaria, pero no suficiente.
  • Es la opinión de nuestros compañeros de equipo que conocen a fondo nuestro trabajo la que más debemos tener en cuenta para determinar si somos buenos en lo que hacemos o no, y es a ellos a quienes tenemos que acudir o prestar atención para ver qué es lo que se puede mejorar (si es que queremos hacerlo).