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

martes, 7 de abril de 2015

Into the void.

Siguiendo con lo del post anterior, el desarrollo de “mi producto de prueba” entra oficialmente en la etapa de autobombo. Voy a tratar de no ponerme monotemático, así que el siguiente link es lo único que voy a poner explícitamente al respecto por ahora:

“Pasen y vean, qué lindas chucherías” (el que no entienda la referencia que busque la frase, vale la pena).

De alguna manera subestimé (para variar) el efecto psicológico del “lanzamiento”.

No había tenido que dar la cara hasta ahora. En mis trabajos anteriores picaba código que luego se testeaba e iba a producción donde fallaba miserablemente. Así fue siempre, igual que ahora. Pero la responsabilidad era compartida. La verdad sea dicha: las consecuencias inmediatas (llamados a media noche, apuros, gritos e insultos) solían recaer más en las áreas de soporte, testing y calidad (cuando había) que en mi escurridiza persona. No se puede hacer bugfixing con 20 monos desesperados gritándote alrededor y otros 20 por teléfono / skype / email / twitter / señales de humo avisándote de que el sitio no anda. Así que mientras yo arreglaba la cosa otros atajaban los sopapos.

Personalmente, esa división de tareas siempre me pareció (y me sigue pareciendo) bien. Pero ahora no hay con quién dividir el trabajo y, lo que es peor, nadie se desespera. Ojalá hubiese alguien tan necesitado de esta humilde herramienta (que feo suena eso) como para llegar al extremo del reclamo. Lo que sucede es algo peor: la nada misma. El vacío, fardos rodando, grillos a la luz de la luna.

Si te fuiste a dormir, tu sitio puede estar en llamas escupiendo 500 para todos lados que no te vas a enterar. ¿Los usuarios? Si te he visto no me acuerdo. Un amigo, un conocido o un familiar manda un mail con un “che, no anda” y con suerte te enterás a la mañana siguiente. Cada uno de esos desconocidos arrastrados a pulso, sangre, sudor y lágrimas (qué exagerado) ahora están viendo (si es que no se fueron ya) la aplicación retorcerse lastimosamente en medio de un infarto de javascript, con el pulgar en el Alt y el índice a punto de dar el Tab definitivo. Por lo menos en el corto plazo.

Por eso, si va a explotar (y al principio va a explotar), mejor que sea entre amigos dispuestos a dar una mano, avisar: probar de vuelta más adelate. Pero eventualmente hay que sacar la red y seguir haciendo piruetas. No es para tanto… si pinchó es que anduvo por un rato, ¿no?

jueves, 26 de marzo de 2015

Jugar en serio.

Desde hace un tiempo vengo jugando con la idea de “desarrollar productos”.

En mi universo de fantasía, mi cabeza bulle con innumerables ideas para SaaS (Software as a Service).

Todos los días (de mi universo de fantasía) se me ocurre una idea, y son todas geniales.

Las voy anotando. Cada tanto elijo una y la desarrollo hasta llegar al MVP (Minimum Viable Product). La implemento (así de fácil y divertido, con una sola palabra, una única acción) como servicio gratuito.

La promociono (otra acción puntual, única, instantánea) utilizando “círculos concéntricos”: empiezo con un grupo reducido de usuarios y, a través de prueba y error voy depurando, limando asperezas, agregando detalles. Una vez agotadas las correcciones (porque se agotan) paso a un círculo de promoción más amplio y vuelvo a empezar.

El desarrollo es incremental, constante, continuo. La base de usuarios se expande. Se genera el feedback suficiente para determinar funcionalidades por las que un subconjunto de ellos estaría dispuesto a pagar (subconjunto que existe y cuyo tamaño es directamente proporcional a la cantidad de funcionalidades, por supuesto). 

Cada usuario paga poco, muy poco. Digamos siempre menos de “10” (dólares, pesos, yenes… no sé, no importa), pero por mes. La cosa “pega” y alcanza un cierto nivel de tráfico, y complemento ingresos con publicidad.

Y ya es hora de dejar este producto estable, tomar otra idea y volver a empezar. La mayoría de los proyectos no llegarán a tanto, pero algunos pegarán, aunque sea mínimamente. Entre todos van dejando un nivel de ingreso que se vuelve razonable en un futuro a mediano plazo.

Me gusta esa sensación de vuelta a lejanas épocas en las que me sentaba en la Atari 800XL y programaba cualquier cosa que se me viniese a la cabeza por el gusto de hacerlo y nada más, plus reconocimiento social (¡los usuarios me adoran!) y monetario.

Eventualmente la pego con algo y vendo algún desarrollo por 7 u 8 cifras. Hago donaciones a proyectos filantrópicos y con el resto me dedico a recorrer el mundo en mi avión privado (con barra). Están invitados.

… y después suena el despertador y tengo que levantarme a trabajar.

Y mientras trabajo pienso que no parece tan fantástico. Parece realizable, incluso fácil.

Por suerte soy bastante escéptico, sobre todo con mis propias fantasías. Otro thread (el pesimista) levanta una interrupción y dice que si fuese tan fácil y divertido todo el mundo lo estaría haciendo (y con éxito). Muchos lo hacen y les va bien (se despierta el primer thread –el optimista-). Son el emergente (responde el pesimista), la punta de un iceberg de proyectos hundidos por los pocos recursos (tiempo, dinero), las malas ideas, las malas ejecuciones, la mala suerte… la puta vida de mierda (y siguen discutiendo en un abrazo mortal).

¿Una fantasía irrealizable o un modelo sustentable? Sólo hay una forma de saber.

Así que me decidí a jugar en serio. Agarré la idea menos prometedora y más divertida y me comprometí a llevarla a través de todo el recorrido, desde el MVP hasta la primera versión hasta la base de usuarios y de ahí hasta donde se pueda.

¿Por qué “la menos”? Para bajar expectativas. Para no desilusionarme ante la falta de éxito inmediato. Y para cometer todos los errores posibles con una idea que ni es original ni vale tanto ni es taaan difícil de implementar, porque (en la vida real) ideas no me sobran y cuando tenga otra (en realidad… no tenía ninguna otra), espero que mejor, no quiero quemarla en un proceso de aprendizaje desde 0.

No es el proyecto en sí lo que importa sino el camino recorrido, los errores cometidos, la experiencia de hacer por uno mismo todo aquello que antes hacían otros miembros de un equipo u organización: la definición, las pruebas, la promoción, el seguimiento, el jiu jitsu comercial y otro montón de cosas que ni siquiera sé que hay que hacer.

En eso estuve estos meses. Algunos de “ustedes” (supongo que somos más o menos  los mismos lectores de siempre) saben de qué la va. La mayoría no y por ahora vamos a dejarlo así, porque estamos en la etapa de los “pequeños círculos concéntricos”.

“Sabía” que la construcción de un sistema es apenas una parte de la cosa. Ahora sé que lo que “sabía” y lo que “sé” va entre comillas.

Una cosa es saber que hay que definir un MVP y que eso es “difícil”, a tratar de hacerlo y darse cuenta de que es MUY difícil.

Una cosa es saber que un proyecto compite con otros y con la necesidad de ingresos, y otra la tentación constante de dejarlo “hasta acá, total para prueba ya está bien” y volver a terreno seguro.

Una cosa es saber que es difícil atraer usuarios y otra estar sentado delante de la pantalla, con el sistema “a disposición de la humanidad toda” y… “¿y ahora qué?” La humanidad está ocupada en sus propias cosas.

Una cosa es saber que hay que hacer networking y otra no tener nada importante para escribir o no tener ganas de sentarse y escribirlo o tomarse el esfuerzo y que no suceda nada (que por otro lado es lo más probable) y juntar los pedazos para probar otra vez.

Y en cada paso hay errores y torpezas y un millón de piedras puntiagudas para pisar.

Termino con lo que quería empezar (se suponía que iba a escribir un solo párrafo de introducción a esto que sigue, pero bueno…): un punteo de lo nuevo que, más que aprender, “sentí en carne propia” durante estos meses.

  • “No sabes nada, Jon Snow”.
  • Uno define un producto en documentos y palabras y bocetos de pantallas y eso está más o menos bien… pero hasta cierto punto. Los documentos iniciales quedan rápidamente en el olvido. No vale la pena dedicarles mucho tiempo ni bajar mucho al detalle: el objetivo principal de la aplicación, un boceto así nomás de “la pantalla importante” y listo. El objetivo no es elaborar el documento sino la idea.
  • Personalmente, no funciono muy bien con el micromanagement del tiempo. Me entusiasmo por momentos, me desinflo por momentos. Es mejor respetar eso, pero manteniendo un balance: Hay que hacer algo todas las semanas, aunque sea forzado, y no dejar nada colgado mucho tiempo. 
  • Sí me funciona bien el establecer una meta a corto plazo: “lo próximo que hay que hacer es…”
  • Un dashboard es imprescindible. No puedo dejar de recomendar Trello.
  • Medir las horas es imprescindible. No puedo dejar de recomendar Toggl.
  • Definir y respetar hasta dónde llega el desarrollo para la primera implementación. A rajatabla. Y cumplirlo. A medida que voy armando la pantalla se me ocurren 10.000 formas mejores de hacerlo, sólo por contraposición con los problemas que veo en el armado actual… Pero esas otras formas también van a tener problemas y llevar a otras soluciones y… así no terminamos más. Se define la primera implementación, se hace y después vemos.
  • Después de un par de meses de desarrollo, la idea original puede parecer una mala idea. ¿No debería empezar de nuevo? No. Así como hay que probar que es una buena idea, también hay que probar que es una mala idea antes de dejarla por el camino. Y para eso hay que implementarla.
  • Otra vez (y van tres): respetar el MVP a rajatabla. Escribir en el backlog es muy terapéutico para descargar tensiones. 
  • Pero ojo, el backlog puede convertirse en una bolsa de gatos muy rápidamente. Hay que mantener el orden, priorizar, jerarquizar, descartar, agrupar, dedicarle un poco de tiempo cada vez, pero constantemente. Es un embole, sí.
  • Con el primer usuario cambia absolutamente todo. Lo que parecía usable es obvio que no, las buenas ideas resultaron malas y la sensación general es, otra vez, la de que esto no va a ningún lado (un pensamiento recurrente). Hay que perseverar, corregir y mejorar sin torcer el rumbo, resistir la tentación de “barajar y dar de vuelta”.
  • Después de la primera implementación se acabaron las ideas, hay que seguir a los usuarios: se arregla o mejora lo que se usa, se implementa lo que nos reclaman. Primero hay que hacer que funcione, pero después todo se trata de que se entienda y se use, y eso es mucho más difícil.
  • Y si nadie lo usa y nadie reclama… insistir con el autobombo y la promoción.
  • Y si nadie lo usa y nadie reclama (después de un tiempo)… bueno, ahí quedó.
  • … pero meter un feature que nadie quiere de vez en cuando sólo porque es divertido mantiene el entusiasmo.
  • En resumen: POCO de todo para la primera versión: pocos documentos, poca funcionalidad, poco código, poca complejidad, poco riesgo, poco tiempo perdido.
  • Salvo paciencia. MUCHA paciencia.


sábado, 21 de marzo de 2015

The Gervais Principle

Para aquellos a los que les gusta (o no pueden para de mirar) The Office y se ríen (o lloran) con Dilbert y están dispuestos a leer largo duro y parejo, y a pelearse con un inglés un poquito más difícil que la media:

The Gervais Principle, Or The Office According to “The Office”

Para tentarlos y de paso tenerlos a mano para cuando se necesite, dos botones de muestra:

hughMcLeodCompanyHierarchy compLifeCycle

Ojo que es más serio que lo que parece. Lo encontré leyendo el blog de Erik Dietrich, también recomendable para agregar al feed.

PD para cuando lean un poco de eso: yo me considero claramente un looser, y -creo- es fácil distinguir a los sociopaths (antes de que alcancen el nivel al que sólo ellos pueden llegar, si no pierde la gracia)... lo divertido es ponerse a discutir quiénes son los clueless. Tengan en cuenta que no todos son tan "puros" como Michael.

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?

martes, 25 de mayo de 2010

La integración de la base de datos. Una cura definitiva para esta enfermedad crónica.

Martín Gargiulo Una colaboración de Martín Gargiulo (*).


Es muy común que los equipos de desarrollo trabajen sobre un ambiente de base de datos compartido. ¿Quién no ha sufrido las consecuencias de los desfasajes entre *la* base de datos y el resto del proyecto? ¿Quién no ha sido víctima –o victimario– de algún improperio cuando, en pleno desarrollo, un cambio en alguna propiedad u objeto de la base deja al resto del código pataleando en el aire?

Nunca, desde mis tempranos inicios, a través de diferentes proyectos en varias compañías, tuvieron estas cuestiones la suficiente importancia como para quitarle el sueño a nadie. Siempre, esto es lo realmente grave, parecieron ser lo normal. Estaba ante una enfermedad crónica.

Desarrollar implica agregar código, depurar, refactorizar y repetir estas operaciones un número de veces hasta obtener una determinada pieza de software. A nadie se le ocurre, hoy en día, que no se disponga de un sistema de control de código fuente, que permita a cada integrante del equipo trabajar localmente y luego, una vez concluida la construcción de la pieza, incorporar sus cambios al repositorio común.

Cada desarrollador debe trabajar en su propio espacio. Siguiendo esta premisa, ¿por qué la base de datos, siendo parte esencial del producto, no se incluye en él? ¿Por qué no es natural que cada desarrollador trabaje sobre su propio ambiente de base de datos?

Surge un problema. Si cada programador opera sobre su propio ambiente, ¿cómo obtienen los demás integrantes del equipo las modificaciones al esquema realizadas localmente? Del mismo modo que lo hace con el resto del código de la aplicación: mediante el repositorio de código fuente.

Por ello es indispensable que los scripts de base de datos estén incluidos en el controlador de código fuente, al igual que todo el resto del código. Es a través de estos scripts que se deben formalizar los cambios en la base que completan la programación de una pieza de software.

La base de datos, a diferencia de una pieza de código común, contiene estado (para eso está). Por eso, además del código para la creación de sus objetos, se deben tener en cuenta scripts de inserción de datos básicos para la aplicación (tablas paramétricas, tablas maestras) y, eventualmente, algún pequeño set de datos de prueba.

…continuará…


(*) Andrés dice: por fin, y luego de dos años de arrastrarme, alguien ha escuchado mis ruegos, dignándose a brindar… ¡una colaboración! Y en buena hora, que en este lugar ya se empezó a juntar el polvo. Esperemos que sea la primera de una larga serie y dé comienzo a una maratón de talentosos desarrolladores ansiosos por ayudarme a mantener vivo este humilde espacio (esperar es gratis, dicen).

jueves, 18 de marzo de 2010

Visibilidad.

Gran Proyecto (con mayúscula), muy “visible” para la gerencia de Megaempresa, vital para la subsistencia de NoTanGranPeroGranConsultoraOSoftwareFactory, y por tanto muy “visible” también para la gerencia de ésta última.

Desde nuestro punto de vista (de sistemas) no es más que otra aplicación de formularios que le pegan una y otra vez a una base de datos, más un sinfín de reportes que vienen a sacar lo que los formularios pusieron.

Otra gran, torpe, enmarañada, traicionera, e incansable generadora de un aburrido caudal de incidentes de fácil solución técnica, y que serían de fácil solución a secas si no fuera por el hecho de que más o menos la mitad de ellos contradice lo que indica la otra mitad.

Así que una horda de programadores armada con papel secante intenta contener ese río, resuelve uno y otro y alterna entre “A” y “no A” en un proceso que arroja tanta agua como recoge.

Hay otro caudal que lo alimenta, un caudal de programadores, analistas, managers y demás sacos de carne “recursos” que van pasando, pasando y pasando a ritmo creciente. Si no fuera por este otro caudal que aporta toda la energía desperdiciada en aquél ida y vuelta inútil el proceso descrito sería el santo grial del movimiento continuo.

Los gerentes, como esto es importante, están en contacto con “el equipo”, los conocen, interactúan con ellos más que con el resto. Es a esto a lo que se le llama “visibilidad” y que consiste en que, de vez en cuando, se abre un espacio en esos cielos y asciende alguno, (probablemente aquél con más habilidad para el alpinismo que para la programación) dejando al proyecto, que es realmente importante, en manos del resto que ahí queda, papel secante en mano (mientras el alpinista saluda desde lo alto).

Así es que, gracias a la “visibilidad” del proyecto los otros reman, reman y reman hasta que, hartos y sin esperanza, abandonan en busca de algún horizonte en donde sus entrenados brazos sean mejor recibidos. Aquellos en los que el entrenamiento no hace efecto y que por lo tanto carecen de otras expectativas (que los hay), esperan pacientemente su turno para el ascenso.

Unos y otros son reemplazados velozmente, así que eso que llamamos “equipo” (y que no es más que un fotograma de una película interminable) apenas puede acumular una muy vaga idea del negocio que el sistema viene a sostener, ya que el escaso conocimiento penosamente adquirido por prueba y error se va tan rápido como se acumula. Del negocio se sabe que es grande y millonario y que no es una verdulería ni un almacén de ramos generales, pero eso no alcanza para dividir las aguas y tomar la mitad de correcciones que corresponde para parar la rueda.

En fin… cualquiera que trabaje en sistemas un tiempo es expuesto a una de las más generosas fuentes de fina ironía que esta vida puede dar: el trabajo y, sobre todo, el trabajo en sistemas y, sobre todo, el trabajo “corporativo”. Si este tiempo es más o menos largo el sistémico, si sobrevive y lo sigue siendo (o mejor dicho, para sobrevivir y seguir siéndolo) habrá desarrollado un fino sentido de la ironía y del humor, o un cinismo a toda prueba, o todo eso junto. Por suerte algunos explotan genialmente esos efectos colaterales esas habilidades, y así tenemos a Dilbert (adivinen de dónde sacó Scott Adams la inspiración para sus personajes), o a Sinergia Sin Control, o al mayor repositorio de esfuerzo sin sentido y desperdicio de inteligencia jamás creado, TDWTF.

Porque lo más irónico de todo es que funciona y que buena parte de nosotros hemos vivido o viviremos de ello, y que es lo que termina financiando los juguetes (lenguajes, metodologías, herramientas, patrones, arquitectura) con los que nos entretenemos mientras, distraídamente, hacemos girar la rueda. En fin…

…que al mundo nada le importa
yira…
yira…

sábado, 6 de marzo de 2010

Visual Studio 2010: T4 Text Templates. Una introducción y recursos para empezar.

Creo que hace ya un par de semanas que ha salido Visual Studio 2010 RC. En el trabajo comenzamos a prepararnos hace ya un tiempo, utilizando la beta, y recién ayer actualizamos a esta última versión.

Mi percepción es que está sobre todo más estable. Han habido más correcciones que cambios, lo que es esperable dado el grado de madurez del producto. Por lo que hizo a mi trabajo en estas semanas (.Net MVC, mucho $(javascript), templates) noté muchas mejoras en el intellisense y la depuración en javascript -que hasta ahora era imposible de utilizar por lo limitado- aunque sigue sin ser la gran cosa comparado con FireBug, mi caballito de batalla.

Pero volvamos al tema del título. Las plantillas T4 para generación de código. De las novedades del 2010, ésta es la que más impacto real (y positivo) ha tenido en los proyectos, automatizando infinidad de pequeñas tareas tediosas y repetitivas.

Dije “pequeñas” porque, para las “grandes” tareas automáticas y repetitivas (generación de entidades, table gateways, row gateways, manejo de recursos de texto, etc.) teníamos ya infinidad de opciones, desde herramientas propias de .Net (como los recursos incrustados o los archivos .resx con sus clases asociadas), frameworks (Entity Framework, NHibernate), diseñadores (como los clásicos diseñadores de Datasets), plugins (no soy de utilizarlos, así que ni ejemplos puedo dar), hasta otros sistemas de generación automática de código, muy similares a T4 (CodeSmith).

Pero el tema con las herramientas mencionadas es que requieren mantenimiento, instalación, depuración, y una inevitable y nada despreciable curva de aprendizaje… pero sobre todo, que son pesadas y abarcativas (para utilizarlas razonablemente tenemos que seguir su paradigma, adaptando nuestro esquema desarrollo a éste en mayor o menor medida). Son muy buenas herramientas, pero nadie va a meterse con CodeSmith (sólo por dar un ejemplo), si no lo está ya utilizando para otra cosa, sólo para actualizar una lista de constantes o una enumeración de ciertos recursos.

Las ventajas de T4 en el Visual Studio 2010 tienen que ver justamente con ésto: están integradas al entorno de desarrollo y su sintaxis es muy intuitiva (a cualquier desarrollador le alcanzará con saber que “es como hacer una página .aspx” para empezar), perfectas para automatizar esas pequeñas, molestas, y usualmente numerosas tareas de copiar, pegar y modificar.

No voy a seguir con mucho detalle, ya que hay mucho (y mejor) escrito sobre el tema. Pero sí los voy a dejar con algunas recomendaciones de punto de partida:

T4: Text Template Transformation Toolkit: este post de Oleg Sych es un muy buen lugar para arrancar velozmente, y tiene además su propia (y extensa) lista de recomendaciones.

La documentación en MSDN es enrevesada y difícil de seguir, como toda la de Microsoft, pero está bueno pegarle una leída, no para empezar sino para conocer más en detalle.

Punto aparte y mención especial para este otro post de Oleg Sych, en el que hace referencia desde el template al modelo de objetos de Visual Studio (EnvDTE) que abre la puerta a posibilidades realmente interesantes.

martes, 2 de marzo de 2010

.Net MVC + jQuery: manejo de excepciones. IV: el lado del cliente.

Finalizamos el post anterior de esta serie con la infraestructura necesaria para atrapar en javascript los errores y excepciones que puedan producirse del lado del servidor y “canalizarlos” hacia tipos conocidos con una codificación similar a la que utilizamos en c#.

Recordemos el ejemplo final. Teníamos, en javascript, una excepción base:

ExceptionBase = function (type, message) {
    this.Type = type;
    this.Message = message;
};

De la que luego derivamos dos excepciones más específicas, una para excepciones de negocio (la que arrojaría nuestro código en el caso de que una operación supere cierto monto permitido, por ejemplo):

ProgramException = function (message, netException) {

    this.Reasons = [];
    this.NetException = netException;

    if (typeof message != "undefined" && message != null)
        this.Message = message;

};
ProgramException.prototype = new ExceptionBase("ProgramException", "Operation Error");

y la otra para errores “fatales”, es decir todos aquellos que indican que la aplicación ha arribado a un estado no contemplado y que por lo tanto no puede seguir utilizándose (que ha cascado, vamos):

FatalException = function (message, netException) {
    this.NetException = netException;
    if (typeof message != "undefined" && message != null)
        this.Message = message;
}

FatalException.prototype = new ExceptionBase("FatalException", "Unexpected Error");

Para ver cómo utilizamos estas clases veamos nuestro ejemplo de llamada $.ajax, donde se establece la diferencia entre errores de negocio y fatales, lanzándose una excepción u otra dependiendo el caso. Más concretamente la sección “success” (lo siguiente es sólo el fragmento correspondiente a “success” dentro de la llamada a $.ajax):

  //... (etc) ....

  success: function (response, status, xhr) {

   //Exception handling.
   var responseStatus = xhr.getResponseHeader("RESPONSE_STATUS");
   var ex = null;
   if (responseStatus == "ApplicationException") 
   {
      var netEx = JSON.parse(response);
      ex = new ProgramException(netEx.Message, netEx);
      ex.Reasons = netEx.Reasons;
   }
   else if (responseStatus == "UnexpectedException") 
   {
      var netEx = JSON.parse(response);
      ex = new FatalException(netEx.Message, netEx);
   }

   if(ex!=null)
   {
      if (async)
         ShowException(ex)
      else
         throw ex;     
   }

   //...(continúa)...

Esta infraestructura de excepción base y derivadas sería una complejidad decorativa y sin sentido si no tomamos, en algún lugar del código, una decisión en base a ese tipo que tanto esfuerzo nos lleva determinar.

La diferencia entre una excepción y otra, desde el punto de vista del front-end, radica en cómo se le presenta al usuario. En nuestro ejemplo, esa tarea le corresponde a la función ShowException:

ShowException = function (exception) {
    //non-fatal exceptions
    if (exception.Type == "ProgramException") {
        var $messageBox = GetMessageBox();
        $messageBox.find("#MessageBoxReasons").html(exception.Reasons.join( "<p/>" ));
        var buttons = {};
        $messageBox.dialog({
            autoOpen: true,
            modal: true,
            buttons: { "Ok": function () { $messageBox.dialog("destroy"); } },
            closeOnEscape: true,
            title: exception.Message,
            close: function () { $messageBox.dialog("destroy"); }
        });
    }
    //fatal exceptions.
    else if (exception.Type == "FatalException") {
        var displayHtml = "<h1>" + exception.Message + "</h1>";
        var doc = window.top.document;
        doc.open();
        doc.write(displayHtml);
        doc.close();
    }
}

Esta función simplemente evalúa el tipo de excepción que recibe y toma las decisiones necesarias. En este ejemplo, muy simple y esquemático, tenemos dos secciones: la primera parte del if crea una ventana utilizando $.dialog para mostrar prolijamente las excepciones de negocio, y la segunda parte (luego del else) “rompe” la pantalla, limpiando todo el html y dejando solamente el mensaje de error.

La función auxiliar “GetMessageBox” simplemente construye el html necesario para mostrar el cuadro de diálogo:

GetMessageBox = function () {
    var top$ = window.top.$;

    var $messageBox = top$("#MessageBoxContainer");
    if ($messageBox.length > 0)
        return $messageBox;

    var $messageBoxContainer = top$("<div />").appendTo(top$("body"));
    $messageBoxContainer.attr("title", ""); 
    $messageBoxContainer.css("display", "none");

    var $innerTable = top$("<table/>").appendTo($messageBoxContainer);
    $innerTable.css("width", "100%");

    var $innerTableFirstRow = top$("<tr/>").appendTo($messageBoxContainer);

    var $innerTableFirstRowFirstCell = top$("<td/>").appendTo($innerTableFirstRow);

    var $messageBoxImage = top$("<img/>").appendTo($innerTableFirstRowFirstCell);
    $messageBoxImage.attr("alt", "");
    $messageBoxImage.attr("src", ""); //TODO: set src.

    var $innerTableFirstRowSecondCell = top$("<td/>").appendTo($innerTableFirstRow);
    $innerTableFirstRowSecondCell.addClass("messageBoxMessage");

    var $messageBoxMessage = top$("<div/>").appendTo($innerTableFirstRowSecondCell);
    $messageBoxMessage.attr("id", "MessageBoxMessage");

    var $innerTableSecondRow = top$("<tr/>").appendTo($innerTable);

    var $innerTableSecondRowFirstCell = top$("<td/>").appendTo($innerTableSecondRow);
    $innerTableSecondRowFirstCell.attr("colspan", "2");
    $innerTableSecondRowFirstCell.addClass("messageBoxDescription");

    var $messageBoxDescription = top$("<div/>").appendTo($innerTableSecondRowFirstCell);
    $messageBoxDescription.attr("id", "MessageBoxDescription");

    var $innerTableThirdRow = top$("<tr/>").appendTo($innerTable);

    var $innerTableThirdRowFirstCell = top$("<td/>").appendTo($innerTableThirdRow);
    $innerTableThirdRowFirstCell.attr("colspan", "2");
    $innerTableThirdRowFirstCell.addClass("messageBoxReasons");

    var $messageBoxReasons = top$("<div/>").appendTo($innerTableThirdRowFirstCell);
    $messageBoxReasons.attr("id", "MessageBoxReasons");

    return $messageBoxContainer;
};

Notarán que en la sección en la que se muestra el error fatal no se utiliza jQuery. En estas situaciones tenemos que evitar cualquier referencia externa al código que se está ejecutando, ya que sólo sabemos que ha ocurrido un error inesperado y no conocemos el estado de los demás componentes de la aplicación (como por ejemplo el plugin de jQuery. El error podría deberse a que el cliente no pudo descargar el script de jQuery). Aquí tenemos que esforzarnos en codificar, siempre dentro de lo posible, código que funcione en circunstancias extremas… y la mejor manera es que sea extremadamente simple.

Hasta aquí hemos abarcado las situaciones más comunes, y sólo en el marco de llamadas $.ajax al servidor:

  • Excepciones de negocio generadas del lado del servidor.
  • Errores producidos en del lado del servidor.

Parece una estructura demasiado compleja para, finalmente, hacer un “if” y determinar si rompemos la pantalla o mostramos un cuadro de diálogo, cubriendo apenas los dos puntos de arriba. Es un buen momento para repasar sus ventajas:

  • El código y la metodología en javascript es asimilable a lo que estamos acostumbrados a hacer en c#. Esto hace que la implementación sea natural,  fácilmente “explicable” y “recordable”. Verán que el resto de los puntos aquí presentados se aplican tanto a nuestra solución de javascript como al manejo de excepciones en c#.
  • Modularidad/Desacoplamiento: su único punto de anclaje con respecto al código correspondiente a la funcionalidad de negocio del sistema son los bloques try…catch en las funciones de manejo de eventos.
  • Reutilización: cualquier excepción que requiera el mismo tratamiento que las ya implementadas puede representarse con alguna de las clases ya existentes.
  • Extensibilidad: si queremos que el sistema reaccione a un nuevo tipo de excepción sólo tendremos que codificar “en las puntas”: allí donde se lanza la excepción (el lugar del throw) y allí donde se maneja (ShowException).

Por otro lado hay mucho para explorar de aquí en más. Recordemos que ni siquiera cubrimos los casos mínimos para una aplicación “aceptable”. Veremos que al contemplar más situaciones (errores y excepciones de lado del cliente, en la comunicación, etc.) y al integrar más funcionalidad (por ejemplo mostrando un mensaje de error especial en ambientes de desarrollo o testing), en el manejo de “pequeños detalles” y casos especiales, será cuando este esquema muestre sus verdaderas ventajas.

lunes, 22 de febrero de 2010

.Net MVC + jQuery: manejo de excepciones. III: Atrapar errores del lado del servidor y comunicarlos controladamente al cliente.

[Continuación de .Net MVC + jQuery: manejo de excepciones. II: El problema.]


works-on-my-machine Espero que se haya entendido el planteo desarrollado en los posts anteriores de esta serie (preguntar es gratis, de cualquier manera)… lo que sin duda ha quedado claro es que el problema es retorcido.

Lo que sigue ahora es mi resolución del tema, más que perfectible por cierto (si alguien tiene sugerencias…), pero lo suficientemente funcional y probada como para ostentar orgullosamente el sello que he aplicado a esta entrada.


El primer paso es poder diferenciar, del lado del cliente, si el error o excepción se produjo durante la ejecución del código que procesa una respuesta o si la comunicación en sí fue fallida.

La importancia de lo anterior radica en que en el primer caso manejaremos la excepción del lado del servidor y podemos devolver al cliente información detallada y en un formato conveniente sobre cómo proceder:

  • si es un error de sistema o una excepción de negocio o un error de validación, si mostrar información detallada o no, si bloquear la aplicación y obligar al usuario a reiniciar (o a joderse), etc.
  • si es un error en la comunicación el cliente deberá tomar sus propias decisiones, de acuerdo con la información que posea en ese momento, pero ya contamos con un dato fundamental: la operación no llegó al servidor, por lo que podemos estar seguros de que nada ha sucedido “del otro lado”.

Recordemos que de forma predeterminada el servidor devolverá un error 500 y el código HTML de la pantalla de error asignada a ese código (por defecto aquella tan bonita de .Net con las letras en rojo catástrofe y el cuadro amarillo con detalles, más las inútiles indicaciones que todos conocemos). Esto no es precisamente un formato fácil de interpretar desde el código a la hora de tomar decisiones en el front-end.

Necesitamos interpretar desde el código la excepción porque tenemos que tener en cuenta que muchos tipos de errores “de aplicación” diferentes. Cada situación puede requerir diferentes propiedades en el objeto Exception para transmitir la información relevante del caso. Consideremos por ejemplo:

public class ShowCaseException : ApplicationException
{
   private List<string> reasons = new List<string>();

   public ShowCaseException()
      : base() { }

   public ShowCaseException(string message)
      : base(message) { }

   public ShowCaseException(string message, Exception innerException)
      : base(message, innerException) { }

   public List<string> Reasons
   {
      get { return reasons; }
   }
}

La excepción anterior contiene una propiedad extra (“Reasons”) con una lista de mensajes para presentar al usuario. Esto es algo que suelo utilizar bastante, ya que si durante una operación se producen varios problemas de validación es bueno presentárselos al usuario todos juntos y no solamente el primero y obligarlo a resolverlo antes de probar de vuelta y encontrarse con otro y así, multiplicando la cantidad de intentos.

Pero no podemos tratar con cada tipo de excepción en particular, viendo en cada caso qué información devolver y cómo. Una solución más práctica es devolver la excepción serializada en formato JSON, más un flag en el header de la respuesta que la diferencie de las respuestas “normales”:

internal static class ExceptionHandlingHelper
{
 public enum ResponseStatusEnum
 {
  Normal,
  ApplicationException,
  UnexpectedException
 } 

 internal static void ApplicationInstance_Error(object sender, EventArgs e)
 {
  Exception exception = HttpContext.Current.Server.GetLastError();
  if (exception is ApplicationException)
   ResponseHeadersHelper.CurrentHeaders.Add("RESPONSE_STATUS", ResponseStatusEnum.ApplicationException.ToString());
  else
  {
   Log.WriteException(exception);
   ResponseHeadersHelper.CurrentHeaders.Add("RESPONSE_STATUS", ResponseStatusEnum.UnexpectedException.ToString());
   if (![DEBUG_MODE])
    HttpContext.Current.Session.Abandon();
  }

  HttpContext.Current.Response.Clear();
  HttpContext.Current.Response.Write(ExceptionHandlingHelper.SerializeException(exception));
  HttpContext.Current.Server.ClearError();
 }
 //.... etc ....
}

Tenemos entonces tres estados posibles para una respuesta:

  • Error de negocio: ResponseStatusEnum.ApplicationException, son las excepciones lanzadas mediante un throw en nuestro código, y que deben derivar, por convención, de ApplicationException.
  • Error inesperado: ResponseStatusEnum.UnexpectedException, son todas las demás, originadas en throws por validaciones de consistencia en nuestro código o directamente desde el framework.
  • Normal: todo bien.

Así que lo primero que hacemos en nuestra rutina de manejo de excepciones es conseguir la excepción y determinar de qué tipo es, agregando un header en la respuesta (“RESPONSE_STATUS”) para que el cliente javascript pueda diferenciarlas fácilmente de las respuestas normales. Si es un error inesperado podemos, adicionalmente, registrarlo en el log y limpiar la sesión del usuario.

Luego se modifica la respuesta que por defecto enviaría el servidor, utilizando Response.Clear(), Server.ClearError() y escribiendo en el objeto Response la excepción serializada en JSON.

La serialización de la excepción es un poco molesta dado que el uso común de JavascriptSerializer arroja una excepción por culpa de la propiedad TargetSite, que se vuelve recursiva en cuanto al tipo. Pero, por otro lado, habíamos establecido que en un ambiente de producción no es conveniente enviar toda la información de la excepción ya que puede exponer datos internos a un ocasional atacante. Así que tenemos que implementar nuestra propia serialización para esta clase.

Una forma sencilla es recorrer el primer nivel de propiedades del objeto Exception y dejar que JavascriptSerializer se encargue del resto. El siguiente ejemplo es una prueba de concepto –no pretende ser una guía de lo que es o no seguro enviar al cliente-, solamente omite en la serialización las propiedades “InnerException”, “StackTrace” y “Source”. Tal vez habría que agregar más restricciones, probando siempre qué información estamos enviando en cada caso.

private static string SerializeException(Exception ex)
        {
            JavaScriptSerializer serializer = new JavaScriptSerializer();

            StringBuilder exSerialized = new StringBuilder("{");
            PropertyInfo[] exProperties = ex.GetType().GetProperties(BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance);
            foreach (PropertyInfo property in exProperties.Where(p => p.Name != "TargetSite"))
            {
                if (![DEBUG_MODE] &&                    
   (property.Name == "InnerException" || property.Name == "Source" || property.Name == "StackTrace"))
                    continue;

                exSerialized.AppendFormat("\"{0}\":", property.Name);
                serializer.Serialize(property.GetValue(ex, null), exSerialized);
                exSerialized.Append(", ");
            }

            if ([DEBUG_MODE]) 
            {
                exSerialized.AppendFormat("\"ToString\":");
                serializer.Serialize(ex.ToString(), exSerialized);
                exSerialized.Append(", ");
            }

            exSerialized.Remove(exSerialized.Length - 2, 2);
            exSerialized.Append("}");
            return exSerialized.ToString();
        }

La información así serializada es fácil de acceder desde el cliente ya que se convierte automáticamente en un objeto similar a Exception, pero en javascript.

Notarán que en el ejemplo incluí, para entornos de desarrollo, una propiedad extra que contiene la cadena devuelta por el método ToString de Exception, que es lo que se suele mostrar por pantalla en caso de errores. Tendremos, en resumen, disponibles en javascript casi todas las propiedades del objeto Exception más una propiedad “ToString” que contiene una cadena con toda esa información en un formato legible para el usuario/desarrollador.

Volvamos a javascript para ver cómo manejamos la respuesta ahora. Partimos del ejemplo del post anterior completando algunos de los “placeholders” que habíamos dejado, ya que ahora sabemos exactamente cómo llega la información desde el servidor:

function Save( data, callback )
{
 var async;
 if( typeof callback == "undefined")
  async=false;
 else
  async=true;
 
 var returnValue = null;
 
 $.ajax({
  url: "Comments/Save",
  data: data,
  async: async,
  success: function (response, status, xhr) {

   //Exception handling.
   var responseStatus = xhr.getResponseHeader("RESPONSE_STATUS");
   var ex = null;
   if (responseStatus == "ApplicationException") 
   {
      var netEx = JSON.parse(response);
      ex = new ProgramException(netEx.Message, netEx);
      ex.Reasons = netEx.Reasons;
   }
   else if (responseStatus == "UnexpectedException") 
   {
      var netEx = JSON.parse(response);
      ex = new FatalException(netEx.Message, netEx);
   }

   if(ex!=null)
   {
      if (async)
         ShowException(ex)
      else
         throw ex;     
   }

   //Normal response handling.
   if (async)
    callback(response)    
   else
    returnValue = response;
  }
  error: function(XMLHttpRequest, textStatus, errorThrown) {
   var ex = [GET_EXCEPTION_INFO];
   if(async)
    ShowException(ex)
   else
    throw ex;
  }
 });

 //async => returnValue == null;
 //sync  => returnValue == response;
 return returnValue;
}

La parte modificada es la de la sección “success” -previa al “Normal response handling”-. Verán que ahora, para determinar si hubo una excepción se consulta el encabezado de la respuesta que incluimos en el manejo de excepciones del lado del servidor -xhr.getResponseHeader("RESPONSE_STATUS")-. No importa qué excepción de negocio se haya producido, este header aparecerá siempre que ésta derive de ApplicationException. En la serialización JSON se devolverán todas las propiedades del tipo específico –en un entorno de desarrollo, recordémoslo siempre- ya que utilizamos JsonSerializer.

Por ejemplo, si la excepción lanzada es aquella ShowCaseException de la que hablamos al principio, sabremos por el header que deriva de ApplicationException y al mismo tiempo conservaremos la propiedad “Reasons”, específica del tipo, al serializar. Si prueban verán que aparece en el objeto netEx cuando se ejecuta var netEx = JSON.parse(response);

Cuando deserializamos la excepción la encapsulamos en un objeto conocido para el cliente. Esto es para reflejar el hecho de que para el cliente de javascript sólo existen dos objetos derivados de Exception relevantes: los de negocio (excepciones “de programa” y los de sistema (“fatales”). Para ello creamos sus clases correspondientes, inspiradas un poco en las de .Net (una excepción base y dos derivadas), pero en estilo javascript. Esto no es necesario, pero me resultó muy cómodo (veremos luego):

//Exception base
ExceptionBase = function (type, message) {
    this.Type = type;
    this.Message = message;
};

//Program Exception
ProgramException = function (message, netException) {

    this.Reasons = [];
    this.NetException = netException;

    if (typeof message != "undefined" && message != null)
        this.Message = message;

};
ProgramException.prototype = new ExceptionBase("ProgramException", "Operation Error");

//Fatal Exception
FatalException = function (message, netException) {

    this.NetException = netException;

    if (typeof message != "undefined" && message != null)
        this.Message = message;

}

FatalException.prototype = new ExceptionBase("FatalException", "Unexpected Error");

Son estas dos clases las que nos permiten codificar luego este tipo de instrucciones:

var netEx = JSON.parse(response);
ex = new ProgramException(netEx.Message, netEx);
ex.Reasons = netEx.Reasons;

Hasta aquí, un paso más en la batalla. Nos resta:

  • Determinar cómo tratar los errores en la comunicación.
  • Determinar cómo mostrar toda esta información (hasta ahora no hicimos nada con el método “ShowException”).

Hasta la próxima, nos leemos. (Actualización: sigue en .Net MVC + jQuery: manejo de excepciones. IV: el lado del cliente.)

miércoles, 17 de febrero de 2010

.Net MVC + jQuery: manejo de excepciones. II: El problema.

[Continuación de .Net MVC + jQuery: manejo de excepciones. I: Requerimientos.]


Tengamos presente nuestro ejemplo de manejo de errores y excepciones en $javascript (jQuery):

$("#Save").click(function () {
 try {
  $("#CommentForm").valid();
  Save({
   User: $("#User").val(),
   Date: $("#Date").val(),
   CommentText: $("#CommentText").val()
  });
 }
 catch (ex) {
  ShowException(ex);
 }
});

Por el lado de las excepciones, tenemos en principio aquellas generadas por nuestro código. En el ejemplo, serían aquellas que arroje la función valid() luego de verificar los datos ingresados por el usuario.

Luego tenemos aquellas que se generen durante la llamada al servidor. Asumiendo que Save() hace una llamada $.ajax para enviar los datos al servidor, tenemos en realidad dos escenarios posibles: sincrónico y asincrónico.

Si la llamada al servidor es sincrónica Save() debe hacer un throw en algún punto del código con la información necesaria para que sea atrapado por el catch del código de arriba y ShowException muestre un mensaje al usuario.

Si la llamada es asincrónica deberíamos pasarle a Save() un parámetro callback, una función a ser invocada cuando el servidor responda. Por ejemplo (muy esquemáticamente):

Save({
 User: $("#User").val(),
 Date: $("#Date").val(),
 CommentText: $("#CommentText").val(),
 function(response){
  alert(response);
 }
});

En este segundo caso es el método Save() el que debe internamente contemplar la posibilidad de excepciones y decidir, de acuerdo con la respuesta, si invoca al callback o a ShowException.

Creo que en algunas situaciones es necesario el uso de llamadas asincrónicas, pero que en general es más fácil manejar la lógica sincrónica, por algo es por lo que se empieza al aprender a programar. Así que personalmente prefiero que cada función javascript que se comunica con el servidor dé la opción de pasar o no una función de callback.

Si juntamos estas dos opciones y hacemos un boceto de Save() quedaría algo por el estilo de:

function Save( data, callback )
{
 var async;
 if( typeof callback == "undefined")
  async=false;
 else
  async=true;
 
 var returnValue = null;
 
 $.ajax({
  url: "Comments/Save",
  data: data,
  async: async,
  success: function (response, status, xhr) {

   //Exception handling.
   if( [RESPONSE_IS_EXCEPTION] )
   {
    var ex = [GET_EXCEPTION_INFO];
    if (async)
     ShowException(ex)
    else
     throw ex;     
   }

   //Normal response handling.
   if (async)
    callback(response)    
   else
    returnValue = response;
  }
  error: function(XMLHttpRequest, textStatus, errorThrown) {
   var ex = [GET_EXCEPTION_INFO];
   if(async)
    ShowException(ex)
   else
    throw ex;
  }
 });

 //async => returnValue == null;
 //sync  => returnValue == response;
 return returnValue;
}

En el ejemplo se asume que si se pasa un parámetro de callback la llamada debe ser asincrónica. En este caso se ejecuta $.ajax, el flujo sigue en la línea siguiente y returnValue será null cuando Save() termine. En algún momento posterior, cuando el servidor responda, jQuery invocará al código que se pasa en el parámetro success de $.ajax. De ser una llamada sincrónica se ejecutará $.ajax e inmediatamente el código del parámetro success.

Si se produce un error o una excepción no controlada del lado del servidor $.ajax invocará a la función especificada en el parámetro “error”. Lo mismo sucederá ante errores en la comunicación en sí.

Tanto en error como en success debemos determinar, de alguna manera (que también veremos luego, en el ejemplo estos agujeros blancos están entre corchetes), si hubo una excepción o error. Si la hubo armamos un objeto “ex” con la información que necesite ShowException. Si no, devolvemos el valor.

La diferencia entre sincrónico y asincrónico en este punto es que en el caso de ser una llamada sincrónica hacemos un throw (que atrapará el catch de Save) o llamamos a la función callback o devolvemos el valor a través del retorno de la función, mientras que en el caso de la llamada asincrónica debemos manejar aquí mismo la excepción o llamamos a la función callback. Si hiciéramos un throw en este punto, durante una llamada asincrónica, la excepción sería atrapada por el navegador y el usuario vería un error de javascript (en el mejor de los casos. En el peor, no vería nada y pensaría que sus datos fueron grabados correctamente).

Hasta aquí las excepciones. Vamos ahora a contemplar la posibilidad de errores. Si no tienen en claro la diferencia les recomiendo –otra vez- hacer un paréntesis y leer este post.

A modo de resumen de ese post, recordemos que si al momento de grabar los datos “La fecha del comentario ingresado corresponde a un informe ya aprobado”, eso es una excepción (una situación contemplada en el código en forma de validación, un throw en nuestro código del lado del servidor. Es un mensaje que tiene utilidad para el usuario, que debe indicar que él ha cometido un error y no el sistema). Pero si al momento de grabar el sistema intenta insertar un valor nulo en donde no se puede, eso es un error (una excepción para el framework o la base de datos, pero una situación no contemplada en nuestro código).

¿Dónde deberíamos esperar errores? Fácil, en todos lados. Lo inesperado puede por definición aparecer en cualquier momento. Pensemos qué situaciones pueden darse y qué hacer en cada caso:

  • Un error en el código de negocio del lado del servidor: en este caso podríamos enviar una respuesta que le indique al código javascript (en success) que se ha producido un error de sistema y a ShowExceptions que debería “romper” la pantalla impidiendo que el usuario continúe utilizando la aplicación o mostrar información detallada, dependiendo de si estamos en un ambiente de desarrollo o testing.

    Un ejemplo sería el intento de utilizar una variable que está en nulo, pero también que la base de datos no esté disponible o muy ocupada, o un timeout de base de datos. Para este caso tendríamos una excepción del lado del servidor, pero que no deriva de ApplicationException.

  • Un error en la infraestructura del lado del servidor: en este caso el problema no está en nuestro código y no es atrapado por él. Es el que se daría si el servidor web está muy ocupado. Puede ser antes o después de la operación, pero en todo caso no es controlado por nosotros.

    Aquí no tendríamos una excepción de .Net del lado del servidor, sino que éste, sin que podamos evitarlo, devolvería un error HTTP (típicamente un error 500, aunque podría ser otro). En este caso es jQuery el que atraparía el error e invocaría a la función especificada en el parámetro “error” de la llamada $.ajax.

  • Un error en la llamada, del lado del cliente: en este caso la llamada $.ajax jamás llega al servidor (por ejemplo si la url es incorrecta o está mal formada).

    Aquí hay un punto de debate: dado que el error se produce enteramente del lado del cliente no tenemos posibilidad de registrarlo, sólo nos enteraremos de él a través de una comunicación del usuario (por mail, teléfono, o como sea). La única información de la que dispondremos será aquella que le mostremos por pantalla… pero no es una buena práctica mostrar detalles internos al usuario final… Pero esto es último es discutible, ya que si vamos a suponer un error provocado por un usuario malicioso es de todas maneras información a la que él puede acceder, ya sea que se la hagamos más fácil o más difícil.

    Lo que es seguro, volviendo al tema, es que ShowExceptions tiene que manejar también esta situación y, por lo menos, asimilarla a la indicada en los puntos anteriores y darle el mismo tratamiento.

  • Un error en el código javascript, del lado del cliente. Es decir, un error de javascript ya sea en la función Save() o en el manejador del evento click. Este tipo de errores sería atrapado por el catch más externo de todos, el de la función que asociamos al evento click.

    En este caso el objeto “ex” que ShowExceptions recibirá como parámetro no estaría armado por nosotros sino por el motor de javascript (y lo que es peor, será ligeramente diferente entre navegadores, e incluso entre distintas versiones del mismo navegador). Así que tenemos que contemplar también la posibilidad de que a ShowExceptions le llegue este otro objeto “más o menos desconocido” proporcionado por el motor de javascript.

  • Un error en el código de manejo de errores. Es decir, un error de javascript adentro de ShowExceptions. Créanme que el andamiaje necesario para todo este control es lo suficientemente complejo como para que tenga sus propios errores, y éste es el peor escenario: el error en ShowExceptions nos estaría ocultando aquél que le dio origen, y deberemos resolverlo antes de enfrentarlo. Dado que ShowExceptions es la última fortaleza del funcionamiento del sistema deberá ser a prueba de balas: ya no importa cómo ni en qué situación, su misión es mostrarnos todos los errores que se hayan producido (propios y ajenos) sin perder información y asegurarse de que la pantalla del lado del cliente quede bloqueada. Tarea ingrata si las hay, pero alguien tiene que hacerlo.

Resumamos, como para ir terminando este post ya demasiado extenso, las situaciones que debemos contemplar:

  • Excepciones generadas desde nuestro código en javascript (las que se generen en la función valid() de validación de datos ingresados por el usuario).
  • Llamadas sincrónicas y asincrónicas. La diferencia está en hacer un throw o llamar a ShowExceptions en la función que se comunica con el servidor.
  • Errores o excepciones generados del lado del servidor. En este caso la comunicación ha sido exitosa.
  • Errores en la comunicación en sí, provocados por la conexión (“se cayó la red”) o por el código (“url mal formada).
  • Errores en el código javascript que implementa una funcionalidad específica.
  • Errores en el código javascript de control de errores.

La vida no es fácil… luego seguimos. (Actualización: sigue en .Net MVC + jQuery: manejo de excepciones. III: Atrapar errores del lado del servidor y comunicarlos controladamente al cliente.)

lunes, 15 de febrero de 2010

.Net MVC + jQuery: manejo de excepciones. I: Requerimientos.

Me pasé las últimas semanas trabajando sobre la beta de Visual Studio 2010 (acaba de salir el Visual Studio 2010 RC) armando un esquema de trabajo (un mini-framework) para proyectos MVC, en un intento de materializar la experiencia acumulada en otros proyectos en la forma de herramientas encapsuladas, probadas y listas para reutilizar.

Uno de los puntos que más trabajo me ha llevado es el manejo de excepciones. Un tema traicionero, simple a primera vista pero complicado y retorcido a medida que se avanza. Siempre lo tuve cubierto (es decir, sin graves problemas) con un poco de alambre, pegamento y parches aquí y allá, y quise aprovechar este nuevo comienzo en limpio para pensar un esquema más consistente y por lo tanto más sólido y fácil de mantener.

Comencemos, como corresponde, por los requerimientos. ¿Qué pretendo de a la infraestructura de un proyecto en cuanto a manejo de errores y excepciones (teoricé sobre la diferencia hace un tiempo, en Errores y excepciones)?

En principio, un uso estándar y transparente. Por ejemplo, en el botón “Save” de una vista, el programador debería codificar, en javascript (+jQuery, por supuesto):

$("#Save").click(function () {
 try {
  $("#CommentForm").valid();
  Save({
   User: $("#User").val(),
   Date: $("#Date").val(),
   CommentText: $("#CommentText").val()
  });
 }
 catch (ex) {
  ShowException(ex);
 }
});

En el ejemplo anterior, el bloque try…catch captura los errores de validación disparados por la instrucción $("#CommentForm").valid(); (es el plugin jQuery Validation) y la función ShowException los muestra en una forma amigable al usuario. Este no es el comportamiento “normal” de la función valid() de jQuery Validation, hay que codificarlo.

Pero no sólo eso. La función Save del ejemplo debe enviar los datos al servidor y el servidor debe pasarlos al modelo de negocio y esperar excepciones. Excepciones del tipo “La fecha del comentario ingresado corresponde a un informe ya aprobado” y cosas por el estilo, tan molestas para nosotros como necesarias para nuestros clientes. Es decir que se comunicará con una acción en un controlador, supongamos:

namespace ShowCase.Web.Controllers
{
    public class CommentsController : Controller
    {
  public bool SaveComment(CommentsModel model)
  {
   model.Save();
   return true;
  }
    }
}

Vemos que la acción devuelve siempre true. Esto es porque espera que los problemas se comuniquen a través de las excepciones. Un ejemplo esquemático para el método Save del modelo sería:

public class CommentsModel
{
 public void Save()
 {
  FinancialReport financialReport = FinancialReports.GetByDate(this.Date);
  if(financialReport.Status == ReportStatus.Approved)
   throw new ApplicationException("The comment's date points to an approved report.");
  
  FinancialReports.SaveComment(financialReport, this);
 }
}

El manejo de este tipo de excepciones generadas del lado del servidor debería ser, para el programador de la funcionalidad, idéntico al de las generadas del lado del cliente. Así que tenemos que llegar, de alguna manera, desde el throw en el método Save de CommentsModel, al catch en nuestra función de javascript.

Para complicar las cosas, agreguemos los errores. Estamos hablando ahora de verdaderos errores del sistema: errores de codificación, de conexión con la base de datos, de conexión entre el cliente y el servidor web, de javascript, etc. Errores del tipo que solemos llamar catastróficos, pero que yo prefiero denominar inesperados, como para restarle dramatismo al asunto (un cliente que no llamaría a las 3 de la mañana por un error “inesperado” tal vez sí lo haga por uno “catastrófico”).

Es el tipo de errores, en definitiva, que deberían “romper” o “cerrar” la aplicación, puesto que se ha arribado a un estado no contemplado, y cualquier acción posterior podría derivar en datos corruptos, inconsistentes, o –por lo menos, y más probablemente- en nuevos, más molestos y más extraños errores.

La función ShowException del ejemplo debería diferenciar entre las excepciones de negocio y los errores. Para los primeros mostrará un mensaje al usuario, y para los segundos deberíamos grabar un archivo de log, y podríamos establecer dos comportamientos posteriores:

  • uno para desarrollo o pruebas, mostrar por pantalla toda la información disponible acerca del error, y dejar que la aplicación siga abierta (el desarrollador o tester decidirá por sí mismo si puede continuar o no sin problemas),
  • y otro para producción, limpiar completamente la pantalla y mostrar un mensaje de error escueto invitando al usuario a reiniciar la aplicación o a llamar al administrador del sistema (siempre y cuando no seamos nosotros).

Es importante que, en entornos de producción, no sólo la función ShowException no muestre más de lo que debe, sino que el servidor mismo no envíe más información que la estrictamente requerida.

Todos estos requerimientos deben implementarse, dentro de lo posible, en una forma transparente al programador de la funcionalidad. Personalmente considero esta transparencia como algo muy importante, y la resigno (obligando al programador a derivar de ciertas clases, a implementar o a invocar ciertas funciones especiales) sólo cuando la alternativa es imposible o extremadamente más complicada. Cuando programamos una funcionalidad tenemos la cabeza puesta en facturas, aprobaciones, balances, fechas de cierre… en ese contexto, dónde fue generada la excepción o qué tipos de excepción hay y cómo deben transmitirse desde su origen hacia el cliente, son sutilezas que de requerir tratamiento especial serán seguramente pasadas por alto.

Pero, en definitiva, qué tan importante es esta transparencia o cuándo una alternativa es “imposible” o “extremadamente complicada” dependerá de cada uno. El hecho es que gracias a .Net MVC y jQuery podemos lograr un nivel muy aceptable de transparencia a través de diferentes puntos de intercepción y extensión convenientemente ubicados en la infraestructura del proyecto.

Suficiente para una primera entrega. En la próxima –en breve- analizaremos más en detalle los requerimientos en busca de las situaciones más comunes y los problemas que deberemos enfrentar al momento de picar el código.

Continúa en .Net MVC + jQuery: manejo de excepciones. II: El problema.

martes, 12 de enero de 2010

Desafíos.

Un desafío es el deseo de alcanzar un objetivo ubicado un poco más allá (no demasiado, porque si no ya sería un sueño) de nuestras capacidades actuales. Implica mejora, autosuperación.

Para mí, lo motivador de mi trabajo es lo desafiante que puede llegar a ser. Frecuentemente se nos plantean proyectos parecidos a aquellos que enfrentamos en el pasado pero con algún matiz, algún punto oscuro, alguna particularidad especial que nunca hemos implementado. Estas particularidades representan nuevos problemas y son las que hacen al proyecto desafiante en esa medida justa que nos permite enfrentarlo con cierta confianza sin llegar al extremo de la sensación del salto al vacío.

Cuando no es así simplemente me aburro, y no tolero bien el aburrimiento. A mediados del 2009 di el salto y cambié de trabajo. En su momento actué guiado más por esos sentimientos –de aburrimiento- racionalizados que por razones objetivas. De alguna manera ese creciente aburrimiento ante los problemas y situaciones han alimentado este blog -inmejorable vía de escape- en la primera parte de este año (y a cuya ausencia se debe, en cierta medida, la poca producción bloggera de la segunda).

Finalmente el tiempo puso las cosas en perspectiva y el nuevo entorno me da un punto de comparación. Me llama la atención lo nimio, lo poco importantes que eran los problemas y situaciones que ponía como excusa del cambio. Ahora es claro que esa necesidad de cambio no estaba dada por el entorno sino que provenía de mí mismo, que su origen no era la gravedad o repetición de algún problema o situación sino el nulo desafío que éstos representaban para mí. Pareciera que “nulo desafío” implica que todo era “fácil”. Nada más lejos de lo que quiero decir, aquí vamos.

El punto central de toda esta cantinela es que uno no sólo “es parte de” sino que también construye la situación que lo rodea. Decir “no habrán nuevos desafíos” es, en realidad, una vuelta retórica por la que esquivamos la responsabilidad en la construcción de esa situación al ubicar a los desafíos en el entorno cuando en realidad están en nosotros.

Los desafíos no están allí, uno no los encuentra sino que los crea. Es decir que constantemente enfrentamos los desafíos que nos hemos creado, como Don Quijote contra los molinos de viento.

Un entorno de trabajo desafiante no es, por si queda alguna duda, algo objetivo. Lo que para unos es tierra fértil es desierto para otros, no por la dificultad o no de los problemas objetivos a resolver –finalmente el único problema objetivo lo constituyen los requerimientos de un cliente-, sino por la predisposición o no de cada uno a crear desafíos en ese entorno en un momento determinado.

Pareciera ser, entonces, que a nivel institucional hay poco que se pueda hacer para crear un entorno desafiante, dado que esta característica reside en cada persona y no en ese espacio creado grupalmente.

En alguna medida es así. No se pueden “crear” desafíos institucionalmente. Pongamos un ejemplo: se plantea “ser referencia en el uso de la tecnología X” como un “desafío en el que todos tenemos que estar comprometidos”. Esto es en realidad apenas un objetivo y una expresión de deseo que puede ser origen de un desafío motivador para algunos pero indiferente o incluso fuente de sentimientos negativos –inseguridad, presión, rechazo- para otros.

Lo que sí se puede hacer institucionalmente es ubicar a cada integrante en un puesto en donde los problemas objetivos –los requerimientos de sus clientes externos o internos- estén alineados con su predisposición a crear desafíos en torno de esos problemas y proporcionar los recursos, la libertad de acción o la guía que necesite cada uno para superarse. Esto será mucho más productivo que el vano intento de imponer desafíos a diestra y siniestra como si todos fuéramos iguales y nos interesaran las mismas cosas.

Pero esto implica un conocimiento muy personal de cada integrante del equipo por parte de la institución… mejor dicho por parte de las personas que definen los puestos en las instituciones y que –erróneamente o no- generalmente están muy alejados del resto de la empresa, si no física por lo menos profesionalmente.

La solución común, dado que a nivel institucional no puede haber un conocimiento personal profundo de cada uno, es dividir la responsabilidad entre persona e institución: la responsabilidad institucional es de abrir las puertas, proporcionar los caminos para que cada uno busque o cree ese lugar desafiante, y está en cada uno la responsabilidad y el esfuerzo de motorizar esa búsqueda. Motorizar es buscar, proponer, convencer, buscar alternativas, concretar.

Es tan común en sistemas la queja constante sobre las posibilidades de desarrollo profesional (“de encontrar nuevos desafíos”) como la inacción de quien las enuncia en cuanto a la creación o búsqueda de ese espacio fértil en desafíos profesionales.

De ser sincero el deseo, de existir esa búsqueda y de ser infructuosa, debería continuar por fuera (pero ya basta de quejarse), la posibilidad de que no haya intersección entre lo que una persona encuentre como desafiante y las necesidades de una institución en particular existe, y no hay más que hacer que seguir buscando.

martes, 5 de enero de 2010

La queja.

Soy quejoso por naturaleza. Siempre le encuentro el pelo al huevo y, a falta de errores o problemas más graves, hago de un charquito una inundación…

A veces exagero un poco. Cuando mis compañeros de trabajo están –razonablemente- ya un poco hartos de mis cancioncitas plañideras les recuerdo, como para equilibrar un poco la balanza, que si bien me quejo

a) estoy en general encargándome activamente o lidiando con el problema, así que eso me da cierto derecho a una breve descarga emocional –a veces no es tan breve como debería-, y

b) estoy constantemente pensando y tratando de proponer soluciones –a veces peores que el problema, pero bueno… prueba y error, y sin error no hay prueba- y que

c) soy el primero en ponerle el hombro a cualquier intento de mejora, incluso cuando no crea que vaya a funcionar –probemos, con un poco de suerte me equivoque-.

Sí, a veces exagero y soy demasiado duro con algún temita en una solución que, en general, funciona bien. Pero bueno, es parte mi forma de motivar –correcta o no, efectiva o no, ésa es- y de motivarme a concentrarse no en lo que salió bien sino en lo que salió mal, que es lo que merece más atención.

Lo que es seguro es que es menos probable –a veces se requiere de más de un golpe- que me vuelva a dar con la misma piedra… aunque luego encuentre otra con la que impactar.

Por otro lado, hay personas en las que la queja es sólo eso, una queja. Una enumeración interminable de problemas, errores y malas decisiones que no las mueve a la acción o al cambio en lo más mínimo.

Estas personas piensan en el pelo antes de hacer el huevo. Como el “pelo a futuro” desmotiva, se quedan en la inacción y la queja se vuelve constante –porque sin acción nada cambia-, autosuficiente e inmortal.

Si es la queja la que lleva a la inacción o si es la excusa para la inacción, es imposible de saber. Pero es en todo caso indiferente, si el resultado es el mismo y es la nada.

Pero ojo, porque el quejoso no es mentiroso y no necesariamente tiene malas intenciones. Normalmente los problemas sobre los que la queja se asienta son reales, la práctica de la observación pasiva lo ha vuelto experto en su detección.

Así que –para mí- tampoco es prudente hacer oídos sordos a su cantinela. Lo que debemos evitar –a veces harto difícil- es quedar atrapados girando en la órbita de su parálisis.

Una solución imperfecta es siempre mejor que nada. Incluso una solución equivocada es mejor que nada, es una posibilidad menos en el camino de la prueba y error. Hacemos sistemas, no estructuras de concreto, y siempre podemos volver atrás y buscar otra salida. El tiempo insumido, al que usualmente calificamos de “perdido”, no será tal hasta que nos demos por vencidos.

lunes, 14 de diciembre de 2009

Monkey Business.

Son casi humanos... somos casi monos, mejor dicho.

Visto en La Pastilla Roja

Me deja pensando, al trasladar la situación a nuestra vida laboral, en que –si bien entre otros factores- las reglas de juego tienen un peso fundamental para determinar si será la colaboración, la competición o la indiferencia la característica predominante en un entorno de trabajo.

En todo caso estoy bastante seguro de que las reglas pesan más que las características personales. Somos ante todo animales sociales –al punto en el que sólo sobrevivimos en sociedad-, mucho más influenciables por los que nos rodean que –en conjunto- ellos por nosotros.

¿Se pueden alcanzar altos niveles de calidad –esa esmeralda perdida- en el desarrollo de software sin colaboración, sin verdadero trabajo de equipo? Yo creo que sí, pero es mucho, muchísimo más difícil que en un ambiente colaborativo.

Los costos de un ambiente de “indiferencia” en el que cada uno “sólo hace lo suyo” se ven reflejados en complejas estructuras de control superpuestas que coordinan el trabajo: líderes de esto y gerentes de lo otro, supervisores de lo de más allá y más acá, capas y capas de jefes de jefes cuyo trabajo es apenas un poco más que distribuir información y controlar el correcto ensamblaje de subproductos que se construyen en la base de la pirámide aisladamente, sin visión de conjunto.

Se podría decir que parte del trabajo de esa jerarquía es, justamente, proporcionar una visión de conjunto. El problema es que, para el momento en el que el flujo de trabajo llega a ese nivel de revisión es un poco demasiado tarde para cambiar nada.

Para el momento de revisar la calidad –siguiendo las metodologías tradicionales- ésta está prácticamente determinada –sí, claro que podemos corregir desvíos funcionales… ya sabemos lo que sucede con la calidad del código durante esas correcciones-. Si el producto no cumple con las expectativas –de calidad, no sólo las de cumplimiento de requisitos funcionales- la única solución real es el retrabajo.

La solución propuesta generalmente es dividir el desarrollo en pequeñas etapas y revisar la calidad en cada una de ellas –siempre al final- con la esperanza de que la “suma de las calidades” sea provechosa –siempre al final-, y si así y todo las etapas presuponen productos demasiado amplios, las subdividimos… y luego ponemos a un responsable de cada división y de cada etapa, y ya estamos donde empezamos: jefes de jefes de jefes y poco trabajo real.

En un ambiente de colaboración las cosas son diferentes. La revisión es constante porque nadie es indiferente a lo que lo rodea. Cada corrección es mínima pero se dan constantemente. Es mucho más probable que la calidad final sea –para bien o para mal- toda la que el equipo pudo dar en un momento determinado. En esta situación el retrabajo cobra otro sentido: no es la corrección de lo que –en teoría- podría haberse hecho bien desde un principio sino la materialización del aprendizaje acumulado en el camino.

Pero son las reglas las que determinan si esa colaboración es posible. Si la ruta, -correcta o no, eso se verá al final- está predeterminada a través de un diseño milimétrico, el combustible de horas/hombre está calculado como para llegar con el tanque vacío y encima el vehículo no tiene ventanillas de poco sirve ponerse a pensar si hay o no un árbol en el camino, más vale rezar y esperar que cuando nos bajemos estemos en donde queríamos estar. ¿Parece difícil, no?

miércoles, 25 de noviembre de 2009

Complejo.

Hace semanas que vengo demorando una entrada sobre la complejidad. Es que hay tanto que decir sobre la complejidad que la cosa se vuelve un poco compleja… y soy poco dado a las cosas complejas.

Así que procrastinando otra vez el bendito post –que a estas alturas ya sé que nunca voy a escribir-, picando de aquí y allá en el reader, me encuentro con un -ya viejo- artículo de Joel: The Duct Tape Programmer.

Sencillamente imperdible, un compendio de frases impactantes delicadamente hilvanadas:

Sometimes, you’re on a team, and you’re busy banging out the code, and somebody comes up to your desk, coffee mug in hand, and starts rattling on about how if you use multi-threaded COM apartments […] and you have no friggin’ idea what this frigtard is talking about, but he just won’t go away, and even if he does go away, he’s just going back into his office to write more of his clever classes […]

Ya ven para donde apunta:

One principle duct tape programmers understand well is that any kind of coding technique that’s even slightly complicated is going to doom your project.

Y básicamente eso es todo lo que hay que decir al respecto de la complejidad. Me hizo acordar a esta otra gran frase, que no recuerdo muy bien de dónde saqué:

“Los buenos programadores resuelven los problemas complejos, los grandes programadores los evitan”.

Resolver un problema es ganar una batalla. Eliminar la necesidad de resolverlo es evitar la guerra. Es como la depuración: no se trata de meter más código sino de quitar el que sobra, no se trata de conocer más patrones, frameworks, herramientas, plugins y la mar en coche, se trata de encontrar la combinación más simple que nos permita implementar la funcionalidad necesaria, y de simplificarla todavía más.

Para evitar un problema es imprescindible crear el ambiente propicio para esas preguntas que ponen incómodos a todos: “¿realmente hace falta hacer esto? ¿por qué? ¿para qué? ¿hay otras posibilidades? ¿qué pasa si no hacemos nada?” Son esas preguntas las que llevan al pensamiento lateral, y es por eso que la aparición de respuestas tautológicas nos alertan tempranamente de que estamos perdiendo el rumbo: “Porque sí”, “Porque el cliente lo pidió así” (es casi decir lo mismo), “No sé, pero hay que hacerlo”, “Esto ya está pensado”…

Lo complejo se torna complicado muy rápidamente, casualmente en el preciso momento en el que los ingeniosos desaparecen.

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?

miércoles, 28 de octubre de 2009

La influencia de la orientación al producto o al cliente en el desarrollo de software - II: La elección de la tecnología.

Esto viene a seguir la serie inciada en el post anterior: I: Los muertos se dejan atrás, que es recomendable leer primero.


La elección de la tecnología.

Durante la vida del desarrollo de un producto de software es inusual, aunque cíclico y fundamental, el momento en donde surge la necesidad de determinar la tecnología a utilizar.

Es el tipo de decisiones que hay que tomar en el momento de la reingeniería. Momento que se suele demorar hasta lo inevitable, por lo que los intervalos entre éstas se miden en años, si no en lustros o décadas.

Las razones son variadas y pueden ser tan técnicas como la degradación del código y la obsolescencia del hardware o software de base, o funcionales, como exigencias del mercado demasiado difíciles de implementar en la tecnología actual o la aparición del fantasma de imagen a “viejo y obsoleto”.

Para una empresa orientada al producto una reingeniería es una gran revolución, y suele venir acompañada de grandes cambios y resistencias en el equipo de desarrollo. Integrantes aferrados a la tecnología y metodología en retirada verán sus posiciones de poder o confort atacadas por aquellos familiarizados con las nuevas. La decisión, entonces, afectará inevitablemente a las relaciones de poder establecidas durante años y a las que se generen durante los próximos.

No es, en resumen, el momento de hacer experimentos. El proceso debería comenzar con la investigación de las tecnologías establecidas (nadie recomienda jugarse por aquellas más novedosas) y seguir con pruebas de factibilidad, y prototipos, y más pruebas y…

En una consultora, por otro lado, se inician pequeños o medianos proyectos todo el tiempo, y cada uno es propicio para la experimentación con nuevas metodologías, frameworks, herramientas, patrones… Un error implicará un proyecto tormentoso o (muy inusualmente) fracasado, a lo sumo un par de víctimas fatales, pero nada demasiado terrible.

Lo que degenera en… un paisano ´e cada pueblo. Y sí. Tengo que reconocer que la sensación de tener asignada una “pequeña” tarea sobre un proyecto X, bajarlo del control de código fuente y abrirlo es como la que producen esas escenas en las películas de terror en las que la mano se acerca al picaporte de la puerta… y se acerca… y se acerca… y lo gira… y la puerta se abre y… y entonces uno respira aliviado o grita (“my eyes!”). Hay de todo. Lindo, feo, malo y bueno, y muchas veces hay de todo en el mismo proyecto, en diferentes secciones o funcionalidades.

Pero también es cierto que profesionalmente uno se mantiene al día. Y las herramientas decantan. A la hora de los grandes proyectos (con suerte) las decisiones son más medidas y basadas en experiencia acumulada en proyectos reales (aunque más pequeños) y no sólo en meras pruebas o prototipos.

Al trabajar durante largo tiempo sobre el mismo producto y con la misma tecnología, los desarrolladores tienen la oportunidad de hacerse expertos en ella, con todas las ventajas (mejores resultados, menores tiempos de desarrollo, proyectos “tranquilos”, estimaciones precisas) y desventajas (obsolescencia, encasillamiento, monotonía, pocos desafíos) que esto puede conllevar (que no necesariamente se darán unas u otras).

En contrapunto, una consultora es caldo de cultivo de experimentadores casi compulsivos (el que mucho abarca poco aprieta, dicen), y proyectos cual criatura de Frankenstein, rejuntes de diferentes frameworks y tecnologías cosidos y emparchados de manera grosera…

…o cosidos prolijamente, pero en todo caso grandes contenedores de soluciones redundantes. Es por haber trabajado largo tiempo con un conjunto reducido de frameworks, patrones y tecnologías que conozco a fondo sus posibilidades y desconfío de las herramientas complementarias y complejas que vienen a solucionar problemas extremadamente simples.

Si el peligro al desarrollar un producto es la obsolescencia o el fracaso en la implementación de una reingeniería (y una dolorosa “vuelta atrás”, o un ciclo corto hasta la próxima), el peligro de la experimentación excesiva al trabajar en proyectos relativamente cortos es el desperdicio de experiencia (descubrir siempre nuevos problemas en vez de solucionar los conocidos) y una dispersión de tecnologías que desemboca en un stock de proyectos que nadie puede entender completamente (a menos que sea un experto en Silverlight, .Net, Servicios, jQuery, NHibernet, Active Record, MVC y la mar en coche) y que por tanto sólo pueden ser parchados una y otra vez.

Como siempre, la solución es huir del riesgo, de los extremos. El faro es la simplicidad, y nos alejamos de ella tanto cuando forzamos a una tecnología (tal vez ya obsoleta) a hacer cosas que no existían cuando fue pensada o cuando nos pasamos de rosca innovando y metemos en la misma bolsa (a nivel proyecto u organización) más herramientas de las que podemos nombrar en cinco minutos.

jueves, 22 de octubre de 2009

Plugimanía, plugiadicción, plugidependencia, pluginitis.

jQuery está buenísimo. Lo uso todos los días, lo dije una y otra vez, y no me cansaré de repetirlo.

Su mayor fortaleza, opino, no radica en su reducidísimo (como corresponde) core sino en la amplísima comunidad de desarrolladores que aportan, oficial o extraoficialmente, nuevas funcionalidades encapsuladas en forma plugins.

Yo imagino que, como en toda comunidad, se sigue la regla del 90-10: 10% aporta activamente y 90% utiliza las funcionalidades provistas “as is” o a lo sumo con un poco de manoseo. Entonces, si derivamos la cantidad de desarrolladores-usuarios a partir de la cantidad de desarrolladores-aportadores obtendremos… no sé, un montón de gente.

Los usuarios-desarrolladores son aquellos que encuentran en jQuery la forma de implementar en cinco minutos aquella funcionalidad que estaban buscando. Es decir que llegan a jQuery a partir de una necesidad concreta que usualmente satisfacen rápidamente para proseguir con su camino, habiendo sumado una herramienta más a su caja de experiencias y dispuestos a volver a utilizarla allí donde consideren que sea más apropiada que otras.

Luego tenemos a los plugimaníacos, aquellos quienes ya han utilizado intensivamente la herramienta y por ello buscan en jQuery el plugin mágico que resuelva algún nuevo problema. Son los que lo crean si es que no lo encuentran o mejoran uno ya existente si es que no cubre totalmente sus necesidades… o por simple y sana diversión. Es aquel 10% del que hablábamos.

Una porción menor de desarrolladores siente que tiene que hacer eso tan difícil en jQuery, que necesita hacerlo en jQuery. Desarrolladores que prefieren implementar un nuevo plugin aún ante la existencia de herramientas o soluciones más apropiadas para el requerimiento concreto en el que están trabajando.

Ahí donde una pequeña animación en flash o un applet de java o silverlight parezcan soluciones más razonables o naturales veremos a los adictos decir “eso puede hacerse con jQuery” y poner manos a la obra, prestos a demostrar sus (en general extraordinarias) habilidades y a defender el orgullo de su herramienta (¡hablamos de jQuery, malpensados!) ante las demás. Son, del 10% de aportantes activos, el 1% que lleva jQuery al límite y hacia nuevos horizontes.

Pero, dentro de estos últimos, otra minoría, los plugiadictos, no sólo se bandean para el lado de lo extremadamente complejo, sino que también lo hacen hacia lo extremadamente simple. Si son de buscar en el mundo jQuery se habrán encontrado con más de uno que ha creado algún plugin que encapsula la más trivial y estándar de las tareas o que ha utilizado una ferrari (del tipo jqGrid) para la más aburrida de las tablitas no paginadas.

Hasta aquí toda gente normal, simpática, o a lo sumo un tanto extravagante. Pero cuando avanzamos en la progresión exacerbando un tanto más estas conductas pasamos de castaño a oscuro y entramos en el terreno de las patologías. Citaré dos de ellas: la plugidependencia y la pluginitis. Es posible, como se verá, que un caso extremo de la primera desemboque en la segunda.

El plugidependiente no es (ni puede ser, se verá) un aportador al repositorio de jQuery. El plugidependiente, debido al uso y abuso de la herramienta (ya sea motivado por él mismo o por su entorno) ha olvidado (o nunca aprendido) que existen cosas llamadas HTML, Javascript o DOM.

Lo reconocemos por un clásico afán de probar que “eso no puede hacerse” mediante la búsqueda infructuosa de un plugin de jQuery que lo haga. En su mundo aquello que jQuery no pueda hacer no puede hacerse, y punto.

Otras características remarcables, en casos extremos, son el desconocimiento de HTML más allá de los tags más básicos (aquellos que sirven de soporte a plugins de jQuery, como DIV y TABLE) y recurrentes intentos de utilizar sintaxis de jQuery en otros contextos (Javascript, o más extremo aún: C#, Visual Basic o cualquier otro lenguaje). Intentos que suelen culminar con un asombrado “¿ah, eso es de jQuery?”.

En el extremo absoluto de esta patología el desarrollador desconoce jQuery como extensión y cree que $(“DIV”).each( function(){…} ) es la sintaxis propia de Javascript o parte de HTML.

Como sucede con todas las herramientas y cosas en general, el abuso vuelve nocivo aquello que en el uso es beneficioso. Llegamos a la pluginitis.

El primer síntoma es una página web que tiene un título y una tablita y tarda unos 5-10 segundos en descargar. ¿Qué sucede? Pues nada, que tiene referencias a jQuery, jQuery UI, jqModal (pero… ¿no hay algo parecido en jQuery UI?) y unas 10 o 20 referencias más a archivos .js externos sin minimizar que no se utilizan absolutamente para nada… tal vez la página ni siquiera tenga un script, sino que simplemente… bueno, pluginitis.

Otros síntomas son: un browser (que no es el IE) que se arrastra, puro contenido generado dinámicamente del lado del cliente, imposibilidad de seguir la generación de ese código o de averiguar siquiera cual de las 3 o 4 extensiones parecidas de las incluidas en la página es la que está dibujando ese contenido.

¿Cómo salir? El primer paso es reconocer el problema. El segundo buscar ayuda y el tercero es una dura combinación de sacrificio, paciencia y constancia.

De mi parte, espero que el pequeño esfuerzo de escribir este post se justifique por haber ayudado, aunque sea a un sólo plugidependiente o pluginoso, a abrirse a una nueva vida. Si ése es tu caso, agradeceré tu comentario.