Genbeta Dev, nuestro portal por y para desarrolladores, se integra en Genbeta

20/07/2018
Artículo original

Genbeta Dev Logo

Weblogs SL presentaba hace poco más de más de siete años Genbeta Dev, una publicación creada por y para desarrolladores. Un espacio que, como os contamos entonces, iba a estar protagonizado por contenidos de índole más técnica, orientados a los profesionales del sector de la programación, el desarrollo y el software en general.

Noticias y artículos sobre plataformas, lenguajes, mercado laboral, gestión del trabajo, productividad profesional... fueron los ingredientes de un medio especializado que ahora se integra aquí, en Genbeta. Os lo explicamos.

Genbeta Dev como sección sobre desarrollo

Desde hoy mismo, todo lo publicado hasta el momento por Genbeta Dev forma parte de nuestro portal y lo que se publique en el futuro aparecerá en un espacio diferenciado de nuestra portada: el que podéis ver bajo estas líneas.

Seccion Genbeta Dev

Basta con desplazarse por la página principal de Genbeta para llegar a esta franja horizontal azulada en la que aparecen los cinco últimos artículos de Genbeta Dev. Para acceder al resto, tendremos que clicar sobre el enlace Ver más contenidos o acceder directamente a la categoría que los agrupa: desarrollo.

Por lo demás, nada va a cambiar para los lectores habituales de Genbeta. Genbeta Dev se integra y pasa a convertirse en una sección, pero sus artículos no aparecerán junto al resto. De forma excepcional puede que algún tema resulta tan importante que lo llevemos a portada, pero no será habitual.

Seguiremos con nuestro trabajo diario y nuestro portal funcionará como de costumbre. Os recordamos que podéis seguir nuestras publicaciones a través de Twitter y Facebook al igual que las de Genbeta Dev, que continuarán enlazándose en sus redes sociales: tanto en Twitter, como en Facebook.

¿Cuáles son las causas más comunes de despido de un programador?

19/07/2018
Artículo original

Tras muchos años trabajando en empresas de software, he llegado a la conclusión de que hay dos departamentos que luchan por la hegemonía en la empresa: programación y ventas.

La gestión de estos departamentos es complicada, tanto por los CEO's a nivel global, como por los mandos intermedios responsables de cada uno de ellos.

La gestión de perfiles de ventas: todo por la pasta

Profesionalmente siempre he estado ligado a empresas tecnológicas de desarrollo de software pero nunca he gestionado a un equipo de programadores, aunque sí he tenido mucha relación directa con ellos. Mi ámbito siempre ha sido el de las ventas, el marketing y la comunicación en los que sí he tenido personas a mi cargo.

En las reuniones con los responsables de otros departamentos tienes la oportunidad de saber qué es "lo que se cuece" en cada uno de ellos, y en muchas ocasiones servimos los unos de paño de lágrimas de los otros ya que es un foro donde los mandos intermedios podemos desahogarnos y decir lo que está pasando en nuestros departamentos en busca de consejo en algunos casos, y como terapia de grupo la mayoría de las veces.

En los departamentos de venta los motivos de despido son en el 90% de los casos muy claros. El comercial no llega al objetivo de venta. Por lo general los buenos comerciales tienen mucho ego, tienen la necesidad de sentirse importantes y les gusta que se les reconozca no solo económicamente su valor, sino públicamente y de manera expresa en reuniones, cenas, eventos y demás contextos en grupo. Las empresas toleran estoicamente el comportamiento individualista y narcisista de sus comerciales y ejecutivos de cuentas bajo una sola condición muy fácil de medir: superar el objetivo de ventas, sí, la facturación obtenida de conseguir nuevos clientes. Vamos, que si un comercial tiene una conducta algo excéntrica y especial se le permite siempre y cuando sea capaz de vender mucho. He visto como juntas directivas enteras y dueños de empresas más pequeñas han pasado por alto conductas que a otros miembros de la empresa les hubiera costado más que una reprimenda.

Como gestor de personas que se dedican a la venta tienes que saber cómo llevar esas situaciones sin que la situación llegue a mayores o afecte a otras personas de tu equipo, y sin que tampoco trascienda el ámbito del departamento. Con algunos perfiles comerciales ese trabajo desgasta mucho a nivel emocional.

La gestión de perfiles de programación: un caso real

En los departamentos de programación, la gestión de las personas es totalmente diferente a pesar de los paralelismos que hay entre los buenos profesionales de la venta y los buenos desarrolladores; porque al fin y al cabo, somos todos personas y nos parecemos mucho entre nosotros.

La capacidad de tolerancia del ego, la ambición y el individualismo es completamente diferente en función de si gestionas comerciales o programadores.

Voy a contar una historia que viví en una de las empresas en las que trabajé con nombres ficticios y sin entrar en detalles que atenten contra la privacidad.

Mi compañero "Javier" acababa de ser ascendido a responsable del departamento de programación de la empresa. El departamento estaba compuesto por varios ingenieros de software y perfiles técnicos de programación, creo recordar que eran unas 10 diez personas en total.

En el equipo había un programador muy curioso ("Álex"), ávido de conocimiento y que era un especialista en buscar soluciones creativas a los problemas de desarrollo. Javier y Álex tenían una buena relación y Javier tenía en alta estima las habilidades técnicas de Álex y su forma de enfocar las tareas de programación, ya que en este ámbito aportaba mucho a la empresa y al departamento.

Pero Álex tenía la costumbre de criticar las políticas de empresa, desde los sueldos y los "extras" hasta la política de recursos humanos, y muchas veces ponía sus quejas por escrito enviando interminables correos electrónicos al CEO de la empresa. Álex además tenía algunos problemas con sus compañeros porque su actitud era de "programador estrella", y no solo criticaba a sus compañeros programadores, también cargaba contra los arquitectos y los diseñadores de UX del software. Javier lo veía como algo positivo ya que para él era bueno tener personas con espíritu crítico en el equipo.

Un buen día Javier recibió la llamada del jefe de tecnología de la empresa (su jefe directo) y le encomendaron la misión de directamente "cargarse" a Álex. Con solo el apoyo del recién llegado Javier y un par de compañeros del equipo, las continuas quejas habían colmado el vaso. Al parecer las quejas de Álex estaban consumiendo la paciencia de la dirección y habían decidido ponerle fin a este monólogo: le pidieron a Javier que tenía que despedirlo.

Javier se puso en contacto con el departamento de recursos humanos y le dijeron que, como responsable directo, era su obligación despedirlo aunque no tuviera muchas ganas de hacerlo. Javier no llevaba ni seis meses en el cargo y era la primera vez que iba a despedir a alguien.

Pasaron varios días, la presión desde dirección era cada vez mayor. Javier, como buen mando intermedio recién nombrado llamó a Álex a su oficina y le despidió, con el guión que le habían preparado desde recursos humanos.

Álex salió de la empresa algo deprimido y contrariado pues nunca se esperaba ser despedido por enviar emails a dirección. Pero fueron un cúmulo de circunstancias las que desembocaron en su despido.

¿Por qué se despide a un programador?

Recientemente pensando en esta historia, me pregunté a mi mismo ¿Por qué motivos se suele despedir a un programador o a un ingeniero de software?

Creo que es una buena pregunta porque, desde mi experiencia, existen miles de razones por las que despedir a cualquier tipo de empleado, y también hay miles de razones por las cuales los mandos evitan despedir a empleados que sí deberían despedir.

Sin embargo, para desarrolladores de software e ingenieros, siempre hay un conjunto de motivos que destacan sobre el resto. Al igual que a un comercial se le despide por no llegar a la cifra de venta, o por tratar mal a un cliente o quitarle cuentas a sus compañeros, los programadores tienen sus propios motivos de despido "tipo".

En algunas empresas de software la relación entre los perfiles técnicos y el personal no técnico no son todo lo fluidas que deberían ser y en ocasiones se tensan tanto que se van a la quiebra. Yo soy un perfil no-técnico y con este artículo pretendo humildemente contribuir a que los nuevos equipos humanos en empresas de software se entiendan un poco mejor.

Pero antes...

Un consejo para perfiles no técnicos

Para aquellos miembros no-técnicos del equipo como lo soy yo, entender cómo a veces se les "etiqueta" a los desarrolladores y otros pequeños puntos débiles comunes que suelen tener puede resultar de gran ayuda. Por ejemplo, en ocasiones a los programadores les cuesta cumplir con los plazos por muchas razones perfectamente válidas y razonables. Muchas veces se desconoce realmente la dificultad o los retos que presenta el problema que tienen enfrente. Pero como ingenieros de software y programadores que son, buscan la perfección y a menudo les cuesta reconocer cuando decir basta, ya que siempre hay margen de mejora cuando te sientas a programar un sistema.

Los no-técnicos tenemos que entender que esa es la naturaleza de los perfiles técnicos que son buenos en su trabajo. Si como perfil no-técnico aprendes a llevar bien esta necesidad de "mejora continua" y no verlo como que están rehuyendo de sus responsabilidades en relación con los plazos, verás que se te va a dar mucho mejor trabajar con tus compañeros técnicos.

Un consejo para perfiles técnicos

Para los perfiles técnicos, que en esta web sois mayoría, y, obviando el hecho de que estoy generalizando, espero que estas palabras sirvan para que tengáis información sobre cómo vuestras conductas y personalidades son percibidas por terceros. Por ejemplo, si sabes que lo que está pidiendo el cliente no es lo que realmente necesita y no lo sabes transmitir con buenas palabras, o si le dices a tus compañeros de trabajo que son idiotas por no ver algo muy obvio, aunque tengas razón, vas a quedar de engreído y lo único que vas a conseguir es enfadar a todo el mundo.

Si caes en este tipo de comportamiento de forma habitual, tenlo claro: te van a despedir. Elige tus batallas, y aprende cómo venderles a los demás tus brillantes ideas. Pero no te limites a simplemente decirles a los demás que están equivocados. Averigua lo que quieren en realidad, lo que necesitan, y busca un camino que te lleve a ello. Al final les podrás convencer y te entenderán mucho mejor, y estarán de acuerdo contigo al completo. Te convertirás así en un activo útil y no en un abusón incomprendido.

He visto cómo despedían a varios perfiles técnicos, ya fueran programadores o jefes de programación. Si quitas las causas objetivas de despido tipo por robar o por mentir en los CV, y quitas también los casos en los que la empresa se ha equivocado de candidato, cuando no se verifican las referencias o el proceso selección se hace sin el debido rigor, se limita bastante el número de causas por las cuales un perfil técnico es despedido, y se pueden resumir en las siguientes categorías:

Principales causas de despido de desarrolladores

  • Incapacidad de cumplir compromisos. Muchos programadores son incapaces de parar o de limitar el alcance de su trabajo, y siempre buscan la solución perfecta. Cuando esto hace que todo el mundo o el proyecto en su conjunto se retrase de forma repetida, termina en despido. Más de un proyecto sólido de software ha fracasado por la incapacidad/imposibilidad de despedir a este tipo de programador, que en muchos casos era el CTO fundador.

  • Incapacidad de trabajar en equipo por ego: hay algunos desarrolladores que no entienden que los días del programador "vaquero solitario" han terminado. La mayoría de los proyectos hoy en día exigen trabajo en equipo y la colaboración interdepartamental. Aquellos que no sean capaces de trabajar en equipo porque "saben mejor que nadie" lo que hay que hacer sencillamente tienen sus días contados en las empresas. Esto no tiene nada que ver con aquellos programadores que sí ponen toda la buena voluntad del mundo a la hora de trabajar en equipos pero que carecen de habilidades de comunicación o de empatía. Cualquier buen responsable de departamento puede gestionar a este perfil, pero los perfiles con mucho ego y narcisistas es mucho más complicado.

  • Incapacidad de trabajar en un producto por ego: a veces un desarrollador de software discuten con clientes o con los diseñadores del producto pensando que sabe más y mejor que los clientes o que los diseñadores de producto lo que hay que hacer, y no hacen lo que se les pide, o lo hacen a regañadientes. Y luego todos los demás son idiotas porque la funcionalidad que ellos proponían y que ya habían diseñado es claramente superior: ¡despedido!.

  • Misoginia: esto no es muy popular decirlo, pero existe cierto sentimiento despectivo hacia las mujeres en la profesión de desarrolladores de software. ¿Por qué? Los motivos son varios y recientemente tratamos en este blog las estadísticas de mujeres en programación y las cosas no pintan bien. Al margen de los motivos, un buen líder en una empresa o un departamento no tolera conductas en este sentido, y en especial cuando van dirigidas en contra de una clase entera de compañeras por su género. Al final este tipo de conductas son inadmisibles por muy buen programador que sea. Si no lo despide el responsable del departamento, lo normal es que RRHH despida tanto al responsable del departamento como al programador que incurre en comportamientos discriminatorios. La empresa no se puede permitir esa actitud bajo ningún concepto, caiga quien caiga.

  • Incapacidad de estar al día: cuando se programa software, las herramientas de desarrollo evolucionan relativamente rápido. Un desarrollador hoy en día utiliza más de un lenguaje, entorno y plataforma. Es bueno tener una base sólida en un lenguaje, pero también lo es tener la capacidad de aprender aquellas tecnologías que pueden ser útiles para empresa y no quedarse anclado. La clave es saber cuándo es el momento, y no caer en modas pasajeras. Si vas a arrancar con un proyecto y exige nuevas tecnologías por motivos de calidad y de mantenimiento, o simplemente sabes que tu empresa va a contratar personal mejor cualificado, lo mejor es que te actualices. Si no estás dispuesto a estar al día de forma sensata sin caer en modas, lo más probable es que te despidan.

  • Introducir nuevas herramientas en la empresa sin consentimiento: una variante del punto anterior es el caso de aquellos programadores que introducen en la empresa nuevas tecnologías a escondidas en contra del criterio de la empresa. Es muy común querer hacerlo, pero es algo que en términos de política de empresa está muy mal visto aunque al final resulte que técnicamente se tenga razón.

  • Fomentar el mal ambiente por frustración: si un responsable de departamento escucha decir "sí, claro, lo que quieras jefe" en tono sarcástico, ya saben de antemano no solo que no se van a cumplir los plazos de desarrollo, sino que encima el software va a ir incompleto y de mala calidad. ¿Cómo lo sabe? Porque si hay discrepancias en torno al diseño del producto, conjunto de funcionalidades, etc. y eso significa que hay miembro del equipo que no está remando en la misma dirección y que encima se cree que sabe más de lo que realmente sabe. La inmadurez y el comportamiento poco profesional no forjan buenos equipos de trabajo. En empresas de programación este motivo de despido es muy común.

  • Código de mala calidad: si alguien sigue metiendo bugs y no hace pruebas con la diligencia debida, el coste de tenerlo en la empresa se vuelve demasiado alto en muy poco tiempo. Sobre todo si sus errores los encuentra el cliente a modo de fallos en el software. Incluso si el equipo de desarrollo no asume el coste del servicio de soporte, sí lo hace la empresa y al final el equipo paga los platos rotos y pierde la confianza del resto de la empresa.

Hay otras razones específicas que causan el despido de un programador, pero yo diría que estas son las más comunes, al menos en mi experiencia, quitando la de una mala contratación por parte de RRHH.

Habiendo dicho todo esto, liderar equipos de desarrollo de software es una tarea complicada y exige empatía, flexibilidad y una dosis de creatividad, de una forma especial y distinta a la gestión de otro tipo de equipos de trabajo. Por esta razón, los malos jefes también son un motivo muy común en el despido de los programadores. He visto salir a grandes técnicos de empresas de programación por motivos ridículos y todo ello por culpa de una pésima gestión. Entremos a valorar los más comunes despidos de este tipo.

Despidos por culpa de la mala gestión

  • Incapacidad de gestionar a los "pesimistas": hay muchos perfiles técnicos que siempre ven el lado negativo y que se han entrenado para identificar problemas y puntos críticos. Este tipo de programadores son muy valiosos y es responsabilidad del jefe tirar de ellos y obtener información sobre lo que sí va a funcionar o sobre cómo arreglar las cosas para que sí funcionen y sacarles del estado "esto nunca va a funcionar" en el que están instalados. Hay algunos responsables de departamento que no saben gestionar esto o que no saben "tirar" del equipo, así que optan por despedir al negativo.

  • Usar las medidas equivocadas: algunos gestores que vienen de otros sectores buscan métricas y objetivos sencillos para medir el rendimiento. En una de las empresas en las que trabajé con una programadora que escribía menos líneas de código que el resto, y el jefe se creía que producía menos que el resto. ¡Y quería despedirla! En esa ocasión fue la propia programadora la que se fue a una empresa mejor.

  • Ego o narcisismo por parte del responsable: a los desarrolladores de software hay que dejarles seguir su propio camino de vez en cuando, y hay que saber qué momentos son buenos para que lo hagan. Si un responsable de departamento va siempre de que sabe más y no es capaz de aprender de su equipo, habrá conflictos graves y despidos a la vista. La gestión de equipos de software requiere mucha capacidad de escucha y la capacidad de saber rectificar y cambiar de opinión.

  • Centrarse en el cómo y no el qué: algunos jefes de programación se centran mucho más en cómo se hacen las cosas que en qué se está haciendo. Se pierde visión y se incurre en micro-gestión de la mala, ya que le resta criterio y conocimiento al equipo de desarrollo y los transforma en programadores de libro. Conflictos y cartas de despido a la vista.

  • Incapacidad de interpretar y traducir: le corresponde a los responsables de proyecto y de desarrollo interpretar y traducir las necesidades empresariales al equipo, y a la inversa, traducir las posibilidades reales de programación y sus tiempos a la empresa. Muchas veces, cuando el responsable no es capaz de hacer esto bien, se instala el caos. ¿Quién paga los platos rotos? Normalmente el programador más vulnerable y no el responsable.

Hay más motivos y podría escribir otras tres mil palabras sobre el tema, pero ahora te dejo a ti. ¡Cuéntanos tu historia en la zona de comentarios!

Variables y tipos de datos en Java: tipos simples, clases y tipos envoltorio o wrapper

17/07/2018
Artículo original

Todo programa de ordenador persigue ofrecer una funcionalidad determinada para la que, por regla general, necesitará almacenar y manipular información. Dicha información, que son los datos sobre los que operaremos, deben almacenarse temporalmente en la memoria del ordenador. Para poder almacenar y recuperar fácilmente información en la memoria de un ordenador los lenguajes de programación ofrecen el concepto de variables, que no son más que nombres que "apuntan" a una determinada parte de la memoria y que el lenguaje utiliza para escribir y leer en esta de manera controlada.

El acceso a esta información se puede mejorar dependiendo del tipo de información que almacenemos. Por ejemplo, no es lo mismo tener la necesidad de manejar números, que letras que conjuntos de datos. Y dentro de éstos no es igual tener que almacenar un número entero que uno decimal. Aunque al final todo son ceros y unos dentro de la memoria de nuestro ordenador, es la forma de interpretarlos lo que marca la diferencia, tanto al almacenarlos como al recuperarlos.

Este el motivo por el que los lenguajes de programación cuentan con el concepto de tipos de datos: se trata de distintas maneras de interpretar esos "ceros y unos" en función de ciertas configuraciones que establecen el espacio utilizado así como la representación aplicada para codificar y descodificar esa información.

Cada tipo de datos se identifica por un nombre y es capaz de almacenar una determinada clase de información así como un rango de valores concreto.

Ahora que ya tenemos claro los conceptos de variable y de tipo de datos, vamos a ver cómo se traduce esto a la práctica en uno de los lenguajes más populares: Java.

Pantallas de ordenador con código - Foto ornamental
Foto por Farzad Nazifi en Unsplash

Tipos de datos primitivos en Java

Java cuenta con un pequeño conjunto de tipos de datos primitivos. Podríamos considerarlos fundamentales, ya que la mayor parte de los demás tipos, los tipos estructurados o complejos, son composiciones a partir de estos más básicos. Estos tipos de datos primitivos sirven para gestionar los tipos de información más básicos, como números de diversas clases o datos de tipo verdadero/falso (también conocidos como "valores booleanos" o simplemente "booleanos").

De estos tipos primitivos, ocho en total, seis de ellos están destinados a facilitar el trabajo con números. Podemos agruparlos en dos categorías: tipos numéricos enteros y tipos numéricos en punto flotante. Los primeros permiten operar exclusivamente con números enteros, sin parte decimal, mientras que el segundo grupo contempla también números racionales o con parte decimal.

Tipos numéricos enteros

En Java existen cuatro tipos destinados a almacenar números enteros. La única diferencia entre ellos es el número de bytes usados para su almacenamiento y, en consecuencia, el rango de valores que es posible representar con ellos. Todos ellos emplean una representación que permite el almacenamiento de números negativos y positivos. El nombre y características de estos tipos son los siguientes:

  • byte: como su propio nombre denota, emplea un solo byte (8 bits) de almacenamiento. Esto permite almacenar valores en el rango [-128, 127].
  • short: usa el doble de almacenamiento que el anterior, lo cual hace posible representar cualquier valor en el rango [-32.768, 32.767].
  • int: emplea 4 bytes de almacenamiento y es el tipo de dato entero más empleado. El rango de valores que puede representar va de -2<sup>31</sup> a 2<sup>31</sup>-1.
  • long: es el tipo entero de mayor tamaño, 8 bytes (64 bits), con un rango de valores desde -2<sup>63</sup> a 2<sup>63</sup>-1.

Tipos numéricos en punto flotante

Los tipos numéricos en punto flotante permiten representar números tanto muy grandes como muy pequeños además de números decimales. Java dispone de 2 tipos concretos en esta categoría:

  • float: conocido como tipo de precisión simple, emplea un total de 32 bits. Con este tipo de datos es posible representar números en el rango de 1.4x10<sup>-45</sup> a 3.4028235x10<sup>38</sup>.

  • double: sigue un esquema de almacenamiento similar al anterior, pero usando 64 bits en lugar de 32. Esto le permite representar valores en el rango de 4.9x10<sup>-324</sup> a 1.7976931348623157x10<sup>308</sup>.

Booleanos y caracteres

Aparte de los 6 tipos de datos que acabamos de ver, destinados a trabajar con números en distintos rangos, Java define otros dos tipos primitivos más:

  • boolean: tiene la finalidad de facilitar el trabajo con valores "verdadero/falso" (booleanos), resultantes por regla general de evaluar expresiones. Los dos valores posibles de este tipo son true y false.
  • char: se utiliza para almacenar caracteres individuales (letras, para entendernos). En realidad está considerado también un tipo numérico, si bien su representación habitual es la del carácter cuyo código almacena. Utiliza 16 bits y se usa la codificación UTF-16 de Unicode.

Tipos de datos estructurados

Los tipos de datos primitivos que acabamos de ver se caracterizan por poder almacenar un único valor. Salvo este reducido conjunto de tipos de datos primitivos, que facilitan el trabajo con números, caracteres y valores booleanos, todos los demás tipos de Java son objetos, también llamados tipos estructurados o "Clases".

Los tipos de datos estructurados se denominan así porque en su mayor parte están destinados a contener múltiples valores de tipos más simples, primitivos. También se les llama muchas veces "tipos objeto" porque se usan para representar objetos. Puede que te suene más ese nombre.

Cadenas de caracteres

Aunque las cadenas de caracteres no son un tipo simple en Java, sino una instancia de la clase String, el lenguaje otorga un tratamiento bastante especial a este tipo de dato, lo cual provoca que, en ocasiones, nos parezca estar trabajando con un tipo primitivo.

Aunque cuando declaramos una cadena estamos creando un objeto, su declaración no se diferencia de la de una variable de tipo primitivo de las que acabamos de ver:

String nombreCurso = "Iniciación a Java";

Y esto puede confundir al principio. Recuerda: Las cadenas en Java son un objeto de la clase String, aunque se declaren de este modo.

Las cadenas de caracteres se delimitan entre comillas dobles, en lugar de simples como los caracteres individuales. En la declaración, sin embargo, no se indica explícitamente que se quiere crear un nuevo objeto de tipo String, esto es algo que infiere automáticamente el compilador.

Las cadenas, por tanto, son objetos que disponen de métodos que permiten operar sobre la información almacenada en dicha cadena. Así, encontraremos métodos para buscar una subcadena dentro de la cadena, sustituirla por otra, dividirla en varias cadenas atendiendo a un cierto separador, convertir a mayúsculas o minúsculas, etc.

Vectores o arrays

Los vectores son colecciones de datos de un mismo tipo. También son conocidos popularmente como arrays e incluso como "arreglos" (aunque se desaconseja esta última denominación por ser una mala adaptación del inglés).

Un vector es una estructura de datos en la que a cada elemento le corresponde una posición identificada por uno o más índices numéricos enteros.

También es habitual llamar matrices a los vectores que trabajan con dos dimensiones.

Los elementos de un vector o array se empiezan a numerar en el 0, y permiten gestionar desde una sola variable múltiples datos del mismo tipo.

Por ejemplo, si tenemos que almacenar una lista de 10 números enteros, declararíamos un vector de tamaño 10 y de tipo entero, y no tendríamos que declarar 10 variables separadas de tipo entero, una para cada número.

Tipos definidos por el usuario

Además de los tipos estructurados básicos que acabamos de ver (cadenas y vectores) en Java existen infinidad de clases en la plataforma, y de terceros, para realizar casi cualquier operación o tarea que se pueda ocurrir: leer y escribir archivos, enviar correos electrónicos, ejecutar otras aplicaciones o crear cadenas de texto más especializadas, entre un millón de cosas más.

Todas esas clases son tipos estructurados también.

Y por supuesto puedes crear tus propias clases para hacer todo tipo de tareas o almacenar información. Serían tipos estructurados definidos por el usuario.

Tipos envoltorio o wrapper

Java cuenta con tipos de datos estructurados equivalentes a cada uno de los tipos primitivos que hemos visto.

Así, por ejemplo, para representar un entero de 32 bits (int) de los que hemos visto al principio, Java define una clase llamada Integer que representa y "envuelve" al mismo dato pero le añade ciertos métodos y propiedades útiles por encima.

Además, otra de las finalidades de estos tipos "envoltorio" es facilitar el uso de esta clase de valores allí donde se espera un dato por referencia (un objeto) en lugar de un dato por valor (para entender la diferencia entre tipos por valor y tipos por referencia lee este artículo. Aunque está escrito para C#, todo lo explicado es igualmente válido para Java).

Estos tipos equivalentes a los primitivos pero en forma de objetos son: Byte, Short, Integer, Long, Float, Double, Boolean y Character (8 igualmente).

A continuación te dejamos un esquema resumen de todo lo anterior para facilitarte que lo "digieras" viéndolo ya ordenado:

Esquema de tipos de datos en Java

Cómo maquetar HTML con el sistema grid de CSS

11/07/2018
Artículo original

El sistema grid de CSS es el nuevo estándar para estructurar elementos en 2 dimensiones en páginas web. A diferencia del sistema flexbox, grid permite definir con precisión la distribución de los elementos en los ejes horizontal y vertical simultáneamente. Además, soluciona muchos problemas clásicos de estilo y estructura con mucho menos código CSS, y nos puede servir como sustituto de frameworks responsive tipo Bootstrap, si únicamente nos hace falta la funcionalidad de columnas.

En este artículo construiremos estructuras comunes de páginas web de forma simple mediante grid, y veremos lo versátil que llega a ser. ¡Vamos allá!

Una estructura clásica: cabecera, dos columnas y pie

Disposición básica

Nuestro primer ejemplo será una página web con una disposición básica: un título arriba, una columna de contenido y otra lateral, y un pie de página.

Teniendo en mente la disposición, antes de nada diseñaremos la rejilla a la que queremos que se ajuste. Una opción razonable es disponer de 3 filas y 4 columnas, de forma que podamos colocar todos los elementos y tengamos una columna vacía a cada lado del contenido central:

Rejilla para la estructura diseñada

Para comenzar a construir la rejilla, necesitamos un elemento contenedor, el que tendrá definidas las filas y columnas de la misma. Dentro de él incluiremos todos los elementos necesarios para componer nuestra página:

<div class="basic">
  <header>Título</header>
  <nav>
    <h3>Navegación</h3>
  </nav>
  <div class="main">
    <h2>Contenido principal de la página</h2>
  </div>
  <footer>Pie</footer>
</div>

Ahora, activamos el uso de grid en el contenedor mediante la propiedad display: grid. Tras haberlo activado, para definir la rejilla en sí se declara su composición mediante las propiedades grid-template-rows, grid-template-columns y grid-template-areas, que definen respectivamente la distribución de filas, columnas y, dentro de éstas, qué nombre queremos darle a las áreas rectangulares definidas por estas filas y columnas.

Según el diseño, puede que no haga falta definirlas todas, sino que se podrán dejar con sus valores por defecto, que activan el comportamiento automático del navegador para colocar los ítems en la rejilla. Como en nuestro caso queremos un número determinado de filas y columnas y cada ítem ocupará una o varias de las áreas resultantes, las definimos todas como sigue:

.basic {
    display: grid;
    grid-template-rows: 4rem auto 200px;
    grid-template-columns: 1fr 1fr 3fr 1fr;
    grid-template-areas:
        "header header header header"
        ".      nav    main   .     "
        "footer footer footer footer";
}
.basic header {
    grid-area: header;
}
.basic nav {
    grid-area: nav;
}
.basic .main {
    grid-area: main;
}
.basic footer {
    grid-area: footer;
}

En este momento, los elementos se organizarán para cumplir el diseño que hemos definido. En particular, en la propiedad grid-template-rows se especifican 3 filas (de alturas 4 rem, automática y 200 píxeles respectivamente), y en grid-template-columns tendremos 4 columnas, la tercera de las cuales será el triple de ancha que las demás.

La unidad fr permite especificar cuantas "fracciones" sean necesarias y que se repartan el ancho de forma proporcional a los coeficientes que usemos. Por ejemplo, 1fr 2fr 5fr 2fr 1fr crearía una estructura de 5 columnas fraccionando el ancho disponible en 11 partes (la suma de los fr) y repartiendo según el valor de cada una.

La propiedad grid-template-areas sirve para dar nombre a las áreas que quedan definidas por la rejilla (usamos un punto para indicar un área vacía), y después se pueden aplicar a los elementos mediante la propiedad CSS grid-area, como vemos en el ejemplo. Si no se definen estas propiedades, entonces cada elemento se irá asignando a una única área en orden de llegada, de izquierda a derecha y de arriba hacia abajo por defecto.

Como hemos visto, en las filas y columnas se pueden especificar tamaños en distintas unidades. Además, auto no es la única forma de definir un tamaño variable: la función minmax declara un tamaño que puede hallarse en cualquier punto entre el mínimo y máximo que se pasan como argumentos. Vamos a utilizarla para mejorar la repartición del espacio disponible en nuestra página:

.basic {
    display: grid;
    grid-template-rows: 4rem minmax(calc(100vh - 4rem - 200px), max-content) 200px;
    grid-template-columns: minmax(10%, 1fr) 1fr 3fr minmax(10%, 1fr);
    grid-template-areas:
        "header header header header"
        ".      nav    main   .     "
        "footer footer footer footer";
}

Como ves, no he modificado las áreas de la rejilla, pero sí el alto de la fila de contenido y el ancho de las columnas de los extremos. Para las segundas, se define un ancho de entre el 10% del disponible y una fracción completa, de manera que se adapte según el ancho disponible. Para la fila central tomamos el alto del viewport (unidad vh) y le restamos las alturas de las otras dos filas: calc(100vh - 4rem - 200px). Establecemos una altura entre dicho cálculo y la altura máxima del contenido (max-content), de forma que éste ocupe al menos la altura completa del navegador y la exceda cuando sea necesario. Así, el pie de la página nunca subirá del borde inferior del viewport.

El resultado lo puedes ver en la siguiente figura, he aplicado algunos estilos para que se diferencien las secciones:

Página con estructura sencilla en CSS Grid

Una vez distribuidos los elementos principales de la página, la disposición de ítems dentro de cada uno se puede conseguir tanto con grid como con otros sistemas como flexbox, si vamos a usar únicamente un eje. Además, si queremos variar la posición de cada elemento según el tamaño de viewport disponible, podemos usar media queries para redefinir las áreas de la rejilla.

Una rejilla responsive: colección de ítems

Si lo que queremos mostrar en nuestra página es una colección de artículos, publicaciones en redes sociales, imágenes, etc. podemos utilizar grid para adaptar la distribución de los elementos al tamaño del viewport. En particular, podemos evitar proporcionar un número de columnas específico de forma que se muestren tantas como quepan en la página en cada momento. Por ejemplo, supongamos que nuestro elemento contenedor es el siguiente:

<div class="collection" id="collection">
  <article>1</article>
  <article>2</article>
  <article>3</article>
  <article>4</article>
  <article>5</article>
  <article>6</article>
  <article>7</article>
  <article>8</article>
  <article>9</article>
  <article>10</article>
</div>

Para que el número de columnas sea dinámico, utilizaremos la función repeat con las palabras clave auto-fit o auto-fill:

.collection {
    display: grid;
    grid-template-columns: repeat(auto-fit, 250px);
    grid-auto-rows: 300px;
    grid-gap: 1rem;
    justify-content: center;
}

En grid-template-columns definimos columnas de 250 píxeles de ancho. En lugar de definir filas, simplemente especificamos su altura con grid-auto-rows, estableciéndolo a 300px. Si el alto de los elementos fuera variable, usaríamos max-content de forma que todas las filas que se creen automáticamente sean tan altas como el ítem más alto.

Las propiedades grid-gap y justify-content ajustan la posición de los ítems de forma que quede un hueco entre cada dos columnas y entre cada dos filas, y el conjunto de elementos se muestre siempre centrado horizontalmente en el espacio disponible, respectivamente.

El resultado de este código lo vemos en la figura siguiente, donde muestro la misma colección de elementos en una ventana ancha y una estrecha. He aplicado estilos adicionales a los ítems para que se distingan mejor:

Resultado de la colección de ítems

Si queremos añadir algo de asimetría y romper la uniformidad de la rejilla, podemos permitir que algunos ítems ocupen dos columnas o dos filas, mediante los valores de tipo span x:

.collection article.wide {
    grid-column-end: span 2;
}
.collection article.tall {
    grid-row-end: span 2;
}

Aplicando las clases wide y tall a algunos de los elementos que habíamos definido, obtenemos la estructura de la siguiente imagen:

Elementos más anchos y más altos

Aún no hemos explotado todo el potencial de grid, pero espero que estas estructuras básicas te ayuden y te animen a construir tus propios diseños. Te dejo el código con los ejemplos y algunos enlaces para aprender más:

Asegura la disponibilidad de tus aplicaciones Cloud con el patrón Service Messaging

06/07/2018
Artículo original

Queue

La Web ha cambiado mucho desde los tiempos en donde los usuarios soportaban largas esperas mientras navegaban de página a página.

En la actualidad, es inaceptable que una operación lanzada por el usuario se alargue en él tiempo o produzca un error general a causa de una degradación del servicio, o una interrupción de la conexión. Y para ello utilizamos frameworks como VueJs o similares.

Con la llegada de la computación en la nube, los problemas de fallos transitorios relacionados con las comunicaciones o la sobrecarga de los servicios de backend, ganan en criticidad y requieren de patrones de aplicaciones especialmente diseñados para Cloud.

Este artículo describe el patrón que ofrece un incremento de la disponibilidad, confiabilidad y resiliencia de nuestras aplicaciones Cloud.

Service Messaging

El patrón de mensajería es aquel que se enfrenta a los problemas que producen las conexiones permanentes entre servicios remotos como son la dependencia, el acoplamiento y la limitación de la reutilización y escalabilidad de estos.

Para ello propone realizar la comunicación por medio de un bus de mensajes que realizan una conexión asíncrona entre ambos servicios (en este caso el cliente y los servicios de facturación y almacén), tal y como se ve en la figura.

Cola De Múltiples clientes comunicándose por un bus de mensajes con el pool de servicios

Los mensajes son unidades de información alfanumérica de pequeño tamaño (en un rango de Kilobytes) que son introducidos en la cola de mensajería por los clientes, para ser consumidos por los servicios de acuerdo con el flujo de trabajo que mejor resultados ofrezca.

Figura 2. Ejemplo de mensaje

Así, de mano, obtengo varios beneficios muy importantes:

  • Disponibilidad. Puedo añadir tantos clientes como quiera, porque los servicios de bus de mensajes en Cloud son “infinitos “y con un rendimiento que escala según el tamaño de la cola.
    • Cliente. El número de peticiones puede situarse muy por encima del volumen medio o el máximo esperado. El cliente no tendrá ninguna percepción de que la ejecución de la compra vaya más lenta.
    • Servicios. Los servicios pueden hacer una previsión de la carga de trabajo dependiendo del número de mensajes que puedan procesar. Evitando el riesgo de tener una degradación del servicio al recibir más peticiones de las que pueda admitir; o de un escalado automático sin límites que desemboquen en un coste que desborde al previsto y aprobado.
Compensacion De La cola de mensajes estabiliza los picos de peticiones, permitiendo un escalado de los servicios estable y previsible.
  • Escalabilidad. Podremos escalar de forma indistinta cualquiera de los servicios implicados de acuerdo con las necesidades de negocio. Por ejemplo, ante la avalancha de ventas, incrementar las instancias del servicio de facturación, dejando sin modificar el de almacén ya que la gestión del stock es mucho menos compleja. Y utilizando el tamaño de la propia cola de mensajes como indicador en las reglas automáticas de crecimiento y decrecimiento del número de instancias.
  • Resiliencia. Si una vez realizada la operación de venta en la caja y enviado el mensaje, hubiese un corte o degradación de las comunicaciones, el cliente no sufriría ninguna desconexión o fallo, siguiendo trabajando en modo local hasta que volviese la conexión y se envíen los mensajes producidos. En el caso de que una instancia del servicio de facturación se cayera o tuviera algún problema, se podría continuar realizando su trabajo con cualquier otra instancia, sin perder información de estado o mensajes.

Bregando con estados

Desde el nacimiento de la web basada en http, me tengo que “pegar” con un sistema sin estado; y aún más si quiero desplegar en el Cloud en donde el crecimiento horizontal basado en instancias es el modelo de escalado por defecto.

Es cierto que puedo utilizar mecanismos más o menos eficientes como registros en base de datos o memorias caché compartidas, pero solo cuando me veo obligado a ello.

De acuerdo con esta arquitectura, sería más correcto implementar un patrón de mensajes con metadatos, incluyendo un valor de estado en el propio mensaje que se modificaría según el servicio que lo haya procesado.

Otra forma de gestionar estados sería utilizar un patrón de colas prioritarias. En donde el propio cliente decide la urgencia de la operación simplemente ingresando el mensaje en una de las colas disponibles, y que son consumidas por los servicios en un orden temporal establecido. Creándose de forma sencilla múltiples “pipelines” o flujos de procesamiento sin necesidad de persistir ningún estado.

Cola La cola superior es prioritaria a la inferior, descargando al servicio de decidir la urgencia de cada operación

Finalmente podríamos enviar la respuesta al cliente que dio de alta la operación de compra, añadiendo la identificación única de a quien hay que remitirle el resultado del proceso de compra en el propio mensaje.

Latencia y complejidad, los efectos secundarios

Pero todo tiene su lado oscuro, y este patrón de aplicaciones en cloud no iba a ser diferente.

Así, los inconvenientes vienen primero por el aumento de la complejidad de la arquitectura de mis aplicaciones. No solamente por tener que añadir más código para que el cliente interactúe con la cola de mensajes, si no también por construir servicios que sean idempotentes, que soporten múltiples lecturas del mismo mensaje, que sepan cómo gestionar los metadatos, o que breguen con el TTL de los mensajes.

Por otro lado, estoy metiendo latencia a mi sistema y un punto de incertidumbre; ya que la arquitectura no está diseñada a las prestaciones puras, si no a la escalabilidad, disponibilidad y resiliencia.

De forma que, dos operaciones realizadas en el mismo momento serian procesadas en un intervalo de tiempo diferente, con una duración indefinida dentro de un rango temporal aproximado.

En resumen, aún siendo un patrón que se puede aplicar en múltiples escenarios, no es una “bala de plata. Y hay que descartarlo en aquellos en donde las operaciones en tiempo real sean requisito.

Show me the Code

Quiero compartir dos excelentes tutoriales paso a paso publicados por Microsoft, en donde muestran lo sencillo que es implantar este patrón con C#:

Partiendo de ambos tutoriales, lo siguiente a implantar sería el reemplazo del uso de la clave de acceso por un patrón Valet Key, para tener cubiertos mis requisitos de seguridad.

En GenbetaDev | 3 patrones de diseño imprescindibles que deberías conocer para tu sistema en cloud: Retry, Valet Key y Sharding

Imágenes: Azure Interactives

También te recomendamos

¿Cuánto queda para ver un sindicato de eSports?

Google lanza PerfKit ¿Cómo evaluar el rendimiento real de las plataformas en la nube?

Cloud9 Un IDE en la nube

-
La noticia Asegura la disponibilidad de tus aplicaciones Cloud con el patrón Service Messaging fue publicada originalmente en Genbeta Dev por Juan Quijano .

FRIKADAS: Una inteligencia artificial que detecta enfermedades oliéndote el aliento

06/07/2018
Artículo original

La inteligencia artificial ha demostrado su gran capacidad a la hora de utilizar algunos "sentidos", por ejemplo la vista (para conducir coches o analizar resonancias magnéticas), el oído (por ejemplo el asistente de tu móvil o Google Home) o el tacto (a la hora de que un robot agarre y levante una caja midiendo su peso, rozamiento, etc...). Parece ser que ahora también pueden usar el sentido del olfato.

Unos investigadores de la universidad de Loughborough en Inglaterra, el Hospital Western General, la universidad de Edimburgo y el Centro de Cáncer de Edimburgo han desarrollado un método basado en deep-learning que podría analizar compuestos en el aliento humano y detectar enfermedades (incluyendo cáncer) con un promedio de aciertos superior al obtenido por especialistas humanos.

"El sentido del olfato lo usan los animales e incluso las plantas para identificar cientos de sustancias diferentes que flotan en el aire. Pero comparado con el de otros animales, el sentido del olfato humano está mucho menos desarrollado y desde luego no se utiliza para llevar a cabo actividades diarias ", escribió la investigadora Andrea Soltoggio en Smithsonian.com. "Por esta razón, los humanos no somos particularmente conscientes de la riqueza de la información que se puede transmitir a través del aire, y puede ser percibida por un sistema olfativo altamente sensible".

Esta técnica se ha utilizado durante ya algunas décadas por parte de laboratorios en todo el mundo. Los cromatógrafos de gases y espectrógrafos de masas son dispositivos que pueden analizar el aire y extraer su composición, pudiendo identificar miles de moléculas que flotan en el aire denominadas compuestos orgánicos volátiles.


Foto por Gem & Lauris RK en Unsplash, retocada por campusMVP

El problema es la gran cantidad de moléculas detectables y el hecho de que, a veces, la más mínima traza de cierta molécula puede ser crucial en el diagnóstico, por lo que el proceso es largo y tedioso, se necesitan recabar muchísimos datos y analizarlos y es propenso a muchos errores u omisiones.

El equipo de investigación que realizó el trabajo al que hacemos referencia hoy, entrenó a una inteligencia artificial para aprender a reconocer ciertos tipos de compuestos a partir de los datos del aliento de pacientes obtenidos con uno de estos sistemas de cromatografía de gases y espectrografía de masas (GC-MS). Para ello se basaron en los conocidos frameworks de código abierto para deep-learning Keras y TensorFlow, con los que crearon y entrenaron a su red neuronal. Como datos de control y entrenamiento utilizaron muestras del aliento de pacientes que sufrían diferentes tipos de cáncer y estaban recibiendo radioterapia. Para acelerar el proceso de aprendizaje utilizaron GPUs Tesla de NVIDIA, y para mejorar la eficiencia de la red neuronal, el equipo incrementó la cantidad de datos de entrenamiento mediante técnicas de aumento de datos, logrando hasta 100 veces más muestras de datos de base que los que tenían realmente.

Gracias a esta red neuronal, una máquina es capaz de dar resultados muy fiables sobre posibles condiciones médicas, en unos pocos minutos y de manera autónoma. Un experto humano puede tardar muchas horas y en ocasiones ser menos preciso.

"Este es el primer intento exitoso de machine learning para aprender patrones iónicos y detectar compuestos a partir de datos en bruto de cromatografía de gases y espectrografía de masas", ha dicho el equipo detrás del proyecto. La red neuronal convolucional logró el mejor rendimiento cuando se implementó con dos características concretas: filtros unidimensionales para adaptarse a la estructura particular de los datos GC-MS, y una entrada de tres canales para leer señales de alta, media y baja intensidad del espectro de GC-MS altamente variable. Este nuevo enfoque descubrió incluso errores de etiquetado en las muestras proporcionadas por personas con experiencia, lo que sugiere un rendimiento promedio mejor que el humano ", explicaron los investigadores.

Los investigadores también usaron la AI entrenada con los datos anteriores para analizar nuevas muestras de aliento e inferir resultados: "Los computadores equipados con esta tecnología solo tardan unos minutos en analizar de forma autónoma una muestra de aliento que anteriormente requería horas por parte de una persona experta", dijo Soltoggio. La inteligencia artificial está haciendo que todo el proceso sea más barato, pero sobre todo lo está haciendo más confiable.

El trabajo se ha presentado en la Conferencia Internacional sobre Redes Neuronales (IJCNN 2018), en Río de Janeiro, Brasil, este mismo mes de julio de 2018. El documento de investigación fue publicado en Research Gate.

Cómo localizar y eliminar los backups de WhatsApp en Google Drive

27/06/2018
Artículo original

Hoy uno rapidito que ando liado :-)

Si eres como yo y como el 80,96% de la población española, usarás Android en tu móvil:

Y si tienes móvil entonces, con casi total seguridad, usarás también WhatsApp. Y siendo con Android tendrás necesariamente una cuenta de Google.

En ese caso, WhatsApp hace todos los días una copia de seguridad a través de Wifi a tu cuenta de Google Drive. Esta copia puede llegar a ocupar bastante (sobre todo si estás en muchos grupos y te mandan muchos videos y tonterías) y puede comerte mucho espacio en tu Google Drive si es gratuito, ya que "tan solo" tienes 15GB.

Pero si entras en Google Drive no verás la copia de seguridad de WhatsApp por ningún lado. ¿Dónde está esta copia? ¿Cuánto espacio me está ocupando?

Bueno, el caso es que no podrás ver estos datos en ninguna carpeta dentro de Google Drive ya que la API que se utiliza para hacer estos backups oculta las carpetas que usan las aplicaciones para almacenar sus datos privados, y este es el caso de WhatsApp.

Sin embargo si que podrás ver cuánto te está ocupando, y también borrar esos datos para que liberar espacio.

En realidad es muy sencillo y te lo explico en el video a continuación (ponlo a pantalla completa para verlo mejor):

¡Espero que te sea útil!

Da potencia y flexibilidad a tus tests con Jest

27/06/2018
Artículo original

Jest

El testing es uno de los conceptos más core de eXtremme Programming (XP). Ya lo decía el gran Kent Beck:

Any program feature without an automated test simply doesn’t exist

Curiosamente, JavaScript ha sido históricamente uno de los lenguajes con más frameworks de test y menos cultura de testing en su comunidad. Los frameworks han ido apareciendo y desapareciendo a la velocidad del rayo y, por fin hoy, podemos decir que tenemos un magnífico ecosistema para realizar pruebas automáticas que ha venido para quedarse.

En este post vamos a hablar sobre Jest, con él que podemos construir tests unitarios trabajando con matchers personalizados, crear mocks o comprobar snapshots de componentes visuales como algo sencillo y accesible.

Instalación y puesta en marcha

Para completar un ciclo de feedback rápido y con la máxima información en cada momento, debemos de elegir un framework de testing flexible, rápido y con un output sencillo y comprensible. Este es el caso de Jest que, basado en Jasmine, destaca por sus funcionalidades potentes e innovadoras.

Jest ha sido desarrollado por el equipo de Facebook y, aunque nace en el contexto de React, es un framework de testing generalista que podemos utilizar en cualquier situación. Una vez que empecemos con Jest, ya no querréis cambiar :)

Comenzado por el principio, es importante conocer la página principal de Jest , la cual es una de las mejoras guías que encontraréis. En ella podremos encontrar todo tipo de ejemplos y documentación que nos ayudará mucho a profundizar en el framework. En cualquier caso, lo primero es lo primero y para comenzar debemos instalarlo.

Como sucede con cualquier otro paquete JavaScript, podemos añadirlo mediante NPM o Yarn a nuestro proyecto:

npm install --save-dev jest
yarn add --dev jest

Si vamos a utilizar Jest con ES6+, entonces necesitamos algunas dependencias extra (partimos de la suposición de que vamos a usar Babel 6 para este ejemplo):

npm install --save-dev jest babel-jest babel-core regenerator-runtime babel-preset-env
yarn add --dev jest babel-jest babel-core regenerator-runtime babel-preset-env

En el caso de que queramos trabajador con React y procesar JSX, entonces añadiremos también el preset correspondiente (recordad la nueva gestión de presets que babel incorpora en sus últimas versiones):

npm install --save-dev jest babel-jest babel-core regenerator-runtime babel-preset-env babel-preset-react
yarn add --dev jest babel-jest babel-core regenerator-runtime babel-preset-env babel-preset-react

Por último, definiremos en el .babelrc los presets necesarios:

{
  "presets": ["env", "react"]
}

NOTA: En este caso, el preset de React se añade únicamente por si queréis a futuro examinar algunas características avanzadas de Jest como el *snapshot testing, pero no sería realmente obligatorio para el propósito de este artículo.*

Con todo el tooling preparado, que no es poco, si queremos ejecutar los tests desde NPM añadiremos una nueva entrada en la sección de scripts del fichero package.json de nuestro proyecto:

{
  "name": "jest-testing",
  "version": "1.0.0",
  "scripts": {
    "test": "jest"
  },
  "devDependencies": {
    "babel-core": "^6.26.3",
    "babel-jest": "^22.4.3",
    "babel-preset-env": "^1.6.1",
    "babel-preset-react": "^6.24.1",
    "jest": "^22.4.3",
    "regenerator-runtime": "^0.11.1"
  }
}

De esta manera, al ejecutar npm test (o npm t si te van los shortcuts), se invocará Jest y se ejecutarán nuestros tests:

Ejecución de Jest sin tests definidos

Por supuesto, la ejecución falla al no encontrar ningún test todavía en el proyecto, pero esto va a cambiar muy pronto :)

Para ir calentando, vamos a crear un ejemplo un poco chorra que nos permita comprobar que todo funciona correctamente a nivel de configuración del proyecto.

Para ello, crearemos un carpeta test donde almacenar nuestras pruebas automáticas y dentro crearemos un fichero sum.test.js con el siguiente contenido:

test('should sum two numbers', () => {
    let result = 3 + 2;        
    expect(result).toBe(5);
});

No hay demasiado que explicar aquí, verdad??? :)

Vamos pues a ejecutar los tests de nuevo y así nos vamos familiarizando con la salida:

Resultado de la ejecución de nuestros tests

Nuestro primer verde!! Ahora ya no podremos parar.

En los siguientes apartados analizaremos como getionar las distintas fases de definición de un test: Arrange, Act y Assert. En el Arrange estableceremos el estado inicial del que partimos (setup/tearDown), luego ejecutaremos en el Act el código que altera ese estado incial y, por último, realizaremos las comprobaciones necesarias en el Assert utilizando los matchers que Jest nos provee o, incluso, definiendo los nuestro propios.

Definición de contextos

Como estructurar nuestros tests es un aspecto fundamental que condiciona su mantenibilidad a medida que crece el proyecto y el número de tests que tenemos. Generalmente, decimos que los tests se agrupan en suites, que no son más que agrupaciones de tests que están relacionados. Personalmente, prefiero que la causa de agrupación de las pruebas sea un contexto común, un arrange compartido en el contexto de la funcionalidad que abordamos. Es en este tipo de situaciones donde realmente veremos cómo interactuan las funcionalidades que se relacionan cohesivamente.

Para la definición de este tipo de contextos, Jest nos da los siguientes niveles de agrupación:

  • Nivel de fichero. Cada suite puede ir en un fichero distinto siempre y cuando sea detectado por Jest. Esto sucede bajo ciertas condiciones como que se llame *.test.js o que esté dentro de un directorio __test__, por ejemplo.
  • Definición de contextos Podemos agrupar tests mediante la construcción describe bajo un concepto compartido. Ejemplo:
describe('User registration', () => {
   test('....', () => {});
});

Los contextos o describe son anidables, aunque normalmente no se recomienda más de dos niveles de anidamiento ya que complican mucho la legibilidad del conjunto.

Gestión del estado inicial del que partimos: Setup y TearDown

Para la ejecución de una prueba automática es necesario establecer antes de nada un escenario específico que reproduca el estado del que queremos partir. Para ello, podemos tener ya código especializado que inicializa ciertas tablas en una base de datos, crea unos ficheros en el sistema o inicializa ciertas estructuras de datos. Sea como sea, es necesario que nuestro "framework" nos proveea de ciertos métodos que se ejecutarán siempre antes y después de cada test. Es el caso de Jest, estos son beforeEach y afterEach:

beforeEach(() => {
  db.init();
});

afterEach(() => {
  db.clear();
});

test('Car should be present in the catalog', () => {
  expect(db.findCar('BMW')).toBeDefined();
});

Es posible que tanto en beforeEach como en afterEach tengamos que realizar llamadas a código asíncrono (conexión a BD, llamada a servicio o similar). Por este motivo, estas funciones son capaces de recibir promesas como resultado de su invocación, de forma que Jest esperará a que la promesa se complete antes de ejecutar el test. El único punto a tener en cuenta es que la promesa se debe devolver siempre como resultado utilizando return:

beforeEach(() => {
  return db.initAsync();
});

Por último, si la inicialización o limpieza que debemos realizar únicamente se produce al principio de lanzar toda la suite y al final, podremos utilizar las expresiones beforeAll y afterAll:

beforeAll(() => {
  return db.initAsync();
});

afterAll(() => {
  return db.clearAsync();
});

test('Car should be present in the catalog', () => {
  expect(db.findCar('BMW')).toBeDefined();
});

test('Motorbike should be present in the catalog', () => {
  expect(db.findMotorbike('BMW')).toBeDefined();
});

Por otra parte, beforeEach y afterEach se pueden utilizar dentro de un contexo definido con describe y, en ese caso, únicamente se aplicarían a los tests que están definidos en ese contexto:

Con el fin de podamos comprender la secuencia concreta de métodos disponibles y como se van ejecutando en Jest, podemos ver el output de la siguiente secuencia de definiciones:

beforeAll(() => console.log('1 - beforeAll'));
afterAll(() => console.log('1 - afterAll'));
beforeEach(() => console.log('1 - beforeEach'));
afterEach(() => console.log('1 - afterEach'));
test('', () => console.log('1 - test'));
describe('Scoped / Nested block', () => {
  beforeAll(() => console.log('2 - beforeAll'));
  afterAll(() => console.log('2 - afterAll'));
  beforeEach(() => console.log('2 - beforeEach'));
  afterEach(() => console.log('2 - afterEach'));
  test('', () => console.log('2 - test'));
});

// 1 - beforeAll
// 1 - beforeEach
// 1 - test
// 1 - afterEach
// 2 - beforeAll
// 1 - beforeEach
// 2 - beforeEach
// 2 - test
// 2 - afterEach
// 1 - afterEach
// 2 - afterAll
// 1 - afterAll

Matchers

Los matchers son las funciones utilizadas por el framework de test para comprobar si el valor esperado por la prueba automática coincide realmente con el obtenido. Evidentemente, si el valor no coincide el test fallará (FAIL) y nos mostrará, marcando la salida en rojo, cual ha sido la discrepancia de valores. Por otra parte, si valor esperado coincide con el obtenido, tendremos ese adictivo verde (PASS) que nos llevará a escribir el siguiente test.

Es por esto que, cuando comparamos dos valores al final de un test, es importante conseguir que no se pierda la intención que queremos expresar con esta prueba automática. Recordemos que los tests son la documentación ejecutable de nuestro proyecto y, si esta no es entendible y sencilla, discernir lo que realmente el software será mucho más complicado y, por consiguiente, tendremos que invertir mucho más tiempo en su mantenimiento y seguramente acabaremos cometiendo muchos más errores.

Cuando comparamos dos valores al final de un test, es importante conseguir que no se pierda la intención que queremos expresar con esta prueba automática

Si partimos de la base, nuestro test en su estructura básica de Arrange, Act y Assert deberá ser rápido de leer y sencillo de entender. No hay nada peor que encontrarse con un test en rojo, leerlo y toparse con una definición compleja que no somos capaces de asimilar en pocos segundos.

Para evitar esta situación y conseguir que nuestros tests sean realmente "semánticos", el naming es fundamental. Utilizar buenos nombres de definición de nuestros tests, buenos nombres de variables, aserciones de negocio (ya veremos más adelante que son) y cualquier otra técnica que mejore la expresividad, nos salvará la vida cuando más lo necesitemos.

Pero bueno, todo esto llegará poco a poco y, si realmente interiorizamos estos principios, podremos ir mejorando nuestra base de test ganando así velocidad de cambio en el futuro.

Para ir avanzado, podemos partir de lo básico que nos ofrece Jest por defecto para comparar tipos primitivos como son cadenas, valores numéricos o colecciones, dejando para la siguiente sección la definición de comparaciones o "matchers" personalizados. Comparaciones disponibles:

Igualdad

Ya sabemos que la igualdad en JavaScript es un tema delicado, así que en este comparador lo mejor es aclarar que se aplicará tal y como se define en el estándar para Object.is (si no conocéis esta función, os recomiendo explorar este y otros métodos de Object que muchas veces obviamos a la hora de escribir código):

expect(3 + 2).toBe(5);

Si en lugar de comprobar igualdad queremos verificar lo contrario, podemos seguir utilizando el comparador toBe precedido de not. Personalmente me encanta esta aproximación desde el punto de vista de la semántica extra que añade, ya que creo que se asemeja mucho más al lenguaje natural y facilita su lectura cuando se compara con otras aproximaciones de otros frameworks.

expect(3 + 3).not.toBe(5);

Para finalizar con las comparaciones de igualdad, no podíamos dejarnos fuera de la ecuación los valores que en JavaScript pueden ser truthy o falsy. Es por esto que en Jest tenemos soporte para cualquier comparación del estilo:

expect(null).toBeNull();
expect(null).toBeDefined();
expect(null).not.toBeUndefined();
expect(null).not.toBeTruthy();
expect(null).toBeFalsy();

Objetos y listas

Para el caso de estructuras complejas como son los objetos y las listas, incluidas aquellas que pueden presentar distintos niveles de profundidad, el operador adecuado no es tanto toBe sino toEqual, al permitir una comparación profunda o deep equality que evalua correctamente la estructura completa:

let data = {one: 1};
data['two'] = 2;

expect(data).toEqual({one: 1, two: 2});
expect(data).toEqual({two: 2, one: 1});

En el caso concreto de las listas, algo bastante habitual es el poder comprobar si el resultado de una operación que devuelve algún tipo de colección contiene el valor buscado. Para ello, haremos uso del matcher toContain, que nos permite explorar esta colección sin tener que iterar sobre ella y sin tener que utilizar funciones como indexOf o includes en la colección:

expect(['cat', 'beer', 'dog']).toContain('beer');

Valores numéricos

Ya fuera de los comparadores más básicos de igualdad, en el caso de valores numéricos disponemos de muchas comparaciones que aportan un valor semántico extra a la comprobación que intentamos expresar. Algunos de ellos son:

expect(2+2).toBe(4);
expect(2+2).toEqual(4);
expect(2+2).toBeGreaterThan(3);
expect(2+2).toBeGreaterThanOrEqual(3.5);
expect(2+2).toBeLessThan(5);
expect(2+2).toBeLessThanOrEqual(4.5);

expect(0.1 + 0.2).not.toBe(0.3);
expect(0.1 + 0.2).toBeCloseTo(0.3);

No hay nada más poco expresivo que encontrarse suites de test que únicamente hacen uso de los matchers más básicos, conformándose con su limitado valor expresivo. En este sentido, preferimos expresiones de este tipo:

expect(2+2).toBeGreaterThan(3);

Sobre otras menos específicas como la siguiente, en la que únicamente nos valemos del matcher más básico como es toBe:

expect(2+2 > 3).toBe(true);

Expresiones regulares

Cuando construimos nuestro conjunto de aserciones en los tests que diseñamos, una buena práctica es no acoplarse en exceso a las fixtures utilizadas o a la implementación de los métodos. En este sentido, lo que buscamos es validar más la estructura de la información procesada que los datos en si. Es por ello que es bastante habitual en las aserciones de nuestras tests encontrar expresiones regulares que validen un patrón en los datos, más que un resultado concreto.

Una buena práctica es no acoplarse en exceso a las fixtures utilizadas o a la implementación de los métodos

Para poder expresar este tipo de comparaciones podemos hacer uso del matcher toMatch y pasarle una expresión regular estándar:

expect('Christoph').toMatch(/stop/);

Excepciones

Partir de lo que conocemos como Happy Path en nuestros tests puede ser una buena manera de empezar a razonar sobre la funcionalidad y explorar así cual sería un diseño adecuado para la misma. Es una práctica habitual si estamos haciendo TDD. Siguiendo este proceso y a medida que avanzamos, es posible que surjan situaciones a tener en cuenta y que, por no ser tan habituales o tratarse de situaciones excepcionales, no habíamos tenido en cuenta en un incio. Para este caso, lo más recomendable es anotarlas y abordarlas en próximas iteraciones, de forma que no nos corten el flujo de trabajo que estemos siguiendo en ese momento.

Por consiguiente, si más tarde volvemos a revisitar estas situaciones excepcionales que habíamos anotado, debemos de saber que Jest nos va a poder ayudar de nuevo a evaluarlas, permitiendo recoger excepciones y otros errores "esperados" ante una Arrange concreto.

const computeValue = () => {
    throw new Error('should fail');
};

expect(computeValue).toThrow();
expect(computeValue).toThrow(Error);
expect(computeValue).toThrow('should fail');
expect(computeValue).toThrow(/fail/);

Matchers personalizados

Como hemos podido comprobar en el apartado anterior, Jest cuenta con un conjunto de matchers muy rico y que aportan mucha semántica a las comprobaciones que necesitamos realizar a la hora de escribir tests. En cualquier caso, estos matchers siguen siendo demasiado generalistas en muchos contextos y, si lo queremos es que nuestro código hable de "negocio", necesitamos poder definir nuestros propios "matchers de negocio".

Tal y como comentaba, un "matcher de negocio" es una matcher que usa expresiones de nuestro dominio a la hora de verificar una comprobación. Si trabajamos con una factura, la idea sería tener un matcher que compruebe toBePaid, en lugar de acceder al importe de la misma y comprobar que el importe es superior a 0 con toBeGreaterThan. Nada que ver!!

// Correcto pero poco semántico
expect(invoice.getPaymentDate()).toBeDefined(); 

// Muchísimos más semántico y próximo al lenguaje del dominio!!! ;)
expect(invoice).toBePaid();

Ahora que tenemos un poco más claro el valor de poder definir matchers personalizados y próximos al dominio, vamos a ver como realmente poder utilizarlos en Jest. Para ello, y si queremos que los matchers que definamos estén disponibles en todas mis suites de test, tendremos que declararlos de forma global.

Simplemente editaremos el fichero package.json y añadiremos la propiedad setupTestFrameworkScriptFile dentro de la clave jest apuntando al fichero que contendrá la definición de los matchers (en este caso setup.js):

{
  "name": "basic-sum",
  "version": "1.0.0",
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch"
  },
  "devDependencies": {
    "babel-core": "^6.26.0",
    "babel-jest": "^21.2.0",
    "babel-preset-es2015": "^6.24.1",
    "babel-preset-stage-0": "^6.24.1",
    "jest": "^21.2.1",
    "regenerator-runtime": "^0.11.0"
  },
  "jest": {
    "setupTestFrameworkScriptFile": "/test/setup.js"
  }
}

Si ahora creamos el fichero setup.js dentro del directorio test de nuestro proyecto, podremos definir haciendo uso de la función extend los matchers que necesitemos (en este ejemplo la posibilidad de comprobar si un valor numérico concreto está dentro de un rango):

import expect from 'expect';

expect.extend({
  toBeInRange(received, lower, upper) {
    if (received >= lower && received <= upper) {
      return { pass: true };
    }

    return {
      message: () => `expected ${received} to be in range`,
      pass: false,
    };
  }
});

Finalmente, ya puedo hacer uso de los nuevos matchers personalizados que he definido en cualquier de los tests que implemente:

test('should sum', () => {
  expect(100).toBeInRange(90, 120);
});

Watch mode

Una de las funcionalidades que resultan más prácticas es lanzar Jest en watch mode para que así pueda ir ejecutando los tests cada vez que se cambie un fichero. Esto que puede parecer mucho en bases de código con suites muy grandes, es especialmente interesante en Jest ya que ante un cambio buscará los tests a los que afecta este cambio y los relanzará, evitando el procesamiento de toda la suite completa cada vez. De hecho, en el caso en el que se encuentre con un error que detenga la ejecución, lanzará el test fallido el primero cuando se vuelvan a tirar los tests, evitando procesar antes los que ya estaban en verde.

Para ejecutar el modo watch, podemos añadir un nuevo script a nuestro package.json, de forma que lo podamos lanzar de una manera simple ejecutando npm run test:watch:

{
  "name": "jest-testing",
  "version": "1.0.0",
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch"
  },
  "devDependencies": {
    "babel-core": "^6.26.3",
    "babel-jest": "^22.4.3",
    "babel-preset-env": "^1.6.1",
    "babel-preset-react": "^6.24.1",
    "jest": "^22.4.3",
    "regenerator-runtime": "^0.11.1"
  }
}

Una vez lanzado, nos encontraremos con una salida como la siguiente:

Captura De Pantalla 2018 06 19 A Las 17 23 25

Como podemos ver en la parte de usage, tenemos también la opción de filtrar qué tests se van a ejecutar con el fin de centrarnos únicamente en un subconjunto de los tests y no estar lidiando con el set completo que puede ser muy numeroso. Maravilloso!! :)

Cobertura

La cobertura de nuestra suite de test es el tanto por cien del código de producción por el que pasa alguna de nuestras pruebas automáticas.

La cobertura es una de esas métricas que las carga el diablo. Si nos sirve para ir detectando partes del código o hot spots en los que hemos incidido menos y que son importantes porque absorben mucho cambio, entonces la cobertura es nuestra amiga. Si la usamos como un indicador de calidad de cara a negocio o que el propio negocio nos exige para cumplir con ciertos requisitos, entonces puede ser peligroso, ya que podemos subirla rápidamente sin realmente estar prestando atención a la efectividad de las pruebas que incorporamos. En general, huid de buscar un valor concreto o de intentar superar un umbral prefijado. Si la tomamos como una métrica orientativa que, apoyada en las historias de usuario que nos entran, nos permite dirigir el cambio y nuestro empeño cuando hacemos testing, entonces nos aportará mucho valor.

La buena noticia es que obtener un análisis de cobertura en Jest es extremadamente sencillo, ya que puede ser obtenida mediante la opción --coverage, de forma que de nuevo podemos ampliar la sección de scripts de nuestro package.json para lanzar Jest con esta opción añadida a la que únicamente querremos recurrir cuando sea necesario:

{
  "name": "jest-testing",
  "version": "1.0.0",
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage"
  },
  "devDependencies": {
    "babel-core": "^6.26.3",
    "babel-jest": "^22.4.3",
    "babel-preset-env": "^1.6.1",
    "babel-preset-react": "^6.24.1",
    "jest": "^22.4.3",
    "regenerator-runtime": "^0.11.1"
  }
}

Cuando ejecutamos Jest con cobertura, se mostrará una tabla resumen extra al final de la ejecución de nuestros tests que tendrá más o menos este aspecto:

Captura De Pantalla 2018 06 19 A Las 17 42 47

Conclusiones

Como te puedes imaginar, esto es sólo la punta del iceberg y Jest esconde muchas otras sorpresas interesantes. Entre otras muchas, cabe destacar el mocking automático a distintos niveles, el soporte para snapshot testing y la integración por defecto de JSDOM para hacer tests sobre código que necesite tener disponible el DOM del navegador (React, Vue, Angular, etc) sin necesidad de levantar un navegador.

Muchas posibilidades para iniciarse en una de las prácticas de XP que más valor aporta al diseño y a la estabilidad de nuestro software.

También te recomendamos

La evolución de las interfaces de comunicación hombre-máquina: escribir, tocar, hablar… pensar

JavaScript BenchMark con Benchmark.js

Karma.js, el concepto de Test Runner

-
La noticia Da potencia y flexibilidad a tus tests con Jest fue publicada originalmente en Genbeta Dev por Ricardo Borillo .

Ponencias DotNet2018: Docker, Xamarin Forms y posibilidades gráficas de los navegadores

27/06/2018
Artículo original

 

El pasado 29 de mayo se celebró en Madrid la DotNET2018, el evento más importante del año de la comunidad .NET, que fue un auténtico exitazo. Las entradas literalmente volaron y no cabía un alfiler. Así que, desde aquí va una rotunda felicitación para los compañeros de Plain Concepts que se encargaron de organizarlo.

Ya os comentamos por aquí en su día que, aparte de ser orgullosos patrocinadores, tres de nuestros tutores participarían en las charlas, así que allí estuvimos en primera fila. Es los malo de tener tutores especialistas de primer nivel, que cada dos por tres son secuestrados para participar en eventos ;-D. 

Pero esto también tiene su lado bueno, y es que nuestros alumnos pueden contar con instructores de primera categoría sin necesidad de moverse de su casa o lugar de trabajo.

En fin, al grano: el evento de grabó por lo que ahora puedes disfrutar de las ponencias aunque no hayas podido asistir.

Esta fue nuestra alineación "titular" en el evento: 

Eduard Tomás: Containers en .NET

La participación de nuestros tutores la abrió hacia el mediodía Eduard Tomás, nuestro profe de Docker. Un auténtico megacrack (VS & Development Technologies MVP) quien, mano a mano con Ramón Tomás, nos dió una charla magistral de buenas prácticas con .NET Core y .NET Framework usando contenedores. También vimos cómo Docker nos puede ayudar en escenarios Full Framework, lift and shift (migraciones) además de mostrarnos un ejemplo de pipeline de integración y despliegue continuo utilizando Docker con Visual Studio Team Services (VSTS).

Encontrarás los ejemplos del vídeo en GitHub.

Vídeo: Containers en .NET

[youtube:hc2jUraOC7o]


José Manuel Alarcón: Por qué Superman lleva los calzoncillos por fuera y qué tiene que ver esto con tu navegador

A Eduard le siguió Jose Manuel Alarcón , Microsoft MVP desde 2004 (casi nada) e instructor especialista en FrontEnd.

A pesar de tener algunos problemillas técnicos sobrevenidos, los solventó con creces y le pegó un buen repaso a las posibilidades gráficas y de vídeo en los navegadores actuales con CSS y JavaScript. Y es que lo que podemos hacer a nivel de imagen y vídeo actualmente en el frontend casi da vértigo si viviste los espartanos inicios de la web.

Y de propina aprendimos por qué Superman lleva los calzoncillos por fuera, que parece que no, pero viene a cuento. Míratela de arriba abajo, que no tiene desperdicio, con mención especial para los ejemplos de efectos chroma en vídeo en tiempo real en el navegador. Tela marinera.

Por cierto, encontrarás los ejemplos de código de la presentación en su perfil en GitHub.

Vídeo de la presentación de José Manuel

[youtube:b0a_aWEBrWA]


Javier Suárez: Xamarin.Forms Everywhere

Y ya por la tarde, cerró la participación de nuestros tutores nada menos que Javier Suárez, otro monstruo (de los buenos) que es Xamarin MVP, Microsoft MVP, además de autor y tutor de nuestro solicitadísimo curso de Xamarin.

En su charla, Javier nos cuenta cómo con Xamarin.Forms ya no nos limitaremos solamente a crear una interfaz para exportar nuestras apps para móviles iOS y Android, sino para un montón de sistemas y dispositivos presentes y futuros. Javier nos enseña también cómo realizar aplicaciones nativas Web, WPF, Tizen o apps para Linux con Xamarin.Forms; y descubriremos cómo combinar elementos 3D en interfaces complejas y cómo gestionar dispositivos IoT con Xamarin.Forms y Xamarin.IoT.

Vídeo de la presentación de Javier sobre Xamarin.Forms

[youtube:Ha6F1dCrEag]

Si te interesan tanto la presentación como los ejemplos de código de Javier, los encontrarás todos en este post de su blog.

Hasta aquí las ponencias de nuestros tutores, pero si te quedas con ganas de ver más no te preocupes: tienes todas las ponencias del evento en el canal de de YouTube de nuestros amigos de Plain Concepts, quienes se encargaron de organizar este auténtico eventazo.

Conceptos esenciales sobre compilación e interpretación

26/06/2018
Artículo original

Si programas habitualmente en unos pocos lenguajes, tendrás la costumbre de seguir siempre los mismos pasos para ejecutar los programas que escribas. Según el lenguaje, eso involucrará utilizar un intérprete, transformar tu código a ejecutable mediante un compilador, o incluso utilizar herramientas de automatización para pasar de un punto a otro. En este artículo nos interesa estudiar cuál es realmente la tarea que cumplen todos estos sistemas y qué los diferencia.

Antecedentes

Primero, no debemos considerar la compilación y la interpretación como la única forma de producir y tratar programas.

Por un lado, existen lenguajes y programas teóricos, es decir, que no llegan a ejecutarse en un ordenador pero también tienen su importancia, al poder razonar sobre ellos de forma lógica: el cálculo lambda de Church es un claro ejemplo. Se trata de un lenguaje diseñado con el razonamiento lógico como propósito, que incorpora reglas sencillas de cálculo (sustituciones y reducciones) para resolver las expresiones. Así, para este lenguaje existen tanto intérpretes como teoremas matemáticos.

Por otro lado, otro tipo de programas que no se interpretan ni compilan son los que se componen directamente en el lenguaje de la máquina que los va a ejecutar. En particular, los primeros programas de ordenador, escritos por Ada Lovelace entre 1842 y 1843, consistían en unas notas especificando en tablas y diagramas cómo realizar el cálculo de números de Bernoulli en la máquina analítica de Charles Babbage. Esta máquina, que podía realizar procesamiento de propósito general, era meramente un diseño y no se pudo construir hasta un siglo después (pese a ello, se pudo comprobar que los programas de Lovelace eran correctos). El equivalente actual sería escribir un programa en ensamblador, que se puede convertir directamente a código máquina transformando cada instrucción en su equivalente en binario.

Por tanto, la compilación y la interpretación son necesarias cuando la intención es ejecutar nuestro programa en una máquina, y el lenguaje de nuestro programa es una abstracción, es decir, no se corresponde directamente con el lenguaje máquina. En este sentido, podemos verlas como tareas de traducción, de un lenguaje origen de alto nivel (el de programación) a un lenguaje objetivo de bajo nivel.

El primer lenguaje de programación de alto nivel fue Plankalkül, diseñado entre 1942 y 1945, pero el primero con una implementación real y de uso común fue Fortran en 1954.

Compilación

La compilación es la conversión de código en un lenguaje en otro, en un paso previo a su ejecución. Normalmente cuando pensamos en compilación hablamos de su versión más tangible, aquella que nos da un binario ejecutable como salida. Se suele denominar a este tipo de compilación ahead-of-time (AOT). Muchos de los lenguajes clásicos y más rápidos se utilizan con compiladores AOT: desde Fortran hasta Rust, pasando por C y C++. Este tipo de compilación permite realizar optimizaciones complejas, por muy costosas que sean, y adaptar el ejecutable final a la máquina donde se va a ejecutar (el proceso de compilar para una plataforma distinta de la de compilación se llama compilación cruzada).

Si la compilación no es de este tipo, puede ser porque no veamos el binario generado, sino que directamente se pase a su ejecución, estrategia conocida como compilación just-in-time o JIT. Esta permite aplicar ciertas optimizaciones que aprovechen la información disponible en tiempo de ejecución sobre la máquina, pero no puede realizar todas las optimizaciones AOT, ya que esto generaría un retraso muy perceptible en la ejecución. Para optimizar al máximo el rendimiento al usar compilación JIT, algunos lenguajes realizan un paso de pre-compilación intermedia que no genera código máquina sino bytecode. En ese paso se trata de conseguir producir un código resultante de bajo nivel independiente de la plataforma de ejecución, de forma que la compilación JIT sea más rápida. Conocidos lenguajes que suelen utilizar compilación a bytecode y JIT son Java y C#. La mayoría de implementaciones de JavaScript son también compiladores JIT.

Por otro lado, cuando la compilación no genera un binario en código máquina sino un resultado en otro lenguaje de programación, se trata de una compilación fuente-a-fuente (source-to-source), en ocasiones conocida como transpilación. Los casos más comunes de transpilación los encontramos en tecnologías web, ya que los navegadores principalmente permiten ejecutar código JavaScript (aunque WebAssembly está llegando), por lo que si queremos programar en el front-end con otro lenguaje debemos compilar a JS. El interés de esto reside en que el lenguaje origen puede proveer características que no estén disponibles en el objetivo, como tipado estático, programación funcional, otras orientaciones a objetos, etc. Algunos lenguajes que se transpilan son TypeScript, PureScript o Dart.

Interpretación y lenguajes interpretados

La forma alternativa de ejecutar un programa a partir del código en un lenguaje de programación es no generar una traducción a código máquina, sino analizar el código y realizar los cómputos que éste indique, bien directamente o bien a partir algún tipo de representación intermedia que no constituya un programa en código máquina. En este caso se dice que el programa es interpretado. Mientras se produce la interpretación, debe ejecutarse en el sistema el programa que la realiza, es decir, el intérprete del lenguaje. Este puede estar interpretado también, en cuyo caso hace falta un programa al final de la cadena de intérpretes que sí esté en código máquina.

Las representaciones intermedias que puede generar el intérprete son generalmente de dos tipos: un código de bajo nivel o una estructura de datos. El código de bajo nivel puede ser bytecode como en compilación JIT, o código enhebrado que consiste únicamente en llamadas a subrutinas del intérprete. Las estructuras de datos, por otro lado, suelen ser árboles de sintaxis abstracta (AST) que se van recorriendo para obtener los resultados de la ejecución. Estas últimas no son tan utilizadas ya que producen mayores sobrecostes que los códigos de bajo nivel. Ejemplos de lenguajes típicamente interpretados son Python (con bytecode), Ruby (usaba ASTs hasta la versión 1.8), PHP (con bytecode) y Perl (utiliza ASTs).

La interpretación de bytecode y la compilación JIT son muy similares, y las diversas implementaciones que existen forman más un espectro que dos categorías diferenciadas. Podríamos considerar que un bytecode es interpretado cuando su traducción a código máquina y su ejecución están totalmente entrelazadas, mientras que es compilado cuando primero se genera la traducción y posteriormente se ejecuta. Sin embargo, muchos intérpretes JIT (conocidos como máquinas virtuales) traducen el bytecode a trozos, en los momentos en que se prevé que son necesarios, desdibujando así la línea entre ambas estrategias.

Fuentes y más información:

Página Anterior Página Siguiente