tag:blogger.com,1999:blog-42510928268414793332024-02-20T16:05:58.588-03:00desDesarrolloDeSoftwareHablamos de desarrollo de software, y de cualquier cosa que venga a cuento de eso. Un poco en joda, un poco en serio, depende el humor del día.AcPhttp://www.blogger.com/profile/16568867429107885585noreply@blogger.comBlogger574125tag:blogger.com,1999:blog-4251092826841479333.post-51986280575262987522015-04-20T13:45:00.000-03:002015-04-20T13:45:00.438-03:00Broken Culture<p>Los norteamericanos tienen una capacidad increíble para estandarizar cualquier realidad y encajarla en un set de historias prefabricadas, por más compleja que sea. Si, todo el mundo estandariza y encaja realidades en historias… es sólo que ellos lo hacen especialmente bien.</p> <p>Mis feeds de “desarrollo” en inglés están llenos de telenovelas: los programadores somos la mucama inteligente, buena y fea; los managers, la malvada bomba sexy; la empresa (el capitalismo), el millonario que parece malo por estar atrapado sus redes pero que en el fondo es bueno. El final es cantado: la mucama inteligente se gana el corazón del millonario a fuerza de honestidad, bondad y trabajo duro, y en la última escena es la que está más buena de todas. La mala cae envuelta en sus propias telarañas y termina desenmascarada como la chupasangre inútil que es, desterrada en quién sabe dónde.</p> <p><a href="http://tim.dreamwidth.org/1890351.html" target="_blank">Tim Chevalier</a> toma la decisión de abandonar la telenovela-sistemas y da sus razones. Después <a href="https://michaelochurch.wordpress.com/2015/04/17/can-tech-fix-its-broken-culture/" target="_blank">Michael O. Church</a> sintetiza esas razones en una pregunta: “Can tech fix its broken culture?”, y responde “si” pero con más palabras. ¿Y cómo? Como dicta la trama: resistiendo los ataques del management-malvada-bomba-sexy y demostrando valía con honestidad, inteligencia y trabajo duro (<a href="https://www.youtube.com/watch?v=qP8BHNiRglg" target="_blank">y muy poca paga</a>) hasta que el capitalismo-sociedad-millonario se de cuenta y expulse a los managers-malvada-bomba-sexy del juego.</p> <p>La afirmación que más fuerte me suena en el post Michael no es “podemos reparar la cultura de sistemas”, sino otra mucho más desesperada y sutil: “hay una historia con un final feliz en el que los buenos que resisten son recompensados”. </p> <p>En algún momento (allá lejos y hace tiempo) en el que hubiese coincidido (y hasta se podría buscar prueba de eso en este blog), un tiempo en el que lo “Agil” prometía ser la movida que desenmascararía las malvadas maquinaciones del management, demostrando de una vez y para siempre que los “techies” somos el verdadero corazón de las empresas.</p> <p>Pero ahora, un par de años después, “Agil/Scrum” es más o menos lo mismo que “CMMI”. Todos los puntos de aquel manifiesto que hubiesen podido cambiar algo fueron ignorados o licuados; sólo quedaron las reuniones. ¿Y los tests y la integración continua y la nube y los frameworks y el open source…? ¿No son avances? Sí son avances… también las pantallas planas, los multi core, el wifi y los celulares… Pero el escenario es el mismo en el que se escribió <a href="http://es.wikipedia.org/wiki/El_M%C3%ADtico_Hombre-Mes" target="_blank">The Mythical Man-Month</a> (en 1975). Sólo cambió el decorado.</p> <p>El argumento es “la mucama con esfuerzo llega al millonario”. En sistemas se dice “los techies programando van a dominar la industria”. El problema en la trama es “que la mala tiene al millonario engañado”. En sistemas, “que los managers son explotadores parásitos de la empresa”. Pero en ambos casos la industria/la empresa/el capitalismo siempre recompensa el valor generado. En última instancia verá quiénes generan el valor cuando los managers, a fuerza de incompetencia, caigan en su inevitable desgracia y se desencadene el final feliz.</p> <p>En pocas palabras: hay que salir y mostrarle al mundo que los técnicos son los que generan valor. Siguiendo esa línea, si fuésemos adolescentes sobreexplotados de McDonalds deberíamos unirnos para revelarle al mundo que somos los que hacemos las hamburguesas.</p> <p>Es una telenovela simplificadora que justifica lo que hacemos ante nosotros mismos mientras estamos dentro. Nos convence de quedarnos y nos explica por qué se van los que se van, separándonos de ellos para que no se nos ocurra tomarlos de ejemplo. “Cualquier parecido con la vida real”, lejos de ser casual, ha sido elaborado, refinado y corregido a lo largo de los siglos (si, siglos: <a href="http://es.wikipedia.org/wiki/Cenicienta" target="_blank">Cenicienta</a>) para identificarnos con la mucama-pobre-heroína de la trama mientras hacemos girar la rueda del molino, caminando en círculos hacia ninguna parte. </p> <p>Como las telenovelas, es un discurso que no tiene ni pretende ser real ni tener sentido, sólo coherente y útil hacia dentro. Si uno rompe “la cuarta pared” y abandona el set ya no ve ni buenos ni malos ni lindos ni feos ni historia ni nada: sólo un montón de actores representando un guión más o menos delirante que no escriben ni escribieron.</p> <p>Bueno, y “Can tech fix its broken culture?”</p> <p>La “cultura de las empresas de tecnología” no está rota. La “cultura de las empresas” surge y prospera en tanto las hace funcionar, sobrevivir y prosperar… a las empresas, no a las personas. No está ahí para conseguir la felicidad ni el bien común de nadie… pero tampoco para impedirlos. </p> <p>Personalmente creo que no hay nada que demostrar ni a quien demostrarle nada; nadie mirando, sólo nosotros mismos. No hay héroes buenos-pero-feos enfrentados a managers-malos-que-parasitan-las-empresas, ni millonarios naive a los que hay que llevar por la buena senda para bien de todos. Ni siquiera hay un “somos” ni un “son”: no hay una historia común encaminada hacia un final feliz, o hacia ningún otro lado. Lo que hay es un momento y un espacio y ciertas reglas más o menos fijas… A algunos les conviene el status quo, a otros no. Para los que no, a veces se puede y vale la pena dar la pelea por el cambio, a veces no. </p> <p>De lo que estoy más seguro es de que no se toman buenas decisiones mirando telenovelas.</p> AcPhttp://www.blogger.com/profile/16568867429107885585noreply@blogger.com2tag:blogger.com,1999:blog-4251092826841479333.post-80973454884811623602015-04-14T13:50:00.000-03:002015-04-14T13:50:00.199-03:00Todo lo demás.<p><a title="http://mockmydata.com" href="http://mockmydata.com/" target="_blank">Todavía le falta algo</a> (dos “cositas”, en realidad) para llegar al “<a href="http://en.wikipedia.org/wiki/Minimum_viable_product" target="_blank">MVP</a> corregido” (el “minimum viable product” redefinido después de haber subido lo que yo creía que era el minimum). <a href="http://desdesarrollodesoftware.blogspot.com.ar/2015/04/into-void.html" target="_blank">Nadie me hizo llegar nada al respecto</a>. El uso es la intersección entre la promoción y la necesidad, y uno sólo puede controlar el primer término. Sin necesidad, la gente juega un poco y listo. Si gusta se agenda el link para después. Está muy bien, pero no es “uso”.</p> <p>¿Qué hubo entre esos dos MVP’s, además de esas “dos cosas”? ¿En qué cosas ni siquiera había pensado?</p> <h5>Le pongo Analytics y listo.</h5> <p>… no. <a href="http://www.google.com/analytics/" target="_blank">Analytics</a> está muy bien tal cual sale de la caja, pero se queda muy corto. Permite ver el impacto de un post, de facebook, de tweeter, del mail, pero nada más. ¿Y el uso? ¿Se usa el upload de esquemas? ¿El login con google? ¿El preview de datos? ¿La ayuda? </p> <p>El <a href="https://developers.google.com/analytics/devguides/collection/gajs/eventTrackerGuide" target="_blank">event tracking</a> no es difícil de implementar. Lo difícil es determinar qué trackear (qué acciones) cuando uno ya tiene todo desarrollado. Lo que no se trackea no se ve. Trackear un par de eventos y dejar por error dos o tres afuera implica tener una visión muy sesgada del uso. Si lo hubiese pensado desde el principio, agregándolo a medida que se desarrolla la funcionalidad, afinándolo desde el momento 0… </p> <p>No, no es “demasiado para un MVP”. Saber hacia dónde dirigir el segundo paso es tan importante como dar el primero. Pero hay un argumento determinante por el cual no se puede dejar para después: recolectar un volumen de datos relevante lleva –con suerte- semanas.</p> <h5>Try-catch-mail alcanza, y por las dudas logueo todo.</h5> <p>Me mando un mail con la excepción y ya está bien para empezar, con eso ya sé dónde buscar en el log. No, tampoco. Cortísimo. </p> <p>Ok, hubo un error. ¿Quién? ¿Haciendo qué? ¿Qué datos de entrada? ¿Qué valores de salida? Los errores que pueden corregirse mirando un stack trace se agotan rápidamente. Después… las cosas se vuelven más complicadas. El “log viewer” de la consola de GAE es por lo menos “rústico”. Excederse por más y generar 1Gb de log por día está bien cuando podemos comprimir, descargar el archivo y procesarlo tranquilamente, pero no es el caso. Una posibilidad es recolectar parámetros de entrada y valores de salida de todas las funciones pero loguearlos sólo cuando hay un error. O afinar el log para cada operación, o guardar los errores aparte, en la base… Lo que sea, pero no es tan simple como un try-catch-all. </p> <p>Cada error es un usuario–casi-perdido, y usuarios no sobran.</p> <h5>Malditos celulares. </h5> <p>¿Para qué miércoles quiero dar soporte a celulares en un data generation tool? Si, que se pueda ver… pero no importa si no es usable, no es una aplicación que tenga sentido en un smartphone. Y en la primera versión ni eso, que se vea como se ve. Total <a href="http://foundation.zurb.com/docs/" target="_blank">foundation</a> ya ayuda bastante sin que hagamos nada. </p> <p>Error. La aplicación no se usa en un smartphone… pero el 50% (50% medido, no es un decir) de los usuarios entra por primera vez desde uno. ¿Por qué? Y… si promociono por twitter y facebook… ¿qué esperaba? No esperaba nada - señal de que estoy viejo. </p> <p>No tiene que ser usable, pero es indispensable que sea “probable”, “jugable” o al menos “agendable”. Como mínimo –muy mínimo- que puedan decir “ok, avisáme después. </p> <p>Lo ideal sería que se pueda jugar un poco, abrir una cuenta y grabar. Bueno… más trabajo (y esto no está incluido en “las dos cosas”). </p> <h5>Consola de administración.</h5> <p>Paráaaaa… ¿un backoffice para un data generation tool? Si. </p> <p>Estoy usando <a href="https://github.com/objectify/objectify" target="_blank">objectify</a>. Muy lindo, muy rápido el desarrollo de todo, pero… el acceso a datos desde la consola es incluso más rústico que el log. Lo que objectify graba ya es bastante difícil de leer… Si un error “rompe” la cuenta de un usuario (se graba algo y luego le da error cada vez que entra) estamos al horno: corregir esa especie de “assembler de datos” es demasiado peligroso o directamente imposible (ni lo intenté). </p> <p>Guste o no hay que hacer una página de administración donde podamos ver la data en forma prolija y modificarla si es necesario. Y no podemos empezar a hacerla sobre el hecho de que ya hay alguien que no puede entrar a su cuenta. Y también va a tener sus errores.</p> <h5>Y todo lo demás.</h5> <p>Y, finalmente, las “dos cosas” de las que hablaba al principio. Esas son las únicas dos funcionalidades propias de la aplicación de toda esta lista. Pero no voy a decir cuáles son. Talvez ni hagan falta.</p> <h5></h5> <h5>Una buena</h5> <p>Son cosas simples (aunque no “tan simples”) si se las tiene en cuenta desde el principio. Bueno, <a href="http://desdesarrollodesoftware.blogspot.com.ar/2015/03/jugar-en-serio.html" target="_blank">para esto era, ¿no?</a></p> AcPhttp://www.blogger.com/profile/16568867429107885585noreply@blogger.com0tag:blogger.com,1999:blog-4251092826841479333.post-72815530911744464562015-04-07T13:30:00.000-03:002015-04-07T13:30:00.241-03:00Into the void.<p>Siguiendo con lo del <a href="http://desdesarrollodesoftware.blogspot.com/2015/03/jugar-en-serio.html" target="_blank">post anterior</a>, 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:</p> <p><a href="http://mockmydata.com" target="_blank">“Pasen y vean, qué lindas chucherías”</a> (el que no entienda la referencia que busque la frase, vale la pena).</p> <p>De alguna manera subestimé (para variar) el efecto psicológico del “lanzamiento”. </p> <p>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. </p> <p>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. </p> <p>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.</p> <p>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?</p> AcPhttp://www.blogger.com/profile/16568867429107885585noreply@blogger.com0tag:blogger.com,1999:blog-4251092826841479333.post-39373252702241897702015-03-26T13:45:00.000-03:002015-03-26T17:25:07.998-03:00Jugar en serio.<p>Desde hace un tiempo vengo jugando con la idea de “desarrollar productos”. </p><p>En mi universo de fantasía, mi cabeza bulle con innumerables ideas para SaaS (Software as a Service). </p><p>Todos los días (de mi universo de fantasía) se me ocurre una idea, y son todas geniales. </p><p>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. </p><p>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. </p><p>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). </p><p>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. </p><p>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.</p><p>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.</p><p>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.</p><p>… y después suena el despertador y tengo que levantarme a trabajar.</p><p>Y mientras trabajo pienso que no parece tan fantástico. Parece realizable, incluso fácil. </p><p>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).</p><p>¿Una fantasía irrealizable o un modelo sustentable? Sólo hay una forma de saber. </p><p>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. </p><p>¿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.</p><p>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. </p><p>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”.</p><p>“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. </p><p>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. </p><p>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. </p><p>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.</p><p>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.</p><p>Y en cada paso hay errores y torpezas y un millón de piedras puntiagudas para pisar.</p><p>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. </p><ul><li>“No sabes nada, Jon Snow”.<br />
<li>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.<br />
<li>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. <br />
<li>Sí me funciona bien el establecer una meta a corto plazo: “lo próximo que hay que hacer es…”<br />
<li>Un dashboard es imprescindible. No puedo dejar de recomendar <a href="http://trello.com" target="_blank">Trello</a>.<br />
<li>Medir las horas es imprescindible. No puedo dejar de recomendar <a href="http://toggl.com" target="_blank">Toggl</a>.<br />
<li>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.<br />
<li>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 <strong>probar</strong> que es una buena idea, también hay que <strong>probar </strong>que es una mala idea antes de dejarla por el camino. Y para eso hay que implementarla.<br />
<li>Otra vez (y van tres): respetar el MVP a rajatabla. Escribir en el backlog es muy terapéutico para descargar tensiones. <br />
<li>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í.<br />
<li>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”.<br />
<li>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.<br />
<li>Y si nadie lo usa y nadie reclama… insistir con el autobombo y la promoción.<br />
<li>Y si nadie lo usa y nadie reclama (después de un tiempo)… bueno, ahí quedó.<br />
<li>… pero meter un feature que nadie quiere de vez en cuando sólo porque es divertido mantiene el entusiasmo.<br />
<li>En resumen: POCO de todo para la primera versión: pocos documentos, poca funcionalidad, poco código, poca complejidad, poco riesgo, poco tiempo perdido.<br />
<li>Salvo paciencia. MUCHA paciencia.</li><br />
<br />
</ul>AcPhttp://www.blogger.com/profile/16568867429107885585noreply@blogger.com0tag:blogger.com,1999:blog-4251092826841479333.post-24854222891203960112015-03-21T12:06:00.001-03:002015-03-21T12:06:34.125-03:00The Gervais Principle<p>Para aquellos a los que les gusta (o no pueden para de mirar) <a href="http://en.wikipedia.org/wiki/The_Office_%28U.S._TV_series%29" target="_blank">The Office</a> y se ríen (o lloran) con <a href="http://dilbert.com/" target="_blank">Dilbert</a> 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:</p> <p><a href="http://www.ribbonfarm.com/2009/10/07/the-gervais-principle-or-the-office-according-to-the-office/" target="_blank">The Gervais Principle, Or The Office According to “The Office”</a></p> <p>Para tentarlos y de paso tenerlos a mano para cuando se necesite, dos botones de muestra:</p> <table width="100%"> <tbody> <tr> <td><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOjR4vVb4Dz9TMUmFSRsUGaZc85c0Ymph_jbwPs2CzFhM9Kicqxb_tJiLrEe3m4Zl66BJrIZfpQZCigQ0DmD00g_AbF_AUs6hsMfOtASDJ3dEixlk47jSO5AUS7DWMwI6ceXCIHmePJow/s1600-h/hughMcLeodCompanyHierarchy%25255B5%25255D.jpg"><img title="hughMcLeodCompanyHierarchy" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px; border-top-width: 0px" border="0" alt="hughMcLeodCompanyHierarchy" src="http://lh6.ggpht.com/-1Q2Bfnl0LfQ/VQ2I858hFgI/AAAAAAAABkk/hhDZpRQgC8w/hughMcLeodCompanyHierarchy_thumb%25255B1%25255D.jpg?imgmax=800" width="244" height="135"></a></td> <td><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEirbHgaOPWM9F9znW-Pk4QjQqpFnzZdUcKlPWwswu9E6m5MzGz3BWJb9qsoNSrJNgnki7mTB3pdQFj8Hi66RbleFN3v-gR5dxaJgC7BSdlZqLbmA1kn_Dkmvng59f7Q3JR30dAZUbFC_Gg/s1600-h/compLifeCycle%25255B5%25255D.jpg"><img title="compLifeCycle" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px; border-top-width: 0px" border="0" alt="compLifeCycle" src="http://lh6.ggpht.com/-zYNAdZ0Myeg/VQ2I95Z9pII/AAAAAAAABk0/ZS-Z09gY0QI/compLifeCycle_thumb%25255B1%25255D.jpg?imgmax=800" width="210" height="244"></a></td></tr></tbody></table> <p>Ojo que es más serio que lo que parece. Lo encontré leyendo el <a href="http://www.daedtech.com/blog" target="_blank">blog de Erik Dietrich</a>, también recomendable para agregar al feed.</p> <p>PD para cuando lean un poco de eso: yo me considero claramente un <i>looser</i>, y -creo- es fácil distinguir a los <i>sociopaths</i> (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 <i>clueless</i>. Tengan en cuenta que no todos son tan "puros" como <a href="https://www.google.com.ar/search?q=The+office+michael&tbm=isch" target="_blank">Michael</a>.</p> AcPhttp://www.blogger.com/profile/16568867429107885585noreply@blogger.com0tag:blogger.com,1999:blog-4251092826841479333.post-55763466762411325752015-02-07T15:02:00.001-03:002015-02-07T15:02:12.767-03:008192<p>Si se preguntaban (no creo que se estuviesen preguntando, es pura retórica) por qué se actualiza tan poco este blog últimamente…</p> <p><a href="http://lh4.ggpht.com/-5MKurUqcEG0/VNZTIMFJTdI/AAAAAAAABis/gev2F5O2mj4/s1600-h/8192%25255B5%25255D.png"><img title="8192" alt="8192" src="http://lh5.ggpht.com/-Zt-I0sHpOm4/VNZTI6CzuwI/AAAAAAAABiw/a21S3_B0pPY/8192_thumb%25255B1%25255D.png?imgmax=800"></a></p> <p>… ya van a haber otras novedades (aparte del 16384). Paciencia…</p> AcPhttp://www.blogger.com/profile/16568867429107885585noreply@blogger.com1tag:blogger.com,1999:blog-4251092826841479333.post-14738513305885658802014-08-30T14:10:00.001-03:002014-08-30T14:10:56.600-03:00Mi aplicación<p>Casi todos (los programadores, obvio) tenemos “mi aplicación”. Ésa que, cuando haya tiempo, vamos a hacer “como se debe”. </p> <p>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. </p> <p>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).</p> <p>¿Cómo es “mi aplicación”? La mía de mi propiedad, al día de hoy, es:</p> <p>-Una aplicación web hosteada en google.</p> <p>-Server side: en java, pero va a devolver exclusivamente JSON. Pura data. Lo juro.</p> <p>-Con alguna base de datos no relacional, porque son re-cool.</p> <p>-Bootstrap.</p> <p>-Pero no pienso escribir una sola regla de css. Lo juro.</p> <p>-Ni tampoco mucha imagen ni iconito. Si puedo quedarme sólo con lo de bootstrap, mejor.</p> <p>-Ni meterle 10.000 plugins (aunque ya empecé… pero lo voy a deshacer, y cuando lo deshaga lo juro).</p> <p>-Jquery</p> <p>-Jquery validation, si hace falta (imagino que sí).</p> <p>-Knockout.</p> <p>-Require.</p> <p>-Y <a href="http://visjs.org/">visjs</a>, porque necesito algo así.</p> <p>-Y NADA más.</p> <p>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.</p> <p>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. </p> <p>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”.</p> <p>“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”. </p> <p>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.</p> <p>Tengo ganas de seguir jugando.</p> <p>Y a todo esto… ¿qué hace, exactamente, “mi aplicación”?</p> <p>Por ahora, nada.</p> <p>¿Qué debería hacer?</p> <p>No está muy claro, ya veremos. Lo importante es que “esté como a mí me gusta”.</p> <p>¿Verá la luz del sol?</p> AcPhttp://www.blogger.com/profile/16568867429107885585noreply@blogger.com2tag:blogger.com,1999:blog-4251092826841479333.post-6988483389518583532014-06-02T22:25:00.001-03:002014-06-02T22:25:30.322-03:004096<p>A small victory.</p> <p><a href="http://lh5.ggpht.com/-CLRb-mDwANY/U40kAGbvA2I/AAAAAAAABbQ/CxT4E97237U/s1600-h/4096%25255B6%25255D.png"><img title="4096" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; border-left: 0px; display: inline; padding-right: 0px" border="0" alt="4096" src="http://lh5.ggpht.com/-iwb74n6gIVE/U40kBamyqhI/AAAAAAAABbY/Ca8zdnjrZDU/4096_thumb%25255B4%25255D.png?imgmax=800" width="644" height="359"></a></p> <p>… <a href="https://www.google.com.ar/search?q=2048+high+scores" target="_blank">buscando en google</a> veo unos screenshots que llegan hasta 65536… ¿fakes? Si sigo así, en un par de meses les confirmo.</p> AcPhttp://www.blogger.com/profile/16568867429107885585noreply@blogger.com0tag:blogger.com,1999:blog-4251092826841479333.post-25023917440344279382014-05-15T11:00:00.000-03:002014-05-15T11:00:06.494-03:00Jetas*<p>En mis tiempos, cuando se quería hacer alguna gráfica (folleto, página web, calcomanía, papel higiénico, mouse pads servilleta o lo que sea) se tiraban un par de print screens de las pantallas más “bonitas” (o se inventaba alguna si es que no había), un poco de bla bla bla “mejorará su vida” y listo. </p> <p>Ahora la onda es poner la jeta* de algún CEO presentable (pocos consiguen), o empleado, o modelito o pobre flaco que iba corriendo una maratón con cara de langa (no, no vi que lo hayan usado, pero podría ser) y nada más. De qué hace el sistema, ni una palabra (o bien abajo), no sea cosa que alguien reclame la letra chica. Si es mujer, morocho, latino, indio, oriental o miembro de alguna “minoría con onda en USA”, mejor, y si es de varias al mismo tiempo (una mujer negra de ojos rasgados vestida de Pancho Villa), mejor que mejor. </p> <p>O gente laburando en unas oficinas con toda la onda, limpias y llenas de luz, muy prolijamente desordenadas, súper informal. Vaya uno a saber ande se trabaja así, tal vez en algún lugar (hay uno de casi todo), pero me juego que no es la regla (al menos no para los que tocan el teclado). </p> <p>O un tipo más o menos joven haciendo garabatos en un pizarrón, y todos alrededor con cara de “estoy aportando ideas”… y siempre alguna mina hay, por eso de las minorías que mencionaba antes. A veces hasta eligen uno o dos medio feos (medio al fondo, a un costado o fuera de foco), para darle realismo. Pero todos tienen los dientes blanquísimos, eso sí (debe ser por los tubos de luz), y nadie con los ojos rojos ni las ojeras negras ni la barba crecida ni la mandíbula colgando en un bostezo interminable.</p> <p>Debe ser para mostrarle al mundo que el desarrollo de software, especialmente el de line-of-business, es producto del trabajo de personas comunes y silvestres y extremadamente cool (a este ventiañero lo vendemos por genio). Trabajo en equipo (lo vendemos diez veces) que demanda un ambiente acorde (total lo encadenamos al teclado hasta que le salga algo), re-creativo (con una play se queda contento), pero de mucha responsabilidad (y si no es la play están los palos, y de última de acá no te vas) y, sobre todo, esfuerzo (te va a costar un ojo de la cara), un gran esfuerzo sostenido en el tiempo (lo vas a tener para el día en que te mueras)… un trabajo casi artístico (así que si sale cualquiera no es culpa nuestra, el arte es así… indomable). </p> <p>Será para hacer el software más humano, digo yo. </p> <p>* jeta = cara, rostro (soy re-internacional).</p> AcPhttp://www.blogger.com/profile/16568867429107885585noreply@blogger.com2tag:blogger.com,1999:blog-4251092826841479333.post-2281955404717636662014-05-13T11:54:00.001-03:002014-05-13T11:54:58.349-03:00Prefiero el picado fino.<p>Creo (comienzo que permite divagar sin molestarse en verificar ni pensar dos veces) que esta preferencia es impronta de los últimos años de trabajo “Senior” (comillas obligatorias) en equipos más o menos medianos, más o menos “rotativos”. Picaba funcionalidad, sí, pero la mayor parte del tiempo tenía que buscar bugs en, verificar o consultar código de otros, en funcionalidad de la que no tenía más que una (valga la redundancia) vaga idea funcional. Entiéndase “no tenía mucha idea de cómo estaba programada o siquiera pensada”.</p> <p>Entonces abría una carpeta (y a veces otra y otra y otra) y por los nombres me iba dando cuenta de cómo venía la mano. Me es cómodo encontrar una lista de archivos con nombres de clase más o menos coherentes, sintiéndome seguro de que no hay más que eso, simplemente porque “un namespace por carpeta, una clase por archivo”.</p> <p>Ése es el picado fino. ¿Se entiende? Si no, querido lector, mejor siga su camino por otros callejones de la web, que acá ya no se le explica nada a nadie.</p> <p>A veces hay un archivo con un enum y nada más. A veces una clase enorme con otras privadas y públicas y enums e interfaces y demás, adentro unas de otras, como muñecas rusas… pero en definitiva siempre una y sólo una clase en el archivo (amén). Se supone, al fin y al cabo, que cualquier otra cosa interna hace al detalle de lo que la contenedora dice (en su nombre) que hace.</p> <p>A otros no. A otros les gusta el picado grueso. Un par de carpetas, las mínimas necesarias para leer el contenido con comodidad, arregladas en una jerarquía más bien plana y a otra cosa mariposa. Los nombres de los archivos se refieren no a clases sino más bien a… bueno, nunca encontré/entendí si había una regla, supongo que obedecen al (o son la medida del) sentido común de quien los crea. </p> <p>Éstos otros, los del picado grueso, suelen argumentar con cierta razón que codificar una funcionalidad, tocando al mismo tiempo 5 o 10 archivos por separado, es un mareo. Uno trabaja en MVC, por ejemplo, saltando entre vista, controlador, modelo (principal y varios secundarios), un par de helpers, algo más aquí, algo más allá… y son como 10 archivos o más, si la cosa es compleja. Y sí, puede ser… a mí no me molesta tanto, y, a la inversa, me es un mareo ver todo junto. “Así es MVC”, o “Así son las reglas de estilo en .Net”, contraataco (por no decir “así me gusta y punto”).</p> <p>Normalmente, de encontrar código “picado grueso” en en una solución compilada de .Net, puteo (a veces para adentro, a veces para afuera)… pero en javascript, por ejemplo… “y bueh, así es la we’ ¿vio?”. Después empecé a usar <a href="http://requirejs.org/" target="_blank">require.js</a> y… ¡picado fino en javascript! Y pienso (a futuro cercano, lo veo verde todavía) empezar con <a href="http://www.typescriptlang.org/" target="_blank">TypeScript</a> y ahí los del picado grueso me van a odiar en serio, porque ya no hay excusa (salvo el gusto personal, argumento tan fuerte como el que lo utiliza –débil en mi caso-).</p> <p>Pero no está mal el picado grueso, no. Es un tema de preferencias, nada más, de idiosincrasia, costumbre, organización mental de cada uno… andá a saber. Alguno dirá “hay que encontrar el punto justo”… y suena bien, pero (en este punto) prefiero seguir un (y sólo un) criterio, aunque se vaya al extremo. Habrá que ponerse de acuerdo y decidir con qué se prefiere lidiar cuando toque: ¿un archivo de 1000 líneas o una jerarquía de 5 carpetas de profundidad con 20 archivos de 30-50 líneas en cada una? Pero a veces una cosa y a veces otra (y a veces ninguna) sí que es un enjambre. </p> <p>Es, en todo caso, una buen tema para animar la conversación alrededor del fuego de algún proyecto incendiado. Basta, me voá trabajar.</p> AcPhttp://www.blogger.com/profile/16568867429107885585noreply@blogger.com0tag:blogger.com,1999:blog-4251092826841479333.post-18300913548639288012014-04-29T15:19:00.001-03:002014-04-29T15:19:04.331-03:0010 Razones para escribir un post después de 4 años de nada.<ol> <li>Hacer una pausa en el trabajo.</li> <li>No poder darle al botón “Eliminar” del blog.</li> <li>Porque si voy a tener un blog desactualizado, que no sea con esa imagen “2000osa” tan horrible.</li> <li>Para empujar hacia abajo los post viejos, links muertos y servicios desaparecidos (pensaba en delicious, feedburner y cia.; pero siguen ahí… medio zombies).</li> <li>Curiosidad de ver cuántos feed readers quedan vivos (estimación: 5 o 6 de más de 100).</li> <li>Para hacerme el muerto a ver quién viene a velarme.</li> <li>Autobombo, porque soy genial.</li> <li>Talvez por estirar los dedos se me caiga una idea…</li> <li>Puesh porque me plaze (con acento español de españa – no offense).</li> <li>Para demostarme a mí mismo que todavía puedo rellenar una lista de “10 boludeces para que vean mi página y pueda cobrar el AdSense”.</li></ol> <p>Yapa: para hablar solo con excusa.</p> AcPhttp://www.blogger.com/profile/16568867429107885585noreply@blogger.com4tag:blogger.com,1999:blog-4251092826841479333.post-34473773899107891452010-05-25T19:20:00.001-03:002010-05-26T12:58:39.334-03:00La integración de la base de datos. Una cura definitiva para esta enfermedad crónica.<p><a href="http://lh6.ggpht.com/_Ieq2x8hAwA8/S_1FK-CRMnI/AAAAAAAABJo/N0A49YhnldQ/s1600-h/24c0572%5B3%5D.jpg"><img style="border-right-width: 0px; margin: 0px 20px 10px 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="Martín Gargiulo" border="0" alt="Martín Gargiulo" align="left" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjw9KU8yCq6WTFeIwo8jpy0gvgyCR_qhyphenhyphenFsfIiAhXEn67vciXDmdSom3G1fntafI0ozmQofMKV20RgS3JdyxOs76I_FbMOe0J7yZnH3Fqzf7NXZGvnGiU3-2RwC-ASmrbkDFAgjBVKSolM/?imgmax=800" width="84" height="84" /></a> <em>Una colaboración de </em><a href="http://ar.linkedin.com/pub/mart%C3%ADn-gargiulo/5/771/953" target="_blank"><em>Martín Gargiulo</em></a> (<a href="#postCmnt">*</a>)<em>.</em></p> <hr size="1" /> <p>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? </p> <p>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.</p> <p>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.</p> <p>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?</p> <p>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.</p> <p>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.</p> <p>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.</p> <p align="right"><em>…continuará…</em></p> <a name="postCmnt"></a> <hr size="1" /> <p style="font-style: italic; text-size: smaller">(*) 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).</p> AcPhttp://www.blogger.com/profile/16568867429107885585noreply@blogger.com3tag:blogger.com,1999:blog-4251092826841479333.post-3685643730886302692010-04-15T09:35:00.001-03:002010-04-15T09:35:29.269-03:00Frases: Lo que sobra.<blockquote> <p><em>[…] como verás, arquitectura y diseño es lo que sobra... sobra porque todos somos arquitectos y diseñadores y hacemos lo que se nos da la gana, no hay directivas.</em></p> </blockquote> <p align="right"><em>“</em>Un amigo”, hablando sobre esas cosas lindas que tienen los sistemas.</p> AcPhttp://www.blogger.com/profile/16568867429107885585noreply@blogger.com4tag:blogger.com,1999:blog-4251092826841479333.post-57700425881435131522010-03-31T11:00:00.001-03:002010-03-31T11:00:53.379-03:00Frases: Liderazgo.<blockquote> <p><a href="http://dilbert.com/strips/comic/2010-03-31/" target="_blank"><img style="border-bottom: 0px; border-left: 0px; display: inline; margin-left: 0px; border-top: 0px; margin-right: 0px; border-right: 0px" title="86283.strip.zoom" border="0" alt="86283.strip.zoom" align="left" src="http://lh3.ggpht.com/_Ieq2x8hAwA8/S7NVlB8LXxI/AAAAAAAABJk/NTpBWaG4_3k/86283.strip.zoom%5B4%5D.gif?imgmax=800" width="105" height="121" /></a> “<em>Leadership is the art of trading imaginary things in the future for real things today”.</em></p> </blockquote> <p align="right"><a href="http://dilbert.com/strips/comic/2010-03-31/" target="_blank">Dilbert</a> es genial.</p> AcPhttp://www.blogger.com/profile/16568867429107885585noreply@blogger.com3tag:blogger.com,1999:blog-4251092826841479333.post-42317780576136665022010-03-19T12:06:00.001-03:002010-03-19T12:06:36.131-03:00Jueguitos de Viernes: Home Sheep Home.<p><a href="http://www.newgrounds.com/portal/view/528824" target="_blank">Home Sheep Home</a> es un muy buen juego en el que tenemos que combinar las habilidades y propiedades de tres ovejas para ayudarlas a atravesar la pantalla.</p> <p><a href="http://www.newgrounds.com/portal/view/528824" target="_blank"><img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="home-sheep-home" border="0" alt="home-sheep-home" src="http://lh5.ggpht.com/_Ieq2x8hAwA8/S6OS-6XG-yI/AAAAAAAABJg/P_uEbJKFTiw/home-sheep-home%5B4%5D.jpg?imgmax=800" width="504" height="356" /></a> </p> <p>Me hizo recordar bastante al <a href="http://us.blizzard.com/es-es/games/legacy/#lostvikings" target="_blank">Lost Vikings</a> (que por cierto se pueden bajar <a href="http://www.abandonia.com/en/games/12/Lost+Vikings,+The.html" target="_blank">en Abandonia</a>).</p> <p align="right">Visto en <a href="http://juegos.microsiervos.com/ingenio/home-sheep-home.html" target="_blank">Juegos Microsiervos</a>.</p> AcPhttp://www.blogger.com/profile/16568867429107885585noreply@blogger.com2tag:blogger.com,1999:blog-4251092826841479333.post-73901522212260031452010-03-18T06:00:00.000-03:002010-03-18T06:00:03.101-03:00Visibilidad.<p>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. </p> <p>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. </p> <p>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.</p> <p>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.</p> <p>Hay otro caudal que lo alimenta, un caudal de programadores, analistas, managers y demás <strike>sacos de carne</strike> “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.</p> <p>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). </p> <p>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.</p> <p>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.</p> <p>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 <strike>esos efectos colaterales</strike> esas habilidades, y así tenemos a <a href="http://www.dilbert.com/" target="_blank">Dilbert</a> (adivinen <a href="http://en.wikipedia.org/wiki/Scott_Adams" target="_blank">de dónde sacó Scott Adams la inspiración para sus personajes</a>), o a <a href="http://sinergiasincontrol.blogspot.com/" target="_blank">Sinergia Sin Control</a>, o al mayor repositorio de esfuerzo sin sentido y desperdicio de inteligencia jamás creado, <a href="http://thedailywtf.com/" target="_blank">TDWTF</a>.</p> <p>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…</p> <p align="center"><em>…que al mundo nada le importa <br /><a href="http://www.youtube.com/watch?v=IpHCyRUtCN4" target="_blank">yira… <br />yira…</a></em> AcPhttp://www.blogger.com/profile/16568867429107885585noreply@blogger.com4tag:blogger.com,1999:blog-4251092826841479333.post-65080693304721056712010-03-15T06:00:00.000-03:002010-03-15T07:38:47.605-03:00VS 2010: Crear versiones optimizadas (minified) de archivos javascript con T4 text templates.<p>Una de las vetas más productivas que encontramos al utilizar los <a href="http://msdn.microsoft.com/en-us/library/bb126445(VS.100).aspx" target="_blank">Text Templates en Visual Studio 2010</a> tiene que ver con la posibilidad de acceder al <a href="http://msdn.microsoft.com/en-us/library/xc52cke4(VS.100).aspx" target="_blank">modelo de objetos de Visual Studio (EnvDte)</a>. </p> <p>Este modelo de objetos nos permite analizar e incluso modificar no sólo la estructura de archivos del proyecto que estamos construyendo (archivos y proyectos incluidos, referencias, propiedades…) sino también del código (namespaces, clases, propiedades, métodos, variables…).</p> <p>Cierto es que es algo que ya podíamos hacer programando un plug-in o un diseñador, pero estas opciones conllevan cierta complejidad. Veremos, a través de este ejemplo, que el uso de T4 para las tareas que antes requerían ese tipo de soluciones simplifica mucho las cosas, si bien el resultado es un poco menos… elegante (que un diseñador, por ejemplo).</p> <p>El ejemplo consiste en crear una plantilla T4 que recorra los archivos javascript de un proyecto y cree y agregue versiones optimizadas de ellos utilizando la librería <a href="http://aspnet.codeplex.com/releases/view/34488" target="_blank">Microsoft Ajax Minifier</a>. Empecemos.</p> <h5>Crear una plantilla preprocesada.</h5> <p>Como ésta es una plantilla que podríamos reutilizar en varios proyectos, vamos a crear una preprocesada (Preprocessed Text Template) y ubicarla en una librería separada que luego podamos distribuir. </p> <p>Así que comenzamos creando una solución de prueba (“T4Sample”), agregando un proyecto de tipo Class Library (“JavascriptCruncher”) y un proyecto de tipo Web Application (“SampleWebApplication”). En el proyecto de librería insertamos un nuevo ítem de tipo “Preprocessed Text Template” (“JavascriptCruncherTemplate”) y una clase en la que ubicaremos los métodos auxiliares que vayamos necesitando (“EnvDteHelper”). La solución debería quedar así:</p> <p><img style="border-right-width: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto" title="JsCruncher01" border="0" alt="JsCruncher01" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiHOe78NRTp8fCX-32N7YWVPY8-4q88V30IpU32cO2-hKVrf3eSDPLNbst4W_KrtrMHw6OyWbOlu1lJ5SbKx91hyphenhyphenKMs-Hskw4MlxSsdMIkJVxkLWn12svMCuaih94F1too2949-RWCuzwI/?imgmax=800" width="261" height="218" /></p> <h5>Obtener una referencia a EnvDte.</h5> <p>El disparador de todo esto ha sido un excelente post de <a href="http://www.olegsych.com/2008/03/how-to-generate-multiple-outputs-from-single-t4-template/" target="_blank">Oleg Sych</a>, al que le vamos a pedir prestado el código para obtener la referencia al modelo de objetos de Visual Studio. Ubicaremos este código en nuestra clase auxiliar, como un método estático:</p> <pre class="c-sharp" name="code">using System;
using System.Collections.Generic;
using System.IO;
using EnvDTE;
using Microsoft.VisualStudio.TextTemplating;
namespace JavascriptCruncher
{
public class EnvDteHelper
{
public static Project GetProject(ITextTemplatingEngineHost host)
{
IServiceProvider hostServiceProvider = (IServiceProvider)host;
if (hostServiceProvider == null)
throw new Exception("Host property returned unexpected value (null).");
DTE dte = (DTE)hostServiceProvider.GetService(typeof(DTE));
if (dte == null)
throw new Exception("Unable to retrieve EnvDTE.DTE");
Array activeSolutionProjects = (Array)dte.ActiveSolutionProjects;
if (activeSolutionProjects == null)
throw new Exception("DTE.ActiveSolutionProjects returned null.");
Project dteProject = (Project)activeSolutionProjects.GetValue(0);
if (dteProject == null)
throw new Exception("DTE.ActiveSolutionProjects returned null.");
return dteProject;
}
}
}</pre>
<p>Para que lo anterior funcione <strong>necesitamos referencias a las librerías “EnvDte” y “Microsoft.VisualStudio.TextTemplating.Interfaces.10.0”</strong>. Esta última (creo) no se incluye en la distribución del VS 2010. Si no la encuentran, pueden obtenerla bajándose el <a href="http://www.microsoft.com/downloads/details.aspx?FamilyID=4659f71d-4e58-4dcd-b755-127539e21147&displaylang=en" target="_blank">Visual Studio 2010 SDK</a>.</p>
<h5>Obtener todos los archivos javascript incluidos en el proyecto.</h5>
<p>En el paso anterior obtuvimos una referencia al proyecto en donde corre el template. El próximo paso es obtener una lista de todos los objetos ProjectItem (una interfaz que representa cada archivo y carpeta en el proyecto) que corresponden a archivos javascript. </p>
<p>El proyecto está representado como una jerarquía de ProjectItems que debemos recorrer recursivamente. Necesitamos todos los archivos “.js”, pero excluyendo aquellos “.min.js”, entendiendo que éstos ya están optimizados (“minificados” podríamos decir maltraduciendo “minified”).</p>
<p>Agregamos entonces las siguientes funciones a nuestra clase EnvDteHelper.</p>
<pre class="c-sharp" name="code">public static List<projectitem> GetJsProjectItems(Project project)
{
List<projectitem> jsProjectItems = new List<projectitem>();
foreach (ProjectItem projectItem in project.ProjectItems)
{
GetJsProjectItems(projectItem, jsProjectItems);
if (projectItem.Name.EndsWith(".js") && !projectItem.Name.EndsWith(".min.js"))
jsProjectItems.Add(projectItem);
}
return jsProjectItems;
}
private static void GetJsProjectItems(ProjectItem parentProjectItem, List<projectitem> jsProjectItems)
{
foreach (ProjectItem projectItem in parentProjectItem.ProjectItems)
{
GetJsProjectItems(projectItem, jsProjectItems);
if (projectItem.Name.EndsWith(".js") && !projectItem.Name.EndsWith(".min.js"))
jsProjectItems.Add(projectItem);
}
}</pre>
<h5>Optimizar o minificar los archivos.</h5>
<p>Este es el momento (si no lo han hecho ya) de bajar e instalar el <a href="http://aspnet.codeplex.com/releases/view/34488" target="_blank">Microsoft Ajax Minifier</a> y <strong>agregar una referencia a la librería ajaxmin.dll en el proyecto (JavascriptCruncher)</strong>.</p>
<p>Ésta librería es muy simple. La clase ScriptCruncher contiene el método “Crunch”, que recibe el código javascript a optimizar (minificar) y una instancia de la clase CodeSettings con las opciones deseadas y devuelve el código minificado como una cadena.</p>
<p>Combinando lo visto en el punto anterior con esta librería, podemos comenzar a escribir el template propiamente dicho. Sería algo así (lo siguiente no es el template completo, sólo un ejemplo del avance hasta ahora):</p>
<pre class="c-sharp" name="code">EnvDTE.Project project = EnvDteHelper.GetProject(this.Host);
List<envdte.projectitem> jsProjectItems = EnvDteHelper.GetJsProjectItems(project);
ScriptCruncher cruncher = new ScriptCruncher();
CodeSettings crunchSettings = new CodeSettings();
crunchSettings.CollapseToLiteral = true;
crunchSettings.LocalRenaming = LocalRenaming.CrunchAll;
crunchSettings.StripDebugStatements=true;
foreach( EnvDTE.ProjectItem item in jsProjectItems)
{
string itemFileName = item.FileNames[0];
string jsCode = File.ReadAllText(itemFileName);
string jsMinified = cruncher.Crunch(jsCode, crunchSettings);
this.WriteLine(itemFileName);
this.WriteLine(jsMinified);
this.WriteLine("--------------------");
}</pre>
<p>El ejemplo anterior solamente muestra el código minificado. Pero tenemos que…</p>
<h5>Crear un archivo con el código minificado y agregarlo al proyecto programáticamente.</h5>
<p>Vamos a recurrir otra vez al bueno de <a href="http://www.olegsych.com/2008/03/how-to-generate-multiple-outputs-from-single-t4-template/">Oleg Sych</a> para ver cómo se lleva a cabo esta tarea. Mirando un poco sobre el código de su ejemplo llegamos a que agregar un archivo es tan simple como utilizar el método AddFromFile de la colección ProjectItems del objeto ProjectItem correspondiente al archivo javascript con el código original. </p>
<p>Como se ve, le pasamos el ProjectItem del archivo javascript original -que utilizará como referencia- y el código minificado. Lo único que hace es cambiar la extensión, grabar (o sobreescribir) el archivo minificado existente y agregarlo al proyecto (si es que no estaba ya agregado). Ubicamos este nuevo helper junto a los demás en EnvDteHelper</p>
<pre class="c-sharp" name="code">public static void SaveMinifiedCode(ProjectItem originalJsItem, string minifiedCode)
{
string outputFileName = Path.ChangeExtension(originalJsItem.FileNames[0], ".min.js");
File.WriteAllText(outputFileName, minifiedCode);
ProjectItem parentProjectItem = originalJsItem.Properties.Parent;
parentProjectItem.ProjectItems.AddFromFile(outputFileName);
}</pre>
<h5>Juntando las piezas en la plantilla.</h5>
<p>Hasta ahora venimos trabajando sobre nuestra clase EnvDteHelper. Suelo recomendar esto ya que, por ahora, el soporte de Intellisense en las plantillas es bastante limitado. Es mucho más fácil, entonces, trabajar dentro de lo posible en estos helpers, que son clases comunes y silvestres, para luego juntar todo en el template en un último paso.</p>
<p>Una vez terminado el trabajo, la plantilla (“JavascriptCruncherTemplate.tt”) debe quedar así:</p>
<pre class="c-sharp" name="code"><#@ template language="C#" hostSpecific="true" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="Microsoft.Ajax.Utilities" #>
<#@ import namespace="System.IO" #>
<#
EnvDTE.Project project = EnvDteHelper.GetProject(this.Host);
List<EnvDTE.ProjectItem> jsProjectItems = EnvDteHelper.GetJsProjectItems(project);
ScriptCruncher cruncher = new ScriptCruncher();
CodeSettings crunchSettings = new CodeSettings();
crunchSettings.CollapseToLiteral = true;
crunchSettings.LocalRenaming = LocalRenaming.CrunchAll;
crunchSettings.StripDebugStatements=true;
foreach( EnvDTE.ProjectItem item in jsProjectItems)
{
string itemFileName = item.FileNames[0];
string jsCode = File.ReadAllText(itemFileName);
string jsMinified = cruncher.Crunch(jsCode, crunchSettings);
EnvDteHelper.SaveMinifiedCode(item, jsMinified);
this.WriteLine( "Processed: {0}", itemFileName);
}
this.WriteLine("done!");
#></pre>
<p><strong>Para destacar</strong>: noten, en el tag inicial de la plantilla, que se especifica la opción “<strong>hostSpecific=true</strong>”. Esto es importante ya que le indica al generador que la clase resultante debe tener la propiedad Host. Si no lo hacemos obtendremos un error de compilación luego (¡me costó un rato darme cuenta de eso!). Vean también <strong>la declaración de los “import”</strong> (el equivalente a “using” en los templates).</p>
<p>El resto del código es simple dadas las herramientas que nos construimos: </p>
<ul>
<li>obtenemos la referencia al proyecto, </li>
<li>luego la lista de ProjectItems que corresponden a archivos javascript. </li>
<li>Inicializamos un objeto ScriptCruncher y otro CodeSettings. Especificamos algunas propiedades que hacen al método de minificación (¿inventé una palabra?). </li>
<li>Luego recorremos la lista de ProjectItems (archivos javascript) y los vamos minificando utilizando el “cruncher”). </li>
<li>Y grabamos cada uno de ellos utilizando el helper que creamos (“SaveMinifiedCode”). </li>
<li>Utilizamos el output del template en sí como un log, escribiendo el nombre de cada archivo que procesamos. Este output es informativo y no se compilará. </li>
</ul>
<h5>Utilizar la plantilla en un proyecto.</h5>
<p>Nuestra librería JavascriptCruncher está lista para ser utilizada en un proyecto de prueba. ¿Cómo la referenciamos? </p>
<p>Lo que debemos hacer es crear una plantilla en cada proyecto en el que querramos utilizar nuestro JavascriptCruncherTemplate que haga referencia a éste.</p>
<p>Hice un proyecto de prueba muy tonto, que tiene dos páginas iguales, una en la carpeta principal y otro en una subcarpeta (para verificar que el template genere ambos archivos). También agregué, en la carpeta principal un elemento de tipo “Text Template” (JavascriptCruncher.tt) que hará referencia al template precompilado que acabamos de crear. La estructura, en resumen, queda así:</p>
<p><img style="border-right-width: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto" title="JsCruncher02" border="0" alt="JsCruncher02" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj86dbETBZtDdtABySHuHD5f7HgsGTYstecnHQTV8TrRJd-2La-QHoXp_8h0WqRTfkJHhszlK7OZqrEN0naIRnyYHGMs_DOW0gGHU2hkpXvm5WYP-bfuZrm8X4bLoGo__0mk3A-lBMTLLg/?imgmax=800" width="299" height="267" /> </p>
<p>JavascriptCruncher.tt referencia al template con este código:</p>
<pre class="c-sharp" name="code"><#@ template debug="false" hostspecific="true" language="C#" #>
<#@ output extension=".log" #>
<#@ assembly name="JavascriptCruncher" #>
<#@ import namespace="JavascriptCruncher" #>
<#
JavascriptCruncherTemplate t = new JavascriptCruncherTemplate();
t.Host = this.Host;
this.Write(t.TransformText());
#></pre>
<p>Noten que simplemente crea una instancia de JavascriptCruncherTemplate, establece la propiedad host (recuerden nuevamente que es importante la propiedad hostspecific="true" de la primera línea) e invoca al método TransformText.</p>
<h5>Referencia a la librería de templates utilizando la GAC.</h5>
<p>La línea en JavascriptCruncher.tt de nuestro proyecto de prueba que hace referencia a la librería de templates que acabamos de crear es la que dice:</p>
<p><#@ assembly name="JavascriptCruncher" #></p>
<p>Puedo hacerlo de esta manera -sin indicar el path a la dll- ya que previamente incluí a <strong>JavascriptCruncher.dll y ajaxmin.dll en la GAC</strong>. </p>
<p>Hay otras opciones, como indica Oleg Sych en <a href="http://www.olegsych.com/2008/02/t4-assembly-directive/" target="_blank">Understanding T4: <#@ assembly #> directive</a>, pero la realidad es que por uno u otro motivo no pude hacer funcionar ninguna de ellas limpiamente, así que yo recomiendo ésta de agregar la librería a la GAC, ustedes experimenten por su cuenta y después me dicen.</p>
<p>Para esto primero hay que firmar JavascriptCruncher.dll, lo que es muy sencillo. En propiedades del proyecto vamos a la solapa “Signing”. Indicamos que firme el ensamblado y elegimos un nombre de archivo, más una password (opcional):</p>
<p><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="JsCruncher03" border="0" alt="JsCruncher03" src="http://lh4.ggpht.com/_Ieq2x8hAwA8/S50qg8wrG-I/AAAAAAAABJU/PQqt-Qo4A64/JsCruncher03%5B4%5D.png?imgmax=800" width="504" height="310" /> </p>
<p>Tenemos que compilar y luego utilizar gacutil para <strong>registrar las dos librerías (la otra es ajaxmin.dll que es referenciada por la nuestra, y que tampoco está en la gac)</strong>. En la línea de comandos de Visual Studio 2010 (“Menú de Inicio / Todos los programas / Microsoft Visual Studio 2010 / Visual Studio Tools / Visual Studio Command Prompt (2010)”):</p>
<p>gacutil -i "<strong><em>[path completo]</em></strong>\JavascriptCruncher.dll"</p>
<p>y no nos olvidemos de ajaxmin.dll, que también deberá estar en la GAC:</p>
<p>gacutil -i "<strong><em>[path completo]</em></strong>\ajaxmin.dll"</p>
<h5>Probando el template.</h5>
<p>Si corremos el template (grabándolo o utilizando la opción “Run Custom Tool” del menú contextual que aparece al hacer click con el botón de la derecha sobre el template) veremos cómo se agregan los archivos javascript minificados:</p>
<p><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhP6AKyIKyhFyL_orujkge7wiF0TbZfXVLQZ-K9ryCIku6PR18n8XG-z6Twom_l37y6hrijfEQNOtNvPFGEBr4vnsJDfSU260NGxcCPdFUcKlyN6QBAslTnm45CVBcjFDy1FxDc1NTijfE/s1600-h/JsCruncher04%5B4%5D.png"><img style="border-right-width: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto" title="JsCruncher04" border="0" alt="JsCruncher04" src="http://lh6.ggpht.com/_Ieq2x8hAwA8/S50qi9WpqXI/AAAAAAAABJc/0bgeYzQqgec/JsCruncher04_thumb%5B2%5D.png?imgmax=800" width="292" height="228" /></a></p>
<h5>Código fuente del ejemplo.</h5>
<p>Pueden descargarse el código fuente de este ejemplo desde <a href="http://desdesarrollodesoftware.googlecode.com/files/T4Sample.zip" target="_blank">el repositorio de desdesarrollo en google code</a> (no se olviden de registrar las librerías en la GAC).</p> AcPhttp://www.blogger.com/profile/16568867429107885585noreply@blogger.com0tag:blogger.com,1999:blog-4251092826841479333.post-35458580167018527232010-03-07T15:49:00.001-03:002010-03-07T15:49:01.659-03:00100 impresionantes publicidades al servicio de grandes causas.<p>Son 100 excelentes publicidades gráficas que apelan a imágenes chocantes para llamar la atención sobre el hambre, el cambio climático, la prostitución, abuso infantil, abuso de drogras y tabaco, entre otros graves flagelos.</p> <p>Algunas de mis preferidas:</p> <p><a href="http://www.leblogdebango.fr/2010/02/100-publicites-chocs-au-service-des-grandes-causes/" target="_blank"><img style="border-bottom: 0px; border-left: 0px; display: block; float: none; margin-left: auto; border-top: 0px; margin-right: auto; border-right: 0px" title="pubs-grandes-causes-054" border="0" alt="pubs-grandes-causes-054" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgx6_VZlRXpqZTw9hfNd7hiOQuoTgDfBIM6jP2PuZR_PDn8SfVeDD2QCFYffrUB28AgMs1M5q6cRe3P9gEAxM_wwG-reRn5hBPQ9XZJKyMFfNYWmVV83zd2fCL9WaRQ6l-RGgct_KVUzxM/?imgmax=800" width="454" height="342" /></a> <a href="http://www.leblogdebango.fr/2010/02/100-publicites-chocs-au-service-des-grandes-causes/" target="_blank"><img style="border-bottom: 0px; border-left: 0px; display: block; float: none; margin-left: auto; border-top: 0px; margin-right: auto; border-right: 0px" title="pubs-grandes-causes-028" border="0" alt="pubs-grandes-causes-028" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiGjNV0RP0ThvymPcGkZoh13UT32fAjiXM-TjquCZRcArBgg-ggRnlUN7gTg_Z1yuzcH3qk7uahgWeRHLERW0LfGZamIwMO7u1-4jSgqSIlaXE2tEasDLpd_ohyEvnep9JDhD3jNM9Lvak/?imgmax=800" width="454" height="640" /></a> <a href="http://www.leblogdebango.fr/2010/02/100-publicites-chocs-au-service-des-grandes-causes/" target="_blank"><img style="border-bottom: 0px; border-left: 0px; display: block; float: none; margin-left: auto; border-top: 0px; margin-right: auto; border-right: 0px" title="pubs-grandes-causes-082" border="0" alt="pubs-grandes-causes-082" src="http://lh4.ggpht.com/_Ieq2x8hAwA8/S5P1FRK_WLI/AAAAAAAABI8/5l9Wa3j62qQ/pubs-grandes-causes-082%5B11%5D.jpg?imgmax=800" width="454" height="322" /></a> <a href="http://www.leblogdebango.fr/2010/02/100-publicites-chocs-au-service-des-grandes-causes/" target="_blank"><img style="border-bottom: 0px; border-left: 0px; display: block; float: none; margin-left: auto; border-top: 0px; margin-right: auto; border-right: 0px" title="pubs-grandes-causes-071" border="0" alt="pubs-grandes-causes-071" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiTs5mMroFpTsURh0B_U8sJMdSoS4y76XcDvdsirj3NWbprAPijRfpDzGAIHB8CPLV0wA2Ky2thh3MhQmo71mIlobldktPq_4VF97iQ2J3Rv4QQ_SO826EEGv_rOVwQsI7Osp20LvzwBuo/?imgmax=800" width="454" height="325" /></a> <a href="http://www.leblogdebango.fr/2010/02/100-publicites-chocs-au-service-des-grandes-causes/" target="_blank"><img style="border-bottom: 0px; border-left: 0px; display: block; float: none; margin-left: auto; border-top: 0px; margin-right: auto; border-right: 0px" title="pubs-grandes-causes-068" border="0" alt="pubs-grandes-causes-068" src="http://lh4.ggpht.com/_Ieq2x8hAwA8/S5P1GpNu86I/AAAAAAAABJE/KBrv3wo60Fc/pubs-grandes-causes-068%5B8%5D.jpg?imgmax=800" width="454" height="349" /></a> <a href="http://www.leblogdebango.fr/2010/02/100-publicites-chocs-au-service-des-grandes-causes/" target="_blank"><img style="border-bottom: 0px; border-left: 0px; display: block; float: none; margin-left: auto; border-top: 0px; margin-right: auto; border-right: 0px" title="pubs-grandes-causes-046" border="0" alt="pubs-grandes-causes-046" src="http://lh5.ggpht.com/_Ieq2x8hAwA8/S5P1HGvJx3I/AAAAAAAABJI/82lIZpXAcys/pubs-grandes-causes-046%5B8%5D.jpg?imgmax=800" width="454" height="303" /></a> </p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p align="left">Éstas y el resto de las imágenes en resolución original, <a href="http://www.leblogdebango.fr/2010/02/100-publicites-chocs-au-service-des-grandes-causes/" target="_blank">en la entrada de Le Blog de Bango</a>.</p> <p align="right">(Gracias a <a href="http://www.blogger.com/profile/02256826719080038028" target="_blank">@Cerebrado</a>).</p> AcPhttp://www.blogger.com/profile/16568867429107885585noreply@blogger.com0tag:blogger.com,1999:blog-4251092826841479333.post-80459303695779349742010-03-06T13:50:00.001-03:002010-03-06T13:50:32.593-03:00Visual Studio 2010: T4 Text Templates. Una introducción y recursos para empezar.<p>Creo que hace ya un par de semanas que ha salido <a href="http://www.microsoft.com/visualstudio/en-us/try/default.mspx#download" target="_blank">Visual Studio 2010 RC</a>. En el trabajo comenzamos a prepararnos hace ya un tiempo, utilizando la beta, y recién ayer actualizamos a esta última versión. </p> <p>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 <a href="http://desdesarrollodesoftware.blogspot.com/search/label/jQuery" target="_blank">(.Net MVC, mucho $(javascript), templates</a>) 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 <a href="http://getfirebug.com/" target="_blank">FireBug</a>, mi caballito de batalla.</p> <p>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. </p> <p>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 (<a href="http://msdn.microsoft.com/en-us/library/bb399572(VS.100).aspx" target="_blank">Entity Framework</a>, <a href="https://www.hibernate.org/343.html" target="_blank">NHibernate</a>), 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 (<a href="http://www.codesmithtools.com/" target="_blank">CodeSmith</a>).</p> <p>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.</p> <p>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.</p> <p>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:</p> <p><a href="http://www.olegsych.com/2007/12/text-template-transformation-toolkit/" target="_blank">T4: Text Template Transformation Toolkit</a>: este post de Oleg Sych es un muy buen lugar para arrancar velozmente, y tiene además su propia (y extensa) lista de recomendaciones.</p> <p>La documentación en <a href="http://msdn.microsoft.com/en-us/library/bb126445(VS.100).aspx">MSDN</a> 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.</p> <p>Punto aparte y mención especial para <a href="http://www.olegsych.com/2008/03/how-to-generate-multiple-outputs-from-single-t4-template/" target="_blank">este otro post de Oleg Sych</a>, en el que hace referencia desde el template al modelo de objetos de Visual Studio (<a href="http://msdn.microsoft.com/en-us/library/1xt0ezx9(VS.100).aspx" target="_blank">EnvDTE</a>) que abre la puerta a posibilidades realmente interesantes.</p> AcPhttp://www.blogger.com/profile/16568867429107885585noreply@blogger.com2tag:blogger.com,1999:blog-4251092826841479333.post-42591376518511703182010-03-03T14:45:00.001-03:002010-03-03T14:45:25.937-03:00Tostitos: And Then There Was Salsa.<p><object width="400" height="225"><param name="allowfullscreen" value="true" /><param name="allowscriptaccess" value="always" /><param name="movie" value="http://vimeo.com/moogaloop.swf?clip_id=9194146&server=vimeo.com&show_title=1&show_byline=1&show_portrait=0&color=&fullscreen=1" /><embed src="http://vimeo.com/moogaloop.swf?clip_id=9194146&server=vimeo.com&show_title=1&show_byline=1&show_portrait=0&color=&fullscreen=1" type="application/x-shockwave-flash" allowfullscreen="true" allowscriptaccess="always" width="400" height="225"></embed></object></p> <p>Mmmm… no parece la gran cosa. <strong>Pero eso es porque no lo han visto </strong><a href="http://www.vimeo.com/9194146" target="_blank"><strong>en su página original</strong></a><strong>. </strong></p> <p>La verdad que un video así los hace merecedores de una compra al por mayor.</p> <p align="right">Visto en <a href="http://www.fayerwayer.com/2010/03/publicidad-en-3d-%C2%BFsin-necesitar-gafas/" target="_blank">FayerWayer</a>.</p> AcPhttp://www.blogger.com/profile/16568867429107885585noreply@blogger.com0tag:blogger.com,1999:blog-4251092826841479333.post-19753452582860816902010-03-02T01:20:00.000-03:002010-03-02T01:33:51.617-03:00.Net MVC + jQuery: manejo de excepciones. IV: el lado del cliente.<p>Finalizamos el <a href="http://desdesarrollodesoftware.blogspot.com/2010/02/net-mvc-jquery-manejo-de-excepciones.html">post anterior de esta serie</a> 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#.</p> <p>Recordemos el ejemplo final. Teníamos, en javascript, una excepción base:</p> <pre class="javascript" name="code">ExceptionBase = function (type, message) {
this.Type = type;
this.Message = message;
};</pre>
<p>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):</p>
<pre class="javascript" name="code">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");</pre>
<p>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):</p>
<pre class="javascript" name="code">FatalException = function (message, netException) {
this.NetException = netException;
if (typeof message != "undefined" && message != null)
this.Message = message;
}
FatalException.prototype = new ExceptionBase("FatalException", "Unexpected Error");</pre>
<p>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):</p>
<pre class="javascript" name="code"> //... (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)...</pre>
<p>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.</p>
<p>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:</p>
<pre class="javascript" name="code">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();
}
}</pre>
<p>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.</p>
<p>La función auxiliar “GetMessageBox” simplemente construye el html necesario para mostrar el cuadro de diálogo:</p>
<pre class="javascript" name="code">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;
};</pre>
<p>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.</p>
<p>Hasta aquí hemos abarcado las situaciones más comunes, y sólo en el marco de llamadas $.ajax al servidor:</p>
<ul>
<li>Excepciones de negocio generadas del lado del servidor. </li>
<li>Errores producidos en del lado del servidor. </li>
</ul>
<p>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:</p>
<ul>
<li>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#. </li>
<li>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. </li>
<li>Reutilización: cualquier excepción que requiera el mismo tratamiento que las ya implementadas puede representarse con alguna de las clases ya existentes. </li>
<li>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). </li>
</ul>
<p>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.</p> AcPhttp://www.blogger.com/profile/16568867429107885585noreply@blogger.com2tag:blogger.com,1999:blog-4251092826841479333.post-91104376340267037352010-02-28T06:00:00.000-03:002010-02-28T06:00:01.302-03:00Frases: imaginación.<blockquote> <p align="center"><em>La mente es como un paracaídas, sólo funciona si se abre.</em></p> </blockquote> <p align="right">Albert Einstein (Buzzeado –nace un nuevo verbo- por Sebas).</p> AcPhttp://www.blogger.com/profile/16568867429107885585noreply@blogger.com0tag:blogger.com,1999:blog-4251092826841479333.post-7734363460698966282010-02-25T10:30:00.000-03:002010-02-25T10:30:00.181-03:00Frases: olvidar, recordar, entender.<p align="center"><em>Díme, y olvidaré, <br />muéstrame, y recordaré <br />involúcrame, y entenderé. </em> <p align="center"> <br />Tell me, and I will forget, <br />show me, and I may remember, <br />involve me, and I will understand.</p> </p> <p align="right">Confucio (450 a.C.).</p> AcPhttp://www.blogger.com/profile/16568867429107885585noreply@blogger.com0tag:blogger.com,1999:blog-4251092826841479333.post-7099430028873188212010-02-24T09:09:00.001-03:002010-02-24T09:10:08.532-03:00Simulador BitTorrent.<p>Un <a href="http://mg8.org/processing/bt.html" target="_blank">simulador o visualizador del funcionamiento de una red p2p</a>, en este caso bajo el protocolo <a href="http://es.wikipedia.org/wiki/BitTorrent_(protocolo)" target="_blank">BitTorrent</a>. Podemos agregar peers y seeds a voluntad (la visualización aguanta bastante bien una buena cantidad de nodos) y removerlos (aleatoriamente). Está hecho en javascript, por cierto, aunque –dice la página, no lo probé- <strong>no funciona en IE</strong>.</p> <p><a href="http://mg8.org/processing/bt.html" target="_blank"><img title="bitTorrent" style="border-right: 0px; border-top: 0px; display: inline; border-left: 0px; border-bottom: 0px" height="421" alt="bitTorrent" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg_h6sqiWJY1J487x07lLpkNGgkYHUZnrO7U5iNl3VI3Ib05s4A_tNgSy81Sbis9pIH52DYUTn189h3kvoXf4YaLdJ5qK9Z5cglPzKkjcrcGlm8qNehCJ6NBrumQtTyO6VlYmvEH6OhO0w/?imgmax=800" width="404" border="0" /></a> </p> <p>Me entretuve bastante dejando un solo seed con un montón de peers y viendo cómo de a poco la red se saturaba de paquetes.</p> <p align="right">Visto en <a href="http://www.microsiervos.com/archivo/internet/visualizacion-como-funciona-p2p.html" target="_blank">Microsiervos</a>.</p> AcPhttp://www.blogger.com/profile/16568867429107885585noreply@blogger.com0tag:blogger.com,1999:blog-4251092826841479333.post-47026395109777125392010-02-22T06:00:00.001-03:002010-03-02T01:24:20.693-03:00.Net MVC + jQuery: manejo de excepciones. III: Atrapar errores del lado del servidor y comunicarlos controladamente al cliente.<p><em>[Continuación de </em><a href="http://desdesarrollodesoftware.blogspot.com/2010/02/net-mvc-jquery-manejo-de-excepciones-ii.html" target="_blank"><em>.Net MVC + jQuery: manejo de excepciones. II: El problema.</em></a><em>]</em></p> <hr size="1" /> <p><img title="works-on-my-machine" style="border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; margin-left: 0px; margin-right: 0px; border-right-width: 0px" height="145" alt="works-on-my-machine" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiT988HrJUv-5J0KhDxjZBbavQM4o3yPkO0bQpTgfy7f95dQrKKqwwZCznjYyeKA6-qrkluPcp0P2PLaJnjP7xEw6vTKfAPPWseNm_7U4mUWG9S2xKOLu_7oko6mlcD5fUeoVsVNDMA6TE/?imgmax=800" width="150" align="left" border="0" /> 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.</p> <p>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.</p> <hr size="1" /> <p>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. </p> <p>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: </p> <ul> <li>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 <strike>(o a joderse)</strike>, etc. </li> <li>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”. </li> </ul> <p>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. </p> <p>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:</p> <pre class="csharp" name="code">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; }
}
}</pre>
<p>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.</p>
<p>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”:</p>
<pre class="csharp" name="code">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 ....
}</pre>
<p>Tenemos entonces tres estados posibles para una respuesta: </p>
<ul>
<li>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. </li>
<li>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. </li>
<li>Normal: todo bien. </li>
</ul>
<p>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.</p>
<p>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.</p>
<p>La serialización de la excepción es un poco molesta dado que el uso común de <a href="http://msdn.microsoft.com/en-us/library/system.web.script.serialization.javascriptserializer(VS.100).aspx" target="_blank">JavascriptSerializer</a> 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. </p>
<p>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.</p>
<pre class="csharp" name="code">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();
}</pre>
<p>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. </p>
<p>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.</p>
<p>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:</p>
<pre class="javascript" name="code">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;
}</pre>
<p>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. </p>
<p>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);</p>
<p>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):</p>
<pre class="javascript" name="code">//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");</pre>
<p>Son estas dos clases las que nos permiten codificar luego este tipo de instrucciones:</p>
<pre class="javascript" name="code">var netEx = JSON.parse(response);
ex = new ProgramException(netEx.Message, netEx);
ex.Reasons = netEx.Reasons;</pre>
<p>Hasta aquí, un paso más en la batalla. Nos resta:</p>
<ul>
<li>Determinar cómo tratar los errores en la comunicación. </li>
<li>Determinar cómo mostrar toda esta información (hasta ahora no hicimos nada con el método “ShowException”). </li>
</ul>
<p>Hasta la próxima, nos leemos. (Actualización: sigue en <a href="http://desdesarrollodesoftware.blogspot.com/2010/03/net-mvc-jquery-manejo-de-excepciones-iv.html">.Net MVC + jQuery: manejo de excepciones. IV: el lado del cliente.</a>)</p>AcPhttp://www.blogger.com/profile/16568867429107885585noreply@blogger.com0