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

martes, 17 de noviembre de 2009

Decisiones sobre calidad.

Del dicho al hecho hay un largo trecho, qué duda cabe. Y cuando se habla de calidad (sobre todo cuando se habla) el trecho es más bien un largo recorrido.

Si hacen el experimento de pasear un poco por sus ambientes de desarrollo preguntando “¿Cómo se implementa un software de calidad?” recabarán respuestas ubicables en un amplio rango que va desde la opinión improvisada (la mía, por ejemplo) hasta la disertación fundada. Increíblemente, la mayoría de las respuestas serán muy razonables (¡hagan la prueba!). Podríamos decir que casi todo el mundo sabe cómo contribuir a la buena calidad de un producto de software.

Qué duda cabe. ¿Y entonces? ¿Qué pasa? Vamos, ustedes saben a qué me refiero (y si no saben tienen un problema más grave todavía). Creo que una –entre tantas otras- cosas que pasan es que un nivel de calidad por sobre la media (abreviemos en “la calidad” de aquí en más) conlleva un costo que muy pocas empresas están dispuestas a asumir.

Un error de concepto muy extendido que ninguna consultora en calidad con sentido comercial corrige es la confusión entre el costo de la calidad y el precio de la implementación o certificación de normas de calidad.

3-leg-qualityLa confusión entre aquel costo y ese precio conviene a ambas partes: en la empresa alguien vende -y alguien compra- que erogando una suma inicial de $xxx y un mensual de $zzz asegura la calidad de su producto o proceso de desarrollo, y la consultora disimula el hecho de que por esos importes sólo se hace cargo de la inducción y formación del personal en determinadas normas y procedimientos, y que el impacto de esta formación sobre la calidad final de los resultados es un tema que estará por verse (y que la relación entre estos dos elementos –formación y resultados- es, por decir lo menos, controversial).

Como escribí más arriba, es mi opinión que pocas empresas están dispuestas a pagar el verdadero costo de la calidad. Pero… si no es el precio que cobra la consultora por certificar la norma “Pepito 9000”… ¿qué es?

Calidad es funcionalidad que hay que programar o documentos que hay que escribir, porque finalmente todo lo que realmente existe tuvo que programarse o escribirse en algún momento, magia no hay (aunque sí mucho humo). Funcionalidad es tiempo, tiempo es dinero. Dinero –poco- con el que se paga a los programadores o –mucho- con el que se compran herramientas (y que termina –en ínfimas proporciones, se deduce de lo anterior- en los bolsillos de otros programadores).

Así que la calidad en la práctica diaria del desarrollo de software, una vez despojada de esa pesada carga ornamental de normas y procedimientos, es simplemente un subconjunto de las decisiones mediante las que día a día, minuto a minuto, intercambiamos funcionalidad por tiempo –que en algún momento futuro se cambia por dinero-. Lo que lo define a este subconjunto de decisiones es que se refieren a funcionalidad que -por una u otra razón- podemos gambetear (esquivar)… en realidad no gambetear del todo, sino patear para más adelante por un tiempo determinado, aunque usualmente se pretenda hacerlo indefinidamente.

¿Cómo que funcionalidad que podemos patear para adelante? Me explico. Otra –a mi entender- confusión conveniente –esta vez a empresas de desarrollo de software- es aquella entre cumplimiento y calidad. Se suele decir que un producto de calidad es aquel cumple con los requerimientos que ha solicitado el cliente, y que por lo tanto la contribución del equipo de desarrollo a la calidad es asegurarse de cumplir con esos requerimientos. Bueno, eso no es así.

En tanto la especificación de un producto de software dista mucho de tener la precisión de los planos para una silla, una mesa o un auto, esa definición disfraza el cumplimiento –entregar lo que según un papel se nos pide- de calidad –entregar un producto acorde a las expectativas del cliente (usualmente esto es, todos lo sabemos, algo muy diferente a lo que el cliente pide)-.

Entre la reducida audiencia de este blog nadie se escandalizará demasiado si digo que tranquilamente podemos cumplir con todos los requisitos con un sistema de mier… ejem, y que también podemos entregar un software de excelentísima e irrefutable calidad que no cumpla, ya sea por error u omisión, con alguna o todas las expectativas (ojo, no los requisitos sino las expectativas)… o que a veces se manipulan las expectativas del cliente de manera tal que coincidan con lo que se desea entregar (y que en muchas de esas veces ese tiro sale por la culata).

La calidad está (como Dios, dicen) en los detalles (mi ateísmo es clara indicación de la calidad de mi trabajo).

La calidad aparece cuando no gambeteamos aquellas funcionalidades que sí podríamos gambetear, cuando no pateamos desesperadamente para adelante (o para afuera), cuando entregamos al cliente no lo que pide sino lo que necesita… y todo ello lleva tiempo e implica riesgos.

¿Cuánto? Dependerá, pero seguro más que no hacerlo.

clint-bowyer-crosses-finish-lineEn resumen, y para decirlo de una vez por todas: el costo de la calidad es el tiempo extra que nos tomamos al entregar más tarde algo bien hecho en vez de entregar a tiempo algo para cumplir…

…y, por sobre todas las cosas, el costo de los fracasos derivados de los riesgos asumidos (todos dicen arriesgar pero nadie piensa en fracasar, y no hay riesgo sin fracaso): nada ni nadie nos asegura hoy que dentro de n días tendremos un producto de calidad, o que nosotros sí sabremos lo que el cliente necesita y no puede expresar, o que lograremos esquivar las balas sin escondernos cobardemente en esa armadura de papel a la que llamamos “requerimientos escritos”.

¿Conocen a alguien realmente dispuesto a ello?

lunes, 19 de octubre de 2009

La influencia de la orientación al producto o al cliente en el desarrollo de software - I: Los muertos se dejan atrás.

Arbitrariamente (como toda clasificación) y a muy grandes rasgos, podemos dividir el mar de equipos de desarrollo de software en dos corrientes principales: enfocados al producto y enfocados al cliente.

Los equipos enfocados al producto son responsables del desarrollo de uno o varios sistemas, siempre orientados a un mercado específico (si bien puede ser definido en forma más o menos amplia): sistemas contables, para la gestión de hospitales, comercios minoristas, logística o para el control de determinados dispositivos. El producto (o la familia de productos, entiéndase de aquí en más) requiere mantenimiento y constantemente se prueban e implementan nuevas funcionalidades y cambios para justificar los lanzamientos y generar ventas.

Del otro lado tenemos desarrollos enfocados hacia el cliente: profesionales independientes y equipos de desarrollo en consultoras y software factories de todo tipo y color que luchan por armar, mantener y ampliar una cartera de clientes a los que usualmente intentan vender de todo, si es software mejor. Algunas están también especializadas en un mercado en particular, pero vale la clasificación en tanto no vendan el mismo producto a varios clientes.

Y después hay grises, pero creo que la clasificación abarca a una amplia mayoría.

Hasta ahora he sido desarrollador de productos (aunque más de uno fue acaparado por algún cliente importante). Tenía formada una imagen bastante pobre acerca de la calidad de los productos y del trabajo en consultoras (sobre todo en las grandes, pero es una experiencia que por ahora no tengo), y crucé la línea cargado con no pocos prejuicios y temores. Pero bueno, hay que alejarse de las zonas de confort, dicen.

Han pasado ya casi cuatro meses desde el salto, puedo decir que por lo menos una de las consultoras (por suerte) no es como me la esperaba. Realmente las cosas no son ni mejores ni peores, simplemente distintas, y a esto iba, a hacer un contrapunto.

Los muertos se dejan atrás (aunque hay zombies indestructible).

La huella de toda consultora es un reguero de cadáveres más o menos malolientes: proyectos cuya calidad interna deja mucho que desear, si bien hacia afuera suelen verse bastante bien (que al cliente hay que cuidarlo). El aprendizaje se da de proyecto en proyecto y los primeros en implementar determinadas herramientas o tecnologías son víctimas fatales de la inexperiencia.

En cambio, cuando trabajamos en versiones incrementales del mismo producto, siempre sobre la base de código de la versión anterior, poco a poco la experiencia se materializa en todo el proyecto a través de revisiones y refactorizaciones. El equipo (si no es testarudo) no tarda en darse cuenta de que si se mueven las lápidas y se dejan las tumbas los muertos se convierten en fantasmas que nos acecharán eternamente. La única forma de sacárselos de encima es buscar el cadáver y darle cristiana sepultura.

A cierta distancia de la línea de entrega de un proyecto que se presentará, aprobará (y si te he visto no me acuerdo), es inútil refactorizar y solucionar definitivamente determinadas cuestiones. Llega un momento en el que el sistema “es así” y punto, y aunque (por lo menos para mí) es difícil de aceptar, hay que dar media vuelta y seguir adelante.

Cuando se trabaja sobre un producto toda refactorización es provechosa y bienvenida. Incluso en las reingenierías el código “del sistema viejo” es referencia para determinar detalles de implementación, por lo que cuanto más legible mejor. Así que es sencillo cuándo determinar cuándo es conveniente hacer una refactorización o resolver un problema de fondo: siempre.

En una consultora determinar a ojo cuándo se ha llegado ese punto de no retorno es muy difícil, y los errores en este sentido son frecuentes. Muchas veces las inevitables horas fuera de presupuesto que insumen las correcciones entre presentación y aprobación superan aquellas que hubiesen sido necesarias para la refactorización, y otras veces se van horas de refactorización en funcionalidades con pocos errores y cuya prolija implementación nunca será vuelta a ver.

En todo caso, y en esto he confirmado mis prejuicios, aunque la consultora en sí no pueda desprenderse de algunos muertos devenidos en zombies, la realidad es que desde el punto de vista de un desarrollador en particular es poco probable que una vez abandonado el proyecto se lo vea volver en la misma dirección.

Suficiente, creo, para una primera entrega. Seguimos en la próxima.

lunes, 24 de agosto de 2009

Cuando los resultados no reflejan la experiencia o el aprendizaje.

Hay veces que llama poderosamente la atención la brecha (otras veces el abismo) entre la calidad de un equipo y la del proyecto sobre el que trabaja. A veces para bien, a veces para mal.

StaffEn algunos se percibe una capacidad para la mejora que excede en mucho la capacidad técnica (inicial) de sus integrantes. La falta de experiencia hace que se reinventen muchas ruedas y se cometan muchos errores de esos que (tal vez pecando de un poco de soberbia) los desarrolladores de más experiencia catalogan de “típicos”, pero que son ampliamente compensados por la motivación a corregirlos, incluso en varias aproximaciones, intentando una y otra vez. Éste es el caso ideal en el que, en todo momento, la calidad del proyecto refleja todo el aprendizaje y la experticia del equipo.

Pero en otros se ve que el resultado, si bien funcional, no tiene la calidad que podría esperarse de acuerdo la experiencia de los participantes, o que surgen (en el peor de los momentos) problemas que derivan de errores que podrían haber sido detectados (e incluso corregidos) con relativa facilidad por los miembros de más experiencia, problemas que luego de un tiempo de incubación terminan impactando transversalmente en todo el proyecto volviéndose de difícil o imposible solución.

Mucho tiene que ver en la calidad de un proyecto la habilidad para resolver problemas de sus integrantes. Mucho más tendrá que ver la experiencia y muchísimo más la motivación.

Es la experiencia la que permite ganar tiempo aplicando soluciones ya probadas y enfocarse en aplicar la habilidad en resolver aquellos problemas que hacen único a cada producto de software. Pero es la motivación lo que lleva a utilizar la (mucha o poca) habilidad disponible y aprender (con mayor o menor velocidad) de los errores y aciertos (propios y ajenos), que es lo mismo que decir “adquirir experiencia”.

Pero una cosa es la motivación a aprender y otra la de aplicar el resultado de ese aprendizaje al proyecto en particular sobre el que se está trabajando y aprendiendo, y de ahí la brecha entre capacidades y realidades que mencionaba al principio. La mayoría aprende de los errores y ve posibilidades de mejora, pero son menos (muchos menos) los que desandan el camino para corregirlos o implementar esas mejoras en el código ya escrito. ¿Por qué?

Se me ocurren un par de factores que hacen de esa decisión (la de seguir sin mirar atrás) la más razonable:

  • Si el alcance del proyecto es acotado y está próximo a entrar en una etapa de mantenimiento (¡exclusivamente!) correctivo, de poco o nada servirá mejorar lo que de todas maneras funciona (suponiendo que funciona razonablemente, ya que de no ser así no hay alternativa posible).

  • Si el problema es transversal y está demasiado extendido, puede ser mejor será tratar de no cometerlo de aquí en más y corregirlo sólo en donde las modificaciones sean imprescindibles.

  • En cualquier caso en el que el impacto sea menor que el esfuerzo que requiera la corrección. El problema aquí es la medición de ese impacto, para la que deberíamos considerar no sólo la situación actual sino también a futuro. Si estamos desarrollando un producto que se pretende mejorar y ampliar indefinidamente (en vez de “cerrar y entregar”) la acumulación de pequeños problemas terminarán afectando la calidad haciendo cada vez más difícil implementar nuevas funcionalidades y forzando una (mucho más riesgosa y costosa) reingeniería.

En definitiva, es cuestión de decidir en forma consciente y explícita si vale la pena el esfuerzo dada cada situación en particular. Pero hay otros factores, menos técnicos y más humanos o de organización, que bloquean las alternativas:

  • Simple resistencia al cambio, el aferrarse a un estado en el que los problemas (graves o no) son conocidos.

  • Una patológica actitud defensiva puede hacer que cualquier sugerencia se vea como una crítica.

  • Soberbia, que impide ver los problemas y desventajas que inevitablemente están presentes en cualquier solución.

  • Desinterés o el clásico “sólo hago lo que me ordenan”.

  • Escaso trabajo en equipo o excesivamente compartimentado, que hace que se pasen por alto los problemas y posibles mejoras transversales.

Resistencia al cambioSon estos factores son los que hay que detectar y combatir porque son los que llevan a decisiones irracionales o por defecto (aquellas que surgen de la no-decisión). El ideal es que el equipo tenga el control del proyecto, lo que implica ser consciente de los aciertos, de las cuestiones “mejorables”, de los errores que hay que corregir y de los que hay que soportar.

lunes, 10 de agosto de 2009

Señales de que eres un mal programador.

Más allá de los muy buenos chistes que contiene y del tono jocoso en general, Signs that you're a bad programmer es un artículo imperdible que debe tomarse en serio.

Está dividido en tres partes: “Señales de que eres un mal programador”, “Señales de que eres un programador mediocre” y “Señales de que no deberías ser un programador”. En cada una de ellas se presentan estas “señales” con sus respectivos síntomas y consejos para superarlas (carreras alternativas, para el último caso).

Aunque la gravedad de cada una en particular es discutible (yo no calificaría de “malo” a un programador con dificultades para seguir la recursión, me parece lo suficientemente complicado como para marearse sin merecer un calificativo tan extremo) es innegable su utilidad para identificar problemas y puntos débiles en uno mismo y los demás (o para decidirse a cambiar de carrera).

Más que puntear las que me parecen más importantes, voy a mencionar los síntomas que me resultan más molestos:

zombie "voodoo (o zombie) code"

Mantener código que no hace al objetivo del programa: inicializar variables que luego no se utilizan, llamar a funciones que no hacen nada o cuyo resultado es descartado, producir información que finalmente no se muestra.

Esto hace que seguir el código sea exasperante y su corrección peligrosa, ya que uno tiene que ir separando la paja del trigo para entenderlo, corriendo el riesgo de sacar algo que sí servía. Una solución posible es no limpiar nada, lo que nos lleva a…

noloborres “no lo borres”

Con lo que me gusta borrar código… Esto me molesta sobre todo cuando aparece en la forma de cierta tendencia -que veo incluso en programadores experimentados- a llevar el control de cambios en medio del código mismo (¿costumbres de una época en la que el uso de sistemas de control de código fuente no era tan común?). Me refiero a éste tipo de comentarios:

“J.P. 25/7/2006: arreglo del incidente nro. XXX. El cálculo del total de…”

Estoy tratando de entender que hace el código ahora, y no me interesa saber qué hacía hace tres años. Para hacer investigación histórica está el historial de cambios. A la basura.

Pero… por algo debe estar ahí, ¿por qué mejor no lo dejamos?…

candados “por las dudas”

Código que se ejecuta “para estar seguro de…” O estamos seguros y no hay que asegurarse o no estamos seguros y hay que molestarse en entender hasta estarlo, ¿no? ¿Seguro que Trim borra los espacios a los dos lados de la cadena? Mmmm… mejor hagamos miCadena=miOtraCadena.LTrim().RTrim()… por si acaso. O escribamos (int)4, no sea cosa que… Para asegurarse está la ayuda, no el código.

Una variación sutil de lo anterior es arreglar un problema en la salida de una función manoseando el código que la consume, “por las dudas” de que “alguien más” la esté utilizando. ¿Alguien más la está utilizando? ¿Tan difícil es ir, fijarse en dónde y en todo caso corregir esos otros lugares? Esto me lleva a un punto que me parece que falta en el artículo referenciado:

anteojeras “programar con ateojeras”

¿Vieron las anteojeras que se le ponen a los animales de tiro para que vayan siempre para adelante sin asustarse con lo que pasa a su alrededor? Hay gente que programa con eso puesto. Si tienen que corregir el incidente n que aparece en la pantalla x van a corregir eso y solamente eso, indiferentes a cualquier otro problema, por más grueso que sea.

incisión Son como cirujanos que acceden al código y extirpan un tumor con precisión milimétrica… sin darse cuenta de que hace tiempo que el paciente está muerto. Finalmente el aporte de estas acciones al proyecto en general es tanto como su alcance… milimétrico.

Bueno, hay mucho más… mejor lean el artículo.

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, 3 de marzo de 2009

Metáforas: la deuda técnica (technical debt).

Paying Down Your Technical Debt, de Coding Horror, trae a cuento esta metáfora pergeñada por Ward Cunningham.

La deuda técnica es, muy básicamente, aquella en la que se incurre cuando se desarrolla funcionalidad rápidamente posponiendo cuestiones menos urgentes tales como legibilidad, escalabilidad, simplicidad, etcétera. La implementación “sucia y rápida” es el capital de esta deuda que, como toda deuda, genera intereses que el equipo va pagando en forma de aumento de costos de mantenimiento y dificultades al desarrollar nuevas funcionalidades. La única forma de quitarse el peso de ese interés es pagando el capital, o sea refactorizar la funcionalidad.

Martin Fowler la desarrolla muy bien en su blog en la entrada TechnicalDebt (en inglés, por supuesto). Para los que prefieran leer en castellano, el blog “Qué quieres desarrollar hoy?” ha publicado un post reciente al respecto.

A leer, que se acaba el mundo.

viernes, 27 de febrero de 2009

La “respuesta” a “¿Cuál es el error?”, la ambigüedad, su resolución y, finalmente, la realidad.

question Estamos hablando del problema planteado en el post ¿Cuál es el error? Si no lo has leído (con sus comentarios) no vas a entender nada (es simple y cortito, no hay excusa).

Me encanta ese problema ya que es disparador de un montón de cuestiones relacionadas a la codificación entre las que encontraremos muchas zonas grises. Algunos (como yo) verán las suficientes para afirmar sin lugar a dudas que programar está a medio camino entre el arte y la técnica, y que para el ojo entrenado no es difícil encontrar cierta belleza (o por lo menos elegancia, una de sus formas) en algunos ejemplos.

Bueh, tal vez me emocioné un poco. De todas maneras lo anterior sirve para aclarar que lo que sigue es mi personalísima opinión y que si bien puede ser contraria a la de algunos de ustedes no pretende ni puede invalidarla en manera alguna ya que, como establecimos, son zonas grises.

La solución “estricta”.

Siendo riguroso con el enunciado del problema, creo que la respuesta más acertada es “No se puede saber”.

Parece que hay un error (y aunque eso parece claro, si leyeron los comentarios en la entrada del planteo verán que hasta eso puede objetarse) ya que la declaración dice que suma pero la implementación resta. No hay forma de decidir qué está mal sin un contexto.

Lo más que podemos decir es que el código es poco legible, que es confuso, ambigüo (para un ser humano) o traicionero. Lo que no podemos decir (sin contexto) con seguridad es que hay un error de esos que le interesan a la gente común, y mucho menos decir cuál es. El único error que podemos verificar es de estilo o prolijidad, y no podemos resolverlo.

De acuerdo al contexto el error podría ser nimio, de esos que uno corrije sin decir nada a nadie (sería obviamente de tipeo, el programador quiso poner “+” y le salió un “-”. Algunos de los muchos números del reporte –por ejemplo- salieron mal, pero eran tantos que era difícil darse cuenta. En las pruebas, para un analista que interprete esos números, será mucho más evidente que para un programador cansado o aburrido de hacer reportes)… o grosero, muy grosero. Que se entienda bien clarito: al programador que utilice eso para restar, repetida y consistentemente en cualquier porción de código, yo le deseo la muerte.

Mejor volvamos a nuestro problema, sin contexto. Uno de los principios de diseño de Guido Van Rossum (autor de Python, entre otras cosas) nos dice:

Cuando te enfrentes a la ambigüedad, rechaza la tentación de adivinar.

Es muy, muy difícil de seguir. Casi tanto como decir “No sé” o “No se puede saber”. La primera respuesta es dolorosa para un orgullo cultivado (y los programadores sí que cultivamos el nuestro) y la segunda requiere mucha confianza y conocimiento del tema que se trata.

Por otro lado las preguntas engañan. No es lo mismo preguntar “¿Cuál es el problema?” que “¿Hay un problema?”.

Vivimos en un mundo imperfecto y una computadora no entiende de imperfecciones. A los programadores nos toca ser el último eslabón entre lo ambigüo por naturaleza (el lenguaje, los negocios, el ser humano, la vida en general) y lo absoluto (una sucesión finita de operaciones), así que lidiamos con este problema -de enfrentarnos a la ambigüedad- todos los días.

Un ego desmedido como el nuestro no digiere fácilmente que (si bien podemos hacer cualquier cosa) no somos dueños de la verdad absoluta (no todo lo que hagamos estará bien -¡aunque funcione!-) y que -peor todavía- existe la verdad absoluta y tiene dueño: el cliente. Utilizando estrictamente las palabras, los programadores somos dueños de la implementación, pero no de la solución. Decidimos cómo pero no qué.

El cliente (o quien lo represente ante nosotros, los programadores) puede equivocarse en muchas cosas, pero cuando hay que resolver una ambigüedad tiene no sólo la última, sino la única palabra. En el trabajo de todos los días no podemos decidir si “una operación revertida aparece en el informe o no”. ¿Por qué no? Porque estaríamos inventando y tal vez invirtiendo tiempo en hacer bien algo que nunca debería haberse hecho.

Recuerden siempre: las preguntas engañan. Siguiendo el ejemplo de la operación revertida, es probable que hayamos arribado a esa opción binaria más por nuestra naturaleza de programadores que por la realidad de la situación. ¿El cliente pensó en esta situación? No. Entonces, ¿cómo podemos saber que la solución es una u otra, y no una tercera, o ninguna, o las dos?

¿Cómo saber si hay que sumar o restar? No lo sabemos. No sabemos qué es lo que hay que hacer si no nos lo comunican, tenemos que preguntar. La frase de Guido nos está diciendo “más fácil inventar parece, pero evita la tentación, porque buscar una respuesta debes” (¿o ése era Yoda?). Ésa es, en principio, nuestra responsabilidad: resolver la ambigüedad preguntando, no inventando.

Es también, entonces, un tema de responsabilidades (“Un gran poder conlleva una gran responsabilidad”): ¿tenemos ese poder para decidir? ¿queremos esa responsabilidad? Recordemos, otra vez, que estaríamos tomando una decisión sin conocer todos los detalles: no conocemos el negocio, al cliente final, ni la situación comercial… tal vez ni siquiera el sistema completo.

Pero, ya lo dijimos, vivimos en un mundo imperfecto. Muchas veces (en todo proyecto real esta situación es más que frecuente) el cliente no está disponible (si estuviese disponible él tiene la respuesta, aunque ésta sea “hacé lo que te parezca” o tire una moneda o cambie con el tiempo) o no entiende la pregunta. ¿Entonces?

La frase nos dice “evita la tentación”, no nos dice “no adivines”. Hay veces que tenemos que adivinar. Presionados por un sinfín de circunstancias, para bien o para mal, hay veces que hay que adivinar y seguir adelante, rellenar los huecos en las especificaciones apelando al sentido común o, en última instancia (es lo que yo lo recomiendo) hacer lo que sea más fácil para nosotros, así por lo menos el tiempo perdido será menor.

Pero tenemos que ser conscientes de que lo estamos haciendo. Y de que está bien si lo hacemos sólo si hemos agotado otras instancias (o no tenemos tiempo de hacerlo) y no nos queda otra, y no para patear un problema debajo de la alfombra.

La razón última por la que un proyecto no puede llevarse a cabo sin la cooperación y compromiso real de los programadores que en él participan (por lo menos de buena parte de ellos) es que (como una computadora no entiende de ambigüedades) estas pequeñas decisiones…

  • que nos vemos obligados a tomar sin información completa
  • o con la poca que logremos recabar investigando hasta donde dé nuestra conciencia,
  • basándonos en nuestra experiencia, sentido común, pálpito y buena suerte,
  • sabiendo que en el mejor de los casos evitaremos un problema y nadie se dará cuenta ni agradecerá nada,
  • y que en el peor de los casos… (mejor no hablar de ello)

…tienen gran impacto (para bien o para mal) en el resultado. Tanto que son determinantes, tal vez no del éxito (que dependerá también de muchos otros factores), pero seguro que del fracaso de un proyecto (que con una buena cantidad de decisiones equivocadas o con apenas un par en los lugares precisos puede darse por muerto).

La mejor manera de sabotear un proyecto es hacer exactamente lo que te piden, sin chistar.

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, 12 de enero de 2009

Errores de programación: los 25 más peligrosos.

Siguendo un twit de Matt Cutts (@mattcutts) me encontré con un interesantísimo documento, 2009 CWE/SANS Top 25 Most Dangerous Programming Errors.

Es una lista de los 25 errores de programación más peligrosos en términos de seguridad informática. Los errores están explicados tanto en forma breve y amigable como detallada y técnica y hay un montón de referencias para seguir.

Esta lista se obtuvo por consenso entre una serie de expertos de Estados Unidos y otros países (más información aquí). De acuerdo al documento, estos errores han sido los causantes de 1.5 millones de vulnerabilidades durante el 2008, que han posibilitado que muchas de las terminales utilizadas para visitar los sitios afectados se conviertan en zombis.

Algunos de estos errores son ampliamente conocidos, como los relacionados con la validación de datos ingresados por el usuario, que llevan a los ataques vía SQL-Injection o Cross-Site Scripting.

Otros son más sutiles o dependen de la actualización de los programadores involucrados en el proyecto, como por ejemplo el uso de algoritmos generadores de números pseudo-aleatorios débiles (o directamente la falta de uso, una de las claves que permitió a un grupo de investigadores y estudiantes de seguridad quebrar absolutamente la seguridad SSL hace menos de un mes).

Ésta es la lista de errores traducida (con mucho esfuerzo) por mí:

Interacción insegura entre componentes

Vulnerabilidades relacionadas con el envío y recepción de datos entre sistemas, componentes, módulos, programas, procesos o hilos separados.

  • Validación defectuosa de los datos de entrada.
  • Escape o codificación defectuosa de los datos de salida.
  • Defectos o fallas en la preservación de la estructura de las consultas SQL (SQL-Injection).
  • Fallas en la preservación de la estructura de las páginas web (Cross-site Scripting)
  • Fallas en la preservación de la estructura de comandos del sistema operativo (OS Command Injection)
  • Transmisión no encriptada de datos sensibles.
  • Falsificación de petición en sitios cruzados (Cross-Site Request Forgery)
  • Errores provocados por la interacción con el entorno de ejecución (Race Condition)
  • Información sensible volcada en mensajes de error.
Manejo de recursos

Vulnerabilidades relacionadas con manejos inapropiados durante la creación, uso, transferencia o destrucción de recursos del sistema.

  • Fallas al mantener una operación dentro de un determinado espacio de memoria.
  • Control externo del estado de la aplicación (por ejemplo al utilizar cookies para mantener el estado).
  • Control externo del nombre o la ruta a un archivo (por ejemplo al construirlo a partir de datos de entrada).
  • Ruta de ubicación de recursos no confiable (explotada por un atacante que logre que la aplicación apunte a una ubicación de recursos determinada).
  • Fallas en el control de la ejecución de código (por ejemplo el generado dinámicamente).
  • Descargas de código sin verificar.
  • Descarga o liberación defectuosa de recursos.
  • Inicialización defectuosa.
  • Cálculos incorrectos (por ejemplo fallas por desbordamiento inducidas).
Defensas porosas

Vulnerabilidades relacionados con técnicas defensivas mal o abusivamente utilizadas, o simplemente ignoradas.

  • Control de acceso impropio (autorización).
  • Uso de un algoritmo criptográfico quebrado (obsoleto).
  • Contraseñas establecidas en el código (hard-coded).
  • Errores en la asignación de permisos a los recursos.
  • Uso de valores pseudo-aleatorios predecibles.
  • Ejecución con privilegios innecesarios.
  • Seguridad implementada en el cliente (en vez de en el servidor).

Como les dije, hay mucha información en el anuncio, y en el detalle de estas vulnerabilidades. Vale la pena la lectura en profundidad.

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.

miércoles, 15 de octubre de 2008

Del desarrollo de software como sistema de comunicación y la importancia de poder leer y escribir.

A veces me pongo extremista y veo al desarrollo de software solamente como un sistema de comunicación. Si lo vemos como una caja negra, se trata de de transmitir y traducir los requerimientos en una serie de instrucciones que un hardware pueda ejecutar, llevando a cabo la tarea que estos requerimientos representan. Por lo menos idealmente.

Si abrimos esa caja negra, encontraremos varios "puntos de retransmisión y traducción", que más o menos se corresponden con las etapas de relevamiento, análisis, diseño y codificación. Cada etapa nos acerca más desde el lenguaje del emisor (el cliente) al lenguaje del receptor (ese equipo de hardware).

En resumen, los requerimientos son el mensaje, el cliente es el emisor y el hardware que finalmente debe ejecutar la secuencia de instrucciones acorde es el receptor. En el camino tenemos varios retransmisores, algunos humanos y otros no: analistas de negocio, analistas funcionales, programadores, entornos de desarrollo, compiladores, personal de soporte al cliente (al instalar), sistemas instaladores. El mensaje se transmite a través de varias formas y a través de varios medios: oralmente, por escrito, electrónicamente.

Bajo esta óptica, somos retransmisores al tiempo que traductores.

Como en todo sistema de comunicación, hay ruido de fondo y ruido generado por el propio sistema. Ambos tipos de ruido deforman inevitablemente el mensaje. La comunicación perfecta no existe, por tanto tampoco existe el software perfecto.

Siguiendo por este camino, en el que suelo entretenerme inventando y perfeccionando analogías, llego usualmente a la siguiente conclusión (bombos y platillos):

La calidad del software depende de es la fidelidad con la que el equipo transmite un mensaje, por ello todos los participantes de un equipo de desarrollo de software deben ser excelentes transmisores de información.

De nada sirve ser un experto programador, analista o visionario del negocio si no se puede transmitir fielmente esa información a través del proceso de desarrollo.

Transmitir implica básicamente dos tareas: recibir y enviar. Incluso un programador independiente que trabaja absolutamente solo debe ser un experto escuchando o leyendo al cliente (recibiendo) y programando (enviando).

Recibir es usualmente escuchar, leer. Transmitir es usualmente hablar, escribir.

El problema de escuchar y hablar es que, como se dice coloquialmente, a las palabras (y muchas veces a las personas que las pronuncian) se las lleva el viento.

¡Cuidado! Ahí vienen los pseudo-ágiles: que el exceso de documentación, que la comunicación informal, que los cambios continuos y un largo etcétera.

¿Cuál es la diferencia entre pseudo-agilidad y verdadera agilidad?

En el desarrollo pseudo-ágil no se preserva nada. Llegamos al punto de encuentro con el cliente y estamos solos. ¿Qué pasó? No tenemos idea.

En el desarrollo ágil se preserva solamente lo indispensable: el mensaje, los requerimientos. Es absolutamente minimalista: hay que preservar sólo el mensaje, pero hay que hacerlo como si fuera la vida misma, y verificar continuamente que no haya sido malinterpretado por nadie. Como recalqué muchas veces: tal es así que en XP las historias del usuario las escribe el mismo usuario, y éste es parte del equipo. Creo que solamente por una cuestión legal las cadenas para retenerlo cerca del equipo (que seguramente figuraban en los primeros bocetos) fueron finalmente suprimidas.

Aclarado que tenemos que preservar al menos el mensaje original, los requerimientos, el tema es cómo.

Me agarra el viejazo cuando me vuelvo tradicionalista, pero todavía nadie me convence de lo contrario: el texto es el rey. Es la herramienta más sencilla para el conjunto de actividades que se realizan alrededor de los requerimientos: recibirlos, discutirlos, modificarlos, transmitirlos, catalogarlos, archivarlos, buscarlos.

Pensemos en otros soportes posibles. Omito los puntos "catalogar" y "archivar" ya que con las herramientas disponibles serán sencillos para cualquier soporte.

Los gráficos son más difíciles de producir y modificar, aunque usualmente mejores para discutir y transmitir. La búsqueda no es sencilla, justamente porque contienen poco texto.

En cuanto a imagen y sonido, grabar la descripción de los requerimientos parece una buena idea, pero no me parece un soporte fácil para recibir o discutir, mucho menos de modificar. No me imagino en una reunión diciendo "poné de vuelta esa parte... no... un poco antes... ahí, ¿no debería decir...?" La transmisión es más complicada y la búsqueda sólo puede realizarse bajando el contenido a texto.

Recordemos que hablamos de requerimientos que cambiarán continuamente. A mayor complejidad mayor resistencia al cambio. Y cambio en los requerimientos es moneda corriente.

Así que si me preguntan a mí, texto.

Resumen: habilidades a valorar en un integrante de un equipo de desarrollo (¿o mejor dicho, de un equipo, a secas?): leer y escribir (porque si no a las palabras -o al integrante- se las lleva el viento). ¿Alguna vez los evaluaron en eso en una entrevista de trabajo?

BTW: Menudo título, ya sé, pero estoy cansado de aparecer en cualquier lado por hacer bromitas con los títulos de los post. No lo hagan.

domingo, 5 de octubre de 2008

El usuario (II)

Recordemos la definición ad-hoc de usuario que había presentado en la primera parte de este artículo:

[...] es el que opera el sistema en una situación real para alcanzar un objetivo determinado que coincide con el que fue requerido al desarrollar el sistema.

Luego lo caracterizaba, un poco maliciosamente, diciendo que

El usuario es tonto o malicioso a menos que se demuestre lo contrario.

para luego mencionar algunas de las características más comunes que todos adoptamos cuando somos usuarios de un sistema:

  • Distracción: no leemos, no miramos.
  • Falta de memoria: no registramos nuestras acciones ni sus resultados.
  • Descontextualización: no interpretamos lo que vemos en su contexto, sino que tendemos a trasladarlo literalmente al nuestro.
  • Mecanicismo: repetimos secuencias automáticas de acciones mecánicas que nos han dado resultado.

El origen (siempre de acuerdo a mi modesto entender en estas cuestiones, de las que no soy un gran teórico) es que el usuario no está utilizando el sistema. ¿Cómo?

No. El usuario no utiliza el sistema. Nadie dice, "mi trabajo es utilizar un sistema" o "estoy un poco aburrido, voy a utilizar el sistema". El usuario quiere hacer otra cosa. Querrá pagar una factura, vender un boleto, consultar el horario de una película, actualizar el stock, divertirse...

Los seres humanos no somos demasiado buenos ejecutando varias tareas al mismo tiempo. Nuestro cerebro tenderá a siempre a concentrarse en aquélla que juzgue más importante o le demande más atención. No es ésta la verdad revelada, precisamente. Es, por ejemplo, la razón principal de que no utilicemos el celular cuando manejamos (no deberíamos, por lo menos) o de que apaguemos la radio del auto cuando nos perdemos. En estas conductas vemos otro punto sutil: tampoco somos muy buenos concentrando nuestra atención conscientemente. El celular y la radio nos distraen, aunque lo neguemos o querramos evitarlo.

Entonces el sistema es una herramienta que, si bien el usuario quiere o debe utilizar, lo distrae inevitablemente de su objetivo principal. Como no puede apagarlo hace lo que hace: le presta la menor atención posible. Cuando somos usuarios desarrollamos toda una serie de conductas que tienden a reducir al mínimo la cuota que nuestro cerebro debe dedicarle al sistema en sí.

Así que tenemos que estar conscientes de que cuando el sistema trata de atraer la atención del usuario está molestando. Siempre. Así que más vale que lo haga por algo importante.

Si están de acuerdo con en el razonamiento que me llevó a la frase anterior, tendrán que coincidir con que los errores respecto de este tema son muy, pero muy comunes en casi todo software:

Molestar con bobadas:

  • Propaganda intrusiva e irrelevante, sobre todo en Internet. Si el usuario quiere leer el diario ¿por qué demoraría más de un segundo en cerrar esa imagen molesta que le impide hacerlo?

    Se me dirá que ese tipo de propaganda funciona. Es verdad. Funciona desde el punto de vista del anunciante, que actúa más o menos como un spammer: molesta a varios cientos de miles para alcanzar a ese par de decenas de usuarios que tienen algún interés en el producto o servicio promocionado.

    Por otro lado ¿no funciona mejor la propaganda contextual? Ese sistema se basa en lograr un encuentro entre lo que el usuario busca y lo que el sistema le ofrece. Si el usuario busca pasajes baratos, encontrará la frase "Viaje casi Gratis" así esté ubicada en el extremo inferior izquierdo de la pantalla, en azul con fondo negro y letra chica.

  • Intentando meterse compulsivamente en lo que el usuario quiere hacer, o insistiendo en enseñarle lo que no quiere aprender. Software que parece un chico de 3 años que demanda atención o quiere demostrar que sabe. Que haga tal cosa si desea hacer tal otra, que mejor si lo hace así, que por qué no lo hace asá.

    El momento en que el usuario está utilizando el software para otra cosa no es precisamente el mejor para brindar capacitación.

    Hay que darle al usuario la posibilidad de que haga lo que quiera hacer de la manera en que lo desee. Lo divertido de los programas que cometen los errores mencionados (Microsoft se lleva las palmas, y el paquete Office el premio mayor) es que cuando el usuario realmente quiere propaganda o capacitación o consejos útiles o ayuda o información detallada no la encuentra, porque si el sistema no la ofrece automáticamente (y de forma molesta) no hay detective que pueda ubicarla.

Molestar con información a destiempo o fuera de contexto.

La medida de lo importante la da el objetivo del usuario: si el usuario está ingresando una factura, poco le importará que se ha ingresado un nuevo producto al stock, por más relevante que esto pueda ser para él en otro momento.

No saber atraer la atención del usuario cuando es necesario.

Si soy un contador y estoy apurado para cerrar ese balance con mi jefe mirando por encima del hombro, el mensaje "se está quedando sin espacio en la partición primaria de la unidad Z" titilando en letras rojas pasará absolutamente desapercibido, por más catastrófico que le pueda parecer a cualquier administrador del sistema.

Esto es un completo despropósito, sobre todo teniendo en cuenta que, en general, ése tipo de alarmas contiene un mensaje más pequeño a continuación, que usualmente reza "Guarde sus trabajo en una ubicación alternativa para evitar pérdida de datos".

¿Qué es más importante para el usuario y tiene más posibilidades de atraer su atención? ¿Que la unidad Z se queda sin espacio o que está a punto de perder su balance casi terminado?

Después el analista funcional no entiende cómo el usuario no vio eso.

Los mensajes tienen que estar orientados al usuario que los lee, y en lo posible (aunque ésto es mucho más difícil) relacionados con la tarea que está realizando. Una buena regla para la redacción de mensajes es que en vez de decirle al usuario lo que le pasa al sistema (que poco le importa) hay que decirle lo que a él le está pasando o le va a pasar.

Mensajes de error incomprensibles.

Este punto es típico, terriblemente típico (vinculé una linda colección de delirios en la entrada Mensajes de error), y que encima se nos vuelve en contra, porque aquí los que solemos quedar como idiotas somos nosotros, los desarrolladores.

Yo tengo dos reglas básicas:

  • La primera es de redacción: el título del mensaje de error es siempre "No se pudo [hacer lo que el usuario iba a hacer]", en palabras del usuario: "No se pudo ingresar la factura", "No se pudo imprimir el tícket", etc. Un mal ejemplo es "El recurso de impresion \\ADMNET\Imp\EPS345 no está disponible".

    El detalle, más pequeño, tiene que responder siempre a la pregunta "¿por qué?", también en palabras del usuario. Es como un diálogo: título - ¿por qué? - detalle del error. "No se pudo cerrar el balance" (¿por qué?) "No hay espacio suficiente en disco". ¿Ven que queda mejor?

  • La segunda tiene que ver con los errores no manejados, es decir, con los errores de programación. Cualquier intento de brindar información al usuario es peligroso, porque el sistema está en un estado desconocido, y puede pasar cualquier cosa (como muestran los ejemplos de la entrada anterior).

    Creo que lo mejor en este caso es el típico "Se ha producido un error inesperado" o algo así, y brindar un curso de acción (número de soporte, reintente tal cosa, pruebe tal otra). Pero ojo, no estupideces, sino cursos de acción razonables teniendo en cuenta que el usuario quiere completar una tarea. Es decir, llamar a soporte técnico es una opción, pero no la mejor en este sentido.

    Lo que voy a poner es brutalmente difícil, yo nunca lo implementé: pero si tengo un error inesperado, tengo que dar una solución de negocio de acuerdo a la operación que el usuario está haciendo. Ejemplo: si estoy en la carga de la factura, tengo que explicarle al usuario que tiene que verificar si se registró la factura ingresada, facturar a mano, y luego llamar a soporte o lo que sea. Es decir, primero le resuelvo el problema al tipo.

    Pero claro, lo anterior implica prácticamente un manual de incidencias para cada funcionalidad. Dije que era brutalmente difícil.

Una última aclaración: cuando el personal de soporte técnico está revisando la instalación, también es de alguna manera un usuario. Lo mismo cuando lo está instalando o cuando el administrador lo está configurando. Al igual que en los ejemplos anteriores, aquí también tenemos que utilizar el lenguaje del usuario, que en este caso es más técnico.

El mismo mensaje ambiguo de error inesperado que para el usuario común es más amable, para éste tipo de usuario es casi ofensivo, ya que la falta de información, que antes se traducía en simpleza, ahora le impide saber qué es lo que está pasando.

En general se utilizan ejemplos mencionando usuarios "comunes", pero no debemos olvidar que también hay usuarios u operarios (si les gusta la diferencia) que juegan un papel más técnico y que por ello deben recibir más información técnica. Como siempre, lo lógico, aunque también lo más difícil, es responder correctamente de acuerdo a la situación.

Para cerrar, digamos que es terrible crear un buen sistema que la pifie justo en el momento en que el usuario realmente lo está utilizando para algo. Y sobre todo si, como en el caso de los mensajes de error, es una cuestión fácilmente evitable durante el desarrollo. Simplemente es cuestión de pensar dos segundos antes de escribir esa frase maldita (o con faltas de ortografía) que nos hará quedar como bobos ante el cliente. ¿No vale la pena?

jueves, 18 de septiembre de 2008

Gestores vs. realidad.

Imprescindible leer "Engañemos a la realidad" de Un Punto Azul Pálido, en el que se desarrolla un inevitable paralelismo entre las causas de la catástrofe del Challenger y algunas situaciones que cualquiera en el desarrollo del software ha vivido (o vivirá, sólo hay que esperar un poco) alguna vez.

No dejen de leer (ya verán los motivos de la insistencia, tanto mía como de Improbable) el imperdible artículo "Cuando los ingenieros se ponen en la piel de los gestores" de Historias de la ciencia, en el que se explica el apéndice de Feynman al informe de la comisión investigadora de la explosión del transbordador el 28 de enero de 1986.

Y si creen que esto es uno de esos posts cortitos de relleno, con más razón lean todo... tiene demasiado que ver con nuestro día a día para mi gusto.

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).