Quantcast
Channel: campusMVP.es
Viewing all 776 articles
Browse latest View live

Cómo crear un botón de menú hamburguesa solo con CSS (sin imágenes ni JavaScript)

$
0
0

En este post aprenderemos a hacer un botón para un menú hamburguesa que cambiará de icono entre las clásicas tres rayitas y un aspa (vamos, una "X") alternativamente cada vez que hagamos clic.

Aquí puedes verlo en acción para que te hagas una idea:

Y todo esto sin usar imágenes ni JavaScript.¿Magia? ¿Brujería? No, CSS.

De hecho, este botón lo podríamos aprovechar para usarlo con este menú escamoteable, también sin JavaScript, que ya vimos en otro post.

Este botón, en realidad, no es un botón. Usaremos un input del tipo check para "guardar el estado" y su label para mostrar los iconos.

¿Cómo va a funcionar este botón?

El plan es este:

  • Gracias al seudoelemento :checked sabremos si el input está marcado o no

  • Con los seudoelementos :before y :after de su label construiremos las tres rayas

  • Por defecto estarán las tres rayas, y si el input está :checked se convertirán en un aspa

Pasamos a construirlo con HMTL y CSS

Este sería el HTML de nuestro botón:

<input id="abrir-cerrar" name="abrir-cerrar" type="checkbox" value="" /><label for="abrir-cerrar" class="toggle-button"></label>

A través de CSS escondemos el input y le damos estilos al label:

    input#abrir-cerrar { 
        visibility:hidden;
        position: absolute;
        top: -9999px;
    }
    .toggle-button {
        display:block;
        width:50px;
        height:50px;
        border:1px solid black;
        position:relative;
        cursor: pointer;
        box-sizing: border-box;
    }

Ahora nos toca retocar los seudoelementos :before y :after del label para dibujar las tres rayitas.

Al :before le asignamos aproximadamente una altura de 1/4 del total de la altura del label y usamos el borde superior e inferior. En el :after simplemente le damos una altura de 1 px y nos apoyamos en su color de fondo.

Además, a ambos le aplicamos ya una transición para que el cambio de estado sea suave:

    .toggle-button:before, .toggle-button:after {
        position:absolute;
        display:block;
        content:" ";
        width: calc(100% / 2);               
        box-sizing: border-box;
        left: calc(100% / 4);
        transition: all 0.2s ease-out;
    }

    .toggle-button:before {
        top: calc(100% / 4);
        height:calc(100% / 4);
        border-top:1px solid black;
        border-bottom:1px solid black;
        background-color: transparent;
    }

    .toggle-button:after {
        height:1px;
        background-color:black;
        bottom:calc(100% / 4);
    }

El comportamiento del botón

Un pequeño recordatorio: las buenas prácticas aconsejan que el comportamiento se maneje siempre con JavaScript, pero el objeto de este post es mostrar la posibilidad de hacerlo simplemente con CSS. Usa este ejemplo bajo tu responsabilidad.

Cuando el input está :checked (o sea, hemos hecho clic sobre él), modificamos el :after y le damos una altura de 1 px y color de fondo como el del :before, les aplicamos una transformación para girarlos 45º en distinto sentido y los posicionamos centrados verticalmente:

    input#abrir-cerrar:checked + .toggle-button:before, input#abrir-cerrar:checked + .toggle-button:after {
        top:calc(100% / 2);
        height:1px;
        border-bottom:0;
    }

    input#abrir-cerrar:checked + .toggle-button:before {
        transform: rotate(45deg); 
    }

    input#abrir-cerrar:checked + .toggle-button:after {
        transform: rotate(-45deg);
    }

Cabe decir que podríamos hacer una versión más sencilla con dos rayas en vez de tres si al :before le aplicamos por defecto una altura de 1 px y color de fondo como en el :after.

Adicionalmente, podemos introducir un par de span dentro del label que nos ayudarán a mostrar un tooltip y mejoraremos la accesibilidad.

Nuestro HTML quedaría finalmente así:

<input id="abrir-cerrar" name="abrir-cerrar" type="checkbox" value="" /><label for="abrir-cerrar" class="toggle-button"><span class="cerrar" title="Cerrar">Cerrar</span><span class="abrir" title="Abrir">Abrir</span></label>

Junto con este CSS para posicionar, mostrar y ocultar los span según nos convenga:

    .abrir, .cerrar { 
        position:absolute;
        top:0;
        right:0;
        bottom:0;
        left:0;
        text-indent: -9999px;
    }

    .abrir {
        display:block;
    }

    .cerrar {
        display:none;
    }

    input#abrir-cerrar:checked + .toggle-button .abrir {
        display:none;
    }

    input#abrir-cerrar:checked + .toggle-button .cerrar {
        display:block;
    }

Ejemplo descargable y más allá

Si quieres probar el ejemplo completo, lo puedes descargar en este enlace.

A partir de aquí puedes hacer múltiples variantes a tu gusto o necesidad para adaptarlo al diseño de tu web. Por ejemplo, en vez de mostrar los tooltips del title, podrías mostrar directamente el contenido de los span.

¿Cómo? ¿Que estás empezando con HTML y CSS y no sabes bien como adaptar el ejemplo? No te preocupes, este curso de HTML y CSS se encargará de que entres en la materia con paso firme.

Y si ya sabes HTML y CSS pero lo que quieres es ponerte al día en las últimas técnicas, este otro curso de maquetación web responsive es justo lo que necesitas.

Espero que te resulte útil el ejemplo de este post. Y si tienes dudas o sugerencias, puedes dejarlas en los comentarios.


15 consejos para programadores que aspiran a ser jefes de proyecto y responsables de equipo

$
0
0

Imagen ornamental. Hombre asomado al borde de una montaña en Yosemite, por Jeremy Perkins, CC0, vía Unsplash

Nota: este artículo también podría haberse enfocado a los responsables o directores de recursos humanos de empresas de programación, ya que son ellos los últimos responsables de fijar los organigramas de los equipos de desarrollo de software.

Hace unos meses tuve una conversación sobre este tema con un compañero de trabajo. Alejandro, programador sénior, me contó su historia:

Soy un programador "nato", y lo he sido durante más de 30 años. Muy orientado a objetivos y a los plazos, una especie de obseso del control. Está en mi naturaleza. Durante mi vida laboral he ejercido de jefe de proyecto dos veces. Lo odiaba. Siempre fui muy bueno en la gestión de mis proyectos y de mi tiempo. Casi siempre cumplía con los plazos, dentro o cerca del presupuesto, superaba las expectativas, y todas las demás cosas que hacen los buenos programadores.

Sin embargo, era absolutamente pésimo en la gestión de los miembros de mi equipo. Siempre me pareció que las cosas que eran obvias para mí -casi instintivas- no lo eran tanto para los demás. A menudo me frustraba que los miembros de mi equipo no pudieran sacar adelante cosas que eran obvias y fáciles de hacer para mí. Rápidamente me di cuenta de que quería programar. No quería sentarme en reuniones -me aburría- repasando los requisitos del proyecto hasta el aburrimiento para acabar detectando que algún miembro del equipo no solo es que estuviese fuera de juego, sino que ni siquiera estaba en el campo de fútbol en relación con el proyecto.

La parte más frustrante es saber que lleva más tiempo (a veces mucho más) explicar algo a un programador que lo que lleva hacerlo uno mismo pero así es como aprenden los programadores novatos. Aun así, es frustrante. A mis 55 años, la única forma viable que veo para volver a ejercer de responsable de equipos sería "desaprendiendo" todo lo que sé y reajustando y reciclando todas mis aptitudes y habilidades en las que destaco, y no volviendo a programar jamás. Peroeso no va a pasar...

En otras palabras, lo que Alejandro me quería transmitir es que la programación exige una forma de pensar muy particular: tiene que ser analítica, focalizada, obstinada, con capacidad de abstracción y de creación de modelos y patrones. Estas son todas importantes aptitudes para un programador, pero no tanto para un coordinador de equipos.

Los responsables de departamento tratan con personas, que son susceptibles e incoherentes, donde las opiniones y los sentimientos importan tanto como los datos y los hechos, donde el responsable debe pasar rápidamente de una actividad a otra, y donde nunca se termina o cierra nada como sí sucede con la programación.

Los buenos programadores, los programadores natos, rehúyen de la gestión de equipos y siempre están buscando la precisión y la fiabilidad de trabajar con máquinas.

Porque la gestión de personas no es el siguiente paso en la trayectoria profesional de la programación, es una trayectoria profesional completamente diferente. Es lo mismo que plantearse por qué los pintores no quieren dirigir galerías de arte 🤔

En pocas palabras, la coordinación no les proporciona demasiada satisfacción a muchos programadores. Los coordinadores no programan, en general, y a muchos programadores lo que les gusta es la programación.

Muchos programadores que pasan a ser coordinadores de equipos suelen ser las primeras víctimas del principio de Peter, que afirma que las personas que realizan bien su trabajo son promocionadas a puestos de mayor responsabilidad, a tal punto que llegan a un puesto en el que no pueden formular ni siquiera los objetivos de un trabajo, y alcanzan su máximo nivel de incompetencia.

Hay muchos casos como el de Alejandro, que aceptan el reto de gestionar un departamento o un proyecto y terminan optando por volver a ser programadores sénior y repudiando el trabajo de coordinación.

Por este motivo, estas palabras pueden servir de advertencia a los responsables de recursos humanos. El mejor programador no siempre es el mejor jefe, ni mucho menos, y eso te lo dicen los propios programadores si se sienten libres para expresar lo que de verdad piensan.

En general, los desarrolladores evitan la gestión porque no quieren ser incompetentes y no quieren ser vistos como incompetentes - pues ya lo han sufrido en sus propias carnes cuando un compañero programador les ha dirigido.

Ahora bien, si eres programador, pero en verdad tienes vocación de líder, estos 15 consejos son para ti. Si el simple hecho de imaginarte haciendo cualquiera de estas tareas ya te repugna, ya sabes que no es lo tuyo 😜

1. Inspira y motiva a tu equipo

Inspira a los miembros de tu equipo para que piensen a lo grande y ayúdales a conseguir sus metas. Debes buscar siempre motivar a través de tus actos y de tus palabras, incluso compartiendo tus experiencias de fracaso personal para hacerles perder el miedo a no tener éxito siempre.

2. Lidera con el ejemplo

Un buen líder jamás exigirá a su equipo realizar un proyecto que no es capaz de abordar por sí mismo. Dar la matraca con charlas motivacionales no basta. Un buen jefe de proyecto tiene que saber planificar, actuar y rendir para liderar con el ejemplo.

3. Ten siempre un plan

Para sacar las tareas adelante con éxito siempre tienes que tener un plan para ir por delante de los acontecimientos. Da igual que la tarea en cuestión sea muy pequeña o si se trata de un gran proyecto, un buen líder siempre tiene un plan bien pensado para lograr cumplirlo.

4. Sé optimista

Tener una actitud positiva es un requisito indispensable. Mantente, junto con el equipo, enfocado en los aspectos positivos. Dale importancia a los detalles y motiva a los programadores con refuerzo positivo.

5. Pon el foco en cómo resolver los problemas

Debes tener un enfoque orientado a solucionar las cosas. Los reproches y estar constantemente buscando culpables no funciona cuando tratamos con desarrolladores. Además, no refleja una personalidad madura de líder. A los programadores les encantan los jefes que son capaces de prever los problemas con antelación y ayudarles a resolverlos, antes de que se conviertan en grandes bugs o errores de programación que obligan a rehacer grandes estructuras de código.

6. Mantén reuniones cara a cara

Para reconducir situaciones conflictivas es mejor sentarse cara a cara con las personas (aunque sea por videoconferencia), y más cuando tratamos con programadores. Los emails y los chats en muchos casos agravan los conflictos. Hay que saber detectar cuando algún miembro del equipo lo que en verdad necesita es tener una conversación en persona. Hay una máxima en gestión de personas que dice que es mejor alabar en público y criticar en privado.

7. Muéstrate accesible

Intenta comunicarte con tu equipo de manera periódica. Sé una persona cercana y déjales saber a los miembros del equipo que tu prioridad son ellos, que tu trabajo es ayudarles y darles consejo. No hay nada más potente en una empresa de desarrollo que contar con líderes que de verdad están ocupados en atender a los problemas de los programadores.

8. No te quejes

Procura no quejarte de todos los pequeños detalles a cada rato. A los programadores no les gusta mucho el estilo quejica de liderazgo. Mantén la serenidad en los momentos difíciles, aunque tengas razón, e intenta gestionar los problemas en frío.

9. Evita el favoritismo

Trata a todos los miembros del equipo de forma ecuánime. Ser laxo con unos y muy exigente con otros no te llevará muy lejos. No seas parcial.

10. Da reconocimientos y halagos

Muchas encuestas dicen que, a partir de un nivel de ingresos determinado, el 65% de los empleados se sienten más felices cuando se les reconoce su trabajo en público frente a un 35% que prefieren un aumento de sueldo. Un buen líder nunca infravalora la importancia de mostrar reconocimiento y aprecio de forma pública y en privado.

11. Sé mentor de tu equipo

A los empleados les gusta que alguien les acoja como tutor. Dales oportunidades de formación e impulsa el desarrollo personal y profesional. 2 de cada 3 programadores afirman que su jefe directo es la persona que más impacto tiene en su crecimiento profesional.

12. Asume riesgos

Ten confianza a la hora de asumir nuevos desafíos, incluso si son arriesgados. Un líder responsable siempre tendrá más margen para cometer errores y aprender de ellos. Anima a tus compañeros a asumir sus propios riesgos, y a que no tengan miedo.

13. Gestiona tu ego

No seas ególatra. La humildad es una fórmula que promueve la creatividad y las nuevas ideas, además te ayudará a ganar la lealtad de los miembros del equipo.

14. Otorga libertad

La libertad de trabajar, hablar y actuar es inherente a cualquier persona. No las limites ni controles todo lo que hacen los miembros de tu equipo. Evita la microgestión. Anímales a hacer su trabajo de la forma que mejor consideren y exígeles resultados. Dales confianza y muestra fe en sus propios criterios.

15. Muestra emotividad

Sé profesional, pero no dejes de mostrar tus emociones. Expresa emoción, aprecio, empatía y alegría cuando sea pertinente. Al final, un buen líder se muestra humano. Ser profesional y mostrarse humano y vulnerable es una buena combinación.

---

Conociendo a muchos programadores, soy consciente de que todas estas recomendaciones a muchos les causa, como poco, irritación (por decirlo de alguna manera 😆).

No obstante, también conozco un buen puñado de grandes líderes que en su día fueron programadores y que hacen un trabajo genial, logrando grandes resultados con sus equipos.

Si se te ocurre algún otro consejo, no dudes en compartirlo en la sección comentarios. ¡Muchas gracias por leernos!

¡Nuevo curso online de Blazor!

$
0
0

Seamos realistas: para la mayoría de los desarrolladores que vienen de otros entornos, la parte más odiosa de hacer desarrollo Web es sin duda el Front-End, es decir, la interfaz de usuario. Había que decirlo y se ha dicho 😉

Lo que les gusta a muchos es la parte de Back-End, el código "puro". Y dentro del Front, hay dos cosas claramente diferenciadas que no suelen gustar:

  • La maquetación con HTML y CSS: que no gusta y además es muy complicada. Desde el diseño en sí (para el que, para empezar, hay que tener buen gusto), hasta adaptar la interfaz a todo tipo de dispositivos, pasando por colocar ese maldito botón o imagen rebelde que no hay forma de que quede donde queremos.
  • El lenguaje JavaScript: ¿pero a quién le puede gustar usar un lenguaje sin tipos, y que hace cosas raras sin parar? Encima, cualquier validación o regla de negocio que metas en el navegador tendrás que repetirla de nuevo en el servidor, y salvo que uses Node.js, lo tendrás que hacer en otro lenguaje muy diferente (y, con suerte, un poco más serio) 💩

Yo mismo soy experto en desarrollo Web Front-End, llevo haciéndolo casi desde que nació la Web, y debo reconocer que sigue sin gustarme, aunque con los años le llegas a tener cariño. Pero la realidad es la que es: si quieres hacer desarrollo Web tienes que usar HTML, CSS y JavaScript.

¿Seguro que esto es así? 🤔

Y de repente... Blazor

Con Blazor los desarrolladores de .NET y C# que creamos aplicaciones para la Web estamos de enhorabuena. Esta nueva tecnología de Microsoft nos permite hacer desarrollo Web usando C# y la plataforma .NET, de manera rápida y robusta, creando componentes reutilizables y separando las responsabilidades de la interfaz de usuario del resto de la aplicación.

Gracias a Blazor podrás librarte del Front-End de dos maneras:

  • La maquetación con HTML y CSS: aunque es inevitable tener que hacerla, la interfaz y los layouts están completamente separados de la lógica y el modelo. Así, el equipo de desarrollo puede hacer simplemente un boceto con los elementos necesarios, y el equipo de diseño se encargará de definirla y dejarla lista en paralelo. Es más, como todo son componentes reutilizables, una vez definidos puedes reutilizarlos por toda la aplicación o en otras, sin repetir el trabajo. Un win-win para todos. Existen ya en el mercado multitud de componentes predefinidos para acelerar el desarrollo, tanto comerciales como gratuitos y de código abierto.
  • El lenguaje JavaScript: todo el código, la lógica, los modelos... los haces en C# y aprovechado toda la potencia de la plataforma .NET. Incluso los eventos del navegador los gestionas con este lenguaje. Así que si quieres ¡te puedes olvidar de JavaScript!. Además, como ambos lenguajes pueden interoperar, si quieres sacar partido al enorme ecosistema de bibliotecas JavaScript, podrás usarlas desde C#. Y al revés: puedes llamar a código C# desde código JavaScript preexistente. Una maravilla.

Además, Blazor genera aplicaciones de tipo SPA (Single Page Application), sin recarga de páginas y con mayor velocidad de interacción con el usuario que las aplicaciones convencionales. Por eso te ahorras tener que aprender algún framework JavaScript como Angular, Vue.js o React.

Si vienes de tecnologías de desarrollo Web antiguas de Microsoft como ASP.NET WebForms te resultará mucho más fácil que otros frameworks, como ASP.NET MVC.

Blazor se basa en .NET Core (en breve .NET 5) y en todo el aprendizaje de estos años en ASP.NET Core, por lo que a pesar de su novedad es robusta, escalable y con un gran rendimiento. De hecho está soportada oficialmente a largo plazo por Microsoft junto con el resto de la plataforma.

Es ideal para crear aplicaciones Web de todo tipo, desde aplicaciones de gestión o corporativas, hasta un emulador de un ZX Spectrum, que puedes ejecutar desde un servidor de aplicaciones en Windows o Linux, o directamente en cualquier CDN "tonta" (no necesitas tecnologías de servidor si no quieres).

Curso online de Blazor

Banner del curso de Blazor

Para ponértelo fácil, en campusMVP hemos creado el mejor curso de Blazor que vas a encontrar.

Como todos nuestros cursos, es una formación seria, que va mucho más allá de las "recetas" y te enseña la tecnología desde sus cimientos hasta llegar a un nivel muy alto. Cuenta con teoría, vídeos prácticos de demostración, prácticas para reforzar lo aprendido y desarrollarás una aplicación realista completa. ¿Qué más se puede pedir?

Pues que el autor y tutor sea José María Aguilar, uno de los principales expertos en tecnologías .NET, con el que tendrás contacto directo durante toda la formación para resolver tus dudas y "atascos". La armoniosa voz que escucharás en los vídeos es la de un humilde servidor 😊.

Si tienes conocimientos de C# y .NET, aprovecha la oportunidad de formarte bien y podrás estar creando aplicaciones Web avanzadas en unas semanas.

Aquí tienes más información sobre Blazor y sobre el curso.

 

Si Visual Studio Community es gratuito ¿lo puedo usar en mi empresa para desarrollar software comercial?

$
0
0

Esta es una pregunta que nos hacen de vez en cuando a través de correo electrónico y en las redes sociales. En realidad, es algo que está claramente indicado en la página de descarga, abajo de todo, y en la licencia, pero vamos aclararlo igualmente desde aquí pues siempre viene bien 😉

Visual Studio es el entorno integrado de desarrollo (IDE) de Microsoft. Aunque mucha gente lo tiene asociado tan sólo con la plataforma .NET y sus lenguajes (C# y Visual Basic .NET), lo cierto es que es un entorno muy capaz que te permite crear aplicaciones de todo tipo y con todo tipo de lenguajes: Java, JavaScript, HTML y CSS, Node.js, Python, C++, F#... así como desarrollo Web, para móviles, juegos, bases de datos, línea de comandos, Linux, Cloud, Machine Learning... Es una de las herramientas más complejas y capaces que puedes encontrar.

Aunque, de entrada, es una herramienta de pago, lo cierto es que hace muchos años que tiene ediciones gratuitas para que cualquiera pueda utilizarla sin pagar ni un euro. Actualmente, y desde hace mucho tiempo, la edición gratuita se denomina Visual Studio Community Edition, y la puedes descargar desde el enlace anterior.

Al contrario de lo que piensa mucha gente, la edición Community apenas tiene limitaciones. Si consultas la tabla de diferencias entre ediciones verás que las ediciones de pago lo único que aportan son ciertas (pocas) herramientas avanzadas para depuración, arquitectura y testing. En realidad, la mayor diferencia entre VS Community y sus hermanas mayores es tan solo una cuestión de licencias.

Microsoft quiere que la herramienta pueda ser utilizada por todo el que la necesite, pero al mismo tiempo quiere que los grandes clientes que le sacan partido les sigan pagando. Al fin y al cabo, su desarrollo implica a centenares de personas y es muy costoso. Para lograr este equilibrio Visual Studio Community tiene limitaciones en cuanto a quiénes la pueden utilizar.

Los que pueden usar Visual Studio Community sin problema son:

  • Desarrolladores individuales, es decir, los que trabajan por su cuenta y no para una empresa, y los que programan por afición o gusto, pero no por trabajo.
  • Los estudiantes, y los alumnos de empresas como campusMVP, que están aprendiendo a desarrollar usando Visual Studio.
  • Los investigadores y aquellos que lo usan para ciencia de datos, crear aplicaciones que les ayuden con sus investigaciones, etc... que lo utilizan desde un entorno académico.
  • Proyectos Open Source. Si el proyecto que desarrollas es de código abierto, entonces lo puedes utilizar sin problema, aunque seáis muchos colaborando en él.
  • Organizaciones no empresariales. Y este es el punto que confunde a mucha gente y objeto de este artículo. Vamos a verlo con calma.

El quid de la cuestión aquí es cómo define Microsoft a las empresas en lo que respecta a esta licencia. Esto está copiado y pegado de documento de licencia de Visual Studio Community:

Se entiende por “empresa” una organización y sus filiales que posean, de forma conjunta, bien (a) más de 250 ordenadores (PC) o usuarios o (b) un millón de dólares en ingresos anuales (o su equivalente en otras monedas), y “filiales” hace referencia a aquellas entidades que dirigen una organización (por propiedad mayoritaria), están dirigidas por otra organización o comparten la dirección común junto con la misma.

Es decir, que si tu empresa tiene menos de 250 usuarios de PC (sean o no programadores) y factura menos de 1 millón de dólares (unos 836.000 euros a cambio de hoy), según Microsoft no sois "empresa" y podréis usar Visual Studio Community sin problemas hasta un máximo de 5 usuarios al mismo tiempo (y esto es importante), para desarrollar lo que queráis, incluso software comercial que vayáis a vender.

Aún no cumpliendo con estos requisitos podríais usarlo sin limitaciones ni siquiera de usuarios, si es para:

  • Un proyecto Open Source
  • Enseñanza
  • Investigación
  • Desarrollo y prueba de extensiones de Visual Studio
  • Desarrollo y prueba de drivers para Windows
  • Desarrollo de SQL Server si es a través de las herramientas que tiene que están basadas en Visual Studio

Aquí tienes las partes relevantes de la licencia donde indica esto marcadas:

Licencia de VS Community

En el resto de los casos, si tu empresa no cumple con estos requisitos, entonces deberéis contratar una de las versiones de pago, que parten de 45 dólares al mes en el caso de la profesional.

Nota importante: evidentemente no somos abogados ni nuestros consejos legales se pueden tomar como una garantía. Pero en este caso nos hemos atrevido a explicarlo porque la licencia de Microsoft es muy clara al respecto y parece fácil saber cuáles son las condiciones. En cualquier caso, debes revisar la licencia en el momento en el que leas esto puesto que puede haber cambiado desde que lo escribimos.

 No te olvides, en cualquier caso, que para poder utilizarlo necesitarás una cuenta de Microsoft (vale una gratuita de Hotmail/Outlook.com, claro), y que la licencia se renueva automáticamente de forma periódica.

¿Cuántos bugs y problemas al programar me puede evitar TypeScript?: los números concretos

$
0
0

Cuando te decides a aprender un nuevo lenguaje de programación, invirtiendo tu dinero sobre todo tu tiempo, lo primero que debes preguntarte es ¿qué obtengo yo a cambio del esfuerzo? La respuesta puede referirse a muchas cosas diferentes: más facilidad de desarrollo, menos errores, más productividad, hacer las cosas de otra manera, simplificar el mantenimiento... Incluso, por supuesto, el mero placer de aprenderlo.

TypeScript es un lenguaje de programación construido por encima de JavaScript que trata de evitar los mayores problemas de este último y que, al final, se transpila a JavaScript "puro y duro" para ser utilizado en el navegador. Es decir, que al final lo que obtienes es de nuevo JavaScript.

Entonces, ¿dónde está la ventaja?

Ya hemos contestado a esta pregunta con detalle en dos ocasiones, que te dejamos aquí por si te resulta interesante:

Aunque las ventajas están claras, en este caso nos vamos a enfocar en una tarea más complicada: cuantificar las ventajas de TypeScript. Es decir, no sólo describirlas, sino ponerles números concretos para saber cuánto puedes ganar utilizándolo frente a JavaScript "puro".

Para ello nos fijaremos en un estudio realizado hace ya algún tiempo, cuando TypeScript era menos potente que en la actualidad, y que se encargó precisamente de eso: darnos números sobre las ventajas de su uso. Lo puedes descargar y leer aquí, desde la web de uno de los autores: To Type or Not to Type: Quantifying Detectable Bugs in JavaScript. Se presentó en la 39th IEEE/ACM International Conference on Software Engineering (ICSE) de mayo de 2017, y lo firmaban Zheng Gao y Earl T. Barr de la University College de Londres, y Christian Bird de Microsoft Research. También puedes ver las slides de la conferencia.

TTL;DR: si tienes prisa por llegar a las conclusiones, la principal es que TypeScript puede evitarte al menos un 15% de los errores que comentes al programar con JavaScript. Y esto siendo conservadores.

Así, de entrada, un 15% puede parecer poca cosa. Pero en la práctica cualquier disminución de bugs en una aplicación se traduce en dinero y satisfacción del cliente a largo plazo. Y un 15% de problemas evitados se traduce en un montón de dinero y satisfacción de clientes.

Lo que hicieron fue echar mano de la fuente pública, de datos sobre JavaScript y sus bugs más importantes, que existe: GitHub y sus issues. Los proyectos JavaScript más importantes y que llevan mucho tiempo con gran actividad, como jQuery, Ember.js o React, tienen una gran cantidad de información útil que se puede utilizar. Y analizando los bugs y sus causas se puede determinar si se habrían evitado usando TypeScript y su sistema de datos tipado.

De hecho, con esta técnica se subestima la efectividad de TypeScript, ya que sólo se usan bugs detectados y parcheados de proyectos públicos, como se muestra en este diagrama extraído del propio estudio:

Diagrama de Venn con los bugs detectados

pero existen otros bugs detectables por TypeScript si el código estuviese anotado y que quizá no se hayan detectad aún, y además TypeScript aporta muchas otras características que aportan robustez a los proyectos, por lo que ese 15% es muy conservador, como decíamos antes, y en la realidad es muy probable que sea aún mayor.

Evidentemente esto es fácil de decir, pero complicado de llevar a cabo. En el estudio referenciado antes tienes los detalles, pero existían casi 4 millones de bugs públicos solucionados en proyectos JavaScript (3.910.969 para ser exactos). Para poder hacer el estudio con un nivel de confianza del 95%, necesitaban analizar 384 bugs, así que analizaron 400, más de lo necesario.

A veces TypeScript es capaz de detectar el error sin ni siquiera tener que anotar el código para indicar los tipos esperados. De hecho, como dicen en el estudio:

...we have striven to be conservative: we annotate variables whose types are difficult to infer with any. Then we type check the resulting program. We ignore type errors that we consider unrelated to this goal. We repeat this process until we confirm that b is ts-detectable because ts throws an error within the patched region and the added annotations are consistent (Section II), or we deem b is not ts-detectable, or we exceed the time budget M.

El "time budget" se refiere a que, de un análisis preliminar de 78 bugs se desprendía que en el 90% de los casos eran suficientes 10 minutos para llegar a una conclusión sobre si se podían haber evitado con tipado estático. Así que cada investigador podía dedicar un máximo de 10 minutos por bug, por lo que puede que incluso algunos hayan pasado inadvertidos y se pudieran haber detectado con TypeScript.

Los mayores obstáculos para ello fueron:

  • Dependencias de módulos muy complicadas
  • La falta de interfaces anotadas en algunos módulos
  • "Ñapas" para solucionar el problema rápido y que les impiden aislar el fragmento concreto que interesa para el estudio
  • Dificultades para entender el programa

En resumen

Con TypeScript puedes obtener como mínimo una reducción del 15% en número de bugs de tu código Front-End, pero seguramente será mayor. Por supuesto esto dependerá del tamaño del proyecto, de la experiencia de tu equipo de desarrollo, etc... En muchos casos puede ser aún mayor.

Ahora te toca decidir si, incluso aunque fuese "tan solo" ese 15%, a tu equipo le merece la pena aprender el lenguaje. Si le sumas todas las demás herramientas avanzadas de Front-End para automatizar tareas, gestionar dependencias, seguir pautas comunes, etc... seguro que os compensa 😉

¿Qué es Blazor? ¿Qué me aporta? La grabación del evento con José María Aguilar

$
0
0

Ayer por la tarde a las 19:00 hora de España peninsular emitimos en directo la entrevista con nuestro tutor José María Aguilar.

Durante la entrevista, José Manuel Alarcón, nuestro director, y él hablaron largo y tendido y de manera muy amena sobre la tecnología, sobre qué es, cómo funciona y muchos otros temas.

En los días anteriores a la entrevista recibimos multitud de correos electrónicos con preguntas para José María, e intentaron contestar las más importantes (muchas se repetían) durante la entrevista.

Más abajo tienes el vídeo completo. Aunque te recomendamos verlo entero (seguro que no te arrepientes) puedes ir a partes concretas de la entrevista usando estos enlaces:

  • 02:20 Quién es José María Aguilar y comentarios generales
  • 06:08¿Qué es Blazor?
  • 11:08¿Cómo sé que esto no es otra "ida de olla" de Microsoft que va a desaparecer?
  • 18:20 Principales ventajas de Blazor frente a Angular, Vue.js o React
  • 23:02 Posibilidad de Compartir código entre cliente y servidor
  • 26:10 Principales ventajas frente a ASP.NET MVC
  • 29:40¿Está lo suficientemente madura para producción?
  • 31:59¿Puedo hacerlo todo? ¿Qué limitaciones tiene?
  • 33:05 Interoperación C# ➡ JavaScript y viceversa
  • 34:34¿Qué rendimiento da? ¿Cómo se compara con otras tecnologías?
  • 39:53¿Y si vengo de WebForms? ¿Tiene algo que ver?
  • 47:14 Migrar de otras tecnologías a Blazor
  • 49:10¿Qué me dirías para "vender" esta tecnología en mi empresa?
  • 54:57 Despedida

[youtube:UPxAhpStZ8M]

Durante el evento en directo también contestamos muchas preguntas a los asistentes en el chat.

Nuestra intención es hacer más entrevistas como estas, con nuestros tutores pero también con personajes relevantes del sector, hablando de manera divulgativa sobre ciertas tecnologías.

Si no quieres perderte este tipo de vídeos, así como los vídeos prácticos que publicamos en el canal, no te olvides de suscribirte😉

El operador de encadenamiento opcional en ECMAScript/JavaScript: evitando errores por nulos

$
0
0

Fotografía ornamental, un signo de interrogación y un punto formado por islas, en Jablines (Francia), por Jules Bss, CC0 en Unsplash

Hace unas semanas hablaba aquí del operador de "unión nulosa" de ECMAScript 2020 utilizado sobre todo para obtener valores por defecto en expresiones, es decir, para asignar fácilmente y de manera legible valores alternativos a elementos nulos necesarios para que trabaje una función. Puede sonar a "cosa rara", pero lo cierto es que es de lo más útil y una vez que lo aprendes lo usarás todo el rato.

Hoy me voy a enfocar en un primo hermano del anterior: el operador de encadenamiento opcional que nos permite simplificar enormemente las expresiones con propiedades o arrays que podrían tener valores nulos.

Un caso habitual es el siguiente: estamos accediendo a un servicio que nos devuelve información de objetos que necesitamos para nuestra aplicación. Estos objetos pueden tener a su vez varios subobjetos a los que necesitamos acceder para hacer cosas, por ejemplo:

if (usuario.direccion.planta.length > 0) {
	//lo que sea...
}

El problema de lo anterior es que es posible que el usuario no tenga dirección, y si la tiene quizá no tiene el campo de la planta en la que vive (porque es una casa unifamiliar, por ejemplo). Por lo que la expresión anterior no se puede utilizar ya que cualquier punto de la cadena de propiedades puede ser nulo y fallar. Por ello, en estos casos se suele escribir algo como esto:

if (usuario && usuario.direccion && usuario.direccion.planta) {
    if (usuario.direccion.planta.length > 0) {
		//lo que sea...
	}
}

Es decir, antes de poder utilizar nuestra expresión original debemos comprobar que todos los elementos de la cadena de propiedades existen (no son nulos), para asegurarnos de que no se produce un error.

Obviamente sólo es necesario comprobar aquellos que puedan estar nulos. Por ejemplo, si el usuario siempre va a existir como resultado de la llamada al servicio (o sea, el servicio no nos va a devolver nunca un nulo), la primera comprobación (if (usuario)) no es necesaria.

Lo anterior funciona porque el operador AND (&&) tiene cortocircuito de expresiones, es decir, dado que para ser cierto ambas partes de la comparación deben ser ciertas, en cuanto la primera no lo es ya no se comprueba la segunda, por lo que en cuanto cualquier parte de la cadena de propiedades se encuentra un nulo, no se evalúan las demás, y no se produce nunca un error por nulos.

Este tipo de expresiones es muy habitual verlas por ahí y funcionan bien, pero son tediosas de escribir, largas y no contribuyen precisamente a la claridad del código.

Por suerte, ECMAScript 2020 ha pensado en esta situación y nos proporciona un elemento específico para facilitarnos la vida: el operador de encadenamiento opcional: ?.. Gracias a él podemos escribir esto:

if (usuario?.direccion?.planta?.length > 0) {
	//lo que sea...
}

Es decir, al ponerlo delante de una propiedad, ésta sólo se evalúa si el objeto precedente no es nulo (o no es "nuloso", o sea, algo que el lenguaje interpreta como nulo).

Lo que se obtiene en caso de que algún punto de la cadena sea nulo o no definido es un valor no definido, o sea undefined.

Esto es equivalente a usar todos aquellos && de antes, pero mucho más claro de leer y sobre todo de escribir.

MUY IMPORTANTE: el primer objeto de la cadena debe existir y estar definido, ya que el operador necesita al menos un objeto "padre" inicial del que partir. Es decir, en nuestro ejemplo, usuario debe existir, estar definido y no ser nulo. Si se obtiene como resultado de una llamada a un servicio, por ejemplo, debemos recibir algo, aunque sea un objeto vacío, un número o lo que sea, si no recibiremos un error.

Llamadas a funciones

Pero este operador sirve para más cosas que simplemente encadenar propiedades de objetos. También es posible utilizarlo para hacer llamadas a funciones, sólo si la función está definida. Por ejemplo:

var res = usuario?.haceralgo?.();

Esta sintaxis tan rara, ?.() lo que quiere decir es que, si está definida la función, llámala, y si no lo está, devuelve un undefined. De este modo podemos crear interfaces que tengan miembros opcionales, que sólo se implementan según ciertas condiciones, o podemos llamar a diferentes versiones de una API sin que rompa todo en caso de que un método no esté implementado. Muy útil.

Es importante señalar que si el supuesto método existe, pero no es un método, se producirá un error de tipos. Es decir, lo anterior funciona si el miembro que queremos llamar no existe. Pero, en nuestro ejemplo, si usuario.hazAlgo existe y es otra cosa que no es una función (por ejemplo un array o una simple propiedad) se producirá un TypeError. Esto es una buena política porque sino, podrían darse otros errores diferentes. De este modo tenemos que ser conscientes de que vamos a llamar a una posible función.

Cortocircuito de expresiones

Este operador también incluye cortocircuito de expresiones. Es decir, que en cuanto se encuentra un eslabón de la cadena que es nulo o no definido, no sigue evaluando los demás. Así, por ejemplo:

var n = 0;
var res = usuario?.haceralgo?.(n++);

...si el usuario o si la función hacerAlgo no existe y por tanto no se ejecuta, la variable n tampoco aumenta de valor ya que se detiene la evaluación de la expresión posterior a ella.

Acceso a arrays

En JavaScript una alternativa a la sintaxis . para acceder a una propiedad es mediante indexación por nombre, así:

var color = usuario.ajustes['color'];

que es equivalente a:

var color = usuario.ajustes.color;

La ventaja de la primera sintaxis es que nos permite decidir el nombre de la propiedad dinámicamente, es decir, no va escrita en el código sino que podemos recibirla en una variable:

var nomAjuste = obtenerNombreAjusteColor(); //De un servicio por ejemplo, devuelve 'color'
var color = usuario.ajustes.[nomAjuste];

cosa que no podríamos hacer con la sintaxis habitual del punto.

En este caso, si la propiedad es nula o no está definida JavaScript devuelve un error. O sea, en este ejemplo si el servicio nos devuelve un null en la propiedad ajustes o si directamente no la define, se produciría un error.

Para facilitar este caso, una tercera sintaxis de este operador nos permite acceder al contenido de arrays o colecciones aunque sean nulas o no estén definidas:

var color = usuario?.ajustes?.['color'].valor;

En este caso está intentando acceder a las propiedades del objeto ajustes por su nombre.

Fíjate además que, por el cortocircuito de expresiones, si ajustes es nulo o no está definido se devuelve un undefined y ya no se intenta leer la propiedad valor del resultado tampoco (de todos modos si creemos que ajustes puede existir pero el ajuste en concreto no, convendría usar ?. también con esta última propiedad, claro).

Nos vale también para arrays normales y corrientes accedidas mediante índice:

var color = usuario?.ajustes?.[0];

De este modo si ajustes no está definido o es nulo no se producirá un error, como sí ocurriría sin este operador.

Devolver algo diferente a no definido

Finalmente, podemos combinar ese nuevo operador con el operador de unión "nulosa" que vimos el otro día para devolver un valor por defecto en estos casos, así:

var color = usuario?.ajustes?.['color'].valor ?? "#fff";

que en caso de no estar definido devolvería un color por defecto (blanco).

En resumen

El operador ?. es muy útil para escribir expresiones concisas y legibles sin arriesgarnos a que un valor nulo o no definido provoque un error en nuestro código. Su uso es extremadamente sencillo y sólo hay que conocer los pequeños detalles asociados que se explican en este artículo.

El soporte de este operador es total en los navegadores evergreen, o sea, en todos los actuales (Chrome, Firefox, Opera, Safari, Brave...), así que podemos usarlo sin miedo salvo que tengamos que dar soporte a versiones antiguas o a Internet Explorer por algún motivo.

¡Espero que te resulte útil!

Cómo crear textos generados por Inteligencia Artificial/Machine Learning y engañar hasta a Hacker News

$
0
0

A estas alturas sería muy raro que no hubieses oído hablar aún de GPT-3, el modelo de lenguaje natural creado por OpenAI que es capaz de crear de la nada sus propios artículos, opiniones editoriales, poemas y, sí, también código. Y aunque hay ejemplos de esto último muy alucinantes como este o este otro con Excel y hay quien vaticina que acabará con los programadores, seguro que el que lo dijo no ha tenido que tomar nunca requisitos de clientes 😁

Bromas aparte, se trata de una alucinante implementación práctica de la potencia de los algoritmos de aprendizaje automático cuando tienen un "corpus" de entrenamiento lo suficientemente bueno y se afinan bien.

Aunque GPT-3 está basado en la misma tecnología de Machine Learning que GPT-2, lanzada el año pasado, esta nueva versión es un modelo muchísimo mayor. Utiliza 175.000 millones de parámetros entrenables (😱) lo que lo hace 100 veces mayor que su predecesor y 10 veces mayor que su competidor más cercano, Turing NLG de Microsoft, con "tan solo" 17.000 millones de parámetros.

Los expertos que han podido probar este modelo están impresionados. Y lo que más impresiona ya no es tanto su tamaño sino sus capacidades de generación de textos, que rivalizan con las de muchos profesionales humanos. Lo único que tienes que hacer es darle una frase inicial sobre un tema y la AI lo continúa de manera coherente y "humana". Se ha utilizado para escribir artículos, historias, canciones, entrevistas, manuales técnicos y hasta poesía. Es el sistema que más cerca está de pasar el test de Turing hasta la fecha.

Lo último que lo ha traído a la actualidad ha sido el caso de un estudiante, Liam Porr, que ha creado un blog generado 100% de forma automática con GPT-3 y que ha logrado llegar a número 1 en Hacker News, nada menos. Porr elegía los temas y ajustaba el título para lograr el mayor impacto de SEO, pero el resto de los contenidos es, tal cual, lo que generó GPT-3. Échale un vistazo al blog (en inglés, aunque la tecnología funciona en muchos otros idiomas) y te costará creer que no sean artículos escritos por una persona.

Tenemos ejemplos desde hace años sobre cómo el Machine Learning es capaz de generar contenidos automáticos (un porcentaje muy alto de los artículos de economía y de deportes ya se generan de esta forma hace tiempo), y se ha especulado mucho sobre cómo GPT-3 y similares podrían afectar al trabajo de periodistas y generadores de contenido en general.

Esta tecnología es alucinante y abre una nueva era en el engaño, las fake news y el contenido hueco, vacío de humanidad. Pero tiene muchas aplicaciones benignas también. Es por esto que Open AI no va a liberar el modelo como Open Source sino que lo expone a través de una API a la que da acceso previa petición y análisis de ésta. De esta forma pueden responder al mal uso de la tecnología, cosa que no podrían hacer si la liberasen. Aparte de esto Open AI, que nació como una organización de investigación sin ánimo de lucro a finales de 2015, se ha convertido en una empresa con objetivos comerciales en la que Microsoft ha invertido 1.000 millones de dólares para crear uno de los mayores supercomputadores que se ejecutan en Azure. Elon Musk también es uno de los inversores en la empresa.

Estamos entrando en la era del Machine Learning y el futuro será de las empresas y organizaciones que sepan seguirle el ritmo🤖


Blazor Server: cómo funciona por debajo

$
0
0

Hace unos días tuvimos una interesante sesión sobre Blazor viéndolo a vista de pájaro, es decir, qué es, qué me aporta, en qué se parece y se diferencia de otras tecnologías, por qué me interesa....

Durante la sesión, en YouTube, alguna gente nos pidió que explicásemos con un vídeo cómo funciona Blazor Server "por debajo", es decir, cómo puede hacer para gestionar en el servidor los eventos y todo lo que ocurre en el cliente sin necesidad de recargar la página. La entrevista no era el momento, así que prometimos colgar un vídeo explicativo sobre ello, y es justo lo que hacemos ahora.

El siguiente vídeo, en realidad, está sacado de nuestro potente curso sobre Blazor y es, de hecho, uno de los primeros del curso, cuando se está presentando la tecnología. Esperamos que esto disipe las dudas sobre qué pasa con Blazor Server "por debajo del capó" 😊

[youtube:dJou3kwxqRU]

Os dejamos también la transcripción:

En este video vamos a ver cómo funciona una aplicación Blazor Server. Para ello tengo aquí una aplicación muy sencilla ya creada. De momento no te preocupes cómo la he creado ni cómo tienes que hacer para ponerla en marcha. Simplemente vamos a centrarnos en la parte general de cómo funciona y luego ya estudiaremos todo esto.

Esta aplicación es muy sencilla. Tienes simplemente tres páginas: esta que es un Hola mundo, esta es un contador que cada vez que pulsamos el botón aumenta este valor, y otra que se trae unos datos, en este caso de previsión del tiempo. Si te fijas, cada vez que navegamos por cualquiera de estas pestañas que tiene en realidad, aunque aquí cambia la ruta, no se está recargando la página. Ya ves que no aparece ningún indicador de que se está recargando. Es decir, es una SPA, una Single Page Application.

Podemos verlo muy fácilmente si sacas las herramientas del desarrollador usando CTRL+ Mayúsculas + I o F12 y ves aquí, en la pestaña Network, que ya tengo seleccionada. Si ahora recargo la página desde el principio para que vuelva a cargar y aparezcan aquí todos los recursos que se descarga y podamos ver qué ocurre, vemos que cada vez que cambio de pestaña, realmente aquí no se carga nada más. Es decir, se carga una primera vez la aplicación y el resto del tiempo no descarga ningún otro recurso estático más. De todo esto que se descarga, la mayoría de lo que ves aquí son elementos de la propia página. Por ejemplo, la hoja de estilos de Bootstrap, que es lo que se usa para esta plantilla, más la hoja de estilos específica con algunos detallitos estéticos y fuentes y poco más.

Las dos peticiones más importantes que hay aquí son BlazorServer.js, que es un archivo de JavaScript que podemos ver aquí. Por defecto viene minimizado, pero si le damos aquí podríamos ver su código, mejor formateado. De todas formas, aquí no vamos a entrar porque esto es algo cambiante, algo que viene en el propio framework Blazor y que puede cambiar en cualquier momento. Este archivo lo importante es saber que es el que se encarga de el mantenimiento del Estado y toda la comunicación con el servidor, para cualquier cambio que podamos hacer. Y la otra petición de verdad que nos interesa es el web socket que se abre entre el cliente y el servidor, que es este de aquí, pero ni siquiera tenemos que buscarlo porque gracias a las herramientas del desarrollador de cualquier navegador moderno podemos venir aquí y filtrar solo por web sockets exclusivamente.

En este caso tenemos este web socket, que es el que hace la comunicación entre cliente y servidor. Si yo ahora me cambio de página y pulso, por ejemplo, este botón, vamos a ver que dentro de esta web socket hay una serie de mensajes que podemos ver aquí que se están intercambiando. Estos son los mensajes que hemos intercambiado desde que cargué la página, que como vemos son muy pequeñitos, de 3 bytes, 349 bytes, 623 el más grande... y que constantemente se están produciendo. Lo que voy a hacer es borrar esto, y voy a pulsar de nuevo el botón para ver qué está pasando exactamente.

Si le doy a "Click me", vemos que hay tres mensajes y se ha incrementado este contador. ¿Qué ha ocurrido aquí? Bueno, el primer mensaje es el clic. Se notifica al servidor a través de un mensaje que se ha producido un clic. Si vemos aquí el contenido de este mensaje, en cualquier caso es muy pequeñito. Vemos que hay una llamada, un mensaje específico a dispatchBrowserEvent, que se está mandando al servidor y dentro de éste se manda toda la información sobre los eventos y analizamos un poquito de esto. Vemos que es un evento de ratón, que es un clic que nos manda la posición en donde estaba la pantalla: ScreenX, ScreenY, si estaba pulsada la tecla de control, etc, etc

El segundo mensaje es simplemente una respuesta del servidor. Fíjate que aquí tiene una flecha hacia abajo y aquí tiene hacia arriba, es decir, este se envía y se recibe. Donde en este caso se manda una instrucción de renderizado con los cambios que hay que realizar sobre la interfaz de usuario. En este caso es un cambio muy sencillo, que es poner un 2 aquí en el contador y que de hecho podemos ver aquí. Y finalmente hay un tercer mensaje que es este "Render complete" que se envía desde el cliente al servidor para indicar que el renderizado se ha realizado, es decir, todo el tiempo.
Nuestra aplicación se está comunicando con el servidor a través de ese web socket con mensajes muy pequeñitos que están coordinando cliente y servidor, las acciones que se hacen en el cliente y instrucciones que se reciben del servidor para actuar sobre la interfaz del cliente.

Todo esto se recarga en la página y utilizando un protocolo muy eficiente y muy rápido como es web sockets y además bidireccional. Como estamos viendo.

Además, otra cosa interesante es que podemos ver que este web socket es una conexión persistente. Todo el rato está funcionando, salvo si perdemos la conexión. Por ejemplo, vamos a simular que se pierde la conexión. Bueno, en este caso no puedo venir aquí a "on line" y ponerlo "offline", porque aunque esto funciona con conexiones normales, voy a volver a mostrar todas. Si aquí hiciera alguna petición normal por HTTP, al poner esto offline no se podría realizar, pero en el caso concreto de los web sockets sí que se siguen realizando. Si pulsa el botón voy a borrar la lista de mensajes. Si pulsa el botón, vemos que sigue funcionando, entonces esto no me vale para probar una desconexión y lo que voy a tener que hacer es parar, en este caso Internet Information Server Express, que está aquí funcionando, a mostrar las aplicaciones y que haya una aplicación con dos puertos HTTP y HTTPS.

Ahora estamos conectados al HTTPS. Es un certificado local, por eso dice que no es seguro, porque tengo que confiar explícitamente en él. Vale, bien, lo que voy a hacer es pararla. Aquí voy a parar directamente todo. En cuanto lo paro, vamos a ver que dice que se está intentando reconectar al servidor. Ya no hay más mensajes, vale, y de hecho si que hay nuevas peticiones HTTP en concreto XHR, es decir, por AJAX, intentando conectarse, lo va a intentar durante unos cuantos segundos y varias veces, y cuando no sea capaz de conectar va a dar un fallo y me va a pedir que me reconecte manualmente cuando crea que vuelvo a tener conexión, en este caso de cara al navegador. Es como si ya no existiera una conexión a Internet. Vamos a esperar un poquito... Ahí está. Me dice que ha fallado la conexión y que debo reintentar.

Bien, para resumir, lo que tenemos que saber es que una aplicación Blazor Server lo que hace es una parte, está en un servidor y una parte está en el cliente y ambas están en comunicación continua. Cualquier cosa que ocurra en el cliente y que sea reseñable para el servidor se va a transmitir hacia el servidor y el servidor va a transmitir de vuelta lo que tiene que hacer el cliente, de manera que siempre tenemos una única página. Es una Single Page Application con una comunicación a través de web sockets todo el tiempo por detrás. Si esa conexión se pierde, pues la aplicación deja de funcionar como en cualquier aplicación web que en un momento dado deje de tener conexión. Este es el funcionamiento básico de este tipo de aplicaciones.

6 consejos para mejorar el plan de formación de un programador

$
0
0

Para retener y maximizar el potencial creativo de los buenos desarrolladores, es necesario implementar programas de formación exhaustivos, orientados a la especificidad y adaptados a las necesidades de cada profesional.

Los departamentos de recursos humanos que trabajan con desarrolladores de software se enfrentan a un enorme desafío repleto de oportunidades, pero también de dificultades. Los programadores de software son competitivos y creativos por naturaleza.

Llegan a los nuevos puestos de trabajo decididos a arrasar con el mundo y si se aprovecha el gran potencial que poseen, las empresas se pueden beneficiar enormemente de ello. Pero si no se les capacita en la medida apropiada, es posible que se vayan a buscar horizontes más prometedores.

La importancia de un buen plan formativo

Los desarrolladores de software poseen habilidades extraordinariamente rentables. Pero son perfiles muy demandados y si no les das la oportunidad de desarrollar sus aptitudes, encontrarán a otro que sí lo haga.

Esto puede suponer un gran problema para la empresa, ya que los costes de reclutamiento y formación de los sustitutos de los desarrolladores de software que se han ido pueden ser muy elevados, especialmente si este ciclo se repite mucho en el tiempo y hay mucha rotación de personas.

Los programas de formación, además de ser útiles y eficaces, también tienen que causar una buena impresión en las personas a las que estás formando.

Cualquier cosa que se haga en nombre de la empresa tiene que hacer que los empleados se sientan valorados. Si la formación que se les ofrece es mediocre, es mejor no ofrecerla, ya que los empleados sentirán que no son importantes o que se les infravalora.

A continuación presentamos algunos consejos que pueden servir para perfeccionar los programas de formación para programadores existentes y aumentar su eficacia y relevancia:

1. No usar enfoques de formación estándar

Imagen ornamental por Neven Krcmarek, CC0 en Unsplash

Las personas tienen personalidades diferentes, y eso es algo que debe ser respetado. Los introvertidos son un grupo muy bien representado entre los desarrolladores de software, pero también existen perfiles extrovertidos, y perfiles de todo tipo. Algunas personas manejan bien la crítica constructiva, mientras que otras se ven profundamente afectadas por la más leve regañina. Unos prefieren los cursos online y otros la formación en formato bootcamp o mediante cursos presenciales.

Puede que no sea evidente de inmediato en qué lugar del espectro de características de la personalidad se encuentran los desarrolladores a los que se tiene que formar. Pero si se presta una atención detenida a lo largo del tiempo, se obtienen pistas, y es prudente ajustar tanto el enfoque como las expectativas de cada individuo en función de las observaciones que se hagan. Además, lo mejor siempre es explicar la visión de la empresa y luego preguntar y escuchar sus preferencias.

Desde campusMVP pensamos que la formación online tutorizada es la mejor forma de aprender a programar y la mejor forma de tener un plan de formación continua para el 80% de los desarrolladores. Pero evidentemente entendemos que hay perfiles para todo y que hay personas que prefieren la formación presencial, aún siendo menos eficiente y productiva. salvo para cursos muy cortos y especializados.

2. Incluir estrategias de seguimiento y motivación

Aunque la personalidad de cada persona es diferente, la necesidad humana de refuerzo positivo es casi universal. Puede que no sea aparente a primera vista, pero muchos desarrolladores de software carecen de confianza en sí mismos y se sienten algo intimidados por los compañeros de trabajo a los que temen que sean más innovadores y creativos que ellos.

Por esta razón, es importante dar reconocimiento a los progresos de los programadores formados a medida que se van produciendo, para mejorar la imagen que tienen de sí mismos y que adquieran una mayor seguridad cuando se les pide que completen tareas cada vez más complicadas.

Por supuesto, es posible que haya algunos alumnos que estén sumamente seguros de sí mismos y que no necesiten elogios ni estímulos para rendir mejor. No obstante, apreciarán ser reconocidos por su nivel de excelencia y tendrán una mejor opinión la empresa cuando se les otorgue el reconocimiento adecuado.

Para ello, los cursos online de campusMVP ofrecen unas herramientas de seguimiento y de evaluación que sirven para que los responsables de recursos humanos estén al tanto, incluso en tiempo real, de los avances de los programadores que se forman. Esto en un curso intensivo presencial es casi imposible de monitorizar.

3. Dar a los desarrolladores de software la oportunidad de aprender más acerca de la ingeniería y la arquitectura de software

Los buenos desarrolladores de software sienten una profunda curiosidad por su profesión, y muchos esperan algún día hacer la transición a ingeniería o a arquitectura de software.

A las empresas les conviene satisfacer este deseo y ayudar a sus desarrolladores a saciar su necesidad de ampliar su base de conocimientos, ya que los programadores comprometidos son los más centrados y los que están más centrados son los que tienen un mejor rendimiento y una mayor productividad.

Los desarrolladores de software más versátiles y completos son un activo para la organización en el futuro: cuando se necesite contratar a nuevos ingenieros o arquitectos, se podrán promocionar desde dentro, ahorrando a la empresa mucho dinero en el proceso, y todo será mérito del departamento de recursos humanos.

4. Encuestas internas sobre formación

Se deberían realizar encuestas internas a los desarrolladores de software para obtener información sobre los programas de formación actuales y para saber cómo se pueden ampliar o modificar.

La encuesta podría ser anónima o basarse en conversaciones privadas y cara a cara, dependiendo de las preferencias de los empleados y del afán por generar una mayor confianza por parte de la empresa.

Los resultados de las encuestas se deben tener en cuenta a la hora de planificar la formación de los programadores, pero no debe ser el único criterio que debe tenerse en cuenta. Los intereses de la empresa, como por ejemplo poder implementar un plan sostenible en el tiempo, también se deben tener en cuenta.

En campusMVP manejamos también encuestas de satisfacción de los alumnos de nuestros cursos, con miles de datos recibidos, que también nos dan una información muy valiosa para saber qué funciona y qué no. Somos las propias empresas de formación especializadas en programadores las más indicadas para ofrecer asesoramiento en todo este proceso, ya que trabajamos con muchas empresas de desarrollo y podemos trasladar todo este conocimiento en temas de formación continua a tu empresa.

5. Ceñirse a formación dirigida por un tutor, ya que es más efectiva y de mejor calidad que los cursos enlatados en vídeo

El mayor problema de los cursos enlatados y la formación online en general es que se vuelven muy impersonales, por muy bueno que sea el profesor que haya hecho el curso.

La única forma de subsanar este inconveniente es contando con un tutor que te guíe y te ayude a avanzar. Por no cualquier tutor. Lo óptimo es que dicho tutor sea la misma persona que ha diseñado y elaborado el curso de programación en cuestión, alguien con experiencia en la tecnología y en formación online, y no un tutor anónimo de los de 9 a 3 y 4 a 7 que lo mismo tutelan un curso de Excel que uno de programación avanzada.

Nadie mejor que un verdadero experto podrá resolver tus dudas. La información directa y en tiempo real que los estudiantes reciben de los instructores es sumamente útil, personalizada y un elemento vital en el proceso de aprendizaje.

6. Las sesiones formativas deberían ser periódicas y relevantes

Los desarrolladores dan por hecho que se tienen que formar constantemente. Como empresa se debe fomentar este hábito y no dejar pasar ni un solo año sin ofrecerle formación a sus programadores planes formativos.

Dados los rápidos cambios en el desarrollo de software, ¿cómo se puede esperar que los desarrolladores se mantengan al día con la tecnología si sus empleadores no son lo suficientemente proactivos para facilitar el proceso?

Los desarrolladores de software tienden a pasar mucho tiempo leyendo y actualizándose sobre los cambios en la industria por su cuenta, pero si se pone toda la carga sobre sus hombros, los programadores insatisfechos verán comprometida su capacidad de mejorar su rendimiento por su incapacidad de estar al día de los cambios en su profesión.

Conclusión

Si como empresa se va a buscar un curso online de programación, se debe buscar uno que compense el esfuerzo y la dedicación y que te garantice un aprendizaje profundo de la materia en un tiempo concreto. Se trata de encontrar un socio que ayude a formar a los programadores de la empresa durante sus trayectorias profesionales.

 

Aclarando conceptos: Inteligencia Artificial, Machine Learning, Deep Learning, Big Data y Ciencia de Datos

$
0
0

El mundo de la tecnología, como cualquier otro, no es inmune a las modas. Y estas modas hacen que ciertas palabras y conceptos se utilicen de manera arbitraria, como simples palabras huecas de marketing, que al final acaban perdiendo la sustancia y la validez de tanto usarlas mal. Así que cada vez que hay una tecnología en ascenso se generan ciertas buzzwords que todo el mundo utiliza y que no puedes dejar de escuchar y leer por todas partes.

Sin duda la tendencia tecnológica más puntera de los últimos años es todo lo relacionado con la inteligencia artificial y el análisis de datos. Y es que de manera relativamente reciente se han dado grandes avances en este campo, que unidos a la disponibilidad de enormes cantidades de datos y cada vez mayor potencia de cómputo están dando lugar a todo tipo de aplicaciones prácticas muy interesantes.

El problema viene cuando los términos relacionados con el campo se convierten en palabras vacías de marketing que en muchos casos son directamente mentira. Es muy habitual hablar de que tal o cual producto usa la inteligencia artificial para lograr algo y, en ocasiones, son algoritmos convencionales tomando decisiones predecibles. Como dice el famoso Tweet de I Am Devloper:

Tú dices: "Hemos añadido IA a nuestro producto" Yo escucho: "Hemos añadido un montón de IFs extra a nuestro código"

O como la siempre estupenda tira cómica de CommitStrip en la que se ve a un comercial presentando a un maravilloso robot que usa IA pero que, al romperlo, dentro solo tiene miles de condicionales:

La tira cómica sobre AI e Ifs de CommitStrip

Pero incluso cuando se usan con toda la buena intención, a veces resulta complicado discernir qué es cada cosa cuando se habla de tantos términos relacionados que no todo el mundo conoce bien.

En este artículo trataré de explicarte de manera concisa y clara qué significan cada uno de los términos más comunes relacionados con la ciencia de datos y la inteligencia artificial.

¡Allá vamos!

¿Qué es la Inteligencia Artificial?

La inteligencia artificial (IA o AI) nació como ciencia hace ya muchos años, cuando las posibilidades de las computadoras eran realmente limitadas, y se refiere a lograr que las máquinas simulen las funciones del cerebro humano.

La IA se clasifica en dos categorías según sus capacidades:

  • IA general (o fuerte): que intenta lograr máquinas/software capaces de tener inteligencia en el sentido más amplio de la palabra, en actividades que implican entender, pensar y razonar en cuestiones de carácter general, en cosas que cualquier ser humano puede hacer.
  • IA estrecha (o débil): que se centra en dotar de inteligencia a una máquina/software dentro de un ámbito muy concreto y cerrado o para una tarea muy específica.

Así, por ejemplo, una IA fuerte sería capaz de aprender por sí misma y sin intervención externa a jugar a cualquier juego de mesa que le "pongamos delante", mientras que una IA débil aprendería a jugar a un juego concreto como el ajedrez o el Go. Es más, una hipotética IA fuerte entendería qué es el juego, cuál es el objetivo y cómo se juega, mientras que la IA débil, aunque juegue mejor que nadie al Go (un juego tremendamente complicado) no tendrá ni idea realmente de qué está haciendo.

Una de las cuestiones cruciales a la hora de distinguir un sistema de inteligencia artificial de un mero software tradicional (por complejo que sea, lo cual nos lleva a los chistes de arriba) es que la IA se "programa" sola. Es decir, no consiste en una serie de secuencias lógicas predecibles, sino que tienen la capacidad de generar por su cuenta razonamientos lógicos, aprendizaje y autocorrección.

El campo ha avanzado mucho en estos años y tenemos IAs débiles capaces de hacer cosas increíbles. Las IAs fuertes siguen siendo un sueño de investigadores, y la base del guion de muchas novelas y películas de ciencia-ficción.

¿Qué es Machine Learning?

El Machine Learning (ML) o aprendizaje automático se considera un subconjunto de la inteligencia artificial. Se trata de una de las maneras de las que disponemos para lograr que las máquinas aprendan y "piensen" como los humanos. Como su propio nombre indica, las técnicas de ML se utilizan cuando queremos que las máquinas aprendan de la información que les proporcionamos. Es algo análogo a cómo aprenden los bebés humanos: a base observación, prueba y error. Se les proporcionan datos suficientes para que puedan aprender una tarea determinada y limitada (recuerda: IA débil), y luego son capaces de aplicar ese conocimiento a nuevos datos, corrigiéndose y aprendiendo más con el tiempo.

Existen muchas maneras de enseñar a una máquina a "aprender": técnicas de aprendizaje supervisado, no supervisado, semisupervisado y por refuerzo, en función de si se le da la solución correcta al algoritmo mientras aprende, no se le da la solución, se le da a veces o sólo se le puntúa en función de lo bien o mal que lo haga, respectivamente. Y existen multitud de algoritmos que se pueden utilizar para diferentes tipos de problemas: predicción, clasificación, regresión, etc...

Puede que hayas oído hablar de algoritmos como regresión lineal simple o polinómica, máquinas de vectores soporte, árboles de decisión, Random Forest, K vecinos más cercanos.... Estos son solo algunos de los algoritmos comunes utilizados en ML. Pero existen muchos más.

Pero conocer estos algoritmos y para qué sirven (para entrenar al modelo) es solo una de las cosas que se necesitan conocer. Antes es también muy importante aprender a obtener y cargar los datos, hacer análisis exploratorio de los mismos, limpiar la información... De la calidad de los datos depende la calidad del aprendizaje, o como suele decirse en ML: "Basura entra, basura sale".

En la actualidad las bibliotecas de Machine Learning para Python y R han evolucionado mucho, por lo que incluso un desarrollador sin conocimientos de matemáticas o estadística más allá de las del instituto, puede construir, entrenar, probar, desplegar y usar modelos de ML para aplicaciones del mundo real. Aunque es muy importante conocer bien todos los procesos y entender cómo funcionan todos estos algoritmos para tomar buenas decisiones a la hora de seleccionar el más apropiado para cada problema.

¿Qué es Deep Learning?

Dentro de Machine Learning existe una rama denominada Deep Learning (DL) que tiene un enfoque diferente a la hora de crear aprendizaje automático. Sus técnicas se basan en el uso de lo que se llaman redes neuronales artificiales. Lo de "deep" (profundo) se refiere a que las actuales técnicas son capaces de crear redes de muchas capas neuronales de profundidad, consiguiendo resultados impensables hace poco más de una década, ya que se han creado grandes avances desde el año 2010, unidos a las grandes mejoras en capacidad de cálculo.

En los últimos años se ha aplicado Deep Learning con un éxito apabullante a actividades relacionadas con el reconocimiento de voz, procesado del lenguaje, visión artificial, traducción automática, filtrado de contenidos, análisis de imágenes médicas, bioinformática, diseño de fármacos... obteniendo resultados iguales o mejores que los de humanos expertos en el campo de aplicación. Aunque no hace falta irse a cosas tan especializadas para verla en acción: desde las recomendaciones de Netflix a tus interacciones con tu asistente de voz (Alexa, Siri o el asistente de Google) pasando por las aplicaciones del móvil que te cambian la cara... todas ellas usan Deep Learning para funcionar.

En general, se suele decir (cójase con pinzas) que si la información de la que dispones es relativamente poca y el número de variables que entran en juego relativamente pequeño, las técnicas generales de ML son las más indicadas para resolver el problema. Pero si tienes enormes cantidades de datos para entrenar a la red y hay miles de variables involucradas, entonces el Deep Learning es el camino a seguir. Ahora bien, debes tener en cuenta que el DL es más difícil de implementar, lleva más tiempo entrenar a los modelos y necesita mucha más potencia de cálculo (normalmente "tiran" de GPUs, procesadores gráficos optimizados para esta tarea), pero es que los problemas normalmente son más complejos también. Para que te hagas una idea, el mítico GPT-3 utiliza 175.000 millones de parámetros entrenables.

Pero no todo son modelos gigantes. Existen redes que pueden resolver problemas avanzados (detección de objetos, atributos faciales, clasificación precisa) ejecutándose en dispositivos móviles y sistemas empotrados, como las MobileNet que pueden tener menos de 1 millón de parámetros.

¿Qué es el Big Data?

El concepto de Big data es mucho más sencillo de entender. Dicho en palabras sencillas, esta disciplina agrupa las técnicas necesarias para capturar, almacenar, homogeneizar, transferir, consultar, visualizar y analizar datos a gran escala y de manera sistemática.

Piensa por ejemplo en los datos de miles de sensores de la red eléctrica de un país que envían datos cada segundo para ser analizados, o la información generada por una red social como Facebook o Twitter con centenares (o miles) de millones de usuarios. Estamos hablando de volúmenes enormes y continuos que no son apropiados para usar con sistemas tradicionales de procesamiento de datos, como bases de datos SQL o paquetes de estadística estilo SPSS.

El Big Data se caracteriza tradicionalmente por las 3 V:

  • Volumen elevado de información. Por ejemplo, Facebook tiene 2 000 millones de usuarios y Twitter cerca de 400 millones, que están constantemente dotando de información a esas redes sociales en volúmenes elevadísimos, y es necesario almacenarla y gestionarla.
  • Velocidad: siguiendo con el ejemplo de las redes sociales, cada día Facebook recoge del orden de 1000 millones de fotos y Twitter gestiona más de 500 millones de tweets, eso sin contar likes, y muchos otros datos. El Big Data lidia con esa velocidad de recepción de datos y su procesamiento de modo que pueda fluir y ser procesada adecuadamente y sin cuellos de botella.
  • Variedad: se pueden recibir infinidad de datos de diferente naturaleza, algunos estructurados (como la lectura de un sensor, o un like) y otros desestructurados (como una imagen, el contenido de un tweet o una grabación de voz). Las técnicas de Big Data deben lidiar con todos ellos, gestionarlos, clasificarlos y homogeneizarlos.

Otro de los grandes retos asociados a la recogida de este tipo de información masiva tiene que ver con la privacidad y la seguridad de dicha información, así como la calidad de los datos para evitar sesgos de todo tipo.

Como ves, las técnicas y conocimientos necesarios para hacer Big Data no tienen nada que ver con las que se requieren para AI, ML o DL, aunque muchas veces se use el término muy a la ligera.

Estos datos pueden nutrir a los algoritmos utilizados en las técnicas anteriores, es decir, pueden ser la fuente de información de la que se nutran modelos especializados de Machine Learning o de Deep Learning. Pero también pueden ser utilizados de otras maneras, lo cual nos lleva a...

¿Qué es la ciencia de datos?

Al hablar de ciencia de datos nos referimos en muchos casos a la extracción de información relevante de los conjuntos de datos, denominado también KDD (Knowledge Discovery in Databases, descubrimiento de conocimiento en bases de datos). Utiliza diversas técnicas de muchos campos: matemáticas, programación, modelado estadístico, visualización de datos, reconocimiento y aprendizaje de patrones, modelado de incertidumbre, almacenamiento de datos y computación en la nube.

Ciencia de datos se puede referir también, de forma más amplia a los métodos, procesos y sistemas que involucran tratamiento de datos para esa extracción de conocimiento. Puede englobar desde técnicas estadísticas y de análisis de datos, hasta los modelos inteligentes que aprenden "por sí mismos" (no supervisados), lo que tocaría parte de Machine Learning también. De hecho, este término se puede confundir con minería de datos (más de moda hace unos años) o con el propio Machine Learning.

Los expertos en ciencia de datos (llamados muchas veces científicos de datos) se enfocan en la resolución de problemas que involucran datos complejos, y buscan patrones en la información, correlaciones relevantes y, en definitiva, obtener conocimientos a partir de los datos. Suelen ser expertos en matemáticas, estadística y programación (aunque no tienen que ser expertos en las tres cosas).

Al contrario que los expertos en Inteligencia Artificial (o Machine Learning o Deep Learning), que buscan la manera de generalizar la solución a problemas mediante el aprendizaje automático, los científicos de datos generan conocimientos particulares y específicos a partir de los datos de los que parten. Lo cual es una diferencia sustancial del enfoque, y de los conocimientos y de las técnicas necesarias para cada especialización.

En resumen

Como has visto en este repaso, todos estos términos tan comunes en la actualidad y muchas veces usados a la ligera, están relacionados entre sí, a veces de manera íntima, pero no son lo mismo y es necesario distinguirlos y utilizarlos con precisión.

El siguiente diagrama, que he hecho yo mismo a mano alzada a partir de las sugerencias de David Charte (que también ha revisado el artículo como experto en la materia), creo que es un buen resumen visual de todo lo que he explicado en el texto del artículo y ayuda a situar cada especialidad en relación a las demás:

Diagrama que muestra el solapamiento entre las diferentes disciplinas del artículo - Autor José M. Alarcón

Espero que estas explicaciones te hayan ayudado a aclarar conceptos y, si tienes interés en dedicarte a este mundo, a decidirte por una u otra en función de tus intereses y capacidades.

Cómo garantizar el soporte a largo plazo de tu aplicación .NET

$
0
0

Con la aparición de versión 5 de .NET es un buen momento para reunir en un solo artículo toda la información sobre las versiones de .NET, la cadencia con la que aparecerán y las políticas de soporte que ofrece Microsoft para cada versión.

El sistema de versiones de .NET Core tiene una manera muy diferente de funcionar respecto a lo que era .NET Framework, así que si vienes del desarrollo de aplicaciones con .NET "clásico" o si estás empezando con .NET Core o .NET, te resultará de mucha ayuda comprender la periodicidad de los lanzamientos del framework, los tipos de soporte que existen y su ciclo de vida, para poder tomar las decisiones correctas sobre qué versión utilizar en cada caso, sobre todo si trabajas en una empresa u organización donde la estabilidad de soporte es importante.

Si necesitamos soporte técnico con .NET, tenemos dos opciones:

  • Soporte de la comunidad: básicamente documentación, foros, Stack Overflow, etc... El búscate la vida de siempre en el que nadie te garantiza una solución ni tampoco una respuesta, pero que la realidad es que es, el que utiliza casi todo el mundo.
  • El soporte oficial de Microsoft: Microsoft pone a tu disposición todo su poder de recursos humanos técnicos para ayudarte a solucionar el problema, con garantía de respuesta. Generalmente este es el que escogen las empresas y las corporaciones cuando el anterior no les da una solución.

Aunque solo vayas a utilizar el soporte de la comunidad, te interesa conocer los ciclos de vida de soporte de .NET, a pesar de pienses que no va contigo. El motivo es que no sólo se regula el soporte técnico directo que recibes de Microsoft, sino también el soporte que obtiene la propia plataforma para recibir actualizaciones y parches de seguridad.

En la propia página de descargas de .NET te indican siempre qué nivel de soporte tiene cada versión y hasta qué fecha dura, pero conviene conocer bien qué significa cada caso:

La página de descargas de .NET

Vamos a verlo...

Tipos de versiones de .NET y su soporte técnico

Las versiones de .NET "tradicional" han utilizado lo que Microsoft denomina ciclo de vida fijo que, como su nombre lo indica, proporcionan un período fijo de soporte, que suele ser largo. Por ejemplo, 5 años de soporte estándar (incluidas las revisiones de seguridad y las que no están relacionadas con la seguridad) y otros 5 años de soporte extendido (solo correcciones de seguridad).

.NET Core y .NET adoptan lo que Microsoft llama ciclo de vida moderno , que es muy diferente ya que adoptan un modelo de soporte más corto y más frecuente.

Así, .NET Core / .NET incluye versiones principales (major), versiones secundarias (minor) y actualizaciones de servicio (parches/patches). Así, por ejemplo:

  • .NET Core 3.0 es una versión principal del producto.
  • .NET Core 3.1 es la primera versión secundaria después de la versión principal de .NET Core 3.0.
  • .NET Core 3.1.7 tiene una versión principal de 3, una versión secundaria de 1 y una versión de parche de 7, es decir, es el séptimo parche para .NET Core 3.1.

Desde una perspectiva del soporte oficial que da Microsoft al framework, existen 2 "pistas" de soporte para las versiones de .NET Core.

  • Las versiones "actuales" (current): estas versiones de .NET Core y de .NET tienen soporte oficial tan solo durante un período de 3 meses tras haberse lanzado la siguiente versión principal o secundaria.
  • Las versiones LTS (Soporte a largo plazo): a estas se les da soporte durante un mínimo de 3 años, o 1 año después de que se envíe la próxima versión LTS (lo que sea más tarde).

Vamos a verlo con ejemplos concretos:

Cuando salió .NET Core 3.0 en septiembre de 2019, se trataba de una versión "current". En diciembre de ese mismo año salió .NET Core 3.1, una versión secundaria de la anterior, así que el soporte de .NET Core 3.0 dura hasta marzo de 2020, o sea, 3 meses después y se supone que deberías migrar a 3.1 lo antes posible para tener soporte, lo cual, al no ser una versión que rompa la compatibilidad, no debería suponer problema alguno.

Un ejemplo de LTS sería .NET Core 2.0, que se designó como LTS en agosto de 2018, así que se soporta durante 3 años como mínimo, es decir, hasta agosto de 2021. La siguiente versión LTS que apareció fue .NET Core 3.1, en diciembre de 2019 y se garantiza el soporte hasta al menos un año después, o sea, diciembre de 2020. Así que como agosto de 2021 es más tarde que diciembre de 2020, la fecha efectiva de fin de soporte de .NET Core 2.0 será agosto de 2021.

La finalización del soporte significa que, después de la fecha oficial de fin, Microsoft ya no lanzará más actualizaciones, parches ni dará asistencia técnica oficial para esa versión de .NET. Así que hay que tenerlas presentes porque antes de esta fecha tenemos que haber pasado nuestra aplicación a una versión que esté soportada.

Si continúas usando una app compilada para una versión que no está soportada, no recibirás las actualizaciones de seguridad que sean necesarias y tus usuarios podrían estar en peligro.

Frecuencia de versiones en .NET Core/.NET

Las versiones de .NET Core y de .NET siempre alternan entre las versiones LTS y Current, por lo tanto, .NET Core 3.1 es una versión LTS, pero la siguiente versión .NET 5 es una versión "current" y su soporte oficial durará menos tiempo que el de .NET Core 3.1.

La siguiente versión LTS será .NET 6 en noviembre de 2021, y así sucesivamente: .NET 7 será current, .NET 8 será LTS...

El lanzamiento de las próximas versiones de .NET y su nivel de soporte

Las actualizaciones de mantenimiento, que se envían (casi siempre) cada mes, incluyen correcciones de seguridad, estabilidad, confiabilidad y compatibilidad y se adhieren a la versión menor anterior. Es decir, .NET Core 3.1.7 es a efectos de soporte lo mismo que .NET Core 3.1, por poner un ejemplo.

Así que, si vas a crear una aplicación nueva que quieres que esté varios años soportada en cuanto a problemas de seguridad, estabilidad, etc.., es mejor hacerla con .NET 3.1 que con .NET 5 si quieres tener ese soporte oficial y conseguir este tipo de actualizaciones.

¿Qué versión utilizan las aplicaciones cuando hay una versión nueva del runtime de .NET?

Las actualizaciones principales y secundarias se instalan en paralelo a las versiones anteriores. Las aplicaciones siempre quedan atadas a una combinación de versión mayor/menor específica, la utilizada durante el desarrollo. Es decir, si instalamos una versión nueva del runtime que, entre otras cosas, soluciona un bug o mejora una característica, no quiere decir que nuestra aplicación la empiece a usar automáticamente. O sea, la aplicación no usa automáticamente la versión principal/secundaria más reciente.

Por ejemplo, una aplicación que se compiló para .NET Core 3.0 no comienza a ejecutarse automáticamente en .NET Core 3.1 cuando lo instalamos, a pesar de que estén dentro de la misma versión principal. Lo que tendrías que hacer es volver a compilar la aplicación contra la nueva versión para asegurarte de que todo funciona correctamente. Una vez recompilada, entonces sí que usará la nueva versión.

Sin embargo, las actualizaciones de "parche", sí que se tratan de manera diferente a las versiones principales y secundarias. Una aplicación creada para .NET Core 3.1 se dirige al tiempo de ejecución 3.1.0 de forma predeterminada, pero avanza automáticamente para usar una versión 3.1 más reciente. De esta manera tendremos un entorno de ejecución más seguro porque esas versiones de "parche" incluyen siempre correcciones de problemas de seguridad y de estabilidad, como ya hemos dicho.

Cómo funcionan las versiones del SDK de .NET

Ahora vamos a ver otro pequeño giro de tuerca...

Una cosa es el runtime de .NET, lo que se usa para ejecutar las aplicaciones, y otra diferente pero relacionada es el SDK de .NET, que es lo que se utiliza para crear las aplicaciones. Lo que comentábamos en el apartado anterior se refiere al runtime.

El control de versiones para el SDK de .NET (o sea, el Kit de desarrollo de software) funciona de manera algo diferente al del runtime de .NET.

Las actualizaciones del SDK de .NET a veces incluyen nuevas características o nuevas versiones de componentes como MSBuild o NuGet para alinearse con las nuevas versiones de Visual Studio. Estas nuevas funciones o componentes pueden ser incompatibles con las versiones que se envían en actualizaciones anteriores del SDK para el mismo valor de versión principal/secundario.

Así que, para versionar los SDK, existen lo que se llaman bandas de características, como una forma de diferenciar entre las actualizaciones de SDK. Las bandas de características se definen en el tercer número de sección y siempre van en alguna centena.

Por ejemplo, el primer SDK de .NET Core 3.1 fue 3.1.100. Esta versión corresponde a la banda de características 3.1.1xx. Así, las versiones 3.1.101 y 3.1.201 del SDK tienen dos bandas de características diferentes, mientras que 3.1.101 y 3.1.199 están en la misma banda de características.

Cuando tenemos instalado en la nuestra máquina el primer SDK de .NET Core 3.1, que es el 3.1.100, cuando sale la siguiente versión dentro de esa banda, la 3.1.101, la versión anterior de la misma banda se elimina automáticamente de la máquina porque son totalmente compatibles. Sin embargo, cuando sale el SDK 3.1.200, que es de otra banda diferente y por lo tanto incompatible, el anterior no se quita y coexisten los dos para poder crear aplicaciones para cada una de las bandas.

¿Y qué pasa con .NET "clásico"?

Como ya sabrás si sigues el blog de campusMVP, .NET 4.8 es la última versión importante que va a salir de la plataforma .NET "clásica".

Si tiene aplicaciones creadas con .NET "clásico", salvo que quieras sacar partido a alguna característica específica de .NET (como el hecho de que es multiplataforma) no merece la pena en general que intentes migrarla a .NET Core/.NET y te garantizas que va a funcionar durante muchos años en Windows ya que Microsoft la va a incluir con el sistema operativo en el futuro previsible, y por lo tanto seguirá dando soporte, correcciones y actualizaciones de seguridad. Así que su funcionamiento está garantizado.

Microsoft ya ha manifestado oficialmente que .NET 4.x está tan entremezclado con Windows que, por razones de compatibilidad, no existen planes de eliminar .NET Framework 4.8 de Windows.

 

Lecciones aprendidas tras migrar más de 25 proyectos a .NET Core

$
0
0

Este artículo es una traducción de "Lessons learned after migrating 25+ projects to .NET Core" de Thomas Ardal, CEO de elmah.io, con su permiso expreso. elmah.io es el principal servicio del mercado para monitorización y logging de aplicaciones .NET.

Hace poco terminamos una de las mayores tareas de refactorización que hemos hecho en elmah.io: migrar todo a .NET Core. elmah.io consta actualmente de 5 aplicaciones web y 57 funciones de Azure repartidas en aproximadamente 25 Function Apps. En este post, compartiré algunas de las lecciones que hemos aprendido mientras llevábamos a cabo esta tarea.

Imagen ornamental

Vamos al grano. He dividido este post en tres categorías. Una sobre los problemas de migración de .NET Framework a .NET Core en general. Otra específicamente sobre la migración de ASP.NET a ASP.NET Core. Y la tercera sobre la migración de las Azure Functions a la versión más reciente. Siéntete libre de sumergirte en el tema que más te interese. Algunos de los contenidos están divididos en temas más específicos que nos fuimos encontrando, mientras que otros son más bien una descripción textual de dónde nos encontramos actualmente.

.NET Core

Para empezar con buenas noticias, esperaba muchos más problemas al migrar el código de .NET Framework a .NET Core. Cuando empezamos a experimentar con la migración, .NET Core estaba en la versión 1.x y faltaban aún muchas características de .NET Framework "clásico". Desde la versión 2.2 en adelante, no recuerdo haber echado de menos nada. Si saltas directamente a la última versión estable de .NET Core (no veo motivos por lo que no debieses hacerlo), hay muchas probabilidades de que tu código se compile y funcione sin necesidad de realizar ningún cambio.

Hay que estar atento a los niveles de soporte

Una cosa que debes tener en cuenta cuando saltes de .NET "clásico" a .NET Core, es un despliegue más rápido de las nuevas versiones. Eso incluye también intervalos de soporte más cortos. Con .NET "clásico", 10 años de soporte eran lo más normal, cuando ahora los 3 años de soporte de .NET Core son el periodo que se ofrece. Además, cuando se elige la versión de .NET Core con la que quieres compilar, hay que mirar el nivel de soporte que te proporciona cada versión. Microsoft marca ciertas versiones con soporte de largo plazo (LTS) que es alrededor de 3 años, mientras que otras son versiones intermedias. Estables, pero aún así versiones con un período de soporte más corto. En general, estos cambios requieren que actualices la versión .NET Core más a menudo de lo que estás acostumbrado o asumir que vas a ejecutar tus aplicaciones en una versión del framework ya no soportada.

Aquí tienes una buena visión general de las diferentes versiones y sus niveles de soporte: Política de soporte de .NET Core.

ASP.NET Core

Migrar nuestros sitios web ASP.NET MVC a ASP.NET Core ha sido la mayor tarea de todas. No quiero asustarte para que no migres y, de hecho, la mayor parte del tiempo lo pasamos migrando algunos viejos frameworks de autenticación y haciendo tareas similares no relacionadas con .NET Core. La parte MVC en ASP.NET Core funciona de manera muy parecida a la antigua y se llega muy lejos haciendo buscar y reemplazar global con algunos patrones. En las siguientes secciones, he enumerado varios problemas con los que nos encontramos durante la migración.

Cómo hacer la actualización

La ruta de actualización no es exactamente directa. Puede que existan algunas herramientas que te ayudan con ello, pero al final acabé migrando todo a mano. Para cada sitio web, hice una copia de todo su repositorio. Después borré todos los archivos de la carpeta de trabajo y creé un proyecto ASP.NET Core MVC nuevo. Luego porté cada cosa una por una. Empezando por copiar los controladores, vistas y modelos y haciendo uso de algunos patrones globales de búsqueda-reemplazo para lograr que compilase. Casi todo fue distinto a partir de ahí. Estábamos usando un viejo marco de trabajo de autenticación, y lo portamos a la característica de autenticación que trae de serie .NET Core (no a Identity). Lo mismo con Swagger, que requiere un nuevo paquete NuGet para .NET Core. etc. Nos llevó mucho tiempo migrar con seguridad el sitio web principal.

Entendiendo el middleware

Como seguramente ya sabrás, muchas de las características de ASP.NET Core están construidas sobre middlewares. El concepto de middleware no existe en ASP.NET y por lo tanto es algo que debes aprender. La forma en que configuras los middlewares y especialmente el orden en el que los instalas es algo que nos ha causado un cierto dolor de cabeza. Mi recomendación sería que estudies muy a fondo la documentación de Microsoft para este caso. Además, Andrew Lock escribió una larga serie de artículos de alta calidad sobre middleware en los que recomiendo a todo el mundo que se sumerja: https://andrewlock.net/tag/middleware/

Newtonsoft.Json vs System.Text.Json

Hasta ASP.NET Core 3.0, la serialización y deserialización de JSON se llevaba a cabo con el extremadamente popular paquete Newtonsoft.Json. Microsoft decidió lanzar su propio paquete para lo mismo en ASP.NET Core 3.0 y versiones posteriores, lo que causó algunos problemas al actualizar de la versión 2.2. a la 3.1.

System.Text.Json parece que ya es una buena implementación y, después de algunas pruebas, decidimos ir adelante con ello (es la opción por defecto en cualquier caso). Rápidamente descubrimos que Newtonsoft.Json y System.Text.Json no son compatibles en la manera en la que serializan y deserializan los objetos C#. Como nuestro cliente usa Newtonsoft.Json para serializar JSON, experimentamos algunos escenarios en los que el cliente generaba JSON que no podía ser deserializado a C# en el servidor. Mi recomendación, en caso de que estés usando Newtonsoft.Json en el cliente, es usar también Newtonsoft.Json en el servidor:

services
    .AddControllersWithViews()
    .AddNewtonsoftJson();

Después de llamar a AddControllersWithViews o cualquier otro método que llames para configurar los endpoints, puedes llamar al método AddNewtonsoftJson para que ASP.NET Core utilice ese paquete.

El cambio necesita de un paquete NuGet adicional:

Install-Package Microsoft.AspNetCore.Mvc.NewtonsoftJson

Mayúsculas y minúsculas en JSON

Un problema al que nos enfrentamos en nuestra aplicación principal fue el uso de mayúsculas y minúsculas al serializar JSON. Cuando la acción de un controlador de ASP.NET Web API devuelve JSON:

return Json(new { Hello = "World" });

El JSON devuelto usa "pascal case" (o sea, todas las primeras letras de las palabras en mayúsculas):

{"Hello":"World"}

Pero cuando se devuelve lo mismo desde ASP.NET Core, el JSON devuelto usa "camel case" (la primera letra de la primera palabra en minúsculas, el resto en mayúsculas):

{"hello":"World"}

Si tú, al igual que nosotros, ya tienes un cliente basado en JavaScript que espera que "pascal case" para los objetos serializados desde C#, puede cambiar el "casing" con esta opción:

services
    .AddControllersWithViews()
    .AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.PropertyNamingPolicy = null;
    });

Compilación en tiempo de ejecución de Razor

Aunque portar las vistas de Razor de ASP.NET MVC a ASP.NET Core no requirió mucho trabajo, no tenía claro que las vistas Razor no se compilaban en tiempo de ejecución en el caso de ASP.NET Core. Escribí un artículo en el blog sobre ello: Agregar compilación en tiempo de ejecución para Razor al desarrollar con ASP.NET Core. En resumen: es necesario marcar la opción "Habilitar la compilación en tiempo de ejecución" de Razor al crear el proyecto, o bien instalar el paquete NuGet Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation y habilitar la compilación en tiempo de ejecución en Startup.cs:

services
    .AddControllersWithViews()
    .AddRazorRuntimeCompilation();

Los archivos Web.config siguen siendo válidos

La mayoría de los documentos y blogs mencionan que el archivo web.config se sustituyó por el archivo appsettings.json en ASP.NET Core. Aunque esto es cierto, todavía necesitamos el archivo web.config para implementar en IIS algunos de los encabezados de seguridad como se describe en este post: Guía de cabeceras de seguridad de ASP.NET Core.

Aquí hay un ejemplo del archivo web.config que utilizamos para eliminar las cabeceras Server y X-Powered-By que se ponen automáticamente en las respuestas HTTP cuando la aplicación se hospeda en IIS:

<?xml version="1.0" encoding="utf-8"?><configuration><system.webServer><security><requestFiltering removeServerHeader="true" /></security><httpProtocol><customHeaders><remove name="X-Powered-By" /></customHeaders></httpProtocol></system.webServer></configuration>

Soporte de Azure

Si tu aplicación se ejecuta en Azure (como las nuestras) debes averiguar qué versiones de .NET Core soporta cada región de Azure. Cuando se lanzan nuevas versiones de .NET Core, las regiones de Azure se actualizan en un período que puede llevar de semanas a incluso meses. Antes de actualizar, debes comprobar si tu región es compatible con la versión a la que te estás actualizando. La mejor forma de averiguarlo la tienes en el .NET Core en App Service Dashboard.

Bundling y minificación

El bundling y la minificación era una de las cosas que, creo que funcionaban muy bien en ASP.NET MVC. En ASP.NET Core tienes bastantes opciones diferentes para hacer lo mismo, lo cual es bueno, pero también un poco confuso cuando vienes directamente de ASP.NET MVC. Microsoft tiene un buen documento sobre el tema aquí: Agrupar y minimizar los activos estáticos en ASP.NET Core.

Al final acabamos utilizando la extensión Bundler & Minifier de Mads Kristensen, sobre la que quizás escriba una entrada específica en el blog. En resumen, especificamos los archivos de entrada y salida en un archivo bundleconfig.json:

[
  {
    "outputFileName": "wwwroot/bundles/style.min.css",
    "inputFiles": [
      "wwwroot/css/style1.css",
      "wwwroot/css/style2.css"
    ]
  }
]

Este archivo se procesa con Gulp y se hace el bundling y la minificación con los paquetes gulp-cssmin, gulp-concat, y otros paquetes npm similares (tenemos un curso fenomenal de herramientas Front-End aquí). Esto hace posible ejecutarlo tanto localmente (conectando las tareas de Gulp al build de Visual Studio si lo deseas), como en nuestro servidor de Azure DevOps.

Mira mamá, no más peticiones potencialmente peligrosas

Si has estado ejecutando ASP.NET MVC y registrando peticiones fallidas, probablemente ya has visto este error muchas veces:

Se ha detectado un valor potencialmente peligroso en Request.Form

ASP.NET MVC no permitía etiquetas <script> y similares como parte de las peticiones POST a los controladores de MVC. ASP.NET Core cambió eso y acepta entradas como esa. Si para ti es importante securizar la aplicación contra contenidos como ese, necesitarás añadir un filtro o algo similar que los compruebe. En nuestro caso, "escapeamos" todo usando Knockout.js, por lo que hacer peticiones en las que haya fallos de marcado no tiene importancia. Pero es algo que sin duda hay que tener en cuenta.

Funciones de Azure

Migrar a la versión más reciente de las Funciones de Azure (actualmente la v3) ha sido un proceso largo. Muchas de las características de elmah.io se basan en tareas programadas y servicios que están en ejecución mucho tiempo, como por ejemplo consumir mensajes desde el Azure Service Bus, enviar un correo electrónico de resumen diario, etc. Hace unos años, todos estos "trabajos" se ejecutaban como tareas programadas de Windows y servicios de Windows en una máquina virtual en Azure. Migramos todos estos servicios menos uno a Azure Functions v1 ejecutándose en .NET Framework.

Cuando salió Azure Functions v2, comenzamos a migrar una aplicación de funciones a v2 ejecutándose en .NET Core con resultados pobres. Existían todo tipo de problemas y el conjunto disponible de clases de la biblioteca base no era lo suficientemente bueno. Cuando salió .NET Core 2.2 finalmente dimos el salto y portamos todas las funciones.

Como parte de la reciente tarea de migración, migramos todas las aplicaciones de funciones a Azure Functions v3 ejecutándose en .NET Core 3.1. El código se ha estado ejecutando extremadamente estable desde que lo hicimos, y yo recomendaría esta configuración para su uso en producción.

Cómo hacer la actualización

La actualización fue mucho más rápida que la de ASP.NET Core. Las versiones v1, v2 y v3 siguen más o menos la misma estructura de archivos y la mayor parte del conjunto de características. Para actualizar, simplemente actualicé el marco de trabajo de cada proyecto así como todos los paquetes NuGet.

Usar Microsoft.Azure.Functions.Extensions

Si tú, como nosotros, comenzaste a crear Funciones de Azure con la versión 1 del runtime y .NET "clásico", probablemente te habrás preguntado cómo hacer la inyección de dependencias o la inicialización de las funciones. Al actualizar a la v2 y .NET Core 2.2 comenzamos a usar el paquete NuGet de Microsoft.Azure.Functions.Extensions. Una vez instalado, puedes especificar un archivo Startup.cs en la app de tu Function, tal como lo habrías hecho en ASP.NET Core. Allí, puedes abrir las conexiones de la base de datos, configurar el registro, etc:

using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

[assembly: FunctionsStartup(typeof(My.Function.Startup))]

namespace My.Function
{
    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            builder.Services.AddHttpClient(...);

            var config = new ConfigurationBuilder()
                .AddJsonFile("local.settings.json")
                .Build();

            builder.Services.AddLogging(logging =>
            {
                logging.AddSerilog(dispose:true);
            });

            builder.Services.AddSingleton<IFunctionFilter, PerformanceFilter>();
        }
    }
}

El código anterior es tan solo un conjunto aleatorio de líneas de configuración que he elegido de una de nuestras funciones. Aunque debería resultar familiar para los desarrolladores de ASP.NET Core.

No uses los bindings de salida

Una de las cosas buenas de las Azure Functions es un catálogo cada vez mayor de bindings de entrada y salida. ¿Quieres ejecutar la función cuando se escribe un nuevo blob en el almacenamiento de blobs? Añade un binding de entrada a blobs. ¿Escribir un mensaje en una cola una vez que la función se ha completado? Añade un binding de salida al bus de servicio... Teníamos un viejo código portado de Servicios de Windows, que hacía toda esta comunicación manualmente y me interesaba empezar a utilizar los bindings de salida de las Azure Functions cuando lo portásemos a v3.

Al final acabé echando para atrás la mayoría de estos cambios y evitando los bindings de salida por completo. Aunque están muy bien pensados, pierdes el control de lo que sucede en esos bindings. Además, cada binding se implementa de forma diferente. Algunos con reintentos, otros sin ello. Esta respuesta de Stephen Cleary lo explica bastante bien. En la iteración de código más reciente, he creado todos los clientes de la base de datos, clientes de temas, etc. en el archivo Startup.cs y los inyecto en el constructor de cada función. Así tengo el control total de cuándo hacer peticiones, cuántos reintentos quiero ejecutar, etc. Otra ventaja es que el código de la función ahora se parece mucho al código de los sitios web de ASP.NET Core, que inicializa e inyecta las dependencias de la misma manera.

Herramientas

Antes de terminar, quiero decir unas palabras sobre las herramientas de migración automática. Cuando comenzamos el proyecto de migración existían algunas opciones. Y desde entonces han aparecido aún más. No he probado ninguna de ellas, pero ahora hay tantas opciones disponibles que, si empezase hoy con la migración probablemente lo haría. Échales un vistazo:

Conclusión

Migrar ha sido, en conjunto, una gran decisión para nosotros. Ya le estamos viendo cantidad de ventajas. Un framework más simple. Tiempos de build más rápidos. La compilación de Razor es mucho más rápida. Más fácil trabajar en código para los que prefieren hacerlo así. Mejor rendimiento en Azure y menos consumo de recursos (principalmente memoria). La posibilidad de mover el hosting a Linux. Y mucho más. Dicho esto, la migración llevó mucho tiempo.

Las secciones de antes las he ido escrito de lo que recordaba mientras escribía el post. Puede que quiera incluir más secciones cuando recuerde algo o encuentre nuevos temas. Si tienes algún tema específico sobre el que quieras aprender más o preguntas concretas sobre la migración, no dudes en contactar con nosotros en elmah.io.

Java: comodines para tipos genéricos - PECS. Diferencias entre

$
0
0

Foto ornamental. Muestra un comodín de una baraja fluorescente con el texto Java Genéricos. A partir de imagen de Tudor Baciu, CC0 en Unsplash

Hace unas semanas, Francisco Charte nos explicaba en un interesante vídeo introductorio qué son lo tipos genéricos, para qué sirven y cómo se utilizan.

En esta ocasión nos vamos a centrar en un tema relacionado directamente con los tipos genéricos en Java: los comodines para genéricos o PECS.

PECS es el acrónimo de Producer extends and Consumer super, que quiere decir en español que el productor usa extends y el consumidor usa super. Esta terminología tan extraña, acuñada por Joshua Bloch, cobra sentido cuando entiendes el funcionamiento y el propósito de estos comodines, cosa que veremos enseguida.

El problema de los tipos incompatibles

Supongamos que tenemos unas clases en nuestro programa, en este caso muy simples, que sirven para representar a unas entidades, en este ejemplo seres vivos:

class SerVivo {
  public String Nombre;

  public SerVivo(String n) {
    Nombre = n;
  }

  @Override
  public String toString() {
    return "Ser vivo: " + Nombre;
  }
}

class Animal extends SerVivo {
  public Animal(String n) {
    super(n);
  }

  @Override
  public String toString() {
    return "Animal: " + Nombre;
  }
}

class Gato extends Animal {
  public Gato(String n) {
    super(n);
  }

  @Override
  public String toString() {
    return "Gato: " + Nombre;
  }
}

En este ejemplo tenemos tres clases: una "genérica" llamada SerVivo que tiene un constructor para poder pasarle el nombre de la criatura en cuestión, y que ha sobrescrito el método toString() para mostrar ese nombre por consola. Además, hay unas clases Animal y Gato que heredan de SerVivo y de Animal respectivamente (o sea, un Gato es también un Animal y un animal es un SerVivo). No tiene misterio alguno.

Ahora supongamos que creo sendas listas de animales y gatos, añadiendo a cada una de ellas algunos objetos de estas clases:

List<Animal> listaAnimales = new ArrayList<Animal>();
//Llenamos la lista de animales
listaAnimales.add(new Animal("Jirafa"));
//Se pueden añadir Gatos a listaAnimales por ser una subclase de Animal
listaAnimales.add(new Gato("Patucas"));
listaAnimales.add(new Animal("Halcón"));

Fíjate en que, en la lista de animales hemos podido añadir no sólo objetos de tipo Animal, sino también gatos, ya que estos heredan de Animal y son, por tanto, animales.

Por ello, podríamos añadir incluso tan solo gatos a la lista y funcionaría perfectamente. Supón ahora que creamos una segunda lista de gatos e intentamos asignar el contenido de la primera a esta segunda:

List<Gato> listaGatos = new ArrayList<Gato>();
listaGatos = listaAnimales;

Entonces se produciría un error de tipos incompatibles. Pero, ¿por qué? Al fin y al cabo todo lo que hemos metido en la primera lista de animales son gatos, así que debería funcionar.

Correcto, pero el problema es que en tu lista de animales puede haber de todo, y en tu lista de gatos sólo puede haber gatos, así que nadie te asegura que no se haya "colado" otro animal diferente en la primera, por lo que el compilador no puede permitirlo.

Pero al revés ocurre lo mismo:

listaAnimales = listaGatos;

produce también un error, a pesar de que todos los gatos son animales y por lo tanto deberían poder convertirse sin problemas.

Vale, de acuerdo, pero... ¿qué tiene que ver este problema con los genéricos?

Trabajo con colecciones genéricas

El problema viene a la hora de trabajar con colecciones de objetos para hacer algo con ellos: bien introducir objetos en una colección o bien procesar colecciones de objetos. Esto ya empieza a parecerse a lo que decía al principio de PECS...

Imagina que tenemos un método para procesar animales (objetos de la clase Animal), por ejemplo, uno que simplemente los muestre por consola, como este:

public static void muestraElementos01(List<Animal> lista) {
    for(Animal elemento : lista) {
        System.out.println(elemento);
    }
}

//lo usamos con nuestras colecciones
muestraElementos01(listaAnimales);
muestraElementos01(listaGatos); //¡¡Falla!!

Cabría pensar que como los objetos de tipo Gato son también animales (un subtipo de la clase Animal), la última línea funcionaría. Pero no es el caso: falla con exactamente el mismo mensaje del compilador que en el ejemplo anterior: tipos incompatibles. Java no permite pasarle una lista genérica de Gato (List<Gato>) a un método que espera una lista concreta de tipo List<Animal>, y por eso rompe.

Siempre podríamos utilizar una lista de Object de modo que le pudiéramos pasar cualquier cosa, pero entonces, por esa regla de tres, ¿para que querríamos los tipos genéricos? 😉 No, evidentemente lo que necesitamos es algún mecanismo del lenguaje para poder gestionar este tipo de situaciones. Este mecanismo son los comodines para tipos genéricos.

Comodines para tipos genéricos en Java

Los comodines para tipos genéricos son la solución específica para un problemas muy concreto: leer y escribir en colecciones genéricas, justo lo que acabamos de ver.

Existen 3 comodines para tipos genéricos que podemos aplicar y que vamos a ver a continuación:

Comodines para tipo desconocido: <?>

Son el tipo más simple de comodines genéricos, y también el más limitado, paradójicamente por su falta de límites. Nos permiten indicar al compilador que no sabemos el tipo exacto que se nos va a pasar para procesar en nuestro tipo genérico. Dado que no tenemos ni idea ni tampoco se lo podemos indicar al compilador. Su sintaxis sería como la siguiente, en la que modificamos el genérico con nuestro comodín:

public static void muestraElementos02(List<?> lista) {
	for(Object elemento : lista) {
      System.out.println(elemento);
    }
}

En el fondo es casi como si estuviésemos usando la solución tradicional con Object. De hecho, los elementos de una colección de este tipo los tendríamos que tratar como objetos básicos, Object, como se ve en el fragmento anterior. Así que le podríamos pasar cualquier cosa, no sólo las dos clases de ejemplo que hemos creado, sino listas genéricas de cadenas de texto, de números o de cualquier otro tipo que quisiésemos.

Mala solución, pero tiene sus usos, así que conviene conocerla. Vamos a conocer a los comodines "buenos"...

Comodín para tipos derivados: <? extends T>

Este comodín quiere decir que se admiten todos los objetos que hereden de la clase especificada. En nuestro ejemplo podríamos escribir esto:

public static void muestraElementos03(List<? extends Animal> lista) {
    for(Animal elemento : lista) {
        System.out.println(elemento);
    }
}

Fíjate en que, dado que cualquier clase que herede de Animal se puede convertir ("castear") a Animal, entonces podemos recibir listas genéricas con ellas y convertir cada elemento a Animal antes de utilizarlo. Es por esto que como variable del bucle se puede usar un elemento de tipo Animal. Y es por esto que podemos pasarle sin problemas nuestra lista de gatos, que se imprimirá al igual que cualquier otra lista genérica de animales.

Es decir, el comodín para tipos derivados actúa como límite superior en la jerarquía de clases que admite la lista genérica, ya que cualquier clase que descienda de Animal, a cualquier nivel, se admitirá sin problemas.

Comodín para supertipos: <? super T>

Volvamos ahora al problema del principio. Como hemos visto, si tenemos dos listas genéricas con tipos compatibles, aún así no podemos asignar una variable de una en la otra, o sea, no podemos por ejemplo asignar una referencia a una lista de gatos en una variable que sea de tipo lista de animales, a pesar de que los gatos son animales (heredan de la clase Animal).

¿Cómo resolvemos el problema de crear una función genérica que permita añadir tan solo objetos de un tipo a cualquier colección genérica de objetos de nuestra jerarquía? O sea, en nuestro ejemplo, cómo creamos una función genérica que nos deje usar una colección de cualquier tipo de Animal para arriba, pero añadir tan sólo elementos de tipo Animal.

Por ejemplo, tenemos una colección de seres vivos, así:

List<SerVivo> organismos = new ArrayList<SerVivo>();
organismos.add(new SerVivo("Protozoo"));
organismos.add(new SerVivo("Árbol"));
organismos.add(new Gato("Calcetines"));

Y ahora, por el motivo que sea, necesitamos crear una función genérica que reciba como argumento una colección de cualquier tipo de la jerarquía que sea Animal o superior (o ArrayList<SerVivo> o ArrayList<Animal>), y que sólo deje añadir elementos de ese determinado nivel de la jerarquía (en este caso Animal, o sea, sólo animales o gatos, que están por debajo).

Podríamos intentar esto:

public static void cargaAnimales(List<SerVivo> lista) {
    lista.add(new Gato("Marramiau"));
    lista.add(new Animal("Pulpo"));
    lista.add(new SerVivo("Ameba"));  //Rompe
}

cargaAnimales(organismos);
cargaAnimales(listaAnimales);  //¡¡Falla!

Y cumpliría con este caso concreto, pero dejaría añadir cualquier tipo de ser vivo, y no solo animales como necesitamos. Además si intentásemos llamarlo con una lista de objetos de alguna subclase de SerVivo, como en la última línea, obtendríamos un error de tipos incompatibles como los que vimos al principio.

Así que podríamos intentar esto otro:

public static void cargaAnimales(List<Animal> lista) {
    lista.add(new Gato("Marramiau"));
    lista.add(new Animal("Pulpo"));
}

cargaAnimales(organismos);  //¡¡Falla! :-(

pero ni siquiera podríamos llamarla porque rompería en tiempo de ejecución por el problema que vimos al principio: tipos incompatibles en la última línea (espera un List<Animal> y le estamos pasando un List<SerVivo>.

O sea, le pongamos lo que le pongamos en el tipo de lista, no podemos cumplir con las dos condiciones expuestas.

Para este caso en concreto existe precisamente el comodín para supertipos: <? super T>. Si definimos la función así:

public static void cargaAnimales(List<? super Animal> lista) {
    lista.add(new Gato("Marramiau"));
    lista.add(new Animal("Pulpo"));
    //lista.add(new SerVivo("Ameba"));  //Rompe
}

cargaAnimales(organismos);  // ¡¡Funciona!!

se están cumpliendo las dos condiciones: le podemos pasar como argumento una lista de objetos de cualquier tipo de la jerarquía que esté por encima de Animal (en este caso de SerVivo), pero sólo le podremos añadir elementos de la clase indicada (o, por supuesto, de sus subclases: Gato en nuestro ejemplo).

El comodín de supertipos sirve para indicar que una clase genérica permitirá recibir una asignación de cualquier elemento que sea del tipo indicado o de sus supertipos (o sea, de las clases de las que hereda). Esto es para hacer la asignación de una referencia, pero el funcionamiento de la clase genérica luego se ciñe a dicho tipo (y por definición a sus subtipos).

Como la jerarquía de nuestra clase Animal es la siguiente:

Object
  |_ SerVivo
        |_ Animal
             |_ Gato

En nuestro ejemplo al indicar List<? super Animal> como argumento de la función, estamos indicando que podemos pasarle cualquier lista de las clases por encima de Animal (incluyendo de Object que es la clase base de todas), y que la lista sólo admitirá objetos del tipo Animal.

Es decir, en la práctica significa que nos deja asignar a esta variable/argumento genérico, objetos que pertenezcan a una determinada jerarquía pero con un límite por debajo, que es la clase que estamos indicando.

O sea, esta sintaxis establece un límite por arriba de la jerarquía y la anterior un límite por debajo.

Buff, es algo complicado de digerir, pero como ves tiene sus aplicaciones. Y esto nos lleva a...

PECS

¿Recuerdas que al principio mencionábamos PECS: Producer extends and Consumer super?

Bien, pues PECS es la regla mnemotécnica para saber cuándo usar estos comodines con genéricos, incluso aunque no entendamos bien lo anterior:

  • Usa el comodín para tipos derivados <? extends T> cuando debas obtener elementos de tipo T (o sus descendientes) en una colección (Producer extends, ya que la función produce algo con ellos, los utiliza).
  • Usa el comodín para supertipos <? super T> cuando debas añadir elementos del tipo T (o sus descendientes) a una colección (Consumer super, ya que la función consume los elementos).

Si seguimos estas reglas no tendremos problemas a la hora de ejecutar el código que use genéricos.

Te he dejado un Repl.it con el código de ejemplo para que puedas ir viendo cada cosa por separado y te ayude a digerirlo todo. Si haces un fork (copia) de este Repl.it puedes retocarlo y hacer los cambios y pruebas que quieras. Dale a ejecutar y verás los resultados enseguida.

¡Espero que te resulte útil!

5 aplicaciones prácticas inesperadas de la Inteligencia Artificial

$
0
0

Imagen ornamental por Amanda Dalbjörn, CC0 en Unsplash

En un post anterior estudiamos los principales conceptos relacionados con la Inteligencia Artificial. Ya sabes que la IA es una herramienta multiusos que tiene muchas aplicaciones en diferentes campos de estudio y en la industria, como en robótica, medicina o marketing.

En este post te presentaré algunos usos de la Inteligencia Artificial menos convencionales que tal vez te sorprendan.

1.- Participar en concursos de televisión con procesamiento de lenguaje natural

Aunque en su momento tuvo bastante repercusión, no está de más recordar una de las ocasiones que dieron a conocer la Inteligencia Artificial a gran parte del público general. Se trata de IBM Watson, un sistema automático capaz de responder a preguntas formuladas en lenguaje natural. Este tipo de sistemas están hoy día en auge gracias a los asistentes de voz como Alexa o Siri, pero Watson apareció públicamente por primera vez en 2011, cuando los asistentes aún estaban en sus comienzos y no tenían apenas funcionalidad.

Los tres concursantes del episodio de Jeopardy! en el que participó Watson

Watson participó como concursante en un episodio especial de Jeopardy!, un programa donde cada ronda consiste en averiguar la pregunta a una respuesta que se da como pista. Físicamente, Watson no era el típico ordenador personal sino que contaba con 90 servidores, sumando en total 720 núcleos de procesador y 16 terabytes de RAM. Era capaz de buscar las respuestas en una gran variedad de fuentes de información: enciclopedias, diccionarios, obras literarias y noticias, pero no estaba conectado a Internet durante el concurso. Participó en las mismas condiciones que los jugadores humanos, utilizando el pulsador para tener oportunidad de responder a las pistas.

El supercomputador acabó ganando la partida frente a dos de los campeones del programa, Ken Jennings y Brad Rutter. Actualmente, IBM sigue utilizándolo para otros fines en la industria médica estadounidense.

2.- Dibujar caricaturas de personas con modelos generativos

Pese a que es una creencia común que los ordenadores no pueden tener capacidad creativa, algunos modelos de aprendizaje automático pueden ser generativos, lo que significa que, a partir de los datos que se les proporcionan, pueden manipularlos de diferentes formas e incluso crear nuevos datos similares o con ciertas propiedades.

Ese es el caso de CariGAN, una red neuronal desarrollada por Microsoft Research que toma fotos y dibuja caricaturas a partir de ellas, bien con un estilo de referencia de otra caricatura, o bien con un estilo propio.

Algunos ejemplos de caricaturas con personas famosas

Los ejemplos anteriores, extraídos del artículo original donde se presenta el modelo, muestran el nivel de libertad que tiene a la hora de manipular las formas y colores, para realzar y exagerar partes del cuerpo sin que la imagen pierda la identidad y se siga pudiendo reconocer a la persona.

3.- Perfeccionar el sabor de una cerveza con aprendizaje por refuerzo

Una colaboración entre la empresa de un doctor en aprendizaje automático de la Universidad de Oxford y una agencia creativa dio lugar a esta interesante creación: una cerveza desarrollada por medio de Inteligencia Artificial.

El proceso de perfeccionamiento de la cerveza, en sus 4 variantes (golden, amber, pale y black), consistió en dar a probar muestras a muchas personas y recoger sus opiniones acerca de ellas mediante un bot. Los datos acumulados por el bot pasaban luego a un sistema de aprendizaje por refuerzo, que mejoraba la receta y el procedimiento de preparación de cada variante según los aspectos que hubieran resultado positivos y negativos en los ensayos.

No es la única cerveza en cuya creación haya colaborado la IA, otra empresa suiza también ha aprovechado estas capacidades de análisis para estudiar el mercado y una base de datos de 157.000 recetas para tratar de obtener una combinación óptima de ingredientes.

Por desgracia, será extremadamente difícil que una de estas cervezas llegue a tus manos, por el momento.

4.- Generar tests de personalidad con una red neuronal profunda (deep learning)

Retomando los modelos generativos y los sistemas de IA creativos, una red neuronal profunda que ha tenido mucho éxito este año, gracias a su habilidad para sintetizar el lenguaje natural. Se llama GPT-3, ha sido desarrollada por OpenAI y te hemos hablado de ella antes. A diferencia de sistemas anteriores, GPT-3 es capaz de "recordar" mucho más acerca de lo que ya ha escrito a la hora de generar las siguientes palabras, lo que facilita que pueda escribir párrafos enteros e incluso artículos enteros.

Una investigadora en IA, Janelle Shane, utilizó GPT-3 para generar test de personalidad humorísticos, del tipo que encontrarías en publicaciones digitales virales. Para ello, le proporcionó el texto de un par de test de cinco preguntas, y el modelo predijo los test que vendrían a continuación😲

Una de las preguntas del test _"Which type of AI are you?"

Puedes responder al test "¿Qué tipo de IA eres?", generado con GPT-3, para averiguar qué rasgos de personalidad determinan tu perfil como IA. También hay otros test como "¿Qué gato radiante legendario eres?" o "¿Qué animal alien eres?" en el post de Shane.

5.- Colaborar en la composición de vacunas con aprendizaje profundo (deep learning)

Por último, y aunque es relativamente conocido que la IA tiene ya un rol en la medicina, suele ser difícil tener idea de lo que ocurre en los laboratorios farmacéuticos. En la actualidad, gracias a la colaboración de diferentes entidades de IA en la búsqueda de una vacuna para el virus SARS-CoV-2, podemos tener una intuición de cómo se aprovecha la IA para identificar potenciales componentes para que formen parte de ella.

Google DeepMind, la compañía que desarrolló AlphaGo, está utilizando su proyecto de predicción de estructuras de proteínas, AlphaFold, para acelerar el estudio de algunas proteínas asociadas con el virus y así facilitar su entendimiento. Microsoft, por su parte, está colaborando con una biotecnológica australiana en el desarrollo de una vacuna, utilizando IA para procesar todos los datos relacionados con los ensayos clínicos.

Aunque no sería la primera vez que se investiga en el uso de IA para desarrollar vacunas, en este caso se dan las circunstancias para que, mediante la colaboración de diferentes equipos y empresas, se llegue a conseguir una vacuna segura en tiempo récord. El récord actual lo tiene la vacuna de las paperas, conseguida en tan solo 4 años.

"Las herramientas de aprendizaje automático pueden predecir, basándose en conjuntos de datos de entrenamiento de patógenos conocidos, qué partes del virus serían más fáciles de reconocer por el sistema inmune."

-- Lo que la IA puede -y no puede- hacer en la carrera hacia una vacuna para el coronavirus

Conclusión

Como ves, la IA puede estar presente en casi cualquier ámbito, y puede jugar papeles importantes, así como servir para fines artísticos y humorísticos. Sus aplicaciones en la industria son bien conocidas, pero también nos la podemos encontrar en sitios inesperados, con resultados sorprendentes.

Espero que te hayan resultado interesantes estos casos de aplicación, y que te hayan ayudado a ver la IA desde otro punto de vista.


JavaScript es el rey del desarrollo web y Python domina machine learning

$
0
0

SlashData es la empresa líder en el análisis de la economía de los desarrolladores. Hace poco ha llevado a cabo su decimonovena encuesta entre más de 17.000 desarrolladores en 159 países. Este informe de investigación se centra en 6 temas principales, estos son:

  1. Necesidades extra de los desarrolladores debido a la COVID-19: el informe explora los efectos de la COVID-19 en las necesidades cambiantes de los desarrolladores en relación con sus actividades de desarrollo.
  2. Lenguajes de programación: en este apartado se proporcionan estimaciones actualizadas del número de desarrolladores de software activos que utilizan cada uno de los principales lenguajes de programación.
  3. ¿Por qué los desarrolladores adoptan o rechazan las tecnologías en la nube?: aquí analizan algunas de las razones que dan los desarrolladores para adoptar o rechazar diferentes tecnologías en la nube y brindan información sobre por qué las cosas son como son.
  4. ¿Quién está interesado en DevOps?: DevOps no es un sector o tecnología única y coherente, lo que a menudo crea confusión en cuanto a quién se considera un profesional de DevOps. En este apartado analizan los roles específicos y los sectores de software que están más asociados con la cultura DevOps.
  5. ¿Qué valoran los desarrolladores en el open source?: según su investigación, el uso de software de código abierto (OSS) es omnipresente en la comunidad global de desarrolladores. En esta sección indican qué valoran exactamente los desarrolladores al utilizar OSS.
  6. Tecnologías emergentes: ponen de relieve qué tecnologías han aumentado y disminuido en popularidad durante los últimos doce meses.

Si tienes interés en algún punto concreto o en leer el informe completo lo puedes descargar desde este enlace.

Nosotros, hoy, nos vamos a centrar en el segundo apartado de esta informe, es decir, en cuáles son los lenguajes de programación más usados.

Lenguajes de programación más utilizados

Gráfico de barras con los datos de los lenguajes más utilizados

JavaScript es el lenguaje de programación más popular por un amplio margen, con más de doce millones de desarrolladores que lo utilizan en todo el mundo. En particular, la comunidad de JavaScript ha ido creciendo de manera constante durante los últimos tres años. Entre el segundo trimestre de 2017 y el tercer trimestre de 2020, casi 5 millones de desarrolladores se unieron a la comunidad, es, con mucho, el mayor crecimiento en términos absolutos en todos los lenguajes. Incluso en los sectores de software donde JavaScript es menos popular, como la ciencia de datos o la AR/VR (realidad aumentada/realidad virtual), más de una quinta parte de los desarrolladores lo usan en sus proyectos.

Casi 5 millones de desarrolladores se unieron a la comunidad de JavaScript en los últimos 3 años 😮

Por segundo semestre consecutivo, Python es el lenguaje más adoptado detrás de JavaScript. Python ahora cuenta con 9 millones de usuarios, después de sumar 2,2 millones de nuevos desarrolladores solo en el último año, superando a Java a principios de 2020.

El auge de la ciencia de datos y del aprendizaje automático (machine learning - ML) es un factor claro de su popularidad. El 77% de los desarrolladores de ML y científicos de datos utilizan Python actualmente y el 22% usa R, el otro lenguaje que a menudo se asocia con la ciencia de datos.

Java, con más de 8 millones de usuarios activos en todo el mundo, es la piedra angular del ecosistema de aplicaciones móviles, Android, así como uno de los lenguajes de uso general más importantes. Es posible que la adopción se haya mantenido estable en los últimos seis meses, pero, en general, la comunidad Java ha ganado 1,6 millones de desarrolladores desde mediados de 2017, lo que corresponde a un crecimiento del 24%.

Cierran el grupo de los seis lenguajes más populares C/C ++ (6.3M), PHP (6.1M) y C# (6M). El hecho de que C# haya descendido tres posiciones en el ranking durante los últimos tres años se explica principalmente por su crecimiento más lento en comparación con C/C ++ y PHP.

C y C ++ siguen siendo lenguajes centrales en proyectos de IoT, mientras que PHP sigue siendo el segundo lenguaje más utilizado en aplicaciones web, después de JavaScript.

C# crece en popularidad, pero a un ritmo inferior que PHP o C/C++

A continuación se muestra la evolución de los lenguajes de programación entre 2017 y 2020 extraída directamente del informe de SlashData.

Gráfico con las variaciones de adopción y popularidad de los principales lenguajes en los últimos 3 años, por trimestre

 

AVIF: todo lo que necesitas saber sobre el nuevo formato de imagen

$
0
0

Imagen ornamental, basada en una ilustración gratuita de Veronica Baranova en mixkit.co/@veronicabaranova)

Empecemos con un breve repaso a los formatos de imágenes de mapas de bits para la Web existentes hasta ahora ya que nos ayudara a entender las virtudes y defectos del nuevo formato...

Los formatos tradicionales de imágenes para la web, GIF, JPG y PNG han logrado estar de plena vigencia durante décadas. Cada uno de ellos tiene sus propios pros y contras, usos apropiados y limitaciones, pero entre los tres logramos gestionar bien las necesidades de imágenes de nuestros sitios y aplicaciones web.

En septiembre de 2010 Google lanzó el formato de imagen WebP. Se basa en el formato de vídeo VP8 y es hermano del formato de contenedor de vídeo WebM, también de Google y lanzado unos pocos meses antes. WebP soporta lo mejor de todos los demás formatos: compresión sin pérdida (y con pérdida si queremos más compresión) y transparencias como PNG, secuencias de imágenes para animaciones como GIF y un peso reducido como JPG. De hecho, las imágenes en WebP suelen pesar sustancialmente menos que las imágenes JPG, para ofrecer la misma calidad.

De todos modos, WebP presenta varios problemas. Está limitado al uso de tan solo 8 bits para la profundidad de color, y usa submuestreo de crominancia que almacena el color a la mitad de la resolución de la imagen, lo cual está bien para vídeo pero no para imágenes estáticas. Además, el resto de los navegadores tardaron demasiado en dar soporte al formato. Firefox empezó a soportarlo en su versión 65 en enero de 2019 (¡9 años después!) y Safari tan solo ha empezado a soportarlo desde septiembre de 2020 con su versión 14, exclusivamente en macOS "Big Sur". O sea, hace nada.

Por lo que, en la práctica, no puedes usar tan solo WebP y garantizar que se vean tus imágenes en todos los navegadores, como sí ocurre con los formatos tradicionales.

El nuevo formato AVIF

AVIF es el acrónimo de AV1 Image File Format y es un nuevo formato de imagen de bits para la Web, de libre uso, creado por la Alliance for Open Media (AOMedia), una organización en la que están todas las grandes organizaciones con intereses en Internet y en especial en el mundo multimedia, como Google, Netflix, Vimeo, Microsoft, Intel, Amazon, Apple, Facebook, NVidia y muchas más.

Al igual que WebP, AVIF es un formato que se basa en un códec de vídeo. En concreto es la combinación del estándar ISO HEIF (High Efficiency Image File Format) para el contenedor, y el codec de vídeo AV1, open source y libre de derechos, desarrollado por AOMEdia.

Dicho de manera básica, al igual que WebP, una imagen AVIF es un frame de un vídeo en un contenedor de imagen para darle soporte de metadatos y otras cuestiones. La principal diferencia es que WebP utiliza el codec VP8, mientras que AVIF usa el mucho más moderno y potente AV1, que, como sabemos, además es libre y de código abierto.

El uso de este codec ofrece varias mejoras frente a VP8, entre ellas un sustancial menor tamaño para la misma calidad de la imagen (más sobre esto ahora) y el soporte para imágenes de alto rango dinámico (HDR), ofreciendo mayor gama y profundidad de colores y más brillo.

Al igual que WebP, soporta imágenes con o sin pérdida, transparencia y animaciones.

¿Menor tamaño en imágenes AVIF?

En la mayor parte de los sitios verás que se hace hincapié en la reducción de peso que tienen las imágenes AVIF respecto a los demás formatos, incluso frente a WebP que era el campeón en esto. Y es cierto: para una misma calidad de la imagen se pueden conseguir reducciones de "peso" de hasta el 50% frente a JPG sin perder calidad apreciable. Frente a WebP las ganancias no son tan grandes (andan en torno al 20%), pero si manejas muchas imágenes (como Netflix), sin duda es un factor que marca la diferencia.

Pero otro modo de verlo, quizá más adecuado, es al contrario: si dejamos fijo el tamaño de la imagen en varios formatos ¿qué diferencias observamos?

Y digo esto porque, en realidad, es posible conseguir casi cualquier tamaño de imagen con cualquier formato con pérdidas como JPG o WebP. Lo verdaderamente importante es ver la relación entre el tamaño y la calidad visual que conseguimos. Si yo le digo a un codificador JPG que preserve pocos detalles puede comprimir muchísimo la imagen. Y al revés: si le digo a un codificador WebP o AVIF que conserve mucho la definición de la imagen poco podrá hacer para reducir el tamaño.

Y en este tipo de comparación AVIF lleva también la delantera. Una forma muy sencilla de comprobarlo es comprimir la misma imagen en 3 formatos con pérdidas. Por ejemplo, he tomado esta imagen de una piscina del fotógrafo Etienne Girardet, que he descargado en tamaño mediano y tiene unas dimensiones de 1950x1300, o sea, un poquito más que Full-HD, y pesa 250KB en formato JPG con poca compresión. La he convertido a AVIF, WebP y JPG consiguiendo que pese en los 3 casos tan solo 17KB. Es decir, más o menos puedes lograr el tamaño que quieras, como ves muy reducido.

Te las dejo aquí en un ZIP (400KB) para que puedas compararlas (ábrelas en Chrome), pero en la siguiente figura te dejo un detalle (sin aumentar, tamaño real) de la misma parte de la fotografía en los 4 casos: original y en los tres formatos pesando muy poco:

Comparación de detalle de las imágenes, a igual peso, con la original

Como se hace evidente, a mismo tamaño AVIF ofrece una definición mucho mayor que los otros dos formatos, aunque pierde calidad sustancialmente frente a la original. Se nota sobre todo en la definición: tanto en la rugosidad del suelo, que casi desaparece, como en la barra de la escalera que se alisa mucho, o el borde de la piscina, que se ve mucho menos nítido. Pero claro, es que pesa menos del 8% que la original, y no se pierde tanto. Especialmente si la imagen es pequeña.

Ahora bien, para conseguir un nivel de nitidez muy similar al de la imagen original, hay que jugar mucho con los posibles ajustes y lo logras con aproximadamente la mitad de peso, para ser exactos un 56% menos, y solo 108KB (la tienes en el ZIP también). Ahí, yo al menos, no noto apenas diferencias. Lo que es un logro estupendo.

Pero al final, lo interesante es que, según la imagen, sus dimensiones y la definición que necesitemos con AVIF podremos conseguir siempre grandes mejoras de peso para esos mismos parámetros comparado con los otros formatos.

Luego veremos, de todos modos, que no todo son alegrías y también hay algunas pegas...

Cómo podemos crear imágenes en formato AVIF

La manera más sencilla de todas, apropiada para trabajar con imágenes individuales, es la fantástica herramienta Squoosh de Google Chrome Labs. Esta app (se puede instalar como PWA) te permite arrastrar o cargar una imagen y convertirla a diferentes formatos usando varias técnicas diferentes y con capacidad para ajustar un montón de parámetros. Además, te muestra la versión original de la fotografía junto a la transformada para que puedas compararlas con una barra deslizante:

La interfaz de usuario de Squoosh

Esta herramienta soporta AVIF y es muy interesante para optimizar una imagen antes de subirla a la Web.

En Windows 10 (versión posterior a la de mayo de 2019) tenemos soporte nativo del formato instalando la extensión AVIF desde la tienda de Windows. Esto nos permite no sólo tener vistas previas de las imágenes en el explorador de Windows, sino que la imagen esté soportada por editores gráficos como Paint, el programa sencillo de edición gráfica que viene con Windows, o mi favorito que uso todo el rato: Paint.net, por lo que podrás usarlo para generar las imágenes en este formato, controlando los parámetros para ello.

Todo esto está muy bien para manejar unas pocas imágenes, pero si necesitas hacer conversiones masivas debes ir por otro camino: el camino de la línea de comandos.

La AOMedia dispone de una implementación escrita en lenguaje C, de código abierto y multiplataforma. Si visitas las releases del proyecto en GitHub podrás encontrar la biblioteca compilada para Windows en forma de 2 ejecutables listos para usar:

Las descargas de LIBAVIF en GitHub

En macOS la puedes instalar directamente desde Homebrew:

brew install joedrago/repo/avifenc
brew install joedrago/repo/avifdec

El uso básico es muy sencillo pues basta con indicar el nombre del archivo de destino con el parámetro -o (o --output) y el nombre que queremos darle al archivo:

avifenc imagenoriginal.jpg -o imagenfinal.avif

Como podemos ver en esta captura:

Codificación en línea de comandos

Como ves, nos muestra los parámetros que usa por defecto e información sobre la imagen final.

Con los parámetros por defecto conseguiremos mejoras de tamaño del 50% o más respecto a un JPG sin mucha compresión. Si queremos afinar más podemos tocar muchos otros parámetros de los mostrados, usando otros tantos switch de línea de comandos. Escribe avifenc -h para ver la ayuda de cada uno de ellos.

Con avifdec.exe podemos obtener información de un archivo AVIF que tengamos ya en disco, para ver qué parámetros utiliza y, si acaso, poder afinarlos con la utilidad anterior:

El comando avifdec en acción

Con avifenc y un poco de maña con los scripts de línea de comandos podremos generar archivos AVIF en masa a partir de archivos JPG o PNG que tengamos.

Finalmente, algunos servicios de transformación de imágenes online en tiempo real como el que tiene Cloudflare o específicos como Cloudinary (en el momento de escribir esto lo tiene en fase experimental), ofrecen la posibilidad de generar automáticamente imágenes AVIF.

Soporte del formato AVIF en los navegadores

El formato AVIF está soportado por Chrome desde su versión 85 (finales de agosto de 2020).

En el momento de escribir esto, los demás navegadores basados en Chrome como Edge u Opera todavía no lo soportan, pero se estima que lo harán pronto. Firefox lo soporta de modo experimental (hay que ajustar el flagimage.avif.enabled en about:config), pero pronto lo tendrá de serie también.

¿Quiere decir esto que no debemos usarlo aún?

En absoluto. Chrome tiene un porcentaje brutal del mercado de navegadores porque mucha gente lo podrá visualizar ya. Para los demás puedes enviar la imagen en WebP (soportado por más todavía) o en JPG (soporte universal). Simplemente genera las tres versiones y usa la etiqueta <picture> de HTML, así:

<picture><source srcset="/imgs/miimagen.avif" type="image/avif"><source srcset="/imgs/miimagen.webp" type="image/webp"><img src="/imgs/miimagen.jpg" alt="Descripción de la foto"></picture>

Con esta etiqueta se verá la imagen AVIF si está soportada, sino se intentará la WebP y si ninguno de los dos formatos se soporta se descargará el JPG para asegurar que se ve. Dado la cuota de uso que tiene Chrome y el buen soporte de WebP que existe hoy en día, solo con esto podrás ahorrar ancho de banda y hacer que las página descarguen más rápido para tus usuarios, especialmente los de móviles, porque la mayoría descargarán la versión AVIF o WebP, y con ambas puedes ahorrar mucho peso.

Otra cuestión que es interesante conocer es que el formato AVIF ofrece muchas más posibilidades que las que disfrutamos en los navegadores hoy en día. Muchas de sus capacidades están pensadas para las cámaras de los móviles, no para la Web. Por ejemplo las fotos "live" que muestran un minivídeo previo a haber sacado la foto, las ráfagas que almacenan muchas fotos en una sola para elegir la mejor, el modo HDR para hacer doble exposición, etc...

Soporte del formato AVIF en los servidores

En el servidor es necesario hacer algún ajuste para que permita su descarga, ya que al ser un formato tan nuevo casi seguro que no tiene su tipo MIME registrado y por lo tanto no permitirá procesarlos hacia el navegador.

El tipo MIME apropiado para los archivos AVIF es image/avif.

Cómo añadir este tipo MIME va a depender del servidor que utilices, claro. En Internet Information Server, que es el servidor que me gusta y que utilizo, basta con añadir esto al archivo web.config de tu aplicación:

<system.webServer><staticContent><mimeMap fileExtension=".avif" mimeType="image/avif" /></staticContent></system.webServer>

y listo. También, claro está, lo puedes añadir globalmente al servidor para que esté soportado por todos los sitios que tengas albergados.

Desventajas de AVIF

Para terminar vamos con las malas noticias. No todo va a ser perfecto 😉

AVIF presenta dos problemas principalmente, aunque solo tienen importancia en ciertos casos:

  • No soporta renderizado progresivo: es decir, al contrario que el JPG progresivo, para que el navegador pueda visualizar una imagen AVIF, primero tiene que descargarla por completo. Con los formatos progresivos es posible obtener una versión de baja resolución antes de descargar la imagen completa, mostrarla y luego seguir descargando, lo que da una sensación de carga más rápida a los usuarios. Con AVIF no es posible.
  • Requiere mucha más potencia de procesador: lo habrás comprobado si has hecho pruebas de codificación con Squoosh. Codificar AVIF lleva mucho más tiempo que hacerlo en JPG, pudiendo tardar varios segundos por imagen. Más importante: decodificar AVIF también requiere más procesador que con JPG (aunque es muy rápido). No es que sea nada apreciable en un ordenador o un móvil moderno, pero en dispositivos muy antiguos sin aceleración por hardware podría ser un problema si hay muchas imágenes (o vídeo codificado con AV1). En la práctica no tendrá demasiada importancia, pero es necesario saberlo.

En resumen

En este artículo hemos dado un repaso a fondo al reciente formado de imagen AVIF. Hemos conocido los formatos que existían antes de este, cuáles son sus ventajas, cómo podemos comparar su rendimiento con el de otros formatos, cómo creamos imágenes AVIF, el soporte de navegadores y servidores y finalmente las desventajas que podríamos tener al usarlo.

Al estar detrás del formato la mayoría de las grandes empresas multimedia y de Internet, y sobre todo Google, la adopción del formato va a ser muy rápida y, como hemos visto, es fácil incluir alternativas para los dispositivos que no lo soporten.

Por todo esto es un formato muy a tener en cuenta y que deberíamos empezar a utilizar en nuestros desarrollos y sitios Web.

¡Espero que te haya resultado útil!

Fundamentos de testing: preguntas y respuestas

$
0
0

Imagen ornamental, un circuito electrónico simple con cables y un medidor, de Nicolas Thomas, CC0 en Unsplash 

Nota: este artículo es una traducción de "Software Testing Fundamentals - Questions and Answers" de Amir Ghahrai, con su permiso expreso. Amir es el fundador de DevQA.io un sitio especializado en ayudar a otros desarrolladores a convertirse en testers técnicos.

Las pruebas de software constituyen una actividad más dentro del proceso de desarrollo de software. Se trata de averiguar si el software ofrece la calidad esperada a las partes interesadas.

¿Qué son las pruebas de software o testing de software?

Diferentes personas han dado diferentes definiciones acerca del testing, pero, en general, su objetivo es:

  • Asegurar que el software cumpla con los requisitos y el diseño acordados.
  • Que la aplicación funcione como se esperaba.
  • Que la aplicación no contenga graves errores.
  • Que cumpla con el uso previsto según las expectativas del usuario.

Las pruebas de software se utilizan a menudo junto con los términos verificación y validación.

  • Validación: ¿estamos haciendo el trabajo correcto?
  • Verificación: ¿estamos haciendo bien el trabajo?

La verificación es la comprobación o test de elementos, incluido el software, para constatar su conformidad y coherencia con una especificación asociada.

La validación es el proceso de comprobar que lo que se ha especificado es lo que realmente quería el usuario.

Las pruebas de software son solo un tipo de verificación, que también utiliza técnicas como revisiones, análisis, inspecciones y recorridos.

¿Qué son las pruebas exploratorias y cuándo deben realizarse?

Una prueba exploratoria consiste en hacer simultáneamente un test de diseño y ejecución de una aplicación. Esto significa que el evaluador (tester) usa su conocimiento y experiencia en pruebas para predecir dónde y bajo qué condiciones el sistema podría comportarse inesperadamente. A medida que el evaluador comienza a explorar el sistema, se le ocurren sobre la marcha nuevas ideas de diseño de prueba y se ejecutan en el software que se está probando.

En una sesión de prueba exploratoria, el evaluador ejecuta una cadena de acciones contra el sistema, cada acción depende del resultado de la acción anterior, por lo tanto, el fruto del resultado de las acciones podría influir en lo que hace el evaluador a continuación, es decir, no hay dos sesiones de prueba idénticas.

Esto contrasta con Scripted Testing, donde las pruebas se diseñan de antemano utilizando los requisitos o los documentos de diseño, generalmente antes de que el sistema esté listo y se ejecutan esos mismos pasos exactos contra el sistema más tarde.

Las pruebas exploratorias generalmente se realizan a medida que el producto evoluciona (ágil) o como una verificación final antes de que se lance el software. Es una actividad complementaria a las pruebas de regresión automatizadas.

¿Qué técnicas de testing existen y cuál es su propósito?

Las técnicas de testing se utilizan principalmente con dos propósitos: para ayudarnos a identificar defectos y para reducir el número de casos a verificar.

  • La partición de equivalencia se utiliza principalmente para reducir el número de casos a verificar identificando diferentes conjuntos de datos que no son iguales y ejecutando solo un test de cada conjunto de datos.
  • El análisis de valor límite se utiliza para verificar el comportamiento del sistema en los límites de los datos permitidos.
  • Las pruebas de transición de estado se utilizan para validar estados permitidos y no permitidos y transiciones de un estado a otro mediante varios datos de entrada.
  • La prueba por pares o todos los pares es una técnica de testing muy poderosa y se utiliza principalmente para reducir el número de casos a verificar mientras aumenta la cobertura de combinaciones de funciones.

¿Por qué es necesario hacer test de software?

Las pruebas son necesarias para identificar cualquier defecto presente en el software que pueda causar algún perjuicio a los usuarios. Sin las pruebas adecuadas, podríamos lanzar un software que podría funcionar mal y causar problemas graves.

Algunos ejemplos son:

  • Software en una máquina de soporte vital que puede causar daños graves a un paciente.
  • El software en una planta nuclear que monitorea la actividad nuclear puede causar daños al medio ambiente.
  • La aplicación bancaria o financiera que calcula los tipos de cambio puede causar pérdidas financieras a una empresa.

Imagen ornamental de una oruga (bug) por Yoal Desurmont en Unsplash, CC0

¿Cuál es la diferencia entre bug, defecto (defect), error (error), falla (failure), fallo (fault), y equivocación (mistake)?

Error y equivocación es lo mismo. Bug, defecto y fallo, también es lo mismo.

En general, una persona comete una equivocación (error) que produce un defecto (bug, fallo) en una aplicación que puede causar una falla (avería).

Los defectos ocurren porque los seres humanos son propensos a equivocarse, además, una aplicación puede ser muy compleja, por lo que la integración de diferentes componentes puede provocar comportamientos extraños.

¿Cuántas pruebas son suficientes?

No hay una respuesta definitiva a esta pregunta. Las pruebas no son absolutas y no tienen límites. Sin embargo, podemos usar métricas de riesgo (test basados en riesgos) para identificar los escenarios probables que pueden causar el mayor daño o las partes del software que se utilizan principalmente, para así concentrar nuestro tiempo y esfuerzo en las partes que son más importantes.

Los test deben proporcionar suficiente información sobre el estado o la salud de una aplicación, para que las partes interesadas puedan tomar una decisión informada sobre si lanzar el software o dedicar más tiempo a las pruebas.

¿Qué es el proceso de prueba fundamental?

Para aprovechar al máximo los test, se debe seguir un proceso definido. Pero antes de que comience cualquier test, se debe emplear gran parte del esfuerzo en crear un buen plan de testing. Un buen plan de testing es de gran ayuda para garantizar que las pruebas se alineen con lo que se pretende lograr.

Esto quizás sea más aplicable a un entorno de pruebas bastante formal (como una misión crítica). La mayoría de las organizaciones comerciales tienen procesos de prueba menos rigurosos. Sin embargo, cualquier esfuerzo de prueba puede utilizar estos pasos de alguna forma.

El proceso de prueba fundamental consta de 5 actividades:

  • Planificación
  • Especificación
  • Ejecución
  • Grabación
  • Comprobación de la finalización del test

El proceso de prueba siempre comienza con la planificación de la prueba y termina con la verificación de la finalización de la prueba.

Todas y cada una de las actividades pueden repetirse (o al menos volver a visitarse) ya que pueden ser necesarias varias iteraciones antes de que se cumplan los criterios de finalización definidos durante la actividad de planificación de pruebas.

Siete principios de testing

A continuación se muestran los siete principios de testing:

1. Los test muestran la presencia de errores

Probar una aplicación solo puede revelar que existen uno o más defectos en la aplicación, sin embargo, el test por sí solo no puede probar que la aplicación esté libre de errores. Por lo tanto, es importante diseñar casos de prueba que encuentren tantos defectos como sea posible.

2. Es imposible hacer test exhaustivos

A menos que la aplicación bajo prueba (AUT – application under test) tenga una estructura lógica muy simple y una entrada limitada, no es posible probar todas las combinaciones posibles de datos y escenarios. Por esta razón, el riesgo y las prioridades se utilizan para concentrarse en los aspectos más importantes a probar.

3. Pruebas tempranas

Cuanto antes comencemos con los test, mejor aprovecharemos el tiempo disponible. Tan pronto como estén disponibles los productos iniciales, tales como los requisitos o los documentos de diseño, podemos comenzar a hacer test. Es común que la fase de prueba se reduzca al final del ciclo de vida del desarrollo, es decir, cuando el desarrollo ha finalizado, por lo que al comenzar las pruebas temprano, podemos preparar las pruebas para cada nivel del ciclo de vida del desarrollo.

Otro punto importante sobre las pruebas tempranas es que, cuando los defectos se encuentran al principio del ciclo de vida, son mucho más fáciles y económicos de corregir. ¡Es mucho más económico cambiar un requisito incorrecto que tener que cambiar una funcionalidad en un sistema grande que no funciona según lo solicitado o diseñado!

4. Agrupación de defectos

Durante las pruebas, se puede observar que la mayoría de los defectos detectados están relacionados con una pequeña cantidad de módulos dentro de un sistema. Es decir, un pequeño número de módulos contiene la mayoría de los defectos del sistema. O sea, podemos aplicar el principio de Pareto a las pruebas de software: aproximadamente el 80% de los problemas se encuentran en el 20% de los módulos.

5. La paradoja de los pesticidas

Si sigues ejecutando el mismo conjunto de pruebas una y otra vez, es probable que en esos test no descubran defectos nuevos. Esto es debido a que a medida que el sistema evoluciona, muchos de los defectos detectados anteriormente se habrán solucionado y los antiguos casos de prueba ya no aplican.

Cada vez que se soluciona un defecto o se agrega una nueva funcionalidad, necesitamos hacer pruebas de regresión para asegurarnos de que el nuevo software modificado no haya roto ninguna otra parte del mismo. Sin embargo, esos casos de prueba de regresión también deben modificarse para reflejar los cambios realizados en el software para que sean aplicables y, con suerte, detectar nuevos defectos.

6. Los test dependen del entorno

Las diferentes metodologías, técnicas y tipos de pruebas están relacionadas con el tipo y la naturaleza de la aplicación. Por ejemplo, una aplicación para un dispositivo médico necesita más pruebas que un software de juegos.

Y lo que es más importante aún, un software de dispositivo médico requiere pruebas basadas en riesgos, cumplir con las regulaciones de la industria médica y posiblemente técnicas de diseño de pruebas específicas.

Del mismo modo, un sitio web muy popular debe pasar por rigurosas pruebas de rendimiento, así como pruebas de funcionalidad para asegurarse de que el rendimiento no se vea afectado por la carga en los servidores.

7. Ausencia de errores: ¡falacia!

Solo porque las pruebas no hayan encontrado ningún defecto en el software, no significa que el software esté listo para entrar en producción. ¿Fueron las pruebas que ejecutamos diseñadas realmente para detectar la mayoría de los defectos? ¿O para ver si el software se ajustaba a los requisitos del usuario? Hay muchos otros factores a considerar antes de tomar la decisión de enviar el software.

Imagen ornamental, caja de color blanco, por Daniele Levis Pelusi, CC0 en Unsplash

¿Qué es el test de la caja blanca?

Las pruebas de caja blanca tratan con la lógica interna y la estructura del código. Las pruebas de caja blanca también se denominan pruebas de cristal, estructurales, de caja abierta o de caja transparente. Los test escritos en base a la estrategia de prueba de caja blanca incorporan medidas como la cobertura del código escrito, de las ramas, las rutas, las declaraciones, la lógica interna del código, etc.

Para implementar las pruebas de caja blanca, el evaluador debe lidiar con el código y, por lo tanto, debe poseer conocimientos de codificación y lógica, es decir, el funcionamiento interno del código. La prueba de caja blanca también necesita que el evaluador busque en el código y descubra qué unidad/declaración/parte del código no funciona correctamente.

Test unitarios

El desarrollador lleva a cabo pruebas unitarias para comprobar si el módulo o la unidad de código en particular está funcionando bien. Los test unitarios se producen a un nivel muy básico, ya que se lleva a cabo a medida que se desarrolla la unidad del código o se construye una funcionalidad en particular.

Análisis estático y dinámico

El análisis estático implica revisar el código para descubrir cualquier posible defecto en el mismo. El análisis dinámico supone ejecutar el código y analizar la salida.

Cobertura de instrucciones

En este tipo de prueba, el código se ejecuta de tal manera que cada instrucción en la aplicación se ejecuta al menos una vez. Ayuda a asegurar que todas las declaraciones se ejecuten sin ningún efecto secundario.

Cobertura de ramas

Ninguna aplicación es posible escribirla de un tirón, en algún momento necesitamos abrir una rama de código para realizar una funcionalidad particular. Las pruebas de cobertura de ramas ayudan a validar todas las ramas en el código y asegurarse de que ninguna ramificación dé lugar a un comportamiento anormal de la aplicación.

Pruebas de seguridad

Las pruebas de seguridad se llevan a cabo con el fin de averiguar cómo de bien el sistema puede protegerse frente a accesos no autorizados, piratería, cualquier daño en el código, etc.. Este tipo de test requiere de técnicas sofisticadas.

Pruebas de cambio

Un tipo de test en el que se prueba la aplicación en busca del código que se modificó después de corregir un error o defecto en particular. También ayuda a descubrir qué código y qué estrategia de codificación pueden ayudar a desarrollar la funcionalidad de manera efectiva.

Ventajas de los test de caja blanca

Como el conocimiento de la estructura de código interna es un requisito previo, resulta muy fácil descubrir qué tipo de entrada / datos pueden ayudar a probar la aplicación de manera eficaz. La otra ventaja de las pruebas de caja blanca es que ayuda a optimizar el código. Ayuda a eliminar las líneas adicionales de código, que pueden provocar defectos ocultos.

Desventajas de los test de caja blanca

Como el conocimiento del código y la estructura interna es un requisito previo, se necesita un evaluador capacitado para llevar a cabo este tipo de pruebas, lo que aumenta el coste. Y es casi imposible examinar cada fragmento de código para descubrir errores ocultos, que pueden crear problemas y provocar fallas en la aplicación.

Imagen ornamental, una caja de color negro intenso sobre fondo negro, por an_vision, CC0 en Unsplash

¿Qué es el test de la caja negra?

En las pruebas de la caja negra, el evaluador prueba una aplicación sin conocer el funcionamiento interno de la misma.

Debido a que las pruebas de caja negra no se relacionan con el código subyacente, las técnicas pueden derivarse de los documentos de requisitos o especificaciones de diseño y, por lo tanto, pueden comenzar tan pronto como se escriban los requisitos.

Técnica de prueba de análisis de valor límite

El análisis de valor límite (BVA – boundary value analysis), prueba el comportamiento de un programa en los límites. Al verificar un rango de valores, después de seleccionar el conjunto de datos que se encuentran en las particiones válidas, lo siguiente es verificar cómo se comporta el programa en los valores límite de las particiones válidas. El análisis de valor límite es más común cuando se verifica un rango de números.

Técnica de transición de estado

La técnica de prueba de transición de estado se utiliza cuando algún aspecto del sistema puede describirse en lo que se denomina una “máquina de estados finitos”. Esto simplemente significa que el sistema puede estar en un número (finito) de estados diferentes, y las transiciones de un estado a otro están determinadas por las reglas de la “máquina”.

En este modelo se basan el sistema y los test. Cualquier sistema en el que obtenga una salida diferente para la misma entrada, dependiendo de lo que haya sucedido antes, es un sistema de estado finito.

Técnica de prueba de partición de equivalencia

La idea detrás de la técnica de prueba de partición de equivalencia es eliminar el conjunto de datos de entrada que hacen que el sistema se comporte de la misma manera y produzca el mismo resultado al probar un programa.

El proceso de la técnica de partición de equivalencia implica identificar el conjunto de datos como una condición de entrada que dan el mismo resultado al ejecutar un programa y clasificarlos como un conjunto de datos equivalentes (porque hacen que el programa se comporte de la misma manera y genere la misma salida) y particionándolos de otro conjunto equivalente de datos.

Ventajas de los test de caja negra

  • La prueba es imparcial porque el diseñador y el evaluador son independientes entre sí.
  • El evaluador no necesita tener conocimientos de ningún lenguaje de programación específico.
  • El test se realiza desde el punto de vista del usuario, no del diseñador.
  • Los casos de prueba se pueden diseñar tan pronto como se completen las especificaciones.

Desventajas de los test de caja negra

  • La prueba puede ser redundante si el diseñador de software ya ha ejecutado un caso de prueba.
  • Los casos de prueba son difíciles de diseñar.
  • Probar cada flujo de entrada posible no es realista porque llevaría una cantidad de tiempo excesiva; por lo tanto, muchas rutas de programas no se verificarán.

 

Cómo es el proceso de extraer conocimiento a partir de bases de datos

$
0
0

Hace unas semanas presentábamos en un artículo previo distintos conceptos relacionados con el campo de la inteligencia artificial (IA), entre los cuales se mencionaba brevemente el término KDD (Knowledge Discovery in Databases o descubrir conocimiento en las bases de datos), que es parte de la ciencia de datos. El objetivo de este artículo es explicar en qué consiste KDD y, al tiempo, cómo se relaciona con otros términos.

Con KDD convertiremos los datos en conocimiento valioso

El acrónimo KDD hace referencia a un proceso, compuesto de múltiples etapas, cuyo objetivo principal es la extracción de conocimiento, que ha de resultar útil y no ser trivial, a partir de los datos a los que se tiene acceso.

Terminología relacionada

El proceso de KDD está estrechamente relacionado con el aprendizaje automático o machine learning (ML) y también con la minería de datos o data mining (DM). Son términos que, en ocasiones, llegan a confundirse y utilizarse de manera indistinta.

Para algunos expertos, KDD y DM son sinónimos, mientras que para otros DM es una de las fases del KDD. Ambos conceptos tienen casi un siglo de existencia y llevan implícita la intervención manual del experto a la hora de preparar los datos en los cuales se espera encontrar el conocimiento al que se hacía mención antes.

En lo que sí hay coincidencia es en que las técnicas de ML son posteriores en el tiempo a KDD y DM, y se centran más en la construcción de los algoritmos que en la preparación de los datos en sí. La finalidad de un algoritmo de ML es, tomando como entrada patrones de datos, generar un modelo que contiene el conocimiento extraído.

Existe un campo completo de estudio relativo a la forma de representar el conocimiento desde una perspectiva abstracta, usando para ello modelos que pueden tomar diferentes formas. Ejemplos de representaciones habituales serían el grafo, con nodos y conexiones entre ellos, y el árbol.

Fases del proceso de KDD

Atendiendo a la visión que fija la minería de datos como parte del KDD, este proceso constaría de las fases que se observan en el siguiente esquema:

Esquema que representa las fases del proceso de KDD

El esquema debe leerse de izquierda a derecha, comenzando por la comprensión del dominio al que corresponden los datos que van a emplearse y la especificación de los objetivos que se persiguen.

Tradicionalmente los datos proceden de sistemas OLTP (On-Line Transaction Processing, procesamiento de transacciones en línea), con bases de datos relacionales diseñadas para el registro continuo de datos más que para su análisis, como por ejemplo una base de datos de gestión, o de ventas en un comercio electrónico. También es habitual contar con datos en otros tipos de recipientes, como pueden ser hojas de cálculo.

La heterogeneidad de formatos, representaciones, unidades de medida y, en general, estructura de los datos, representa un obstáculo en el proceso de extracción de información a partir de los mismos.

La primera etapa del KDD consiste en recopilar los datos de todas esas fuentes y homogeneizarlos e integrarlos, produciendo lo que se conoce como conjunto de datos o dataset. Esta es una base de datos con un formato unificado y, además, con una estructura más adecuada para el análisis de los datos.

Etapas de preprocesamiento

El trabajo de preparación de los datos no se limita a la integración y homogeneización, sino que abarca varias tareas más que suelen incluirse bajo la denominación genérica de preprocesamiento de datos. Entre estas, las más habituales son:

  • Limpieza de los datos: durante la recogida de datos, ya sea esta manual o automática, así como en su posterior codificación y transmisión hasta llegar a formar parte del conjunto de datos, es habitual que se produzcan errores. Estos se traducen normalmente en dos tipos de problemas. El primero viene dado por datos que son claramente erróneos y, por tanto, introducirían ruido en el proceso de extracción de conocimiento en lugar de resultar útiles. El segundo estriba en la pérdida de algunos datos, casuística que dejaría huecos en el conjunto de datos. Las tareas de limpieza de datos, por ejemplo mediante algoritmos de eliminación de ruido o de imputación de valores ausentes, abordarían estos obstáculos.
  • Selección de datos relevantes: no todos los datos obtenidos de las fuentes originales son necesariamente relevantes para el KDD, por lo que la selección de aquellos que realmente son útiles es otro paso más en el preprocesamiento. Dos de las tareas más usuales en esta fase son la selección de variables y la selección de patrones. Esencialmente consisten en eliminar aquellos datos que, por estar repetidos o pueden estimarse a partir de otros, no aportan mejora en la extracción de conocimiento. Estas dos técnicas forman parte de los métodos de reducción de dimensionalidad.
  • Transformación de los datos: una vez que los datos están limpios y no contienen redundancias, aspectos de los que se ocupan las operaciones previas, podría pensarse que ya pueden usarse para el aprendizaje de un modelo. No obstante, hay acciones que podrían mejorar los datos de cara a hacer más efectivo ese aprendizaje. Entre ellos están la normalización, el escalado y la discretización. Se trata de operaciones que transforman los datos originales, casi siempre de manera reversible, produciendo una nueva versión más conveniente para el análisis KDD.

Tareas de preprocesamiento de datos

La fase de minería de los datos

Tras el preprocesamiento, los datos están preparados para la siguiente fase. En esta se usa un algoritmo de minería de datos a fin de extraer de estos el conocimiento que no resulta obvio ni es trivial, aunque esté implícito en ellos.

Existen multitud de métodos que es posible aplicar en este punto, incluyendo diversas técnicas estadísticas, algoritmos matemáticos de optimización y, por supuesto, métodos de Machine Learning.

A diferencia de en los pasos previos, donde se requiere una exploración de los datos que determine qué operaciones es preciso llevar a cabo y la intervención del experto es imprescindible, en la fase de minería de datos es donde suele recurrirse al Machine Learning.

Un algoritmo de aprendizaje automático procesa los datos y lo que genera como salida no son nuevos datos, como es habitual en la mayoría de algoritmos de ordenador, sino un modelo que representa el conocimiento extraído:

Algoritmo de procesamiento de datos vs algoritmo de aprendizaje automático

Este modelo resultante puede tener distintas aplicaciones:

  • Modelos descriptivos: para describir la estructura subyacente de los datos
  • Modelos predictivos: sirviendo para realizar predicciones en el futuro
  • Base de reglas: convirtiéndose en los cimientos de un sistema de apoyo a la toma de decisiones.

Evaluación y vuelta atrás

Como puede apreciarse en el esquema que resume el proceso de KDD, prácticamente todos los pasos cuentan con una conexión hacia atrás que identifica su naturaleza iterativa. Ese retorno a pasos previos tiene múltiples destinos en la fase final de evaluación e interpretación del conocimiento que se ha obtenido.

Dependiendo del tipo de modelo que se haya generado y de cuál sea su finalidad, hay disponibles diferentes conjuntos de métricas que permiten medir cómo de bueno es ese modelo. La mayor parte de esas medidas están acotadas, es decir, facilitan valores entre un intervalo conocido, por lo que es posible determinar si el conocimiento extraído es útil o no de manera inmediata.

Las métricas usadas con modelos ML evalúan su rendimiento en una tarea concreta, por ejemplo a la hora de realizar predicciones, y también facilitan la comparación de diferentes modelos a fin de determinar cuál es mejor.

En función del rendimiento obtenido, y del análisis efectuado para determinar qué problemas plantea el modelo, sería preciso realizar ajustes en el algoritmo de Aprendizaje Automático (ML) o bien introducir cambios en los pasos previos de preprocesamiento y preparación de los datos.

Volumen de trabajo de las fases del KDD

El proceso de KDD, en el que se basan la mayor parte de los proyectos de Inteligencia Artificial que implican aprender a partir de datos existentes y, en consecuencia, el uso de algoritmos de Machine Learning, conlleva un volumen de trabajo considerable. Este, no obstante, no se reparte por igual entre las distintas fases.

Se considera que las fases de preparación y preprocesamiento de los datos consumen entre un 80% y un 90% del tiempo total dedicado a la extracción de conocimiento. Son pasos que precisan una intervención manual del experto y una supervisión minuciosa a la hora de decidir qué operaciones se aplican, según la naturaleza y estructura de los datos. Además, es frecuente tener que volver atrás entre sus etapas, realizando los ajustes apropiados hasta conseguir un conjunto de datos apropiado como entrada para la fase de minería de datos.

El paso en que el que se alimenta el algoritmo de ML con ese conjunto de datos, en parte debido a los enormes avances en potencia de procesamiento, comparativamente suele precisar muy poco tiempo. Lógicamente depende de la cantidad de datos con que se cuente y la complejidad del modelo producido, pero en muchos casos la respuesta se obtiene en pocos minutos.

Esperamos que tras leer este artículo hayas obtenido una visión general sobre qué es el proceso de KDD, cuál es su objetivo y las fases de que consta.

Muchos de los sistemas actuales de IA, aplicados en multitud de escenarios como te contábamos en 5 aplicaciones prácticas inesperadas de la Inteligencia Artificial, están basados en dicho proceso.

 

Cómo convertir imágenes de fondo en monotono con CSS

$
0
0

Ejemplo del resultado de convertir una imagen en monocromo con CSS

En CSS tenemos la posibilidad de usar filtros para alterar imágenes insertadas en el código html sin necesidad de pasar antes por Photoshop u otro editor de fotos.

¿Pero qué pasa con las imágenes de fondo? A las imágenes de fondo no le podemos aplicar filtros directamente, pero tenemos varias alternativas a nuestra disposición.

A ver, se les puede aplicar la propiedad backdrop-filter pero tiene un soporte regulinchis. Por suerte sí que podemos usar background-blend-mode para llegar a un resultado parecido

En este post veremos cómo modificar una imagen de fondo para convertirla en una imagen monotono. ¿Por qué querríamos hacer esto? Pues, por ejemplo, para darle más presencia a un color corporativo en nuestra página.

Partiremos de esto:

Pantallazo de la web de ejemplo con la imagen de fondo a todo color. Imagen de Qingbao Meng en Unsplash CC0

Para llegar a algo parecido a esto:

Pantallazo del ejemplo con la imagen de fondo en monotono azul

Colocamos la imagen de fondo ocupando el 100% de la pantalla

En este caso vamos a colocar la imagen de fondo del bodyocupando la pantalla completa y asegurándonos de que sea responsive.

No es el objetivo principal de post, pero no está de más explicar cómo hacer esto.

El html con el que vamos a trabajar es muy sencillo:

<!DOCTYPE html><html lang="es"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Ejemplos imágenes de fondo monotono - campusMVP.es</title></head><body><main><a href="https://www.campusmvp.es/"><img src="assets/logo-campusmvp.png" alt="Logo de campusMVP.es"/>campusMVP.es</a></main></body></html>

Como elementos html solo usaremos el body, un elemento main y (dentro de este) un enlace a campusMVP con el logo y la url a modo de contenido.

Colocaremos nuestra imagen de fondo del body, centrada y fija. El principal CSS para definir el background-image sería este:

    body {
            background-image: url(assets/foto.jpg);
            background-position:center;
            background-repeat:no-repeat;
            background-size:cover;
            background-attachment: fixed;
        }

La clave está en background-size:cover; y background-attachment: fixed;. Ojo, hay más CSS involucrado que quizá te interese y que podrás consultar en el código del ejemplo que te dejo al final del post, pero estas reglas son las más importantes.

Así que ya tenemos nuestra página inicial:

Imagen inicial de los ejemplos

Ahora lo que queremos es convertir la foto en una imagen monocromática del mismo color (o un tono parecido) que el fondo del logo. Vamos a ver tres formas distintas de hacerlo:

1 - Con un filtro de escala de grises y un color de fondo

¿Pero no decías que no se podían usar filtros? A la imagen de fondo no, pero a su elemento contenedor sí. Quien hace la ley hace la trampa. ¯\_(ツ)_/¯

En honor a la verdad, te diré que este es el método más engorroso de los tres, pero aún así es interesante conocerlo. Esta técnica tiene dos partes: En primer lugar consiste en colocar la imagen de fondo dentro de un pseudoelemento ::before posicionado en absoluto por encima a la vez que le aplicamos un filtro de escala de grises :

    body::before {
        content: " ";
        position: absolute;
        top: 0;
        right:0;
        bottom:0;
        left: 0;
        background-image: url(assets/foto.jpg);
        background-position:center;
        background-repeat:no-repeat;
        background-size:cover;
        background-attachment: fixed;
        filter: grayscale(100%);
    }

    main {
        position: relative;
    }

Y tendríamos esto:

Imagen convertida en escala de grises con CSS

A continuación, modificamos su opacidad para que se mezcle con el color de fondo del body que le dará el monotono:

    body {
        background: rgba(2, 34, 58, 1);
    }

    body::before {
        content: " ";
        position: absolute;
        top: 0;
        right:0;
        bottom:0;
        left: 0;
        background-image: url(assets/foto.jpg);
        background-position:center;
        background-repeat:no-repeat;
        background-size:cover;
        background-attachment: fixed;
        filter: grayscale(100%);
        opacity: 0.4;
    }

    main {
        position: relative;
    }

Por cierto, quizá te habrás fijado en que el elemento main lleva position:relative. Esto es para que se posicione por delante del ::before.

Ojo curvas: Si en vez de trabajar con el body lo haces con otro elemento (por ejemplo, un div), deberás aplicarle también position:relative para definir el contexto de apilamiento sobre el que posicionar el :before. Si esto te suena a chino estúdiate este post, o mejor aún, quizá te vendría bien hacer el curso de HTML y CSS.

El resultado final sería este:

Imagen final del ejemplo 1 en monocromo azul

2 - Con un falso filtro de grises y una sombra interna

Este método ya es más sencillo. Aprovechamos que en CSS3 podemos usar varias imágenes de fondo para convertir la imagen en una falsa escala de grises usando linear-gradient y background-blend-mode: saturation; y después le aplicamos por encima color con una sombra interna usando box-shadow.

La propiedad background-blend-mode funciona de forma muy parecida a los Modos de fusión de capa de Photoshop. A modo de curiosidad, el valor saturation toma la saturación de los elementos que están por encima (la sombra interna) y deja el tono y la luminancia de los que están por debajo (en el background-image).

Este es el CSS

    body {
        background-image: linear-gradient(black, black), url(assets/foto.jpg);
        background-blend-mode: saturation;
        box-shadow: inset 0px 0px 3000px 3000px rgba(2, 34, 58, 0.6);
    } 

Y el resultado es muy similar:

Imagen final del ejemplo 2

3 - Atacando la luminosidad de la foto y un color de fondo

En este caso usamos el modo de fusiónluminosity. Como el fondo de color está por debajo, solo toma de la foto (que está por encima) la luminancia:

    body {
        background-color: rgba(2, 34, 58, 1);
        background-image: url(assets/foto.jpg);
        background-blend-mode: luminosity;
    }

Y así tenemos otra alternativa, aunque en este caso la imagen queda un poco más clara:

Imagen final del ejemplo 3

Tienes los tres ejemplos en un único archivo htm que va dentro de este .zip. Ábrelo con un editor y vete descomentando las reglas CSS de cada ejemplo para ir probándolos (descomenta uno de cada vez, no los mezcles).

¿Te ha parecido interesante? ¿Se te ocurre algún otro método sin tener que recurrir a JavaScript? Cuéntamelo en los comentarios.

Viewing all 776 articles
Browse latest View live