Variable not found

Enlaces interesantes 513

enero 30, 2023 07:05

Enlaces interesantes

Ahí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

Por si te lo perdiste...

.NET Core / .NET

ASP.NET Core / ASP.NET / Blazor

    Conceptos / Patrones / Buenas prácticas

    Data

    Machine learning / IA / Bots

    Web / HTML / CSS / Javascript

    Visual Studio / Complementos / Herramientas

    .NET MAUI / Xamarin

      Publicado en Variable not found.

      » Leer más, comentarios, etc...

      Header Files

      Me gusta el mueve mueve

      enero 29, 2023 10:00

      Introducción

      Cuando se presentó C++11 hace más de 12 años, los amantes de C++ vimos cómo comenzaba una nueva era para el lenguaje, una modernización del mismo, y nos hizo tener que volver a estudiarlo (si es que alguien deja de hacerlo con C++), con ahora clásicos como el “Effective Modern C++” (Scott Meyers).

      C++11 introdujo un montón de nuevas características, tales como templates variádicos, range-for, inicializadores de listas, inferencias de tipos (auto), constante nula real (nullptr), enumeraciones de tipo estricto (enum class), nuevos literales, multitarea (hilos, mutex), static_assert, constexpr, r-values, semántica de movimiento, funciones lambda, herencia de constructores, punteros inteligentes, especificadores de herencia override y final, expresiones regulares, tipos de enteros de tamaño fijo (int32_t, uint8_t, …), generadores de números aleatorios extensibles y type traits, entre tantos otros.

      Como se ve, esta versión trajo multitud de mejoras tanto en su núcleo como en la biblioteca estándar, no sólo poniendo al día al lenguaje sino sentando las bases para futuras actualizaciones, que no ha parado desde entonces (se presentan nuevas versiones cada 3 años: C++14, C++17, C++20 y próximamente C++23).

      Volviendo a la lista anterior, de entre todas las incorporaciones, una de las menos entendidas es la semántica de movimiento, no por su complejidad sino por confusión que genera, especialmente en los que recién comienzan a usar el C++ moderno. Veamos un poco de qué va eso del move.

      Referencias rvalue

      Primero decir que un lvalue es una expresión con nombre, a la que se le puede asignar un valor. Se llaman así porque suelen aparecer a la izquierda (left) de una asignación. Así, tenemos además referencias a lvalue (T&) y referencias constantes a lvalue (const T&, o T const& para los east-const).

      Por el contrario, un rvalue es un temporal, un sin nombre, al que no se le puede asignar un valor. Lo que C++11 introduce entonces es el concepto de referencia a rvalue, con la sintaxis T&&. El punto central de todo esto está en que una referencia a rvalue puede ser modificada, sólo que como lo que se modifica es un rvalue, es decir, un temporal, podemos aprovecharnos de eso para hacer grandes optimizaciones.

      Ejemplos

      Expresión Tipo
      a=1 a es lvalue, 1 es una constante
      a=b a y b son lvalue
      foo() El objeto devuelto por foo() es un rvalue
      a+b r-value

      std::move

      Antes de proseguir, es importante comentar el segundo caso, donde aunque b está a la “derecha” de la igualdad, no es un rvalue, ya que (digamos) no es un temporal.

      Con la función std::move podemos convertir una referencia a lvalue en una referencia a rvalue (si la referencia ya es a rvalue, no hay cambios). Nótese que esto no es más que una forma de forzar tipos de cara al compilador: std::move no tiene coste alguno a nivel de ejecución. De hecho, veremos, citando a Mayers, que std::move no mueve nada.

      Constructores de movimiento

      Así como en C++03 teníamos el constructor de copia (que recibe una referencia constante a lvalue, const T&), en C++11 se introduce el constructor de movimiento, que recibe una referencia a rvalue (T&&).

      Así, una expresión como

      std::string foo() { return "foo"; }
      
      std::string bar{foo()};
      

      llamaría al constructor de movimiento en lugar del de copia, porque foo() se interpreta como una referencia a rvalue.

      Lo anterior parece una tontería, pero permite construir un objeto sacando partido de que sabemos que el argumento que recibmos es un temporal. Un ejemplo típico es el de los contenedores:

      Tomemos como ejemplo a std::vector. Un constructor de copia tradicional (C++03) debería reservar por lo menos la misma cantidad de memoria que el vector de origen, y posteriormente copiar todos los elementos. Puede verse que ésta es una operación que tiene un coste, y dependiendo del tamaño del contenedor, éste puede ser alto. Si a esto añadimos que el argumento es un objeto temporal, adicionalmente a lo anterior tenemos que sumar el destructor del objeto temporal y el hecho de que durante un tiempo hemos duplicado el consumo de memoria de esa función. Por ejemplo

      template<class T>
      class MyVector {
          T* m_data{nullptr};
          size_t m_size{0};
      
      public:
          ~MyVector() {
              delete[] m_data;
          }
      
          explicit MyVector(const MyVector& o) {
              if (o.m_size > 0) {
                  try {
                      m_data = new T[o.m_size];
                      m_size = o.m_size;
                      for (size_t ii = 0; ii < m_size; ++ii) {
                          m_data[ii] = o.m_data[ii];
                      }
                  } catch (...) {
                      delete[] m_data;
                      m_size = 0;
                  }
              }
          }
      };
      

      Un constructor de movimiento sabría que el objeto que recibe será destruido inmediatamente después, por lo que podría, en lugar de reservar un nuevo bloque de memoria y copiar los elementos, simplemente intercambiar el puntero del nuevo objeto con el del temporal. Esto convierte una operación de orden lineal a una de orden constante (el sueño de todo optimizador). Además, el destructor del temporal sería una operación muy simple, ya que llamaría a un delete[] nullptr, que como sabemos no hace nada (y es legal, para los que no lo supiesen). Nuestro ejemplo anterior podría lucir así después de añadir un constructor de movimiento trivial:

      template<class T>
      class MyVector {
      public:
          // ...
          explicit MyVector(MyVector&& o) {
              std::swap(o.m_data, m_data);
              std::swap(o.m_size, m_size);
          }
      };
      

      Nótese el uso de std::swap; esto es debido a que el objeto pasado como referencia a rvalue aún existe y debe ser destruido al finalizar su tiempo de vida, por lo que si simplemente copiamos el puntero en o.m_data nos quedaríamos con un dangling pointer que llevaría a una violación de segmento al primer intento de acceso. No, debemos asegurarnos que el rvalue queda en un estado consistente y que su destrucción no afecte al objeto construido con él.

      Como podemos imaginar de todo lo anterior, la diferencia de rendimiento es enorme, tal y como ejemplifica este benchmarking donde se compara la copia y el movimiento de un std::vector de 100.000 enteros (adjunto el código resumido):

      constexpr size_t N{100000};
      
      void CopyVector() {
          std::vector<int> v(N);
      
          auto w = v;
      }
      
      void MoveVector() {
          std::vector<int> v(N);
      
          auto w = std::move(v);
      }
      

      copy-vs-move-vector

      Pero es que además hay algo aún mejor: todos los contenedores de C++11 han sido optimizados para sacar partido de la semántica de movimiento, por lo que solamente con actualizar a C++ moderno y recompilar es suficiente para aprovecharse de esta nueva optimización allá donde sea posible.

      Para terminar esta sección, comentar de pasada que todo esto aplica además al operador de asignación, que desde C++11 tiene una nueva sobrecarga para aceptar referencias a rvalues:

      T& T::operator=(T&& rhs) { ... }
      

      No es oro todo lo que reluce…

      …ni más rápido todo lo que pasa por std::move; y es que esta función realmente no mueve nada (S. Mayers), solamente indica que se puede usar la semántica de movimiento, pero si dicha semántica no está implementada, o no puede sacar partido de las condiciones que rodean a ese rvalue, pues no obtendremos ventaja alguna.

      Vimos antes que uno de los grandes beneficiados de la semántica de movimiento es la inicialización (o asignación) de contenedores a partir de referencias a rvalues, ya que podían sustituir una nueva reserva de memoria y la consiguiente copia (lineal), por un simple intercambio de valores.

      De hecho, y esta es una pregunta que suelo realizar a muchos candidatos, si tuviésemos una estructura con 400 floats y añadiésemos un constructor de movimiento como el anterior, primero, no estaríamos mejorando nada, y segundo, ¡lo estaríamos incluso empeorando!: un constructor de copia realizaría 400 asignaciones, pero el de movimiento… ¡haría 1.200 (3 por cada swap)!

      La semántica de movimiento sólo ayuda cuando somos capaces de ahorrar trabajo basándonos en el hecho de que el argumento va a ser destruido en cuanto acabe la operación. Si esto no nos aporta ninguna ventaja, entonces no ganamos nada.

      Regla general

      El movimiento de tipos básicos o de composiciones de los mismos no aporta ninguna ventaja frente a la copia.

      Ahora bien, la presencia de punteros (incluyendo punteros inteligentes), es un claro indicador de que podríamos mejorar el rendimiento mediante la semántica de movimiento, si bien no reduciendo la complejidad algorítmica del mismo (como con los contenedores), al menos evitando las llamadas al sistema para reservar recursos.

      Otros usos de la semántica de movimiento

      Además de permitir optimizaciones, la semántica de movimiento juega un papel muy importante en la definición de tipos de datos no copiables. Pondré tres ejemplos tomados de C++11: std::thread, std::mutex y std::unique_ptr. Dado el objetivo de cada una de estas clases, la copia no tiene ningún sentido y, por ende, no debe estar permitida. ¿Qué es copiar un hilo: arrancar uno nuevo, copiar el estado actual? ¿Tiene sentido copiar un mutex que está garantizando un acceso exclusivo a un recurso? ¿No es contraditorio permit tener más de una copia de un objeto puntero único?

      Por otro lado, debemos tener alguna forma en la que dichos objetos puedan ser trasladados de un lugar a otro (por ejemplo, como retorno de una función). Es acá donde la semántica de movimiento entra en juego proporcionando las condiciones para garantizar que los datos de estos objetos no se copian sino que se mueven de un objeto a otro.

      Copy elision

      No tiene una relación directa con la semántica de movimiento, pero se confunde con ésta alguna veces. El copy elision es una optimización que permite construir un objeto directamente en la dirección de memoria final de una expresión, omitiendo los constructores de copia intermedios. Por ejemplo, en:

      T foo() { return T{}; }
      
      T bar = T{T{T{foo()}}};
      

      sólo se llamaría una vez al constructor por defecto, y directamente sobre la dirección de memoria de bar, en lugar de la cadena de constructores de copia (o movimiento) y destructores.

      Es una optimización muy usada y, de hecho, es la única que viola la regla de as-if (se aplicar la optimización aunque el constructor de copia o movimiento que se omiten tiene efectos secundarios).

      Existen otras variantes, el RVO (Return Value Optimization) y NRVO (Named Return Value Optimization). La primera está garantizada (si se dan las condiicones el compilador no la puede obviar) desde C++17. Para más información sugiero consultar cppreference y algún hilo en Stack Overflow.

      Conclusión

      La introducción de las referencias a rvalues es una de las principales mejoras introducidas en C++11 ya que asienta las bases para un nuevo tipo de optimizaciones de gran calado, así como la introducción de tipos de datos no-copiables fundamentales.

      En este artículo hemos repasado brevemente su sintaxis y su impacto en el código, así como señalado las situaciones en las cuales no aporta mejora alguna, y en qué lo diferencia de algunas optimizaciones del compilador.

      » Leer más, comentarios, etc...

      Blog Bitix

      Los juegos de Epic Games y GOG en GNU/Linux con el lanzador Heroic Games Laucher

      enero 26, 2023 05:00

      Que aún GNU/Linux es un sistema minoritario y lo seguirá siendo es una realidad pero que en GNU/Linux no se puede jugar a los mismos juegos de Windows es ya un mito. El cliente de Steam para GNU/Linux da acceso al amplio catálogo de juegos de esta tienda donde la mayoría de juegos se puede jugar. En este artículo muestro como instalar y usar el cliente Heroic Games Launcher para las tiendas Epic Games y GOG que combinado con los juegos que regala semanalmente la Epic Games da acceso a un buen catálogo de juegos.

      » Leer más, comentarios, etc...

      Blog Bitix

      Los juegos de Epic Games en GNU/Linux con el lanzador Heroic Games Laucher

      enero 26, 2023 05:00

      Que aún GNU/Linux es un sistema minoritario y lo seguirá siendo es una realidad pero que en GNU/Linux no se puede jugar a los mismos juegos de Windows es ya un mito. El cliente de Steam para GNU/Linux da acceso al amplio catálogo de juegos de esta tienda donde la mayoría de juegos se puede jugar. En este artículo muestro como instalar y usar el cliente Heroic Games Launcher para las tiendas Epic Games y GOG que combinado con los juegos que regala semanalmente la Epic Games da acceso a un buen catálogo de juegos.

      » Leer más, comentarios, etc...

      Variable not found

      Enlaces interesantes 2^9

      enero 23, 2023 07:05

      Enlaces interesantes

      Ahí van los enlaces recopilados durante una griposa y febril semana pasada. Así que espero que os resulten especialmente interesantes. ;-)

      Por si te lo perdiste...

      .NET Core / .NET

      ASP.NET Core / ASP.NET / Blazor

      Azure / Cloud

      Conceptos / Patrones / Buenas prácticas

      Data

      Machine learning / IA / Bots

      Web / HTML / CSS / Javascript

      Visual Studio / Complementos / Herramientas

      .NET MAUI / Xamarin

        Publicado en Variable not found.

        » Leer más, comentarios, etc...

        Picando Código

        Nueva versión de la gema para TheGamesDB

        enero 20, 2023 10:55

        Esta semana actualicé mi gema Ruby gamesdb a la versión 2.1.0. Incluye cambios de mantenimiento, como asegurar compatibilidad con las versiones nuevas de Ruby (3.2) y actualizaciones en los tests. Refactoricé un poco el código extrayendo varias funciones utilitarias de manejo de imágenes y más a su propio módulo Gamesdb::Utils.

        Ruby: GamesDB 2.1

        Manejo de errores

        La nueva funcionalidad más importante es el manejo de errores. Anteriormente, en caso de error de conexión o en el pedido HTTP, el código levantaba la excepción directamente de Net::HTTP o cualquier otro error en la función principal perform_request. Pero si había un error desde la API (401, 403, etc.), el código no lo trataba como error e intentaba procesar la respuesta como si el pedido hubiese sido correcto. Ahora me fijo el código devuelto por la respuesta HTTP y levanto una excepción del tipo Gamesdb::Error, para que sea más fácil corregir errores y capturar excepciones de manera más granular.

        $ rake console
        3.1.2 :001 > client = Gamesdb::Client.new('test')
         => # 
        3.1.2 :002 > client.platforms
        /home/fernando/workspace/picandocodigo/gamesdb/lib/thegamesdb.rb:66:in `http_error': Gamesdb: 503 - Service Unavailable (Gamesdb::Error)
                from /home/fernando/workspace/picandocodigo/gamesdb/lib/thegamesdb.rb:47:in `perform_request'
        

        Escribiendo esta entrada empecé a probar cosas y terminé actualizando un poco el código porque el sitio web de TheGamesDB está devolviendo un error 503 🫤
        Así que ahora también capturo errores del estilo, se puede ver en el código de ejemplo que agregué a esta entrada, y también en los tests que están fallando en GitHub Actions con un error 503.

        Cuando vuelva a estar en línea el sitio web de TheGamesDB, ejecuto el rake release de la versión 2.1.1 con las últimas actualizaciones en el manejo de errores.
        Actualización: El sitio de TheGamesDB está de vuelta. Pude correr los tests y que todo pasara, así que ya está disponible la versión 2.1.1.

        No sé qué tanto uso tiene la gema, pero con sólo 3 días de publicada ya lleva más de 100 descargas. Hay que tener en cuenta que varias descargas de Rubygems van a ser de bots, pero con suerte alguien más la está encontrando útil. Todavía no he publicado el proyecto original para el cual desarrollé la gema. Estaba hosteado en el servicio gratuito de Heroku, así que ya ni siquiera está online. Pero con suerte algún día me voy a poner a terminar ese proyecto Rails y hacerlo público. Mientras tanto, seguiré manteniendo también la gema gamesdb.

        El post Nueva versión de la gema para TheGamesDB fue publicado originalmente en Picando Código.

        » Leer más, comentarios, etc...

        Blog Bitix

        Construir, desplegar y observar aplicaciones de forma uniforme con Waypoint

        enero 19, 2023 07:00

        Cuando se tienen muchas aplicaciones si no se tienen unos procesos definidos seguramente cada una requerirá su propio proceso, herramientas de construcción y despliegue. Estas diferencias añaden complejidad y no es deseable para tener un flujo de trabajo ágil. La herramienta Waypoint de HashiCorp permite construir una aplicación independientemente del lenguaje en el que esté implementada, desplegar en diferentes proveedores de computación en la nube y observar las aplicaciones una vez desplegadas, todo con la misma herramienta y de forma uniforme que simplifica en gran medida el flujo de desarrollo.

        » Leer más, comentarios, etc...

        Bitácora de Javier Gutiérrez Chamorro (Guti)

        C++ Builder. La elección de FileOptimizer

        enero 18, 2023 03:11

        Embarcadero organizó el concurso Enterprise Software Development Article Challenge en el que se premia a aquel texto que describa el desarrollo de cualquier herramienta profesional que utilice C++ Builder o Delphi.

        Por supuesto decidí participar, presentando mi FileOptimizer, y me permito a continuación daros una traducción mezcla de manual y automática para que podáis leerlo en español. Sin embargo el jurado valorará la cantidad de tráfico dirigido a la entrada, así como la calidad de la misma, de modo que si os apetece apoyarla, no tenéis más que pinchar en este enlace:

        C++ Builder. The Choice For FileOptimizer by Javier Gutiérrez Chamorro

        Introdución

        Soy Javier Gutiérrez Chamorro, el fundador y desarrollador principal del galardonado FileOptimizer. Esto no es algo que diga yo, sino que viene respaldado por el apoyo de entidades como Sourceforge que lo premiaron en varias ocasiones como proyecto de la semana y del mes, y por supuesto, Embarcadero, que lo reconoció en el «Building Good with C++ Builder Contest 2021«.

        Para comprender el contexto, lo primero es lo primero. Soy un profesional involucrado en tecnología desde hace casi 25 años, pero no estoy involucrado con Delphi ni C++ Builder desde hace más de una década. Pertenezco a la industria de Javascript, HTML, PHP y las toneladas de bibliotecas, frameworks y toolchains asociados. Como suele ocurrir, durante mi trabajo habitual me surgió una necesidad, un programa que me permitiera optimizar fácilmente las imágenes que se cargarían en webs y en aplicaciones iOS con el fin de reducir su tamaño, y así mejorar la velocidad de carga y de renderizado.

        FileOptimizer es esencialmente una interfaz gráfica de usuario (GUI) para herramientas por línea de comandos, aplicaciones muy potentes pero a su vez difíciles de utilizar y que son las que se encargan de realizar el trabajo duro, la compresión sin pérdida de calidad. No importa si se trata de una imagen PNG, un vídeo MP4, un folleto PDF o un documento de Word, FileOptimizer lo reconocerá y encargará el trabajo a plugin apropiado para optimizarlo. ¿Cuál es el punto? Bueno, que conseguirás archivos que se ven exactamente igual que el original, que usan exactamente el mismo formato y que se pueden abrir con las mismas herramientas que el original; con la única diferencia que serán mucho más pequeños.

        Desarrollo

        Los archivos pequeños eran un punto crucial en la década de 1980, cuando la memoria y el espacio de almacenamiento en disco eran escasos. Hoy en día con gigabytes de RAM y terabytes de disco puede sonar como algo del pasado, pero no lo es. Muchos dispositivos tienen capacidades de hardware limitadas (teléfonos inteligentes, tabletas, …) y necesitas sacar el máximo provecho de ellas; y en otros casos, el ancho de banda es la limitación. El ancho de banda es caro, y estoy seguro de que todos preferiríais colocar una imagen de 100 K en la web en lugar de esa misma imagen pesando 1.000 K. Sé que hay formatos modernos que reducen el tamaño del archivo a medida que los en comparación con los existentes, solo echa un vistazo a H265 para vídeos o WebP para imágenes. Desafortunadamente, esos nuevos formatos también requieren nuevas herramientas. Nuevos programas para editarlos o convertirlos, nuevas apps para visualizarlos… Así que la necesidad de reducir el tamaño de un archivo, pero manteniendo el formato original sigue tan presente como en los 80.

        Siempre he sido usuario de la plataforma Windows, al menos después de que DOS y los viejos tiempos de Turbo Pascal y Turbo C++ eclipsaran. Realmente creo en las ventajas de las aplicaciones nativas, los binarios compactos y la velocidad de ejecución rápida. La filosofía de la vieja escuela de aprovechar al máximo el hardware. Cuando comencé FileOptimizer en 2012 no había tantas opciones, por lo que ir con Delphi o C++ Builder estaba claro. Personalmente, siempre me sentí más cómodo con C++ que con Object Pascal: el rendimiento de la programación de sistema a bajo nivel cuando es necesario, el mejor rendimiento en tiempo de ejecución y, especialmente, su capacidad para desarrollar cerca del hardware o a un nivel superior gracias a la encapsulación VCL.

        Llevo usando C++ Builder desde su lanzamiento con la versión 1.0, tanto para desarrollos de hobby como profesionales. Me gusta mucho su enfoque de tener un verdadero entorno RAD con la potencia de C++, algo que Visual C++ aún no ha logrado, al igual que Power++/Optima++ no lo hizo en el pasado. Mi elección estaba clara, lo haría con Embarcadero C++ Builder.

        La magia del desarrollo rápido de aplicaciones me permitió tener un prototipo funcional en solo unos días. Por supuesto a medida que pasaba el tiempo, y desde entonces he lanzado más de 50 versiones de FileOptimizer, fui agregando más y más características, pero lo importante es que conseguí un producto mínimo viable en tiempo récord.

        La experiencia siempre te enseña, al menos si quieres aprender. Lo que yo aprendí fue a reducir las dependencias al mínimo. Proyectos anteriores como XPlorer usaban muchos componentes de terceros, una clara ventaja para evitar tener que reinventar la rueda, pero también un problema a la hora de dar soporte a un nuevo sistema operativo o al actualizar la versión de C++ Builder. Esa fue la razón por la que FileOptimizer sólo se basa en componentes VCL tal cual, de sistema y por supuesto también en la API nativa de Windows.

        El tiempo confirmó que aquello fue una buena decisión, según el registro de cambios, comencé FileOptimizer con C++ Builder XE2, y ahora va ya por C++ Builder 11.2 y ha sido probado con la beta de Malawi. Una década de historia es muchísimo tiempo en la vida del software, logrando convertirlo en obsoleto, en inútil o reemplazado. Vérsión tras versión de C++ Builder y de Windows pude irlo actualizando, y también aprovechando algunas nuevas características en cuanto aparecieron: TaskDialogs en Windows Vista, compatibilidad Wine en Linux… Todo ello sin romper la compatibilidad con versiones antiguas gracias a la carga dinámica de bibliotecas.

        Mientras que C++ es un lenguaje de programación eficiente, C++ Builder es un entorno productivo para él. Fácil de depurar gracias a su Debugger integrado, escritura de código rápida concon CodeInsight, un potente editor y un estupendo diseñador visual que permite crear diseños complejos sin escribir ni una línea de código.

        Sin embargo, las nuevas características hacen crecer exponencialmente el código, aumentaron su complejidad hasta aproximadamente las 10,000 LOC (líneas de código), todo al tiempo que había que dar soporta a las más de 400 extensiones de archivo que se soporta actualmente, y a la gestión de nada menos que 100 programas de línea de comandos diferentes, ¡cada uno con su propia sintaxis!

        En los tiempos actuales que todo es inteligente, FileOptimizer también. Por ejemplo puede detectar tipos de archivo analizando su contenido interno, sin necesidad de basarse en la extensión. Así por ejemplo si un imagen GIF esta guardada como MiImagen.foto, la lógica en FileOptimizer determinará de forma transparente para el usuario que realmente se trata de un GIF. El sistema es sencillo y conocido durante años, un motor de detección basado en los «magic bytes» de las caceberas que usan los formatos de archivo más habituales y que gracias a las facilidades que ofrece de C++ al manejar bytes y bloques de memoria se simplifica mucho. No está de más recordar que uno de los objetivos de diseño del lenguaje C/C++ fue precisamente ese.

        Conclusión

        Dudo mucho que el éxito y la longevidad de FileOptimizer hubieran sido posibles sin algo como C++ Builder. En primer lugar, tenemos la continua evolución del lenguaje C++ y del entorno C++ Builder. Imaginad por un momento que lo hubiera creado con Visual Basic 6, el clásico, una herramienta que lleva años sin soporte real y siendo defacto incompatible.

        El segundo argumento es la madurez, C++ Builder es una solución estable con un paradigma bien conocido y documentado que hace que sea rápido de aprender y fácil de dominar.

        Finalmente, aunque menos habitual hoy en día, está su compatibilidad y rendimiento. Una herramienta que es capaz de funcionar incluso en una máquina Windows XP con 256 MB de memoria, pero que sacará todo el partido de un Windows 11 de última generación con 64 GB de memoria.

        » Leer más, comentarios, etc...

        Variable not found

        El filtro [OutputCache], por fin de vuelta en ASP.NET Core 7

        enero 17, 2023 07:05

        ASP.NET Core

        Desde la llegada de ASP.NET Core, hace ya algunos años, muchos hemos echado de menos el filtro [OutputCache] de ASP.NET MVC "clásico". Aunque el nuevo framework ofrece alternativas interesantes para gestionar la caché tanto en el lado cliente como en el servidor, ninguna aportaba las funcionalidades que este filtro nos ofrecía.

        Como recordaréis, a diferencia de las opciones ofrecidas actualmente por ASP.NET Core, como el filtro [ResponseCache] o el middleware ResponseCaching, que básicamente se regían por los encabezados presentes en peticiones y respuestas HTTP, el filtro [OutputCache] es una solución de caché residente exclusivamente en el servidor. En este caso, la decisión de si el resultado a una petición se almacena o no se realiza completamente desde la aplicación, de forma totalmente independiente a encabezados o requisitos procedentes del lado cliente.

        En ASP.NET Core 7 este filtro ha vuelto a la vida en forma de middleware, que ofrece sus funcionalidades con dos sabores distintos:

        • Con anotaciones aplicables a endpoints implementados con Minimal API.
        • Como filtro, aplicable a controladores y acciones MVC.

        Echémosles un vistazo.

        El middleware OutputCache

        La utilización del middleware OutputCacheMiddleware requiere que registremos sus servicios en el inyector de dependencias:

        var builder = WebApplication.CreateBuilder(args);
        builder.services.AddOutputCaching();
        ...

        Tras ello, podemos insertar el middleware en el pipeline usando el típico extensor:

        var app = builder.Build();
        app.UseOutputCache();
        ...

        Ojo: ¡el orden importa! UseOutputCache() debe usarse después de UseCors() y UseRouting() (si existen), para que funcione correctamente.

        Una vez posicionado en el pipeline el middleware examinará cada petición entrante y determinará si ésta puede ser resuelta directamente desde el contenido almacenado en caché.

        • En caso afirmativo, retornará el contenido directamente al lado cliente sin llegar a ejecutar el handler que debería procesarla.
        • En caso negativo, el middleware delegará la petición al manejador correspondiente, que, además de generar la respuesta, tendrá la capacidad de decidir si la misma debe ser almacenada en caché o no y de qué forma.

        Cacheo de resultados en endpoints minimal API

        En endpoints definidos mediante minimal APIs, podemos utilizar el extensor CacheOutput() para indicar la política de cacheado a aplicar en ese punto.

        El siguiente ejemplo usa la configuración por defecto para cachear durante un minuto el resultado de las llamadas a la raíz de la aplicación web:

        var builder = WebApplication.CreateBuilder(args);
        builder.Services.AddOutputCache();

        var app = builder.Build();
        app.UseOutputCache();

        app.MapGet("/", () => DateTime.Now)
        .CacheOutput());

        app.Run();

        El extensor CacheOutput() dispone de varias sobrecargas, mediante las cuales podemos ser más específicos a la hora de configurar el cacheado de los resultados. La más sencilla consiste en usar un delegado de tipo OutputCachePolicyBuilder en cuyo interior podremos configurar el comportamiento del caché usando distintos métodos de utilidad.

        Por ejemplo, en el siguiente código podemos ver el uso de Expire() para indicar que el resultado del endpoint debe ser cacheado durante cinco segundos:

        app.MapGet("/5secs", () => DateTime.Now)
        .CacheOutput(p => p.Expire(TimeSpan.FromSeconds(5)));

        O podemos configurar algo más complejo, como en el siguiente ejemplo donde, además de especificar el tiempo de expiración, indicamos que la caché debe tener en cuenta el valor de un parámetro determinado de la query string y de un encabezado, con objeto de que no se mezclen las respuestas entre distintos usuarios y distintas búsquedas:

        app.MapGet("/search", (string q) => {
        // Retornar resultados de búsqueda
        }).CacheOutput(p => p
        .Expire(TimeSpan.FromSeconds(5))
        .SetVaryByHeader("authorization")
        .SetVaryByQuery("q")
        );

        Cacheo de resultados de acciones MVC

        Aquí es donde realmente estaremos ante la reencarnación del clásico filtro [OutputCache], pues podremos utilizarlo poco más o menos como hacíamos en versiones anteriores de MVC.

        En MVC, basta con aplicar este filtro a acciones (o controladores, si queremos que se vean afectadas todas sus acciones) indicando las políticas de cacheo a aplicar mediante sus parámetros. El ejemplo más simple sería el siguiente, donde se cacheará el resultado de la acción Index() durante el tiempo por defecto de un minuto:

        public class HomeController : Controller
        {
        [OutputCache]
        public DateTime Index()
        {
        return DateTime.Now;
        }
        }

        Para cambiar el tiempo de expiración, podemos usar el parámetro Duration, donde indicaremos el número de segundos que queremos mantener el resultado de la acción en caché:

        [OutputCache(Duration = 5)]
        [HttpGet("/5secs")]
        public DateTime FiveSecs()
        {
        return DateTime.Now;
        }

        Y de la misma forma que vimos antes, podemos usar parámetros como

        [OutputCache(Duration = 5, VaryByQueryKeys = new [] {"query"}, VaryByHeaderNames = new[] {"authorization"})]
        public IActionResult Search(string query)
        {
        // Retornar resultados de búsqueda
        }

        Hay mucho más que contar sobre el nuevo output caching, como el uso de políticas para simplificar las configuraciones, la posibilidad de usar orígenes distintos a la memoria para almacenar los resultados (p.e. Redis), o el bloqueo del "efecto estampida" pero eso lo dejaremos para ocasiones posteriores ;)

        Publicado en Variable not found.

        » Leer más, comentarios, etc...

        Variable not found

        Enlaces interesantes 511

        enero 16, 2023 07:05

        Enlaces interesantes

        Ahí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

        Por si te lo perdiste...

        .NET Core / .NET

        ASP.NET Core / ASP.NET / Blazor

        Azure / Cloud

        Conceptos / Patrones / Buenas prácticas

        Data

        Machine learning / IA / Bots

        Web / HTML / CSS / Javascript

        Visual Studio / Complementos / Herramientas

        .NET MAUI / Xamarin

        Publicado en Variable not found.

        » Leer más, comentarios, etc...

        Blog Bitix

        Construir la imagen del contenedor de la aplicación usando Buildpacks

        enero 11, 2023 07:00

        Los sistemas y servicios son significativamente complejos por sí mismos, además en sistemas que se componen de varios de ellos cada uno con sus diferencias añade más complejidad al sistema. Eliminar toda la complejidad posible y simplificar el sistema es algo deseable. La herramienta Buildpacks aplica a la construcción de las aplicaciones lo que los contenedores aplican en tiempo de ejecución de las mismas, uniformizando las aplicaciones independientemente del lenguaje y plataforma que usen.

        » Leer más, comentarios, etc...

        Blog Bitix

        Construir imágenes de contenedor de aplicaciones usando Buildpacks

        enero 11, 2023 07:00

        Los sistemas y servicios son significativamente complejos por sí mismos, además en sistemas que se componen de varios de ellos cada uno con sus diferencias añade más complejidad al sistema. Eliminar toda la complejidad posible y simplificar el sistema es algo deseable. La herramienta Buildpacks aplica a la construcción de las aplicaciones lo que los contenedores aplican en tiempo de ejecución de las mismas, uniformizando las aplicaciones independientemente del lenguaje y plataforma que usen.

        Buildpacks

        Packeto

        Los contenedores son una gran tecnología ya que hace muy simple ejecutar servicios independientemente de cómo están implementados y en qué lenguaje, uniformizan los procesos a ejecutar y tratarlos a todos de la misma forma y con la misma herramienta.

        Al igual que en los barcos los contenedores facilitan transportar mercancías, en el ámbito de la tecnología los contenedores simplifican mucho la infraestructura en tiempo de ejecución permitiendo tratar los servicios de forma uniforme.

        Los contendores se basan en una imagen y una herramienta que los ejecuta, una de las herramientas más conocidas para ejecutar contenedores es Docker y el archivo con las instrucciones para construir las imágenes son los archivos Dockerfile.

        Es muy útil poder ejecutar procesos de forma uniforme independientemente del lenguaje y de forma aislada con todas las dependencias del entorno dentro de la imagen lo que permite ejecutar tantos servicios como se deseen sin que las dependencias de estos entre en conflicto.

        La misma utilidad y propiedades de los contenedores en tiempo de ejecución es deseable en el momento de construcción de las imágenes de los servicios.

        Contenido del artículo

        La herramienta Buildpacks

        La herramienta Buildpacks analiza el código fuente y permite construir imágenes de contenedores compatible con OCI con la misma herramienta independientemente del lenguaje o plataforma que utilice y sin utilizar archivos Dockerfile. Además, permite reutilizar las instrucciones de construcción sin tener que implementarlas.

        Los buildpacks son los módulos que añaden el soporte e implementan las instrucciones para construir las imágenes. Hay un buildpack específico según una necesidad de construcción, una aplicación puede necesitar varios buildpacks al mismo tiempo.

        Por ejemplo, una aplicación Java necesita una versión de la JVM para ejecutarse que es proporcionado por un buildpack, si la aplicación utiliza Gradle como herramienta de construcción hay un nuildpack para construir aplicaciones Gradle. Y hay buildpacks para Java, Node, Python y otros lenguajes.

        Los conceptos que usa Buildpacks son:

        • Builder: es la imagen del contenedor con la que se realiza la construcción, contiene todos los componentes necesarios junto con los buildpacks para ejecutar la construcción.
        • Buildpack: es el ejecutable que construye y ejecuta la aplicación. Al realizar la construcción contribuye los archivos necesarios a la imagen del contenedor resultante.
        • Lifecycle: orquesta la construcción con los buildpacks y ensambla los artefactos resultantes en la imagen del contenedor.
        • Stack: se compone de dos imágenes de contenedor, la imagen builder que proporciona la imagen para realizar la construcción y la imagen run que proporciona la imagen para la ejecución.

        Buildpacks es extensible y es posible crear y usar builders y buildpaks propios ajustados a las necesidades de la organización o aplicación si los existentes no son suficientes.

        Buildpack Buildpack builder

        Buildpack

        El proyecto Paketo

        Paketo es un proyecto de código abierto que proporciona numerosos buildpacks para diferentes lenguajes de las aplicaciones más populares entre los que por supuesto están Java, Node, Python y Go entre otros.

        En el proyecto de GitHub de Paketo están los diferentes repositorios de los buildpacks junto con sus opciones de configuración y código fuente, además proporciona varios builder basados en diferentes versiones de Ubuntu.

        Instalación de Buildpacks

        Hay varias formas de instalación de Buildpacks según el sistema operativo que se use, junto a varias formas de instalación usando el gestor de paquetes nativo del sistema operativo o una instalación manual en el sistema.

        Builpacks es una herramienta programada en el lenguaje Go que una de sus ventajas es que generan un único binario que incluye todas las dependencias con lo que su instalación manual es muy sencilla, basta con descargar y copiar un único archivo.

        En GNU/Linux basta descargar el paquete de distribución para este sistema operativo, descomprimirlo e instalar el binario en el path del sistema de modo que al ejecutar el comando el intérprete del shell lo encuentre. En GNU/Linux el directorio /usr/local/bin permite añadir al sistema comandos adicionales sin que entren en conflicto con los que se instalan a través de paquetes de la distribución.

        1
        2
        3
        
        $ ls -l /usr/local/bin/
        total 23M
        -rwxr-xr-x 1 root       root         ene 10 11:31 pack
        
        ls-bin.sh

        Construcción de la imagen del contenedor

        Utilizando una aplicación escrita con Java con Gradle como herramienta de construcción y que usa Spring Boot, el comando para construir la imagen OCI del contenedor es el siguiente.

         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        12
        13
        14
        
        $ pack build spring-injection-point \
            --env "BP_JVM_TYPE=JDK" \
            --env "BP_JVM_VERSION=11" \
            --env "BP_GRADLE_BUILT_ARTIFACT=app/build/distributions/app.zip" \
            --builder paketobuildpacks/builder-jammy-base \
            --buildpack gcr.io/paketo-buildpacks/ca-certificates \
            --buildpack gcr.io/paketo-buildpacks/syft \
            --buildpack gcr.io/paketo-buildpacks/procfile \
            --buildpack gcr.io/paketo-buildpacks/adoptium \
            --buildpack gcr.io/paketo-buildpacks/gradle \
            --buildpack gcr.io/paketo-buildpacks/executable-jar \
            --buildpack gcr.io/paketo-buildpacks/spring-boot \
            --default-process app \
            --path .
        pack-build.sh

        Los builpacks permiten ser configurados a través de las variables de entorno, en el comando es posible configurar el buildpack de adoptioum para que use la versión de Java que se desee o el tipo de máquina virtual, distribución JDK o JRE. En el archivo README.md de cada uno de los buildpacks están documentados las variables de entorno con su descripción y en algunos casos sus valores.

        El comando especifica los buildpacks ya que el builder utilizado no incluye ninguno. En el parámetro path se indica la ruta al código fuente de la aplicación.

        El buildpack paketo-buildpacks/procfile permite especificar diferentes comandos de ejecución en un archivo. El entrypoint que añade el builder en la imagen permite especificar el proceso a iniciar en el comando de ejecución del contenedor.

        Ejecución del contenedor con Docker

        Docker permite almacenar en local y ejecutar las imágenes construidas con Buildpacks como un contenedor normal. El comando para iniciar la aplicación es el siguiente que inicia el proceso por defecto de la imagen.

        1
        2
        
        $ docker run -it --rm -p 8080:8080 spring-injection-point
        
        
        docker-run.sh
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        
        Setting Active Processor Count to 8
        Calculating JVM memory based on 13813724K available memory
        For more information on this calculation, see https://paketo.io/docs/reference/java-reference/#memory-calculator
        Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx13215722K -XX:MaxMetaspaceSize=86001K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 13813724K, Thread Count: 250, Loaded Class Count: 12770, Headroom: 0%)
        Enabling Java Native Memory Tracking
        Adding 124 container CA certificates to JVM truststore
        Picked up JAVA_TOOL_OPTIONS: -Djava.security.properties=/layers/paketo-buildpacks_adoptium/java-security-properties/java-security.properties -XX:+ExitOnOutOfMemoryError -XX:ActiveProcessorCount=8 -XX:MaxDirectMemorySize=10M -Xmx13215722K -XX:MaxMetaspaceSize=86001K -XX:ReservedCodeCacheSize=240M -Xss1M -XX:+UnlockDiagnosticVMOptions -XX:NativeMemoryTracking=summary -XX:+PrintNMTStatistics
        Property (app.property): value
        
          .   ____          _            __ _ _
         /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
        ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
         \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
          '  |____| .__|_| |_|_| |_\__, | / / / /
         =========|_|==============|___/=/_/_/_/
         :: Spring Boot ::                (v2.7.6)
        
        2023-01-11 19:53:33,542 INFO  [main]     io.github.picodotdev.blogbitix.springinjectionpoint.Main Starting Main using Java 11.0.17 on d5713708092f with PID 1 (/workspace/app/lib/app.jar started by cnb in /workspace)
        2023-01-11 19:53:33,550 INFO  [main]     io.github.picodotdev.blogbitix.springinjectionpoint.Main No active profile set, falling back to 1 default profile: "default"
        2023-01-11 19:53:34,402 INFO  [main] org.springframework.boot.web.embedded.tomcat.TomcatWebServer Tomcat initialized with port(s): 8080 (http)
        2023-01-11 19:53:34,423 INFO  [main]                     org.apache.catalina.core.StandardService Starting service [Tomcat]
        2023-01-11 19:53:34,424 INFO  [main]                      org.apache.catalina.core.StandardEngine Starting Servlet engine: [Apache Tomcat/9.0.69]
        2023-01-11 19:53:34,524 INFO  [main] .apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/] Initializing Spring embedded WebApplicationContext
        2023-01-11 19:53:34,524 INFO  [main] .boot.web.servlet.context.ServletWebServerApplicationContext Root WebApplicationContext: initialization completed in 931 ms
        2023-01-11 19:53:34,932 INFO  [main] org.springframework.boot.web.embedded.tomcat.TomcatWebServer Tomcat started on port(s): 8080 (http) with context path ''
        2023-01-11 19:53:34,944 INFO  [main]     io.github.picodotdev.blogbitix.springinjectionpoint.Main Started Main in 1.858 seconds (JVM running for 2.534)
        2023-01-11 19:53:34,946 INFO  [main]  io.github.picodotdev.blogbitix.springinjectionpoint.Service Hello World!
        System.out

        Los buildpacks añaden un launcher que permite varias formas de ejecutar el contenedor de la aplicación. También es posible especificar otro proceso de la imagen con la opción –entrypoint y el nombre del proceso.

        1
        2
        
        $ docker run -it --rm --entrypoint app -p 8080:8080 spring-injection-point
        
        
        docker-run-entrypoint.sh

        A veces interesa iniciar una shell para inspeccionar el sistema de archivos del contenedor y ejecutar procesos manualmente. En este ejemplo se muestra la versión de Ubuntu que forma la imagen base del contenedor y se aprecia que está basada en la versión 22.04.

        1
        2
        
        $ docker run -it --rm --entrypoint launcher -p 8080:8080 spring-injection-point bash
        
        
        docker-run-entrypoint-bash.sh
        1
        2
        
        $ cat /etc/issue
        Ubuntu 22.04.1 LTS
        
        issue.out

        Finalmente, es posible inspeccionar la imagen del contenedor sin iniciarlo para obtener información acerca de cómo se ha construido.

        1
        2
        
        $ pack inspect-image spring-injection-point
        
        
        pak-inspect-image.sh
        Terminal

        El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando siguiente comando:
        ./pack-build.sh

        » Leer más, comentarios, etc...

        Variable not found

        Enlaces interesantes 510

        enero 10, 2023 07:05

        Enlaces interesantes

        Ahí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

        Por si te lo perdiste...

        .NET Core / .NET

        ASP.NET Core / ASP.NET / Blazor

        Azure / Cloud

        Conceptos / Patrones / Buenas prácticas

        Data

        Machine learning / IA / Bots

        Web / HTML / CSS / Javascript

        Visual Studio / Complementos / Herramientas

        .NET MAUI / Xamarin

        Otros

        Publicado en Variable not found.

        » Leer más, comentarios, etc...

        Variable not found

        Top ten 2022 en Variable Not Found

        enero 09, 2023 07:36

        2023 Top ten

        Ante todo, aprovecharé esta inigualable ocasión para desearos lo mejor para este nuevo año 2023 que se está empezando a dibujar. Espero que tengáis oportunidad de cumplir vuestros proyectos y de disfrutar del camino que os lleve a ellos.

        Y para empezar con buen pie, como es habitual, vamos a repasar los 10 artículos más leídos en Variable Not Found durante este 2022 que acaba de terminar.

        Top ten 2022 en Variable not found

        Comenzamos el ranking por el décimo lugar con el post Raw string literals en C# 11, una revisión a la que considero una de las características más interesantes de la última versión de C#. Si aún no tenéis claro de qué se trata, ya estáis tardando en echarle un ojo, porque os cambiará la forma en que codificáis las cadenas multilínea en vuestros proyectos.

        La novena posición la ocupa un artículo revisando la nueva capacidad de los mecanismos de interoperación .NET-JavaScript de Blazor, que permite introducir archivos JavaScript independientes por componente. Todo un invento para mantener ordenado y limpio el código JavaScript de los proyectos de este framework.

        Le sigue de cerca un artículo sobre ASP.NET Core MVC que muestra la solución a un problema concreto. En Requerir parámetros de la query string en ASP.NET Core 6 veíamos cómo distinguir entre valores vacíos (como el cero en un int) y la ausencia de los mismos, y cómo impedir este segundo caso tanto en tipos valor como referencia.

        Y continuando con ASP.NET Core en la séptima posición, nos respondemos a la pregunta ASP.NET Core 6, ¿dónde está mi middleware DeveloperExceptionPage?. En esta ocasión, dedicamos un ratillo a averiguar el paradero de este middleware, misteriosamente desaparecido en la penúltima versión del marco de trabajo.

        Seguidamente encontramos lo que creo que es un completo tutorial sobre cómo interactuar con Google Sheets desde .NET: Añadir filas de datos a Google Sheets desde .NET. Como en otras ocasiones, el tema surgió de una necesidad personal en un proyecto, pero parece que hay más desarrolladores que andan buscando información sobre ello en Google.

        Ya en la mitad de la clasificación, como quinto contenido más visitado del año, comenzamos un debate (bueno, prácticamente monólogo 😁) sobre la conveniencia de utilizar o no tipos booleanos. En ¿Usar booleanos? ¿Sí, no, a veces? hice un recorrido sobre distintos usos que damos a este tipo de dato, y cuándo es una mala idea hacerlo.

        El cuarto puesto está ocupado por el post PeriodicTimer: Temporizadores asíncronos en .NET 6, una revisión a distintas formas de crear temporizadores y la nueva clase PeriodicTimer aparecida con .NET 6.

        Ya en el podium, en tercera posición vemos el post ¡No uses ToLower() o ToUpper() para comparar cadenas en C#! que, a modo de imposición dictatorial, intentaba llamar la atención sobre algo que todos hemos hecho en algún momento, y que no es correcto tanto desde el punto de vista del rendimiento como del aprovechamiento de la memoria.

        El subcampeón de esta edición, Verificar si una cadena de texto es un JSON válido exponía la fórmula más eficiente que encontré para comprobar si un texto era JSON "de verdad". Pero lo mejor de todo es que el gran Javier Campos encontró una vía aún mejor, dando lugar al post Una forma más eficiente de comprobar si un texto es un JSON válido. Sin duda, una gran prueba de que una comunidad potente y el intercambio de información nos hace mejorar a todos :)

        Y para terminar, el ganador, con una diferencia importante sobre el siguiente es el post Cómo recibir un JSON como string en una acción ASP.NET Core MVC, algo que, como en otras ocasiones, escribí para analizar las vías que tenía para resolver un problema concreto del día a día, pero nunca pensé que pudiera ser útil o interesante para cientos de personas más.

        Una vez más, ¡feliz año 2023 a todos! Y no olvidéis que seguimos viéndonos por aquí

        Publicado en Variable not found.

        » Leer más, comentarios, etc...

        Blog Bitix

        Obtener valores de configuración antes iniciar una aplicación con Spring Boot

        enero 06, 2023 12:00

        El mecanismo de configuración de Spring Boot es muy flexible y por otro lado Spring Boot proporciona varias formas de extensión. En el proceso de inicialización de la aplicación Spring Boot permite recibir eventos y realizar las acciones que se deseen. Uno de estos eventos es ApplicationEnvironmentPreparedEvent antes de inicializar el contexto y crear los beans del contenedor de dependencias, cyando se lanza este evento es posible acceder a las propiedades de configuración.

        » Leer más, comentarios, etc...

        Picando Código

        Películas para ver en 2023

        enero 05, 2023 05:00

        Como hace un buen tiempo que no hago una lista de películas para mirar en el año, decidí armar una para 2023. ¿Se acuerdan los posts sobre películas que esperamos con ansias durante el año? Para refrescar la memoria: películas para ver en 2009, en 2011, en 2012 (no puedo creer que hace más de 10 años de esos posts), en 2018 y 2019. No he mirado demasiadas películas en los últimos tiempos, ni tampoco he ido mucho al cine. Pero se viene alguna cosa interesante este año.

        Seguramente vaya actualizando esta entrada a medida que vaya viendo más películas que me interesa ver. Si saben de alguna buena película que no esté en la lista, agradezco la mencionen en un comentario.

        Las películas de superhéroes de cómics perdieron un poco del encanto para mí. Avengers Endgame me resultó el cierre de todo lo que quería ver en el Universo Cinematográfico de Marvel. Quedé más que satisfecho, y pensé «¿para dónde se puede seguir después de esto?». No sé si alguna de las películas de Marvel desde entonces me sorprendió o emocionó tanto.

        Por otro lado el universo cinematográfico de DC ya no me llama y está bastante desorganizado. The Joker fue de las películas más aburridas que he mirado. El mejor Superman de los últimos tiempos, Henry Cavill, ya no va a ser más Superman después de que DC lo hiciera volver a ponerse el traje para un cameo… El mejor Batman de los últimos tiempos, Batfleck, ya no vuelve como Batman y la última película protagonizada por el actor que también fue vampiro fue otra extremadamente aburrida para mí. Fui al cine a ver Black Adam y me entretuvo, pero dudo que gaste una entrada de cine para otra película de DC este año que no sea Shazam.

        Vamos con la lista de películas a mirar, empezando por los superhéroes de cómics:

        Ant-Man and the Wasp: Quantumania

        Las películas de Ant-Man vienen siendo divertidas. No les pido mucho, así que mis expectativas son medidas. Tiene pinta que va a ser entretenida, y aparece Bill Murray.

        YouTube Video


        Estreno: 17 de Febrero

        Guardians Of The Galaxy Vol. 3

        En su momento me gustó mucho la primera película de los Guardianes. Parece que seguirá siendo más de lo mismo. Me decepcionó un poco Thor: Love and Thunder el año pasado. Todo el desarrollo del personaje Thor volvió a cero y se convirtió de nuevo en el Thor choto y «graciosito». Espero que no pase lo mismo con GotG Vol. 3.

        Interesante que en esta película aparece Adam Warlock, hay lugar para expandir un poco el universo cinematográfico por el lado cósmico de Marvel. Deberían aprovechar por ahí.

        YouTube Video


        Estreno: 5 de mayo

        Spider-Man: Across The Spider-Verse

        La primera película de Miles estuvo excelente. Me encantan los Spider-Man alternativos. En esta secuela aparecen Miguel O’Hara (Spider-Man 2099) con la voz de Oscar Isaac (que cuando no está haciendo el mega odioso acento inglés de Moon Knight, me gusta), vuelve mi Spider-Man preferido Peter B. Parker (Spider-Man depresivo) y aparece Spider-Punk. Se rumorea que también va a estar Ben Reilly (Scarlett Spider). Veremos qué tal, le tengo fe:

        YouTube Video

        Estreno: 2 de junio

        The Marvels

        Vuelve Captain Marvel de Marvel, esta vez con Ms. Marvel y Maria Rambeau. Aparentemente en la película las tres protagonistas intercambian lugares al usar sus poderes. Lo que me recuerda a los cómics de Captain Marvel donde Genis-Vel intercambiaba su lugar con Rick Jones. Otra que puede estar buena.

        Estreno: 28 de julio

        Shazam! Fury of the Gods

        La primera película de Shazam me encantó. Fury of the Gods se atrasó varias veces, tengo idea de que hace años que está por estrenarse. ¡Espero que esté buena!

        YouTube Video

        Estreno: 17 de marzo

        Aquaman and the Lost Kingdom

        No sé qué esperar de esta película. La primera Aquaman estuvo buena, pero con los quilombos que tiene DC estos días, se puede esperar cualquier cosa…

        Estreno: 25 de diciembre

        The Flash

        Ezra Miller -quien interpreta a Flash- resultó ser una persona terriblemente siniestra. Creo que vuelven Michael Keaton y Ben Affleck como Batman, pero es otra de «esas películas» de DC para mí… Ojalá me sorprenda y esté buena. Hace años me intrigaba y tenía ganas de verla, pero ahora voy a esperar a que esté disponible en streaming para verla.

        Estreno: 16 de junio

        No sólo de super héroes vive el cine, y este año se viene la primera película animada del plomero que me ha acompañado toda la vida y nunca me abadona:

        The Super Mario Bros. Movie

        Es difícil ver a Mario con la voz de Chris Pratt. El tipo había salido a decir que esperaran a escucharlo antes de criticarlo, porque había estado trabajando mucho en una voz para Mario. Cuando lo escuchamos por primera vez… Sonaba exactamente igual que el protagonista de The Lego Movie, Star Lord en Guardianes de la Galaxia y Andy de Parks and Recreations. ¡Suena a Chris Pratt, Chris Pratt!

        Sacando la distracción de que la voz de Mario no es la voz de Mario, Jack Black hace una excelente voz de Bowser, y el trailer se ve bastante bueno. Toma muchas cosas de distintos títulos de la saga de Mario, y la película se ve muy bien. Shigeru Miyamoto estuvo involucrado así que tengo mucha anticipación por esta:

        YouTube Video

        Estreno: 7 de abril

        Indiana Jones and the Dial of Destiny

        Finalmente sale la quinta entrega de la saga de Indiana Jones, algo que se viene desarrollando desde los 70’s. ¿Será lapidaria como «The Crystal Skull» o redimirá a Indy? Espero que esté buena, seguramente la última vez que veamos a Harrison Ford como Indiana Jones en el cine.

        Varias escenas de la película fueron filmadas en Escocia. Un día caminando por Edimburgo vi un montón de gente de cine filmando una escena enfrente a un hotel conocido de la ciudad que después leí en algún lado online que era por esta película. Veré si era Indiana Jones o no…

        YouTube Video

        Estreno: 30 de junio

        Dungeons & Dragons: Honor Among Thieves

        Agregada gracias al comentario de tintxo1891. Parece divertida, no le pediría mucho, con que sea mejor que la película Dungeons & Dragons de 2000, me conformo:

        YouTube Video

        Estreno: 31 de Marzo

        M3gan

        He estado mirando bastante cine de terror dentro de lo poco que miro. M3gan se ve súper perturbadora y la historia está co-escrita por James Wan, así que puede que esté muy buena. Estoy esperando que se estrene para ir a verla al cine:

        YouTube Video

        Actualización: Parece que como la película se volvió viral entre adolescentes en algunas redes sociales, la re-editaron para hacerla PG-13 (que la pueden ver menores de hasta 13 años aunque tiene contenido inapropiado). Así que sacaron un montón de escenas de violencia para poder generar más ingresos entre la población joven. De todas formas está confirmado que la versión sin censura existe y probablemente se publique de alguna forma más adelate. Por mi parte, voy a esperar a la versión «Rated R» para adultos, así veo la película original (y suena más divertida que una versión para jóvenes).

        Estreno: 13 de enero

        The Nun 2

        The Nun 2 es otra película de terror co-escrita por James Wan, en el universo de The Conjuring. Todavía no vi la primera, pero me gustan las películas de Wan y particularmente The Conjuring. Debería mirar la primera y ver si me engancho con esta monja que tanto aterra.

        Estreno: 8 de setiembre

        Evil Dead Rise

        Quinta entrega de la saga Evil Dead, después del remake del compatriota Fede Álvarez. Parece que ni él, ni Bruce Campbell ni Sam Raimi estuvieron demasiado involucrados (los últimos dos productores ejecutivos). El trailer se ve interesante. Por cierto, recomiendo la serie Ash Vs Evil Dead si les gusta la saga Evil Dead.

        YouTube Video


        Estreno: 20 de Abril

        John Wick Chapter 4

        Debo confesar que llegué tarde a John Wick. Recién el año pasado miré las primeras dos películas, y tengo pendiente mirar la tercera. Pero, ¿Keanu Reeves cagando a palos todo lo que se le encuentra? Me encantaron. Voy a tener que mirar la tercera antes de ir al cine a mirar la cuarta entrega:

        YouTube Video

        Estreno: 24 de marzo

        Cocaine Bear

        Inspirada en una historia real, Cocaine Bear parece una película divertida y sumamente ridícula. Una película sobre un oso que toma mucha cocaína…

        YouTube Video

        Estreno: 24 de febrero

        65

        Siempre me quejo que no hay suficientes películas con dinosaurios estos días. De los escritores de «A Quiet Place» y producida por Sam Raimi, este año llega 65: un thriller de ciencia ficción protagonizado por Adam Driver.

        El protagonista llega a la tierra hace 65 millones de años y debe luchar con criaturas prehistóricas (incluyendo dinosaurios) para sobrevivir. He notado que distintas fuentes paleontológicas se refieren a la extinción de los dinosaurios hace 65 o 66 millones de años. Pero el título de la película me lleva a inferir que el desafío final es volver a casa antes que llegue el asteroide que mató a los dinosaurios. En fin, dinosaurios y Adam Driver, compro:

        YouTube Video

        Estreno: 17 de marzo

        Transformers: Rise of the Beasts

        Lo otro que siempre viene bien en el cine además de dinosaurios, son robots gigantes. Me gustó mucho la primera película de Transformers en su momento, aunque les perdí el hilo después. Volví con la película de Bumblebee que estuvo bastante bien, parece que ésta es una secuela. Aparentemente influenciada por la serie Beast Wars que veíamos en la televisión por cable en nuestra infanciadolescencia. El trailer se ve bastante entretenido:

        YouTube Video

        Estreno: 9 de junio

        Nueva película Godzilla de Toho

        Este año se festeja el cumpleaños 69 (jeje) de Godzilla y Toho anunció una nueva película de la franquicia. Va a estar producida completamente en Japón, y aparentemente va a ser independiente de Shin Godzilla. Shin Godzilla fue un reboot japonés de Godzilla en 2016, está muy buena. Esperemos que esta nueva entrega -todavía sin título confirmado- vaya por ese lado. Habrá que esperar por más información….

        Godzilla @ Edinburgh Castle

        Estreno: 3 de Noviembre

        Otra película que se viene este año es Teenage Mutant Ninja Turtles: Mutant Mayhem (gracias de nuevo tintxo1891 por recordarme). Depende de mi reacción cuando vea el trailer si la agrego a esta lista o no, le tengo mucho miedo…

        El post Películas para ver en 2023 fue publicado originalmente en Picando Código.

        » Leer más, comentarios, etc...

        Picando Código

        Este post no es un repaso de 2022 o resoluciones para 2023. Es una excusa para escribir…

        enero 04, 2023 05:30

        Bajo Presión

        Bajo Presión – Dibujado en Octubre de 2021 como parte de Inktober

        El fin de año y el «Año nuevo» me irritan un poco. No sólo no planeo mucho ni celebro entorno a estas fechas, cada año me molesta más todo el tema. Será por estar cada año más viejo, o que me molesta más esa imposición de «festejar» y «estar feliz» en fechas determinadas en las que el mercado nos tiene condicionados a hacerlo.

        De todas formas, la vuelta entera (relativa y todo eso) del planeta al Sol sirve como buena excusa para hacer un repaso de la unidad de tiempo que termina. También es una oportunidad para definir metas o planear cosas para hacer en el nuevo ciclo. Sea como sea, podemos tomar el mes de enero como un punto de referencia para empezar cosas nuevas u organizarnos hacia un nuevo segmento de tiempo en el que el planeta le de una vuelta al Sol.

        Todo esto para decir que una de las cosas que quiero hacer más en 2023 es escribir. No solamente escribir en este blog, escribir en general. Tengo un montón de ideas en la cabeza, algunas se vienen fermentando hace años. Tengo un montón de cuentos cortos y experimentos literarios ya escritos de la época en que hice un taller con Ignacio Alcuri. Tengo un índice lleno de notas para un libro que algún día quiero sentarme a escribir. Tengo ideas como para otras tantas cosas que me gustaría escribir. Iré viendo si publico más por acá.

        En parte publicar las cosas que escribo me aterra un poco. Cada vez que publico algo (incluyendo los posts de este blog) o le muestro a alguien algo que escribí, se filtran al mundo ideas desde el lugar más íntimo de mi cabeza. Incluso comentar esto mismo se siente como que estoy dejando salir más de lo que debería. Ni que hablar del miedo de «escribir un libro». No tengo ni noción del tiempo y trabajo que debe llevar escribir un libro, ¿y quién lo querría leer? ¿lo publicaría yo mismo? ¿quién más podría interesarse en publicar algo escrito por mí?

        En líneas generales creo que publicar un libro es una idea muy linda en la teoría, pero en la práctica asumo que es demasiado trabajo y poco probable que algún día me proponga hacerlo en serio. Después veo cada libro que dan vergüenza y pienso «si ellos pueden, yo también debería poder». Pero me parece que por el momento está fuera de mi alcance (para empezar, ¡hay que escribir un libro entero!).

        Tengo la costumbre de a veces escribir por acá «metas» como «escribir más». Como si dejarlas publicadas generara un compromiso mayor y me motivara a cumplirlas. Pero lo cierto es que no sé cuánto ayuda realmente. Por lo menos si en un tiempo veo un comentario de alguien que lee el blog reclamando que no cumplí, de repente me motivo a escribir algo.

        2022 fue un año distinto en el blog, me tomé un recreo bastante largo de Picando Código. Posiblemente uno de los más largos de su historia. Publiqué algo en agosto de 2021 y no volví a publicar nada hasta junio de 2022. Obviamente esto fue en parte consecuencia de los dos años que pasamos encerrados sin ver a nadie ni poder hacer mucha cosa fuera de nuestros respectivos hogares (seguiremos pagando las consecuencias de esa pandemia en años que vendrán…). Pero eventualmente volví.

        Una cosa que creo que hice bastante bien en 2022 y quiero seguir haciendo más en 2023 es dibujar. En 2022 tuve varias rachas de dibujar como costumbre. Ya tengo varios cuadernos llenos de dibujos, algunos menos peores que otros, y pude experimentar con colores, distintos medios y demás. Algo comenté al respecto en los 15 años del blog, voy a tener que empezar a acompañar los posts con dibujos…

        Me resulta particularmente interesante cómo con el dibujo a veces limito el tiempo para completar un dibujo, y otras tengo una idea que me resulta fácil y entretenida y simplemente me dejo seguir hasta considerarlo «terminado». Supongo que aplico la misma metodología a escribir, pero lo veo más en el dibujo (sobretodo en cosas que dibujé y «no me gustan» pero «cumplen lo suficiente con lo que me propuse»).

        No sé cómo explicar bien el sentimiento de estar «escribiendo al vacío» que me genera el blog. Hace unos años parte de la gracia de tener un blog era las interacciones sociales que generaba con gente online. Hoy en día es más por el orgullo y la libertad de tener mi rinconcito propio en Internet donde publico lo que quiero, y soy el consumidor principal del contenido. Al final del día es eso, una bitácora, y cada tanto caerá también alguna persona buscando la solución a un problema de Linux o Programación que escribí en algún momento. No lo veo como algo negativo, de hecho me genera menos presión a la hora de escribir o elegir si publicar algo o no el no tener una presión de un supuesto público. Pero a veces se extraña un poco la interacción.

        Creo que los posts de 2022 reflejan bastante la temática general del blog: alguna cosa sobre Emacs, posts sobre Ruby y programación, cómics, videojuegos, películas (¡ciencia ficción!) y muchos libros. Habrá que ver cómo sigue la cosa en 2023. Por lo menos ya empecé mejor, escribiendo el primer post del año y no dejando pasar casi un año sin escribir nada. Si tuviera que elegir mi post preferido de 2022, sería «El Retorno del videocasete del Regreso del Jedi», un viaje en el tiempo lleno de nostalgia para aficionados de Star Wars.

        No tengo muchas ideas sobre qué estaré escribiendo en 2023. Pretendo seguir mi racha de lectura, vengo disfrutando mucho el leer. También hay varias películas y videojuegos que estoy esperando este año, de repente salen algunas reseñas a partir de ahí. Pero capaz que empiezo a agregar algo de ficción. Sé que hay algo de eso publicado por acá en años anteriores, pero veremos si vuelve.

        Lo importante es que quiero escribir más, por más que no sepa sobre qué escribir, como al momento que me senté a escribir este post. Por lo menos para mantener la costumbre…

         

        El post Este post no es un repaso de 2022 o resoluciones para 2023. Es una excusa para escribir… fue publicado originalmente en Picando Código.

        » Leer más, comentarios, etc...

        Picando Código

        2022 fue un buen año para la lectura

        diciembre 29, 2022 06:23

        Terminé de leer The Dispossessed por Ursula K. Le Guin antes de terminar el año. Fue un libro que me llevó bastante tiempo leer debido a varias razones, la vida se interpone… Pero al final me resultó interesante y creo que volvería a leerlo estando en una situación mental más estable y tranquilo. Es un clásico de ciencia ficción anarquista utópica. Tiene un montón de partes interesantes y ya sobre el final me enganchó mucho el desenlace (¡al punto que me llegaron a arder los ojos del cansancio de leer en papel!).

        The Dispossessed - Ursula K. Le Guin

        En el futuro, existen dos planetas Anarres y Urras que son la Luna cada uno del otro. Anarres está habitado por «humanos» que vinieron de Urras hace un par de siglos, y formaron una sociedad anarco sindicalista donde la ayuda mutua es imprescindible. Urras tiene varios estados pero del que más leemos es A-Io, con un sistema capitalista y patriarcal (también está Thu con un sistema autoritario).

        El protagonista es un habitante de Anarres que viaja a Urras a modo de embajador. Se tratan varios temas que me resultaron muy interesantes como el anarquismo, críticas al capitalismo, gobiernos y temas filosóficos sobre la sociedad y el individuo. Todo esto en un contexto de ciencia ficción, excelente.

        Algo que me di cuenta después de leer una cantidad vergonzosa de capítulos, es que el libro alterna los capítulos entre el pasado en Anarres y el presente en Urras. Me dejó bastante confundido al principio, me cuesta muchísimo recordar nombres (de personajes y lugares, varias veces tuve que volver a leer cuál planeta era el anarquista y cuál el otro). Además los primeros capítulos los leí con bastante tiempo entre cada uno (y en un estado mental no ideal, sumado a una buena dosis de privación de sueño), hasta que me di cuenta lo que estaba pasando y me hice un resumen general de todo para que mi cerebro supiera dónde estaba parado dentro de la historia. No se vuelvan viejos… Cuestión que una vez que aclaré eso,  el libro se volvió mucho más disfrutable. Definitivamente tendría que volver a leerlo.

        La autora tiene varias historias más en este mismo universo, así que posiblemente en algún momento también lea algún otro título del «Ciclo de Hainish».

        Con eso alcancé los 16 libros leídos en 2022. Creo que es la primera vez que cuento cuántos libros leo en un mismo año. Todo empezó en enero cuando terminé el primer libro del año y se me ocurrió publicarlo en Twitter. Desde hace un tiempo venía intentando publicar en Twitter casi exclusivamente recomendaciones o al menos difundiendo cosas que disfrutaba o me resultaban interesantes de leer. Hay tanta basura y negatividad en las redes sociales que me dispuse a intentar siempre tratar de agregar algo positivo. Si algo no me gusta, lo ignoro. Si algo me gusta, lo comparto y en lo posible le hago saber a la persona responsable que me gusta (¡es muy satisfactorio!). Es difícil, hay tanta razones para enojarse, molestarse, frustrarse y demás. Pero por lo menos intentaré no agregar más negatividad a la internet.

        El hilo en cuestión siguió siendo algo así como una tradición, hasta que decidí escribir un post sobre 10 libros que leí en 2022 (donde inclui los 7 libros del hilo original y más). Desde entonces leí algunos libros más que recomiendo y también comenté en el blog: The Last Man in Europe de Dennis Glover, The Modern Bestiary por Joanna Bagniewska y La novia de Johnny Storm ve la vaca y llora por Ignacio Alcuri.

        Además de los mencionados, leí también Native – Life in a Vanishing Landscape por Patrick Laurie. Está basado en la vida del autor, que se compra una granja para reconectar con su tierra nativa de Galloway en Escocia. Lo compré en la tienda cuando estuve de visita en el Castillo Urquhart en Loch Ness con mi padre para tener algo que leer en los recorridos largos en omnibus de ese viaje.

        Me interesó por tener un concepto similar a «The Ponies at the Edge of the World» por Catherine Munro. «En este libro, explora la conexión duradera entre humanos, la agricultura y la naturaleza para crear un registro profundamente personal y conmovedor de la vida en un paisaje que se desvanece» se lee en la contratapa. Hay un paralelismo con los Ponies de Shetland en el otro libro mencionado y las aves «Curlews» (Wikipedia me dice que son aves del género Numenius) y la raza Galloway de ganado en este libro. Si bien está interesante, aprendí algunas cosas y entretuvo de a partes, no logró engancharme tanto como el de los Ponies.

        También leí Autonomous por Annalee Newitz, un libro que tenía en mi lista para leer desde hace un buen tiempo. Lo encontré en una de mis visitas a librerías y decidí comprarlo. Justo estaba buscando algo de ficción en ese momento. No recuerdo exactamente en qué mes del año fue que lo adquirí o cuándo lo leí. La autora es la fundadora de io9, uno de los blogs que leía bastante en su momento, pero leo cada tanto éstos días.

        Si bien tiene un montón de conceptos interesantes, no me terminó de enganchar. Incluso diría que me costó terminarlo. En varios momentos pensaba «estos temas deberían engancharme muchísimo», pero se ve que el estilo de la autora no es para mí. A veces me hacía acordar a cosas específicas que no me gustaron del estilo de escritura de Cory Doctorow en Walkaway. Tiene mucha coincidencia con Doctorow en la temática, en esta novela en particular se habla del tema de las patentes para las drogas, y si bien es ilegal, si es ético piratearlas para hacer que las drogas sean más accesibles. De repente el mensaje está bueno, pero no la entrega.

        Al igual que «Native», algunas cosas me gustaron, pero en su momento no me generaron ganas de escribir al respecto. No me llegaron tanto. Lo recomiendo igual porque tengo la sospecha de que tampoco estaba en mi mejor momento. Pero si algún día me quedara sin nada para leer en la biblioteca (pocas chances que ocurra), le daría otra oportunidad.

        No sólo vengo leyendo libros por ocio, también estuve estudiando Gaélico, un poco sobre Ruby y Elasticsearch. Vengo leyendo también varios cómics, las revistas Retro Gamer y Ninty Fresh y alguna cosa más.  Así que la lectura siempre me acompaña.

        Ya empecé a leer otro libro y probablemente lo termine para 2023. Y tengo una pila de libros por leer que parece crecer constantemente, además de un wishlist virtual donde sigo anotando libros que me resultan interesantes. Ya que este año me tomé el trabajo de llevar la cuenta, me propuse como meta para 2023 leer más de 16 libros. No es una cantidad sorprendente. Posibleente algún año habré leído más, y seguramente muchos años leí menos. Pero vengo disfrutando mucho la lectura, así que espero mantener el hábito y con suerte leer algún libro más en español.

        Actualización Enero 2023: Ni bien terminé de leer The Dispossessed, empecé a leer Wild de Amy Jefffs y fue el último libro que leí en 2022.

        Wild - Amy Jeffs

        Cada capítulo de Wild empieza con un cuento corto inspirado en historias y leyendas de la mitología británica. Algunas de las fuentes de estas historias son poemas en Inglés Antiguo del Libro de Exeter, poemas antiguos en Galés y tradiciones Irlandesas. Las historias de la autora se basan en esos cuentos, a veces mezclan más de uno, manteniendo la esencia pero agregando su interpretación personal. El resto de los capítulos agregan conexiones entre estos distintos cuentos, hechos e historia de las islas británicas, además de paseos y conexiones con los paisajes en los que se llevan a cabo. Me resultó muy interesante, entretenido y fácil de leer.

        Así que la suma total de libros leídos en 2022 fue de 17. El objetivo en 2023 es leer por lo menos 17 libros y con suerte más (¡al menos 20!).

        El post 2022 fue un buen año para la lectura fue publicado originalmente en Picando Código.

        » Leer más, comentarios, etc...

        Picando Código

        Se viene Ruby 3.2: Ruby muy eficiente y rápido, gracias en parte a Shopify

        diciembre 23, 2022 11:37

        RubySe viene Ruby 3.2, la nueva versión de Ruby que estaría disponible a partir de el próximo día de Navidad. Shopify es una de las empresas que ha venido apoyando mucho al lenguaje de programación, además de tener la aplicación Ruby on Rails más grande del mundo (o así lo afirman).

        Ufuk Kayserilioglu, Gerente de Ingeniería en el equipo de Infraestructura de Shopify, compartió que «hay tantas cosas geniales en 3.2 que no pudimos esperar al día de la publicación, y ya las usamos en los pedidos a Shopify».

        Quedé bastante emocionado con todo lo que describe, y lo hace de manera muy didáctica. Así que decidí compartir por acá toda esta información.

        Todos los pedidos al frente de las tiendas online de Spotify son servidos por la última versión de Ruby con el YJIT habilitado. Esto acelera todos los pedidos aproximadamente en un 10%. Así lo comentó su CEO.

        YJIT: Yet Another Ruby JIT – fue desarrollado por Maxime Chevalier y su equipo en Shopify. Es un JIT minimalista en Ruby desarrollado dentro de CRuby. Más información. Si bien está disponible ya en Ruby 3.1, en 3.2 está etiquetado como «listo para producción». El mayor problema resuelto fue el uso de memoria extra en la versión anterior. Ahora utiliza menos memoria (en el caso de Shopify redujeron la configuración del tamaño de memoria ejecutable por defecto de 256MB a 64MB). YJIT asigna la memoria que necesita usar de manera «lazy».

        También hay ahora un Code GC, recolector de basura de código, que invalida y colecta código de máquina que ya no es válido o necesario. Esto permite a aplicaciones que llenan el tamaño de la memoria ejecutable a reclamar algo de memoria para poder continuar beneficiándose del JIT.

        Los benchmarks muestran que  a pesar del uso más eficiente de memoria, el nuevo YJIT es más rápido que el anterior, y hasta un 40% más rápido que CRuby.

        La última versión de Ruby también implementa Variable Width Allocation, lo que permite que la máquina virtual almacene objetos más grandes para mejorar la información a nivel local.

        Hasta Ruby 3.1, la máquina virtual de Ruby asignaría objetos usando slots fijos de 40 bytes. Usaría algo de los 40 bytes para llevar el registro, y podía almacenar algo de información en los restantes 20 o tantos bytes. Pero la mayoría de los objetos almacenan más información que eso. Así que Ruby asignaría memoria extra del sistema para guardar la información extra. La ubicación de la memoria estaría lejos de la ubicación del slot del objeto, lo que significaba que se necesitarían más lecturas de memoria para leer objetos, lo cual es ineficiente.

        VWA implementa slots de varios tamaños para permitir a objetos más grandes almacenar su información en el mismo slot. Esto mejora la memoria localmente y permite el acceso a objetos de manera más rápida. La tecnología existía para Ruby 3.1 pero está activada por defecto en Ruby 3.2. Los benchmarks muestran un aumento de velocidad de entre un 2 y 10%.

        El trabajo fue realizado por Peter Zhu, Matt Valentine-House y Aaron Patterson. Y hay una charla de RubyKaigi 2021 donde explican más:

        YouTube Video

        Por último, Object Shapes,es una técnica para guardar propiedades de objetos de manera eficiente. La técnica viene de Smalltalk y está siendo usada por V8, TruffleRuby y otras máquinas virtuales. La idea es tratar a un objeto como una bolsa de propiedades, y considerar objetos que tengan definidas las mismas propiedades de la misma manera tener la misma «forma». Con propiedades se refiere al «estado» que tiene un objeto, como variables de instancia y el estado congelado. Representar objetos de esa manera permite optimizaciones nuevas. Chri Seaton (Q.E.P.D.) dió una charla explicando la idea en RubyKaigi 2021:

        YouTube Video

        Maxime Chevalier se dió cuenta trabajando en YJIT que si Ruby implementaba Object Shapes, se podría mejorar acceso a las variables de instancia desde el intérprete y el JIT. También explicó la idea en RubyKaigi 2021:

        YouTube Video

        La implementación estuvo a cargo de Jemma Issroff y Aaron Patterson. Los resultados finales mejoraron por 6 veces la velocidad de acceso a variables de instancia. Jemma Issroff habló de esta implementación en Euruko:

        YouTube Video

        Ufuk Kayserilioglu también habla sobre mejoras en la estabilidad. Este año Shopify ha estado ejecutando Integración Contínua de su aplicación contra Ruby HEAD (hasta cada hora), por lo que cuando algo falla, o lo corregían en su aplicación o reportaban el bug a Ruby. Gracias a ese trabajo, se identificaron y arreglaron varios bugs.

        También están trabajando en un nuevo parser para Ruby, que ya está siendo usado en su servidor Ruby LSP.

        Y todo esto sin entrar en los detalles nuevos que traerá el lenguaje, pero eso será información para otro día. Espero con ansias la nueva versión de Ruby para empezar a probarla con mis proyectos…

         

        El post Se viene Ruby 3.2: Ruby muy eficiente y rápido, gracias en parte a Shopify fue publicado originalmente en Picando Código.

        » Leer más, comentarios, etc...

        Header Files

        Revisión de código

        diciembre 16, 2022 03:00

        Introducción

        En los últimos años he tenido la oportunidad de trabajar con grandes profesionales del desarrollo de software, y de todos ellos he aprendido muchísimo. Asímismo, en las empresas donde he trabajado he podido comprobar cómo ese conocimiento se transfiere de forma natural de un miembro del equipo a otro, día a día, logrando una verdadera simbiosis.

        Esta transferencia tiene lugar de muchas formas, desde charlas formales sobre un tema dado y discusiones acerca de un proyecto o problema puntual, hasta anécdotas contadas durante un café o una cerveza. Además, muchas veeces ocurría de forma indirecta, o inclusivo podríamos decir que pasiva, durante los procesos de revisión de código.

        Revisión de código

        La revisión de código, para aquellos que no la conozcan, consiste en una actividad en la que otros miembros del equipo ven, estudian, evalúan, critican y proponen mejoras sobre la tarea que tenemos en ese momento entre manos.

        Esto se puede hacer de muchas formas, por ejemplo, solicitando directamente a un colega su opinión acerca de una determinada solución; pero la más común es mediante comentarios sobre los cambios en una pull request (o merge request, dependiendo de la plataforma).

        Comment in code review

        Así, durante la revisión, otros miembros del equipo tienen la oportunidad de conocer, cuestionar y proponer mejoras a nuestro código antes de que éste sea integrado (se entiende con estas palabras que nuestros cambios está en una rama y aún no se ha hecho un merge a la rama de desarrollo).

        Algunos equipos llevan este proceso un paso más allá y requieren de una aprobación explícita antes de poder incluir los cambios hechos en la rama destino (develop, master…). De esta forma se garantiza que el código ha sido revisado antes de completarse la tarea. Se puede definir que se requiera un mínimo de aprobaciones (por ejemplo 2), y además se puede definir quién puede dar esa aprobación. Así por ejemplo, en ramas normales la aprobación podría ser dada por cualquier miembro del equipo, mientras que la integración con master u otras ramas de producción requerirían la aprobación de los responsables del producto.

        Pero, ¿en qué consiste exactamente una revisión de código? Durante una retrospectiva, hace unos meses atrás, salió este tema y, después de hablarlo por un rato, llegué a la conclusión que podríamos dividir las revisiones de código en 3 niveles: rápida (o general), detallada, y en profundidad; o sencillamente, como las solíamos llamar: de nivel 1, 2 y 3. Esta clasificación nos ayudó mucho a centrar los esfuerzos de revisión, pudiendo exprimir al máximo esta gran herramienta.

        Nivel 1: revisión rápida o general

        En este nivel el revisor mira el código como un conjunto de líneas casi independientes entre sí: no revisa la tarea como tal sino aspectos genéricos, entre ellos:

        • Conformidad con la guía de estilo
        • Buenas prácticas de programación para el lenguaje utilizado
        • Detección de funciones sin ningún test asociado
        • Falta de documentación
        • Errores en la documentación, en traducciones, fallos en los recursos

        De cara a la guía de estilo de código, si bien no es algo obligatorio, y en muchas empresas no la hay, también es cierto que permite centrar la atención en lo importante en lugar de perderlo pensando en cómo indentar una función. Además, si todo el código tiene el mismo estilo, el paso de varios programadores por el mismo no se notará y reducirá el número de cambios entre commits a lo escencial.

        Por otro lado, detectar que se han introducido nuevas funciones sin sus correspondientes tests nos ayuda a aumentar la cobertura del mismo de forma natural y por anticipado. Y si lo que se ve es que se ha modificado el comportamiento del código sin tener que actualizar las pruebas existentes, nos da una clara señal de que dichas pruebas no eran tan buenas como creíamos y que deberíamos dedicarles un tiempo a revisarlas.

        Pero lo más importante de estas revisiones es que pueden ser hechas por cualquier miembro del equipo ya que no requieren de un especial entendimiento ni de la tarea ni de la solución. Es particularmente útil para los juniors (ayudándoles a ver código más maduro), como a nuevas incorporaciones (adquiriendo familiaridad con el proyecto y las tareas); y dado que pueden hacerse sólo sobre una parte del código, es posible realizarla en cualquier momento libre, o incluso para despejar la mente de otra tarea.

        Además de la importante ganancia que tiene para un desarrollador que cualquier miembro del equipo (o de otro equipo incluso) pueda mejorar su código, está el hecho de que los revisores se empapan del trabajo de sus compañeros, tanto de la tarea que se etá llevando a cabo, como del aprendizaje que puedan sacar de ver código ajeno.

        Hay que tener en cuenta que este nivel de revisión es suceptible de ser automatizado en gran medida mediante analizadores estáticos, formateadores de código (clang-format ejecutado durante el pre-commit, por ejemplo), herramientas de coverage automático, etc. Estas automatizaciones no eliminan por inutilizan por completo este nivel de revisión, sino que permiten dedicar el tiempo a otro tipo de comentarios (por ejemplo, decidir si la documentación actual es entendible o si ha quedado desactualizada).

        Nivel 2: revisión detallada

        Acá ya se requiere un nivel de lectura más detallado, buscando entender mejor los cambios propuestos y lo que de ellos se deriva:

        • Efectos secundarios
        • Posibles interacciones con otros componentes
        • Cobertura
        • Relación con otras tareas (pasadas, en curso, o planificadas)
        • Propuestas de mejora (optimizaciones, refactorings)

        Se busca entender si los cambios aplicados pueden generar efectos en otras partes del código o alterar comportamientos existentes. Ejemplos: cambios de un API, nuevos valores por defecto, comportamientos ocultos, código no documentado con soluciones hackeos históricos, etc. Sería recomendable revisar la cobertura de código en caso de que se encuentren efectos secundarios o cambios indirectos.

        Se puede analizar el impacto en otros componentes, por ejemplo, proponiendo un refactoring para evitar la duplicidad de código o exponer funcionalidades útiles. Asímismo, esta labor puede extenderse a traer experiencia de tareas pasadas, buscar coordinación o ayuda con tareas en curso, o definir mejor tareas futuras.

        Debido al mejor entendimiento del código es posible para los revisores proponer optimizaciones que generen un impacto positivo (se entiende acá además de que se puede reportar cualquier presunta degradación del rendimiento).

        Es un buen momento además, aprovechando la dedicación de tiempo, para realizar una prueba de cobertura más a fondo (en el caso de que no esté automatizada).

        Puede verse que este nivel requiere de una dedicación mayor que el nivel 1 y un mejor entendimiento tanto de los cambios como del código en general. Si bien todavía podríamos decir que cualquiera puede hacerlas, estas revisiones suelen ser realizadas más por miembros senior del equipo así como afines a la tarea.

        Nivel 3: revisón en profundidad

        Este último nivel suele estar reservado a personas afines a la tarea y a arquitectos de software, ya que requiere un fuerte conocimiento tanto del trabajo que ha de realizarse como del producto en general. En este nivel es más difícil definir una lista de comentarios posibles, ya que dependen de cada tarea, pero sí podemos resumir los objetivos que persiguen:

        • Validación de la solución
        • Discusión a fondo de la misma
        • Preparación para producción

        Más allá de la implementación detallada, se ha de revisar que la tarea se resuelva por completo (de nada sirve un código maravilloso si no soluciona el problema que debe). Esto implica haber analizado el problema (requerimientos, posibles implementaciones, causas del error, etc.), así como su validación por parte del equipo de QA. Bien podría decirse que la primera parte debe formar parte más del definition of ready que de la revisión de código, pero es importante que esté hecha y entenderla para poder analizar la solución propuesta. Del mismo modo la validación es clave para saber que la teoría se ha llevado a la práctica, por lo que la cobertura de los tests unitarios debe ser adecuada y considerar todos los casos borde posibles.

        En este nivel se pueden sugerir mejoras globales de la arquitectura, optimizaciones más agresivas, modificaciones en los procesos de validación para mejorar la cobertura funcional, así como posibles tareas relacionadas pero que se salen del ámbito del problema actual.

        Asímismo, hay que mantener la atención en que la solución debe ser production ready (salvo el caso de pruebas de concepto o tareas parciales). Esto incluye verificar que todos los aspectos que rodean al cambio, tales como traducciones, instalación de dependencias, feature flags, mecanismos de despliegues a tener en cuenta, notificación de cambio de APIs, entre otros, hayan sido tenidos en cuenta (obviamente, si existe una tarea diferente para ello se ha de relegar a la misma).

        Consideraciones finales

        La revisión de código es una herramienta técnica que atañe principalmente a los implicados en la ejecución de la tarea (desarrolladores principalmente, aunque podríamos considerar a DevOps y QAs si el código está relacionado con dichas áreas). No tiene mucho sentido que los product owners o managers se paseen por las revisiones de código de normal: para saber lo que hace el equipo se disponen de otras herramientas, tales como las Scrum dailies.

        Por otro lado, si bien la implicación de un QA en la revisión de código genérico no es obligatoria, personalmente siempre he obtenido mejores resultados cuando están en contacto cercano con la tarea. En algunos casos se puede definir una tarea de validación explícita antes de dar por bueno el desarrollo, que podría implicar, per se, el desarrollo de nuevas pruebas automatizadas, tests de regresión, etc. En otros casos el ticket se reenvará a los equipos de validación y pruebas para su consideración para el siguiente lanzamiento.

        Para finalizar, es importante hablar acerca de los modales: la revisión de código es una parte de nuestro trabajo, y debe realizarse con la misma profesionalidad y respeto hacia nuestros colegas. Así, si hay que decir que un cambio no es correcto o incluso dañino, se dice, pero con respeto y amabilidad. De la misma forma también se puede aprovechar para valorar positivamente un buen trabajo. De cara a recibir comentarios, recordad que el objetivo de los comentarios no es el autor sino la mejora del código, del producto y de la empresa; por lo que hay que tomarlos de forma constructiva. En lo personal, creo que he aprendido tanto durante las revisiones de código como de Stack Overflow 😉.

        Conclusiones

        Hemos visto una breve introducción a las revisiones de código y su importancia, así como un breve esquema de los diferentes tipos de revisiones que podemos hacer para sacarles el mayor beneficio posible.

        » Leer más, comentarios, etc...

        Picando Código

        Charge – Corto animado ciencia ficción hecho en Blender

        diciembre 15, 2022 05:03

        Blender Studio, la parte creativa de Blender, presentó Charge, su nuevo corto animación. Se trata de un proyecto (antes conocido como «Project Heist») con alto impacto visual, lleno de acción, inspirado en cinemáticas de videojuegos y formatos de demo en tiempo real.

        Es la decimocuarta Película Abierta de Blender Studio. Podemos encontrar los archivos fuente y más extras en el sitio web Blender Studio. El proyecto se presentó públicamente hoy a través de una transmisión en vivo con el equipo desde la sede de Blender HQ.

        La transmisión tuvo partes bastante interesantes, incluyendo detalles de desarrollo, animación y demás. Hay mucho material e información interesante en el enlace al proyecto en Blender Studio, siendo fieles al espíritu abierto de la producción.

        El impresionante corto a continuación:

        YouTube Video

        Es una historia corta de ciencia ficción, con animación súper detallada tanto en los robots como al protagonista humano. Se nota mucho trabajo en los detalles, por ejemplo el pelo del personaje, texturas y demás.

        Blender es software de gráficos 3D libre usado para crear animaciones, efectos especiales, arte, modelos imprimibles 3D, aplicaciones y videojuegos. Mediante la Fundación Blender, se han producido varios proyectos de alta calidad con el objetivo de demostrar, validar y mejorar las capacidades del software.

        Creo que ya pasó la época en la que me sorprendía por los resultados de trabajar con software libre. Particularmente con proyectos tan maduros como Blender. Se usa a nivel profesional en mega producciones desde hace años, así que ya me acostumbro a ver resultados espectaculares. Pero estas películas son usadas justamente como demo de lo que es capaz de hacer este software, y sin duda no tiene limitaciones por ser software libre.

        Blender es una de esas herramientas que me encantaría ponerme a aprender algún día. Parece una forma muy interesante de canalizar creatividad. Pero pasará a la lista interminable de cosas que quiero aprender…

        Se ha progresado bastante en materia de animación desde Tears of Steel, producido hace casi 10 años! Otras películas abiertas recientes: Spring y Coffee Run.

        El post Charge – Corto animado ciencia ficción hecho en Blender fue publicado originalmente en Picando Código.

        » Leer más, comentarios, etc...

        info.xailer.com

        Xailer 8.1 – Servidor Web

        diciembre 13, 2022 03:46

        Xailer 8.1 incorpora un potente y probado servidor Web. Esta funcionalidad ya se introdujo en versiones anteriores de Xailer, pero ahora podemos asegurar es completamente operativa.

        Ha sido probada de forma exhaustiva por el grupo de Nefele for Harbour, a los cuales les agradecemos todo el trabajo de test que han realizado.

        El servidor web incorporado en Xailer no pretende competir con Apache o IIS, porque entre otras cosas, no incluye soporte de PHP, por ejemplo. Está pensado principalmente para ser usado en todo el proceso de compilación, enlazado y ejecución de archivos CGI en su fase de desarrollo.

        Para probar nuestro servidor tan sólo tiene que elegir como tipo de aplicación, el tipo CGI:

        Y hacer una pequeña aplicación como esta:

        FUNCTION Main()
        
           LOCAL cHtml
        
           TEXT INTO cHtml
           <html>
        
           <head>
           <meta http-equiv="Content-Language" content="es">
           <meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
           <title>Xailer first CGI</title>
           </head>
        
           <body style="background:#6699cc">
        
           <p><b><font size="7">Hello World!</font></b></p>
        
           </body>
        
           </html>
           ENDTEXT
        
           StdOut( cHtml )
        
        
        RETURN NIL

        Cuyo resultado es:

        Futuras versiones de Xailer van a sacar auténtico partido de esta nueva funcionalidad y las grandes oportunidades que nos ofrecen los archivos CGI para desarrollar software web.

        Un cordial saludo

        » Leer más, comentarios, etc...

        info.xailer.com

        Xailer 8.1 – Generador de ayudas

        diciembre 12, 2022 07:34

        La nueva versión incorpora un completo generador de ayudas en formato HTML que permite crear las ayudas de cualquier aplicación de gestión de una forma muy sencilla y potente.

        A partir de la versión 8.1 el control TForm (formulario) incluye una nueva propiedad de nombre XHtmlHelp. Cuando pulse sobre el botón de edición en el inspector de propiedades se le mostrará esta pantalla de edición HTML:

        El ID se corresponde con el identificador de la página de ayuda para ese formulario y el título, es el texto que se mostrará en la parte superior de la pagina. Es obligatorio introducir algún ID, sin embargo, no es obligatorio el título. Si lo deja en blanco, simplemente esa página no tendrá título y el área que ocupa el título desaparecerá.

        Por cada ayuda que usted cree, Xailer creará un archivo con el mismo nombre que el fuente, pero de extensión .XHH en el directorio ‘source‘ con el contenido HTML del documento recién creado, que incluye todas las imágenes que haya incorporado.

        Nuestro sistema de control versiones XVC gestiona de forma automática la existencia de estos archivos y los incluye en el repositorio, sin que tenga que hacer nada más. Si utiliza SVN, deberá añadir los archivos de forma manual a través Tortoise SVN o por línea de comando.

        Pulsando la opción de menú Proyecto->Ayudas del proyecto se nos mostrará el gestor de ayudas, que tiene el siguiente aspecto:

        En el lado derecho se muestran inicialmente todas las ayudas asignadas a cada uno de los formularios y es su responsabilidad el moverlas al lado izquierdo para construir la tabla de contenidos. Una vez movida a la tabla de contenidos (lado izquierdo), podrá utilizar los botones de dirección para subir, bajar o cambiar su anidación de una forma sencilla. No es necesario que todas las páginas de ayuda que usted cree, aparezcan en la tabla de contenidos.

        Observe que existe un botón con unos engranajes que permite compilar las ayudas en un único archivo de extensión .XHLP. Lógicamente, dicho archivo deberá incorporarse como un archivo más de la aplicación que desea distribuir o entregar a su cliente. Xailer crea con todas las ayudas un único archivo comprimido, con un formato propietario, sólo accesible a través de nuestro gestor de ayudas.

        En el editor puede incluir todo el texto de se ayuda. Es posible incorporar imágenes, tablas, estilos de texto y enlaces a páginas Web o cualquier otra página de ayuda. Para esto último deberá utilizar como protocolo la palabra ‘help://‘. Por ejemplo si quisiese poner un enlace a la página ‘NuevoCliente’, escribiría el enlace como: ‘help://Nuevocliente‘. Eso es todo.

        Para que todo nuestro sistema de ayudas funcione sólo hay que añadir una línea de código al inicio de nuestra aplicación que es:

        oApplication:oHelp := THelp():New( "MiFichero.XHLP" )

        Cuando el usuario pulse la tecla F1 en cualquier formulario, se le mostrará la ayuda de ese formulario en concreto. Por ejemplo:

        Ya no hay escusas para hacer aplicaciones 100% profesionales. Espero que os haya gustado.

        Un saludo

        » Leer más, comentarios, etc...

        IOKode

        La inteligencia artificial en el ámbito de la programación

        diciembre 05, 2022 07:43

        El contenido de este blog se escribe en Markdown, pero hace tiempo que llevo dándole vueltas a la idea de cambiar a un formato igualmente ligero, pero algo más potente. Entre otras cosas, me interesa que se trate de un formato extensible. Mi amigo Alejandro Quirós me recomendó que el echase un vistazo a Asciidoc, por lo que he estado durante un tiempo viéndolo y probándolo localmente.

        Asciidoc parece tener todo lo que necesito, es un formato de texto ligero que, por defecto, contiene todos los tipos de elemento de Markdown y algunos más, por ejemplo, los admonitions, que permiten crear cajas de advertencia o tip.

        Echando un ojo atrás a mis entradas, me he dado cuenta que en algunas de ellas hago comentarios sobre diferentes tweets, por lo que he pensado que me vendría bien la capacidad de poder insertar un tweet dentro de una entrada. Asciidoc por defecto no permite insertar un tweet, pero al ser extensible, se puede crear un plugin para ello.

        El software que transforma documentos Asciidoc en documentos HTML se llama Asciidoctor y, está escrito en Ruby. Las extensiones para Asciidoctor por lo tanto se escriben en Ruby.

        ChatGPT

        Salvo que hayas estado viviendo en una cueva los últimos días, sabrás que es ChatGPT, una inteligencia artificial conversational capaz de tener conversaciones a través de un chat.

        ChatGPT es capaz de mantener todo tipo de conversaciones manteniendo el contexto con los mensajes anteriores y, si se le pide, es capaz de programar.

        Dado que yo no tengo experiencia alguna con Ruby, he pensado en pedirle a ChatGPT que se encargue de programar dicha extensión por mí. Tras intercambiar una larga lista de mensajes pidiendole ciertos cambios, avisandole de errores que me ocurrían, y hacer algunos cambios a mano sobre el código generado, la IA ha conseguido escribir un plugin para Asciidoctor completamente funcional para insertar tweets en un documento.

        La conversación con ChatGPT

        Lo primero que he hecho es pedirle en líneas generales lo que necesito, y esto es lo que ha ocurrido:

        Ivan:
        Can you create an Asciidoctor extension in Ruby to embed a tweet inside an Ascidoc document?

        ChatGPT:
        Yes, it is possible to create an Ascidoctor extension in Ruby to embed a tweet inside an Asciidoc document. To do this, you would first need to create a Ruby class that extends the Asciidoctor::Extensions::BlockProcessor class. This class would define a method for processing the tweet block in your Asciidoc document.

        To embed the tweet, you would need to use the Twitter API to fetch the tweet by its ID and then generate the necessary HTML code to display the tweet on the page. You can then use the Asciidoctor::Document#create_block method to create a new block containing the HTML code for the tweet and add it to the document.

        Here is an example of how this might look:

        class TweetBlock < Asciidoctor::Extensions::BlockProcessor
          use_dsl
          named :tweet
          on_context :open
        
          def process parent, target, attrs
        	tweet_id = target
        	
            # Fetch the tweet using the Twitter API
            tweet = TwitterClient.fetch_tweet(tweet_id)
        
            # Generate the HTML code for the tweet
            html = generate_tweet_html(tweet)
        
         	# Create a new block containing the tweet HTML and add it to the document
        	create_block parent, :pass, html, attrs
          end
        
          def generate_tweet_html tweet
            # todo
          end
        end

        Once you have created this class, you can register it with Asciidoctor by adding it to the list of extensions in your Asciidoctor configuration. This will allow you to use the tweet block in your Asciidoc document to embed tweets.

        Here is an example of how you might use this extension in your Asciidoc document:

        tweet::1234567890[]

        This would create a block containing the tweet with the ID 1234567890, as fetched from the Twitter API.

        Por lo que podemos ver, en este código ocurren tres cosas:

        1. Utilizando el API de Twitter para Ruby, obtiene el contenido del tweet.
        2. Le pasa el tweet a una función para que lo transforme en HTML.
        3. Crea un bloque de Asciidoc con el contenido HTML.

        Analicemos el código un poco. Una de las primeras cosas que hace es pasarle el ID directamente al API de Twitter, pero no comprueba que sea válido (númerico), así que se lo hago saber a la IA.

        Ivan:
        tweet_id should contains the ID of the tweet, but this script doesn't validate that is a valid tweet ID. Can you update it to add this validation?

        ChatGTP:
        Yes, you are correct that the current example code does not validate that the id attribute contains a valid tweet ID. This could lead to errors if an invalid ID is provided.

        To add this validation, you can use the Twitter API to check if a tweet with the specified ID exists. If the tweet does not exist, you can raise an error to let the user know that the ID is invalid.

        Here is an example of how you might update the process method to add this validation:

        def process parent, target, attrs
          tweet_id = target
        
          # Validate that the ID is a valid tweet ID
          unless TwitterClient.tweet_exists?(tweet_id)
            raise  "Invalid tweet ID: #{tweet_id}"
          end
        
          # Fetch the tweet using the Twitter API
          tweet = TwitterClient.fetch_tweet(tweet_id)
          
          # Generate the HTML code for the tweet
          html = generate_tweet_html(tweet)
          
          # Create a new block containing the tweet HTML and add it to the document
          create_block  parent, :pass, html, attrs
        end

        This code will check if a tweet with the specified ID exists using the TwitterClient.tweet_exists? method. If the tweet does not exist, it will raise an error with the invalid tweet ID. Otherwise, it will proceed to fetch the tweet and generate the HTML code to embed it in the document.

        Yo esperaba que validase que sea numérico pero, sorprendentemente, hace uso del API de Twitter para comprobar si existe. Aunque es una respuesta correcta, esto conlleva una llamada extra al API, que tiene un límite de peticiones. Lo ideal para mi es que la ejecución del script utilice únicamente una llamada y no varias. Se lo comento y le soy más específico con la validación que necesito.

        Ivan:
        That's a correct validation, but it requires to make an extra call to Twitter API, that is rate limited. A tweet ID is ever numeric. Can you update the script to validate that the tweet ID is numeric instead of checking if the tweet exists?

        ChatGPT:
        Yes, you are correct that making an extra call to the Twitter API to validate the tweet ID can be wasteful if the ID is not valid, as it counts against your API rate limit. Additionally, tweet IDs are always numeric, so you can use this information to validate the ID without making an extra API call.

        To do this, you can simply check if the tweet_id variable contains a numeric value, and raise an error if it does not. Here is an example of how you might update the process method to do this:

        def process parent, target, attrs
          tweet_id = target
        
          # Validate that the ID is numeric
          unless tweet_id.is_a?(Numeric)
            raise  "Invalid tweet ID: #{tweet_id}"
          end
        
          # Fetch the tweet using the Twitter API
          tweet = TwitterClient.fetch_tweet(tweet_id)
         
          # Generate the HTML code for the tweet
          html = generate_tweet_html(tweet)
         
          # Create a new block containing the tweet HTML and add it to the document
          create_block  parent, :pass, html, attrs
        end

        This code will check if the tweet_id variable contains a numeric value using the Numeric class, and raise an error if it does not. This will ensure that the tweet ID is valid without making an extra API call.

        Como no tengo experiencia progrmando con Ruby, desconozco que hace el método is_a?, por lo que le pregunto directamente.

        Ivan:
        What is_a? method returns?

        ChatGPT:
        The is_a? method in Ruby returns a boolean value indicating whether the object on which it is called is an instance of the specified class or one of its descendants.

        For example, if you have the following code:

        x = "hello"
        
        if x.is_a?(String)
          puts "x is a string"
        end

        This will output x is a string to the console, because the x variable is an instance of the String class.

        On the other hand, if you have the following code:

        x = 3
        
        if x.is_a?(String)
          puts "x is a string"
        end

        This will not output anything to the console, because the x variable is not an instance of the String class (it is an instance of the Fixnum class, which is a descendant of the Numeric class).

        In general, the is_a? method is useful for checking the type of an object at runtime and taking different actions based on that type.

        Tal como ha dicho, equivaldría al operador instanceof de Java o is de C#. Debemos asumir que Asciidoctor enviará el ID como un string (así me encargaré yo mismo cuando cree los documentos Asciidoc), por lo que le informo de ello.

        Ivan:
        Ok. We should asume that tweet_id is ever an string. Can you check if the string is an integer value?

        ChatGPT:
        Yes, you are correct that the tweet_id variable in the process method is always a string, even if it contains a numeric value. To check if this string contains an integer value, you can use the String#to_i method, which converts the string to an integer if it contains a valid integer value, or returns 0 if it does not.

        Here is an example of how you might update the process method to use the String#to_i method to check if the tweet_id variable contains a valid integer value:

        def process parent, target, attrs
          tweet_id = target
        
          # Validate that the ID is an integer value
          unless tweet_id.to_i != 0
            raise  "Invalid tweet ID: #{tweet_id}" 
          end
        
          # Fetch the tweet using the Twitter API
          tweet = TwitterClient.fetch_tweet(tweet_id)
         
          # Generate the HTML code for the tweet
          html = generate_tweet_html(tweet)
         
          # Create a new block containing the tweet HTML and add it to the document
          create_block parent, :pass, html, attrs
        end

        In this code, the tweet_id.to_i != 0 expression will check if the tweet_id string contains a valid integer value. If it does not, it will raise an error with the invalid tweet ID. Otherwise, it will proceed to fetch the tweet and generate the HTML code to embed it in the document.

        La IA ha entenido perfectamente lo que le he querido decir y corrige de nuevo el script, además me explica como funciona. ¡Perfecto!

        Nótese que en cada cambio que está haciendo, además está actualizando los comentarios para que estos no queden desactualizados. ¡Un 10!

        Una vez hecha la validación del tweet, vamos a continuar. Le voy a pedir que implemente el método generate_tweet_html que me había dejado únicamente con un TODO.

        Ivan:
        Please, implement generate_tweet_html method.

        ChatGPT:
        To implement the generate_tweet_html method, you will need to use the Twitter API to fetch the tweet by its ID and then generate the necessary HTML code to display the tweet on the page.

        Here is an example of how you might implement the generate_tweet_html method:

        def generate_tweet_html(tweet)
          # Use the Twitter API to fetch the tweet by its ID
          tweet = TwitterClient.fetch_tweet(tweet_id)
        
          # Generate the HTML code for the tweet
          html = <<~HTML
            <blockquote class="twitter-tweet">
              <p>#{tweet.text}</p>
              <a href="https://twitter.com/#{tweet.user.screen_name}/status/#{tweet.id}">
                #{tweet.created_at.strftime("%b %d %Y")}
              </a>
            </blockquote>
          HTML
        end

        In this code, the generate_tweet_html method takes a tweet object as an argument. It then uses the Twitter API to fetch the tweet by its ID and generates the necessary HTML code to display the tweet on the page. The generated HTML uses the <blockquote> element to display the tweet text and the <a> element to link to the tweet on Twitter.

        Once you have implemented the generate_tweet_html method, you can use it in the process method to generate the HTML code for the tweet and add it to the document.

        Here is an example of how you might use the generate_tweet_html method in the process method:

        def process parent, target, attrs
          tweet_id = target
        
          # Validate that the ID is an integer value
          unless tweet_id.to_i != 0
            raise  "Invalid tweet ID: #{tweet_id}" 
          end
        
          # Fetch the tweet using the Twitter API
          tweet = TwitterClient.fetch_tweet(tweet_id)
         
          # Generate the HTML code for the tweet
          html = generate_tweet_html(tweet)
         
          # Create a new block containing the tweet HTML and add it to the document
          create_block  parent, :pass, html, attrs
        end

        This code will use the generate_tweet_html method to generate the HTML code for the tweet, and then create a new block containing the HTML and add it to the document. This will allow the tweet to be displayed on the page when the Asciidoc document is rendered.

        Aquí ocurren varias cosas. La primera de ella, es que dentro de este método, está usando el API de Twitter para obtener de nuevo los datos que ya conocía previamente, pues son los que les viene por parámetro. Yo mismo me encargo de borrar la primera línea.

        También se puede observar, la IA me ha devuelto de nuevo el contenido del método process sin ningún cambio. Esto es una constante lo largo de toda la conversación, por lo que a partir de aquí, obmitiré esa parte de sus respuestas.

        Mi siguiente problema es que para hacer uso del API de Twitter para Ruby, este require instalarlo a través de una gema. Cargar gemas desde Asciidoc, aunque es posible, es relativamente complicado, por lo que le pido a la IA que por favor lo reemplace haciendo directamente llamadas HTTP al API de Twitter.

        Ivan:
        I cannot load any gem, so I need to use only libraries available in Ruby core. Can you replace TwitterClient by a method that make an HTTP request to the Twitter API?

        ChatGTP:
        Yes, it is possible to make an HTTP request to the Twitter API using only the libraries available in the Ruby core. To do this, you can use the Net::HTTP class to make an HTTP request to the Twitter API and parse the response.

        Here is an example of how you might use the Net::HTTP class to fetch a tweet by its ID:

        def fetch_tweet tweet_id
          # Build the URI for the Twitter API endpoint
          uri = URI("https://api.twitter.com/1.1/statuses/show.json?id=#{tweet_id}")
        
          # Set the authorization header for the request
          headers = {
            'Authorization' => 'Bearer <YOUR_BEARER_TOKEN>'
          }
        
          # Make the request and parse the JSON response
          response = Net::HTTP.get(uri, headers)
          tweet = JSON.parse(response)
        
          # Return the tweet object
          tweet
        end

        In this code, the fetch_tweet method takes a tweet_id as an argument and uses the Net::HTTP class to make an HTTP GET request to the Twitter API endpoint for fetching a tweet by its ID. It sets the appropriate authorization header for the request and parses the JSON response into a Ruby object. It then returns the tweet object, which can be used to generate the HTML code for the tweet.

        Once you have implemented this method, you can use it in the generate_tweet_html method to fetch the tweet and generate the HTML code for it. Here is an example of how you might do this:

        def generate_tweet_html(tweet_id)
          # Use the Twitter API to fetch the tweet by its ID
          tweet = fetch_tweet(tweet_id)
        
          # Generate the HTML code for the tweet
          html = <<~HTML
            <blockquote class="twitter-tweet">
              <p>#{tweet.text}</p>
              <a href="https://twitter.com/#{tweet.user.screen_name}/status/#{tweet.id}">
                #{tweet.created_at.strftime("%b %d %Y")}
              </a>
            </blockquote>
          HTML
        end

        This code will use the fetch_tweet method to fetch the tweet by its ID and generate the HTML code for it using the <blockquote> and <a> elements, as in the previous example.
        Como se puede ver, no sólo me ha implementado método fetch_tweet, si no que además me ha modificado los métodos generate_tweet_html y process para que hagan uso de estos.

        Once you have implemented these methods, you can use them in the process method to fetch the tweet and generate the HTML code for it, and add it to the document. Here is an example of how you might do this:

        def process parent, target, attrs
          tweet_id = target
        
          # Generate the HTML code for the tweet
          html = generate_tweet_html(tweet)
        
          # Create a new block containing the tweet HTML and add it to the document
          create_block parent, :pass, html, attrs
        end

        Aquí no sólo me ha implementado un método para obtener el contenido del tweet, si no que además ha modificado el método generate_tweet_html para que lo llame, y ha eliminado esta llamada del método process. No soy capaz de entender a qué se debe ese cambio en la organización del código, pero no me preocupa y sigo con lo mío.

        Este método que ha implementado utiliza el API 1.1 de Twitter. Para acceder al API 1.1 necesitas hacer una petición formal a Twitter y pedir que te habiliten el acceso. Mi única opción es hacer uso del API 2.0 de Twitter, por lo que se lo comento. Dicho y hecho.

        Ivan:
        Can you use Twitter API 2.0 in fetch_tweet method?

        ChatGPT:
        Yes, it is possible to use the Twitter API 2.0 to fetch a tweet by its ID in the fetch_tweet method. To do this, you will need to update the URI and the authorization header for the request to use the new API endpoint and authentication scheme.

        Here is an example of how you might update the fetch_tweet method to use the Twitter API 2.0:

        def fetch_tweet tweet_id
          # Construct the URL for the Twitter API request
          tweet_url = "https://api.twitter.com/2/tweets/#{tweet_id}"
          uri = URI.parse(tweet_url)
        
          # Create the HTTP request and set the necessary headers
          request = Net::HTTP::Get.new(uri)
          request['Authorization'] = 'Bearer <YOUR_BEARER_TOKEN>'
          request['Accept'] = 'application/json'
        
          # Make the HTTP request and retrieve the response
          response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
            http.request(request)
          end
        
          # Parse the JSON response
          tweet = JSON.parse(response.body)
        
          # Return the tweet object
          tweet
        end

        In this code, the fetch_tweet method uses the new Twitter API 2.0 endpoint for fetching a tweet by its ID, and sets the appropriate authorization header for the request. It then makes the request and parses the JSON response into a Ruby object, just like in the previous example.

        Como se puede observar, en el header de autorización me pone un string que yo debería de reemplazar por mi bearer token. Le pido que lo obtenga directamente desde una variable de entorno. Otra vez más, dicho y hecho.

        Ivan:
        Can you replace 'Bearer <YOUR_BEARER_TOKEN>' string by an environment variable?

        ChatGPT:
        Yes, it is possible to use an environment variable to store the bearer token for the Twitter API instead of hardcoding it in the fetch_tweet method. To do this, you can use the ENV global variable in Ruby to access the value of the environment variable.

        Here is an example of how you might update the fetch_tweet method to use an environment variable for the bearer token:

        def fetch_tweet tweet_id
          # Construct the URL for the Twitter API request
          tweet_url = "https://api.twitter.com/2/tweets/#{tweet_id}"
          uri = URI.parse(tweet_url)
        
          # Create the HTTP request and set the necessary headers
          request = Net::HTTP::Get.new(uri)
          request['Authorization'] = "Bearer #{ENV['TWITTER_BEARER_TOKEN']}"
          request['Accept'] = 'application/json'
        
          # Make the HTTP request and retrieve the response
          response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
            http.request(request)
          end
        
          # Parse the JSON response
          tweet = JSON.parse(response.body)
        
          # Return the tweet object
          tweet
        end

        In this code, the fetch_tweet method uses the ENV global variable to access the value of the TWITTER_BEARER_TOKEN environment variable and use it as the bearer token for the request. This allows you to store the bearer token in an environment variable and use it in the code without hardcoding it.

        Ahora le pido a la IA que modifique el método fetch_tweet para que lance un error si no existe.

        Ivan:
        Can you raise an error if tweet id doesn't exists?

        ChatGPT:
        Yes, it is possible to raise an error if the tweet ID does not exist by checking the response from the Twitter API. If the tweet does not exist, the API will return a response with a 404 status code and an error message in the body. You can check for this response in the fetch_tweet method and raise an error if it is received.

        Here is an example of how you might update the fetch_tweet method to raise an error if the tweet ID does not exist:

        def fetch_tweet tweet_id
          # Construct the URL for the Twitter API request
          tweet_url = "https://api.twitter.com/2/tweets/#{tweet_id}"
          uri = URI.parse(tweet_url)
        
          # Create the HTTP request and set the necessary headers
          request = Net::HTTP::Get.new(uri)
          request['Authorization'] = "Bearer #{ENV['TWITTER_BEARER_TOKEN']}"
          request['Accept'] = 'application/json'
        
          # Make the HTTP request and retrieve the response
          response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
            http.request(request)
          end
          
          # Check the status code of the response 
          if response.code == '404'
            raise  "Tweet not found: #{tweet_id}"
          end
        
          # Parse the JSON response
          tweet = JSON.parse(response.body)
        
          # Return the tweet object
          tweet
        end

        In this code, the fetch_tweet method checks the status code of the response from the Twitter API. If the status code is 404, it means that the tweet does not exist, and the method raises an error with the message "Tweet not found: #{tweet_id}". If the tweet exists, the method parses the JSON response and returns the tweet object as before.

        Esto está bien para cuando el tweet no existe, sin embargo podría haber otros motivos por los que la respuesta no sea correcta, como usuario no autenticado, o API caída. Le pido a la IA que lo compruebe.

        Ivan:
        That's is correct, but it only checks that the tweet doesn't exists. There are any other reasons because the API can returns a response that is not 200, like unauthorised. Can you raise an error if response is not OK?

        ChatGPT:
        Yes, it is possible to raise an error if the response from the Twitter API is not 200 by checking the status code of the response. If the response has a different status code, it means that there was an error and you should raise an error with the appropriate message.

        Here is an example of how you might update the fetch_tweet method to raise an error if the response is not 200:

        def fetch_tweet tweet_id
          # Construct the URL for the Twitter API request
          tweet_url = "https://api.twitter.com/2/tweets/#{tweet_id}"
          uri = URI.parse(tweet_url)
        
          # Create the HTTP request and set the necessary headers
          request = Net::HTTP::Get.new(uri)
          request['Authorization'] = "Bearer #{ENV['TWITTER_BEARER_TOKEN']}"
          request['Accept'] = 'application/json'
        
          # Make the HTTP request and retrieve the response
          response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
            http.request(request)
          end
          
          # Check the status code of the response 
          if response.code != '200'
            if response.code == '404'
              raise "Error: Tweet not found with ID #{tweet_id}"
            else
              raise "Error: #{response.code} #{response.message}"
            end
          end
        
          # Parse the JSON response
          tweet = JSON.parse(response.body)
        
          # Return the tweet object
          tweet
        end

        Ahora comprueba que el la respuesta sea sí o sí 200, y no sólo que no sea 404. Además, manteniendo el contexto anterior, ha mantenido el caso de que pueda ser 404. ¡Bravo!

        Llegados a este punto, parece que hay una extensión de Asciidoctor que podría funcionar. Hay algunos detalles más que pulir, como por ejemplo que la plantilla que genera el HTML espera un objeto como el que proporciona el método TwitterClient.fetch_tweet que hemos visto al principio, en vez de un Hash que devuelve el método JSON.parse. He intentado que lo arregle, pero cada cambio que añadía la rompía más, y eso me ha tocado arreglarlo a mí.

        También le ha faltado añadir los imports necesarios para que pueda encontrar las clases JSON, Net::HTTP entre otras. La comprobación de que el ID del tweet sea un entero tampoco funcionaba. Tras arreglar todos estos detallitos, algunos por mí mismo, otros con ayuda de la IA, tengo una extensión para Asciidoctor totalmente funcional.

        El código de la extensión está disponible en un repositorio que he creado en GitHub.

        Conclusiones

        Aunque he tenido que hacer algunos cambios a mano, la IA ha hecho la mayor parte del trabajo. Yo no sé nada de Ruby y hacer algo así me habría costado varias horas o incluso días, entre aprender la sintaxis de Ruby, buscar como se hacen llamadas HTTP, como se parsea un JSON, como se comprueba una expresión regular, etc.

        En cambio, ordenando a la IA y, con algunas modificaciones menores propias, he conseguido mi objetivo en una hora.

        El ahorro de tiempo y trabajo que esta tecnología supone hoy en día es algo que no tiene precedentes.

        » Leer más, comentarios, etc...

        IOKode

        ¿Es necesario aprender programación orientada a objetos para empezar a programar?

        diciembre 03, 2022 02:17

        Hace unos días, me crucé con el siguiente tweet que preguntaba “¿Crees que sea necesario aprender POO (Programación Orientada a Objetos) para empezar a programar?”, a lo que respondí que no. En esta entrada quiero extender esta respuesta.

        Para reflexionar sobre ello, primero necesitamos definir qué es programar y qué es la programación orientada a objetos.

        ¿Qué es programar?

        Programar se puede definir como escribir programas que ejecutará una computadora.

        La base de un programa de computadora es secuencia, selección y repetición. Todo programa durante la ejecución se reduce a ello y, aquellos diseñados con un lenguaje orientado a objetos, no son una excepción.

        ¿Qué es la programación orientada a objetos?

        La programación orientada a objetos es un paradigma de programación que impone cierta disciplina sobre la organización del código y la transferencia del control, pero es sólo eso, una disciplina organizativa. Al final, se ejecutan los métodos, no las clases ni los objetos.

        Aprender a programar

        Hechas les definiciones, toca reflexionar sobre ello cuando se está empezando a aprender a programar.

        Cuando una persona quiere aprender a programar, es mucho más importante que aprenda primero los fundamentos más básicos y, luego ya, podrá pasar (o no) a aprender sobre el paradigma de programación orientada a objetos.

        Sobre estos fundamentos, me gustaría hacer especial mención a la encapsulación y al polimorfismo por su estrecha relación con la programación orientada a objetos.

        Los lenguajes orientados a objetos como Java o C# facilitan muchísimo la implementación de encapsulamiento y polimorfismo y, aunque son conceptos fundamentales e importantes, no es necesario aprender POO para entenderlos ni para aplicarlos. Ya en el lenguaje C, que no es orientado a objetos, se podía hacer encapsulamiento y polimorfismo, aunque no fuese algo tan trivial y cómodo de hacer como en Java o C#.

        » Leer más, comentarios, etc...

        Meta-Info

        ¿Que es?

        Planeta Código es un agregador de weblogs sobre programación y desarrollo en castellano. Si eres lector te permite seguirlos de modo cómodo en esta misma página o mediante el fichero de subscripción.

        rss subscripción

        Puedes utilizar las siguientes imagenes para enlazar PlanetaCodigo:
        planetacodigo

        planetacodigo

        Si tienes un weblog de programación y quieres ser añadido aquí, envíame un email solicitándolo.

        Idea: Juanjo Navarro

        Diseño: Albin