La épica del informático

programmingHoy toca una entrada para alabar la tarea de nuestros trabajadores del conocimiento (eufemismo clase Dios) que son, como no, ninguneados e ignorados silenciosamente por el público en general y por sus jefes en particular. Pero esperad, antes de eso, un «mea culpa»… ¿Porqué los profesionales de la informática son tan despreciados en esta, una sociedad construida en base a su esfuerzo? Os diré mi opinión… Porque acostumbramos mal a nuestros clientes. Nos piden lo imposible y se lo damos, nos quejamos, nos revolvemos contra la ignoracia atrevida del que nos pide la luna pero finalmente cedemos y eso es lo que nos conduce inexorablemente a las mazmorras de nuestro propio talento.

Si alguien pide un presupuesto a un fontanero para que le ponga un grifo debajo de la cama (por estúpido que eso sea) y consigue un profesional que no se descojone de su ocurrencia y le de el presupuesto lo más normal es que consiga su grifo y nada más. Imaginemos la conversación:

  • Cliente: ¿Qué es esto que me has puesto aquí?
  • Fontanero: un grifo
  • Cliente: pero está debajo de la cama, ¿cómo voy a poder usarlo así?
  • Fontanero: es lo que pidió
  • Cliente: si, claro, pero es de sentido común que un grifo debajo de la cama debe tener algo que permita que se use desde encima de la cama
  • Fontanero: eso no es mi problema
  • Cliente: venga, no me vengas con esas, un grifo que no se puede usar no te lo voy a pagar.
  • Fontanero: es lo que has pedido y es lo que tienes que pagar
  • Cliente: ya, pero al menos harás que se pueda usar desde la cama
  • Fontanero: solo tienes que bajar de la cama y amorrate al caño
  • Cliente: eso no es práctico
  • Fontanero: ya ¿y?
  • Cliente: venga, me haces un apaño para que pueda usarlo desde la cama y luego te contrato la pila del baño
  • Fontanero: ni de coña, aquí tiene la factura.
  • Cliente: bueno, vuelve dentro de un mes y te pago junto con el resto de..
  • Fontanero: ahora
  • Cliente: esto…
  • Fontanero: ¿Pagas o me llevo el grifo y se te inunda la casa?

Bueno, algo así… Los clientes suelen ser más razonables con los fontaneros que con los informáticos, porque, total, seguro que a los informáticos les gusta hacer mal su trabajo y no ponen todas las cosas que tienen que poner porque son vagos.

¡Pues no!

Los informáticos, y los programadores principalmente, son héroes. Gente que consigue lo imposible, que cumple con requisitos inverosímiles, que estira el tiempo disponible hasta lo absurdo para conseguir terminar algo que, probablemente, luego sea mal juzgado por algún ignorante en la materia. Los programadores más experimentados crean obras de arte en unas líneas de código que, aunque podrían ser admiradas en museos donde otros programadores no puedan más que deshacerse en halagos, al final son ejecutados una vez por alguien que no está preparado ni para programar una lavadora y obtienen un comentario estúpido sobre el número de clicks que hay que hacer… Estos héroes anónimos mueven nuestro mundo, allí donde mires hay un programa que escribió alguien, que hace más sencilla tu vida, o que te permite hacer cosas impensables hace solo unos años. Estos héroes sin nombre que, por lo menos en España, están mal pagados, mal vistos (como si picar código al azar en un editor fuese programar), mal dirigidos por gente sin criterio y sin empatía. Formando parte de hojas excel que los mide en kilos de carne y los vende y los compra a otros patanes con ínfulas que creen que hacer informática es un simple problema de coste.

Por todo ello, amigo gerente, si alguna vez un programador te intenta explicar lo difícil que es conseguir algo, entiéndelo como un cumplido que te hace intentando dejar que veas una porción pequeña de su complejo mundo interior. Intenta comprender que no te está intentando dar una excusa, sino describiéndote el peligroso campo de batalla en el que va a meterse en tu nombre y dar hasta su última gota de sudor y sangre para acabar con el enemigo y conseguir esa funcionalidad que tu describes en una frase pero que eres completamente incapaz de explicar en sus detalles (o, simplemente, no quieres hacerlo por pereza).

Día a día, proeza a proeza, sin parar, sin esperar recompensa, sin aparecer en los libros de historia (ni siquiera viendo reconocida su propiedad intelectual muchas veces), el programador anónimo sigue siendo el motor de esta sociedad y, desde aquí mi más sincero agradecimiento a todos ellos. Y, por favor, dejad de tratar a los gerentes como niños, que ellos no quieran escuchar los problemas no significa que estos no estén ahí y que merezcan verlos con sus ojos… Igual así despiertan y os empiezan a tratar como lo que sois, los nuevos magos de la tecnología moderna.

Internacionalizar una web en php

main_icoDespués de muchos meses trabajando en una aplicación que, por azares del destino, alguien decidió que se realizase en php utilizando como frontend Drupal (del que solo se utiliza el sistema de registro y poco más) y después de infinitos test de usuarios (que nunca son suficientes) y después de generar un proyecto con 31.000 líneas de código propias (el total sin contar drupal suma más de 203.000) llega el momento de venderlo.

El primer cliente siempre es el peor, pero es que el segundo ¡es holandés! y, claro, lo quiere en inglés… Y algún lumbrera le asegura que tendrán la versión en inglés ¡¡en una semana!!. Lo bonito del tema es que cuando aseguró eso no preguntó a nadie del equipo de desarrollo. ¿Para qué? Las fechas las ponen ellos, qué más da que sea algo imposible.

Total, que, de casualidad, nos enteramos de que hay que hacer este «pequeño» cambio, las traducciones no serán un problema, porque tenemos gente que puede hacerlo bien y rápido, pero, ¿cómo internacionalizamos el php? La parte que cubre Drupal (y que podría traducirse desde el mismo sistema) es mínima, en total tenemos 425 archivos php donde hay textos en castellano en cualquiera de ellos, además, parte en html, parte en php y parte en javascript… Cualquier otro programador hubiese presentado su carta de renuncia o hubiese pedido una baja laboral. Además, ya nos han dicho que no piensan pagarnos las horas extras y que si cometemos errores tendremos que arreglarlos en nuestro tiempo libre.. ¡Eso si que son condiciones laborales y no las de los mineros de asturias!

Sin embargo mi responsabilidad como profesional me obliga a atender las peticiones por absurdas que sean e intentar dar soluciones… Así que os pongo un resumen de lo que hicimos:

Problema número 1: qué sistema de internacionalización usar.

Al contrario que Java, que dispone de un sistema i18n con properties desde el principio, php adolece de este mecanismo estandar. Después de valorar varias opciones (muchas usadas en programas populares como prestashop, etc.) descubrimos que lo mejor es utilizar gettext.

Gettext es un proyecto GNU que, afortunadamente, es utilizado muy ampliamente en Linux para proporcionar internacionalización a sus programas. Es por ello que está presente en la mayoría de las distribuciones (incluyendo las que usamos para desarrollo y pruebas) y, además, tiene un módulo para php que también está normalmente incluida en las instalaciones normales de php (incluso en XAMPP está).

¿Cómo funciona gettext? Básicamente se basa en localizar todas las cadenas de texto ya escritas y sustituirlas por una función que la traduce. En el caso de php esta suele ser la función gettext(‘cadena’) o, abreviada _(‘cadena’). De esta forma, el texto en html:

<p>Esto es un texto</p>

Lo modificaremos en:

<p><?php _("Esto es un texto")?></p>

Luego el sistema localizará, según los parámetros de idioma, su traducción y la escribirá. Si no encuentra ninguna escribirá esa misma cadena.

Así que el primer, y más tedioso, problema es sustituir todas las cadenas de texto en nuestro programa por estas sustituciones. No hay una forma única y automatizada de hacerlo, porque podemos encontrarnos texto dentro del código php, dentro de etiquetas html o incluso dentro de código javascript y dependiendo de dónde se encuentre esa cadena deberá traducirse (o no).

Problema número 2: traducir las cadenas

Una vez que tenemos «marcadas» todas nuestras cadenas, deberemos generar un archivo con las traducciones… Esto ya es más sencillo de automatizar porque podemos utilizar herramientas que ya existen. La más adecuada es poedit. Esta herramienta nos permite escanear directorios de fuentes y almacena las cadenas que no están traducidas en un catálogo para después permitir que un traductor las vaya traduciendo sin tener que saber nada de php.

Antes de utilizar poedit es muy recomendable que establezcamos las rutas de los directorios donde vamos a guardar los catálogos traducidos. Generalmente esto se hace en el directorio raiz de la aplicación creando estos directorios:

locale/xx_XX/LC_MESSAGES

donde xx_XX es el código del idioma y país (en_GB en nuestro caso). Dentro de cada uno de esos directorios almacenaremos los .po (y .mo que compilamos) que generaremos con poedit.

Una vez creados los directorios, arrancamos el poedit y lo configuramos indicando que el directorio principal será «.» y los directorios a escanear serán ../../../ (suponiendo que hemos respetado la estructura que os he comentado). También es importante el tema de los plurales que, en caso del español sería:

nplurals=2; plural=n != 1;\n

Una vez todo en su lugar solo tendremos que actualizar para que poedit busque en todos los archivos y nos encuentre todas las cadenas que se pueden internacionalizar. Utilizando el mismo programa podemos hacer que los traductores nos traduzcan las cadenas y nos devuelvan el mismo .po ya traducido.

Problema número 3: hacer que el sistema use el idioma del usuario

Para que funcione gettext para php hay que incluir las siguientes líneas al principio del script:

putenv("LC_ALL=$locale");
setlocale(LC_ALL, $locale, 'english');
bindtextdomain("messages", __DIR__."/Locale");
bind_textdomain_codeset("messages", 'UTF-8');
textdomain("messages");

Lo relevante ahí es que $locale ha de contener el idioma (en_GB) messages es el nombre que tiene el catálogo poedit (así tendremos un messages.po en el directorio correspondiente) y que a bindtextdomain hay que pasarle el directorio donde hemos colocado nuestros locales.

Para el caso de drupal vamos a utilizar el idioma que el usuario ha puesto en su perfil, por lo que el código adicional a utilizar es:

global $language;
$locale = $language->language;
if ($locale=='en')
    $locale="en_GB.utf8";
else
    $locale = 'es_ES.utf8';

Esto es así porque los locales han de ser tal como están en el sistema operativo y, sin embargo, los idiomas en drupal solo indican el idioma y no el locale.

Seguimos en ello (hay muchos archivos) y además, nos queda pendiente la internacionalización de los correos y de los informes que generamos… Seguiremos informando.

Los errores SI se pagan

Tengo un jefe que dice que su empresa no paga errores y que las tareas dedicadas a depurar y corregir errores deben hacerse en el tiempo libre.

human-error-in-finance-640x324A mi, que no soy muy dado a cometer errores y que tampoco dispongo de mucho tiempo libre eso me parece simplemente una barbaridad, y no solo porque estemos hablando de ingeniería del software, sino porque estamos hablando, en general, de trabajo hecho por humanos. Es completamente imposible asegurar que el 100% de un trabajo realizado por un ser humano está exento de errores, enmiendas u omisiones. Decir que solo se paga por el trabajo «perfecto» es como decir que te están pagando solo una fracción del tiempo que dedicas. Y eso, simplemente, es explotación.

Según Frederik P. Brooks, Jr. El autor, entre otros, del conocidísimo libro «The mythical Man-month» y autoridad donde las haya en el campo de la ingeniería del sofware, el 50% del tiempo total de un proyecto software debe dedicarse a pruebas, depuración y corrección de errores. ¡Un 50%! más tiempo que el dedicado a programar o a cualquier actividad de gestión. ¿Eso significa que si se hubiese aplicado la política de «yo no pago errores» el software habría salido por la mitad de precio? No, significa que los errores en el software son inevitables y que el buen software no es el que se construye sin cometer errores sino el que se hace teniendo siempre presente que los errores están ahí y hay que corregirlos antes de sacar nada al mercado.

Escudarse en la excusa de que un programador experimentado es el que no comete errores, es una falacia completa. El programador experimentado es aquel que ya ha cometido más errores y, por tanto, tiende a no volver a cometerlos o, al menos, a reconocer los errores cuando los encuentra más rápidamente que otro programador que no ha tenido esa experiencia. Además, muchos «errores» no son más que malas interpretaciones de requisitos o malas integraciones de sistemas distintos que no se habrían podido evitar sin cambiar las políticas de comunicación de la empresa (¿alguien recuerda el pufo de las distintas unidades de medida en la Mars Climate?).

Por más controles que se pongan, por mejores herramientas que se utilicen, por más que estemos concentrados en lo que hacemos el 100%, es imposible evitar la introducción de errores en el código que se produce. Las tareas de pruebas y depuración son muy importantes y determinantes a la hora de medir la calidad del resultado final. Intentar no pagar por esas horas es como invitar a alguien a comer y decir que los entremeses y el postre lo tiene que pagar él. ¿Qué invitación es esa?

Espero que vosotros no os encontréis esta situación que, por otra parte, no debería darse entre gestores formados y experimentados en el campo de la ingeniería del software. Hora que trabajas, hora que se paga. Si eres malo en lo que haces te pagarán menos por cada hora y terminarás en la calle, pero si eres bueno, igualmente cometerás errores (y los corregirás) en horas pagadas acorde a tus habilidades. El resto son milongas.

Páginas estáticas multilingues y con plantilla tiles en struts2

Si, es un título un poco largo, pero he tardado un poco en encontrar una solución a este problema y quiero compartirlo para todos aquellos que os encontréis en la misma tesitura.

Primero, un pequeño resumen de la situación:

  • Aplicación java con struts2+tiles+urlrewrite
  • internacionalización mediante i18n con archivos de properties
  • Necesidad de un número no trivial de páginas «estáticas» con textos largos poco susceptibles de entrar como property.

Los problemas:

  1. No queremos tener una entrada en el tiles.xml por cada página estática pero queremos utilizar las plantillas existentes.
  2. No queremos generar una acción nueva por cada página
  3. No queremos tener que hacer una nueva entrada urlrewrite por cada página
  4. Queremos poder generar páginas de error si no encontramos una página en un idioma determinado.
  5. Queremos poder generar el texto completo en cada idioma como una página html, no como properties.

Si todavía no os habéis hecho una idea, no os preocupeis, es algo normal. Esta situación no se da todos los días.

La solución

O mejor dicho, mi solución…

Voy a crear una única acción que se encargue de determinar el jsp a cargar dentro de la plantilla tiles y voy a modificar una plantilla existente para inyectarle esa nueva página. Además, como el título de la página estará en la plantilla, voy a encargarme de generar el texto en el idioma adecuado. Además, voy a crear un prefijo para el urlrewrite que permita que todo esto quede bonito para google…

Paso a paso:

Modificación del urlrewite.xml:

<rule>
    	<from>^/web/(.*)$</from>
    	<to>/Estaticas.action?pagina=$1.jsp</to>
    </rule>

Con esta regla le decimos que siempre que tengamos una url con la forma /web/mipagina llamaremos a la acción Estaticas y le pasaremos como parámetro mipagina.jsp

Modificacion en struts.xml:

<action name="Estaticas" 
           class="com.yoprogramo.web.action.EstaticasAction">
  <result>/estatica.jsp</result>
</action>

Con esa acción lo que hacemos es llamar a EstaticasAction.java (luego lo vemos) y redirigir a estatica.jsp que tiene este contenido (quitando las cabeceras):

<tiles:definition name="estatica.modif" extends="estatica">
  <tiles:putAttribute name="body" value="${pagina}" />
</tiles:definition>
<tiles:insertDefinition name="estatica.modif" />

Lo que estamos indicando es que se modifique la plantilla con nombre estatica, definida en el tiles.xml y que ponga como atributo body el valor que la acción nos ha devuelto en pagina, de esta manera estaremos utilizando la plantilla definida en el tiles.xml, pero pasándose un jsp distinto. Esta forma de utilizar tiles es lo que se denomina «mutable», para poder utilizarla hay que incluir en el archivo web.xml:

  <context-param>
        <param-name>org.apache.tiles.factory.TilesContainerFactory.MUTABLE</param-name>
        <param-value>true</param-value>
  </context-param>

Ahora que tenemos todo en su sitio, solo tenemos que crear una estructura donde guardar las páginas de cada idioma y poder localizarlas facilmente. Por ejemplo, yo he creado una con esta estructrura:

Arbol de páginas estáticas

Bajo «es» pondré las páginas en español, en «en» las páginas en inglés y en «multi» las páginas multilingues que utilizan el packages.properties para traducir sus claves.

Lo único que nos queda ahora es programar la acción EstaticasAction.java .. Os dejo el código del execute:

		// Eliminamos ruta de lenguaje
		int idx=pagina.lastIndexOf("/");
		String page_name = pagina;
		
		if (idx>0)
			page_name = pagina.substring(idx+1);
		
		idx = page_name.lastIndexOf(".");
		if (idx>0)
			page_name=page_name.substring(0,idx);
		
		//Parte multilingue.
		//Vamos a crear una página para cada idioma, excepto para las
		//que sean muy simples y esas estarán en el directorio /multi
		if (!pagina.contains("/multi")) {
			String lang = getText("locale.language");
			if (lang==null)
				lang="en";
			pagina = "/"+lang+"/"+pagina;
		}
		
		// Ahora comprobamos si existe la página y si no existe redirigimos
		// a una página de error del tipo multi
		String servletContext = 
                     ServletActionContext.getServletContext()
                                                 .getRealPath("/estaticas");
		String filePagina = servletContext+pagina;
		File f = new File (filePagina);
		if (f.exists())
			pagina="/estaticas"+pagina;
		else
			pagina="/estaticas/multi/noexiste.jsp";
		
		// Ahora ponemos el título, que deberá estar como un texto 
		// en package con la forma web.<nombre_pagina>.titulo
		String key = "web."+page_name+".titulo";
		titulo = getText(key);
		
		return SUCCESS;

Una vez todo puesto en su sitio la mecánica para crear páginas estáticas y usarlas en nuestra aplicación es bastante simple:

  1. Crear un jsp con el texto para cada idioma y colocarlos bajo el directorio correcto (p.je. es/mipagina.jsp y en/mipagina.jsp)
  2. Crear una entrada en el package_en.properties y package_es.properties con la clave web.mipagina.titulo indicando el título de la página en cada idioma. (Recordemos que el titulo está en el head de la página y eso suele estar en la plantilla, no en el jsp que estamos modificando).
  3. Ya podemos acceder a /web/mipagina y ver cómo queda dentro de nuestra plantilla.

Igual inicialmente parece mucho trabajo, pero una vez hecho esto podréis hacer tantas páginas estáticas como queráis sin ningún esfuerzo y ligadas a las plantillas del resto de vuestra aplicación.

Mi primera aplicación para iPhone. (Experiencias con Apple)

Algo que tenía pendiente, y que sabía que tarde o temprano tendría que aprender es a programar para iOS, desde que tengo el iPhone no dejo de sorprenderme de las cosas que se pueden hacer con este aparatito. Aprovechando la excusa de hacer un cliente móvil para BiblioEteca me puse manos a la obra y pude comprobar los pros y contras de programar para iOS.

Pero antes de continuar escribiendo nada sobre el particular, me gustaría que pudieseis ver el video que preparé (gracias al iMovie) sobre la aplicación gratuita que ya está en la appstore de apple:

El resultado no es perfecto, pero si que es lo suficientemente llamativo para ser considerada una aplicación adecuada para el móvil. todavía no he terminado su desarrollo y tengo más ideas interesantes para implementar en la aplicación, pero ya me ha permitido programar en la plataforma iOS y subir a la appstore una aplicación y una actualización de la misma. Este post es un resumen de mis experiencias con apple.
Sigue leyendo