Picando Código

Recomendando blogs en español - Parte II

noviembre 27, 2025 10:00

Recomendaciones de blogs

Hoy tengo un par de blogs en español más para recomendar. Ver Recomendando blogs en español parte 1

Interlan

Interlan es el sitio personal y profesional de Drk0027:

Soy un desarrollador web y he trabajado con múltiples herramientas, lenguajes de programación y Frameworks. Aquí les comparto mis capacidades y experiencia.

Este blog lo conocí gracias a un pingback (¿o trackback?) en el post Aventuras de resaltado de sintaxis en el blog. En ese post escribí sobre mis experiencias mostrando código con resaltado de sintaxis en Picando Código. Después de usar un plugin por años, decidí usar HTML puro pasando el código por un resaltador de sintaxis.

El pingback vino desde Articulo: Resaltado de Sintaxis de Código en WordPress.

En el artículo en cuestión comenta su propia experiencia, viviendo el mismo problema: WordPress no tiene soporte nativo para resaltado de código, plugins que se quedan obsoletos y el trabajo de hacerlo todo a mano. Al final creo que ambos nos quedamos con la misma solución de usar HTML directo, y no enfocarnos tanto en actualizar el código fuente de posts viejos que usaban un plugin para esto.

Gracias Drk0027 por el pingback, espero que con esta entrada te llegue uno también en respuesta a tu artículo. Qué bueno seguir usando estas tecnologías como el pingback que permiten la interacción entre distintos sitios.

También pueden seguir a Drk0027 en Mastodon.

Sidiostedalimones

Sidiostedalimones es el blog de Chuck:

Soy un diseñador gráfico que aprendió a maquetar, y 27 años después soy un desarrollador front con mucho cuidado por el detalle y muy pendiente de la semántica y la accesibilidad. Sin embargo, aquí me gustaría escribir sobre temas más del día a día, series o pelis que haya visto, ese videojuego en el que ando atascado y algún que otro dibujo.

Este blog lo agregué a mi lista de feeds RSS hace un tiempo por medio de la serie de entrevistas de tecnolocuras. Chuck escribe sobre varias cosas como dice la descripción. Comparte experiencias y publica reseñas de películas, el fediverso, programación y más. Entre sus varios artículos interesantes se encuentra por ejemplo este sobre VS Codium que empieza con "Cada vez es más difícil tener cosas bonitas, a veces parece que ya es imposible", y comparto el cinismo...

Además, el blog tiene un blogroll, que me hizo visitar más blogs en español y me hizo actualizar mi propio blogroll acá en mi blog sacando algunos enlaces que ya no se actualizan o están dados de baja.

También pueden seguir a Chuck en Mastodon.

Fuente de la imagen original: Hall of Quiet Study, Anónimo, siglo 19 China. Bajo dominio público.

El post Recomendando blogs en español - Parte II fue publicado originalmente en Picando Código.

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

Picando Código

Hyrule Warriors: Age of Imprisonment - Nintendo Switch 2

noviembre 25, 2025 12:00

Hyrule Warriors: Age of Imprisonment es el primer título de la saga Hyrule Warriors exclusivo para Nintendo Switch 2. Fue desarrollado por Koei Tecmo bajo la supervisión de Eiji Aonuma y Nintendo. El estilo de juego está basado en Dynasty Warriors, otra serie creada por Koei Tecmo. El concepto básico es manejar un personaje en un mundo 3D con vista en tercera persona enfrentando hordas de enemigos.

Hyrule Warriors: Age of Imprisonment

En base a eso, se construye todo un género parte de la familia hack and slash. Contamos con distintos personajes, combos, armas diversas y enemigos de todo tipo, incluyendo jefes que exigen más daño y esfuerzo. También dentro de cada mapa tenemos que implementar un poco de estrategia con más de un personaje a la vez para lograr distintos objetivos. El juego puede aparentar ser bastante sencillo o monótono, pero tiene varias características que hacen que uno se enganche y vuelva a jugar varias veces.

Un aspecto que hace bastante especial a Hyrule Warriors es la historia. Ya con el primer Hyrule Warriors (originalmente en Wii U, 3DS y eventualmente una versión definitiva en Nintendo Switch) Koei Tecmo logró contar una historia muy interesante y entretenida para seguidores de The Legend of Zelda. Si bien ese primer título está fuera de la línea de tiempo "canon" de Zelda, en general hacen mejor trabajo que Nintendo en ese aspecto.

Después vino Hyrule Warriors: Age of Calamity. Eiji Aonuma se acercó a Koei Tecmo para usar este formato para contar los eventos de La Gran Calamidad, que conocimos a través de flashbacks en The Legend Of Zelda: Breath of the Wild. En esa ocasión trabajaron en conjunto con el equipo Zelda en Nintendo, usando los mismos gráficos, voces y personajes. La historia estaba directamente conectada a otro título.

Empecé Hyrule Warriors: Age of Calamity en Nintendo Switch. Después de mucho tiempo sin jugarlo volví, pero con el anuncio del Nintendo Switch 2, su mayor capacidad y compatibilidad con juegos de Nintendo Switch original, lo volví a dejar en pausa. Finalmente lo terminé en Nintendo Switch 2, donde noté una mejora importante en el rendimiento. Al ser más potente la consola y poder jugar los juegos del Switch original, se nota un aumento del rendimiento, con mejores frames por segundo y demás.

Terminé la historia y muchas de las misiones opcionales. No llegué a terminarlo 100%, pero eso es mucho trabajo. Estos juegos tienen abundante contenido, y terminarlos por completo es una odisea que sólo los más dedicados van a poder completar. Hay contenido para rato.

Hyrule Warriors: Age of Imprisonment

Tenía mucha expectativa por este juego después de haber visto los trailers para Nintendo Switch 2. La fluidez de los gráficos y la cantidad de enemigos en pantalla a la vez, lo hacían ver muy bien.

Mis primeras impresiones fueron muy positivas. Estos juegos están hechos para jugar en sistemas con hardware de más alto rendimiento como el Switch 2. Los 60 frames por segundo hacen una experiencia totalmente distinta, y hace difícil volver a los anteriores (que se fijan en 30 frames por segundo). Además se ve tan bien como prometían los tráilers. La sensación que tuve en las primeras escenas donde manejamos a Zelda recién llegada al pasado, con los modelos de los personajes mas "grandes" en pantalla, me hicieron pensar "esto es un preview del juego de Zelda que podemos llegar a tener en Nintendo Switch 2".

Hyrule Warriors: Age of Imprisonment - Zelda

Hyrule Warriors: Age of Imprisonment - Zelda con Rauru y Sonia -antiguos reyes de Hyrule- de fondo

El juego se ve y se juega espectacular. En las más de 30 horas iniciales del juego donde terminé la historia principal, noté una baja en la cantidad de frames apenas 2 o 3 veces en momentos muy intensos (muchos enemigos, varios personajes, muchas partículas y efectos de luz en pantalla), pero no duraron más de uno o dos segundos. Es sumamente fluido, los tiempos de carga son rápidos, y todo se ve muy lindo.

Los paisajes son familiares si ya jugamos Breath Of The Wild, Age of Calamity o Tears of the Kingdom. El mapa cuenta con las mismas tres capas que vimos en Tears of the Kingdom: El cielo, la tierra y las profundidades. Así que hay misiones en esos distintos ambientes y los varios biomas de la superficie. También vuelven los elementos conocidos como los constructs, el gloom y dispositivos Zonai.

La cantidad de enemigos en pantalla sumada a las escenas cinematográficas dan una verdadera sensación de estar participando de una batalla épica contra el mal. La ambientación que logran con las capacidades del Switch 2, sumado a la música e historia, hacen sentir mucho más la magnitud de la guerra.

El juego encontró archivos guardados de The Legend Of Zelda: Tears of the Kingdom y Hyrule Warriors: Age of Calamity en mi Switch. Con eso nos regalan un par de armas de yapa. Está bueno cuando los desarrolladores hacen estas cosas, agrega a esa sensación de que estos títulos están conectados.

Personajes

La protagonista es Zelda. Ya en versiones anteriores se le daba bastante importancia y está bueno poder jugar como ella y ver el protagonismo que tuvo en la historia previa a Tears of the Kingdom. Tiene poderes mágicos de luz y su tradicional arco y flecha. Es uno de los personajes que estuve usando a lo largo de toda la historia, y sus poderes son particularmente fuertes contra enemigos con gloom, que viene bien hacia el final.

Hay que señalar que igual el juego sufre un poco del "no podemos hacer un Zelda sin Link", al igual que pasa con The Legend Of Zelda: Echoes of Wisdom. Si bien Zelda es el personaje principal, y Link no es parte de la historia directamente, tenemos un Link "por las dudas", para no salir del todo de la fórmula. En Echoes, Zelda tiene un poder especial (limitado) que básicamente la transforma en Link (además de jugar con Link en unas pocas partes). En esta ocasión, el Construct se juega como una versión actualizada del Link del Hyrule Warriors anterior. Sus poderes son bastante similares, y se maneja parecido, tanto usando una espada como una lanza. Y no digo más para no contar parte de la historia.

Por cierto, Echoes of Wisdom es otro juego que obtuvo una actualización para Switch 2, ¡debería probarla!

Otro de los personajes que me gustan es Calamo. Es un guerrero korok que lucha junto al Construct y su arco emula el de una película (buddy picture) de pareja dispareja que terminan siendo los mejores amigos. Calamo empieza su aventura queriendo viajar por Hyrule para encontra un lugar donde literalmente "echar raíces" para convertirse en el árbol más grande del reino y crear un bosque nuevo. Es un personaje bastante simpático, con diálogos divertidos, y no entiendo cómo Nintendo no lo usó para crear un amiibo nuevo.

Vuelven los Goron, Zoras, Rito, Koroks, Hylians, Zonai, y están presentes los antiguos reyes de Hyrule Rauru y Sonia. También podemos jugar como Mineru, la hermana de Rauru que se encarga de investigar a los constructs y los usa como armas en diversas formas que recuerdan a la creación de dispositivos con Ultrahand. Cada especie tiene un estilo único y armas específicas. Me pareció un poco mucho la cantidad de personajes disponibles. Intenté mantener en un nivel alto a un equipo variado, pero tenemos dos personajes de cada especie, así que subir de nivel a todos llevaría cientos de horas y no sé si no se volvería demasiado repetitivo. Podemos subir de nivel entrenando a los personajes, pero eso exige rupíes que no crecen en los árboles (pero sí se encuentran escondidos al azar en cajas y jarros por el reino). 

Durante las batallas tenemos oportunidades para que los personajes interactúen entre sí, lo que sube un medidor de afinidad que permite combinar dos personajes en un ataque especial. Esto está muy bueno, y combinar nuestro guerrero con otros nos da distintos resultados divertidos de ver. Un aspecto más de lo intenso que es el juego en cuanto a contenidos.

El gameplay puede aparentar ser repetitivo, y de repente un poco lo es. ¡Pero es divertido! Y está la sensación de progreso al ir destrabando cosas en el mapa y cumplir con misiones y objetivos. Para el ojo no entrenado se puede ver el gameplay como un "button mashing" donde simplemente apretamos todos los botones hasta ganar. Pero esto desacredita la profundidad de los estilos de juego sumado a la estrategia y diversion que aporta la diversidad de escenarios, personajes y misiones. Algo que recuerdo repetitivo del título anterior fue la cantidad de Moblins que tuve que vencer. En este título se siente un poco más variado, como que se equilibra más la diversidad de los jefes con Moblins, Bokoblins, Hynoxes y Lynels.

YouTube Video

Star Fox

A veces otros nos proveen lo que Nintendon't. El último título en la saga de Star Fox fue Star Fox Zero para Wii U. Como con otros títulos de esta serie, Nintendo lo usó como demo técnico para una tecnología nueva, en este caso el gamepad del Wii U. Resultó que jugar un juego con dos pantallas con distintas vistas a la vez, y controles por movimiento, no era tan cómodo o divertido. Fracasó tanto comercialmente como en la crítica. Tampoco le pusieron demasiado esfuerzo a la historia, básicamente reciclaron Star Fox 64.

Personalmente disfruté del juego porque me encanta Star Fox. Pero reconozco que el control es horrible, recuerdo particular frustración en los últimos jefes donde se ponía difícil la cosa. Si sacaran una versión actualizada para jugar con controles tradicionales, no estaría mal. Pero para eso tuvimos Star Fox 64 3D en 3DS.

Después de eso, tuvimos Starlink: Battle for Atlas. Un juego que pre-ordené para Nintendo Switch ni bien salió, y pocos meses después se encontraba por un 10% del precio en todos lados porque no lo compraba nadie. El tema es que la versión para Nintendo traía una nave Arwing con Fox McCloud y misiones con Star Fox. Jugué bastante a ese juego, y las misiones de Star Fox estaban muy bien. Es más, escribiendo esto me dieron ganas de volver a jugarlo...

El tema es que desde entonces no hemos tenido nada nuevo de Star Fox por Nintendo. Pero Koei Tecmo encontró una forma de básicamente reimplementar Star Fox en el universo de Zelda. Tenemos varias misiones con Calamo y Knight Construct en las que Calamo pilotea el Construct en forma de nave/avión/Arwing. Estas misiones son muy buenas y muy similares a Star Fox. Podemos hacer un barrel roll para esquivar ataques, fijar la mira a enemigos con un ataque más fuerte, y más. A veces cambia de cámara y lo hace como un shooter 2D, y encima agrega otras mecánicas tradicionales del juego. Muy buenas las pantallas de este estilo, pero me dejaron ansiando más un Star Fox nuevo. Nintendo, ¡queremos Star Fox Armada!

La historia

La historia del juego relata los acontecimientos que llevaron a los eventos de The Legend Of Zelda: Tears of the Kingdom. Zelda viaja al pasado y conoce a los reyes antiguos de Hyrule. Si bien mi memoria no funciona tan bien, por lo que no recuerdo la historia de cada uno. Pero de todas formas la historia de los Zelda no es necesariamente algo en lo que Nintendo invierta demasiado tiempo. Son el tipo de equipo que empieza con la forma de jugar y va armando la historia alrededor de eso en base a las necesidades del juego. Y la línea temporal de tiempo de Zelda es un despelote. Nintendo no tiene vergüenza de rebootear y abrir otra rama de leyenda en cada título nuevo.

No voy a contar muchos detalles, porque creo que gran parte del disfrute de los Hyrule Warriors es ver las escenas cinematográficas y ver cómo se va desarrollando. Si bien más o menos tenemos una idea del desenlace, está muy bueno cómo se llega a eso. Se sintió más conectada a Tears of the Kingdom que Age of Calamity a Breath of the Wild. Esto puede que tenga que ver con los tiempos en los que jugué cada uno (Tears of the Kingdom lo terminé hace no mucho), pero también sin entrar en terreno de spoilers, este juego es más "fiel" al desenlace.

Al ver los créditos tras terminar la historia principal, descubrí que el juego está disponible en Español de Latinoamérica. Ni siquiera me había fijado al empezarlo, así que decidí probarlo. Están los textos en español, pero también las voces de los personajes. Probé jugar un par de misiones en español, pero volví al inglés, no me gustaron mucho las voces. Creo que el problema que tengo con los doblajes al español es que intentan hacer un idioma neutro que no me suena natural. Latinoamérica es muy grande, y tenemos tantas variedades de español como de personas. Y tratar de hacer algo neutral termina sonando raro. De repente estaría mejor que cada personaje tuviera un acento natural, ahí de repente sonaría mejor. En fin, bueno saber que está disponible, pero ya acostumbrado a jugarlo en inglés me quedé con el inglés.

Conclusión

Hyrule Warriors: Age of Imprisonment es mi título favorito de la saga hasta ahora. Le vino muy bien su salto a una plataforma nueva con mejor hardware. Así es como se deben jugar estos juegos. Es muy entretenido e incluso después de terminar la historia principal se descubre suficiente contenido nuevo para mantenernos enganchados por un buen tiempo. La mecánica de juego es muy entretenida y vencer a cientos de enemigos a la vez puede resultar catártico y hasta un poco adictivo. Siendo seguidor de la serie, está buenísimo jugar como distintos personajes introducidos en este y otros juegos. Lo recomiendo ampliamente y seguiré jugando un buen tiempo. Lo que tengo en el debe por probar es jugar en modo cooperativo.

Entré a nueva generación de Leyenda de Zelda en 2017 con Breath of the Wild. Ese mundo se expandió (se podría decir que de manera lateral, en una rama de tiempo paralela si nos ponemos exquisitos) con Hyrule Warriors: Age of Calamity. Volví por más con The Legend Of Zelda: Tears of the Kingdom, otro juego que disfruté inmensamente y terminé en Nintendo Switch 2 con la edición especial con mejores gráficos y 60 fps. Ahora lo estoy disfrutando nuevamente  gracias a Hyrule Warrios: Age of Imprisonment.

Después de más de 8 años y cientos de horas jugadas, todavía sigo disfrutando volver a este mundo. Muy conforme hasta ahora, pero también me genera expectativa imaginar cómo llegará a ser el próximo juego de Zelda. Breath of the Wild fue revolucionario tanto para ser un Zelda como en lo que respecta a juegos Open World. Habrá que esperar y ver qué se traen con el hardware más potente del Nintendo Switch 2 y la experiencia adquirida. Mientras tanto, seguiré teniendo aventuras en Hyrule en sus versiones disponibles...

El post Hyrule Warriors: Age of Imprisonment - Nintendo Switch 2 fue publicado originalmente en Picando Código.

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

Variable not found

Enlaces interesantes 629

noviembre 24, 2025 07:05

Programador usando un ordenador Minivac en los años 60

Años atrás, en los inicios de la informática, se programaba de forma bastante diferente: conectando cables y usando paneles de interruptores. Por eso me ha parecido muy curioso el simulador del Minivac 601, un ordenador de los años 60,  donde podemos probar de primera mano cómo era su experiencia de uso.

Y continuando con temas vintage, Microsoft ha anunciado la liberación del código fuente de los juegos Zork I, II y III, la mítica saga de aventuras conversacionales de los años 80. Pura historia del software.

También esta semana encontramos una interesante lectura de Sudhir Mangla donde explica cómo usar patrones modernos y características recientes de C# para construir modelos más expresivos, seguros y mantenibles que los que ofrece la aplicación estricta de SOLID.

El resto de enlaces interesantes recopilados esta semana, entre los que podéis encontrar información sobre .NET 10, ASP.NET Core, IA, desarrollo web y mucho más, los tenéis a continuación.

Por si te lo perdiste...

.NET

ASP.NET Core / ASP.NET / Blazor

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Machine learning / IA

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

.NET MAUI

Otros

Publicado en Variable not found.

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

Variable not found

Novedades de C# 14

noviembre 20, 2025 11:52

C# 14 ya está aquí

Hace unos días se lanzó .NET 10 y, con él, C# 14, una nueva versión del lenguaje que viene con varias novedades interesantes que mejoran la productividad y la experiencia de desarrollo.

Las más destacables son:

Les damos un vistazo rápido a continuación.

Miembros extensores

Hasta C# 13, los métodos extensores (extension methods) eran la única forma de añadir funcionalidad o características a tipos existentes sin necesidad de modificar su código fuente original. Sin embargo, esta técnica tenía limitaciones, como la imposibilidad de añadir propiedades o eventos, o definirlas sobre clases estáticas. Además, la forma en la que los implementábamos no favorecía especialmente la legibilidad del código.

En C# 14, los métodos extensores siguen siendo válidos y retrocompatibles a nivel de código, pero el concepto ha sido ampliado significativamente. A partir de esta versión, se han introducido los bloques extension como delimitadores para definir miembros extensores de un mismo tipo de una forma más estructurada y legible. Observa el siguiente código, donde podemos ver la comparativa entre la sintaxis clásica y la nueva:

// C# 13 y anteriores
public static class StringExtensions
{
    public static bool IsCapitalized(this string str) 
    {
        return !string.IsNullOrEmpty(str) && char.IsUpper(str[0]);
    }
    public static bool IsInteger(this string str) 
    {
        return int.TryParse(str, out _);
    }
}

// C# 14:
public static class StringExtensions
{
    extension(string str)
    {
        public bool IsCapitalized()
        {
            return !string.IsNullOrEmpty(str) && char.IsUpper(str[0]);
        }
        public bool IsInteger()
        {
            return int.TryParse(str, out _);
        }
    }
}

Pero la cosa no queda ahí 🙂. Además del bloque extension, C# 14 introduce la posibilidad de definir otros tipos de miembros extensores, tanto de instancia como estáticos:

public static class StringExtensions
{
    extension(string str)
    {
        // Propiedad extensora:
        public char? FirstCharacter
        {
            get => string.IsNullOrEmpty(str) ? null : str[0];
        }
    }

    extension(Math)
    {
        // Extensión de clase estática:
        public static int Factorial(int n)
        {
            if (n < 0) throw new ArgumentException("Invalid negative value", nameof(n));
            return n == 0 ? 1 : n * Factorial(n - 1);
        }
    }
}

Podéis leer más sobre miembros extensores en este post de hace unas semanas.

Asignaciones condicionales

Llevamos tiempo usando operadores como null coalescing (??), null coalescing assignment (??=) o safe navigation (?.) para lidiar con valores nulos de una forma más concisa y legible. En C# 14 se ha extendido este último para permitir su aplicación en el lado izquierdo de una asignación, permitiéndonos asignar valores a propiedades o campos de objetos que podrían ser nulos, sin necesidad de realizar comprobaciones explícitas.

// C# 13 y anteriores:
var friend = GetFriendOrNull(1);
if (friend != null)
{
    friend.Name = "John Doe";
}

// C# 14:
var friend = GetFriendOrNull(1);
friend?.Name = "John Doe"; // Si friend es nulo, no se lanza excepción y no se asigna valor

Si queréis profundizar algo más, hace algún tiempo hablamos de esta característica en este otro post.

Propiedades semi-automáticas

Las propiedades semiautomáticas son una mezcla entre las propiedades automáticas y las tradicionales con backing field, pero tomando lo mejor de ambos mundos. Gracias a esta característica podemos definir propiedades con un campo de respaldo generado automáticamente, pero al mismo tiempo nos dan la flexibilidad de acceder a él mediante la palabra clave contextual field, lo que nos permite personalizar los accesores get y set según nuestras necesidades.

// C# 13 y anteriores:
private int _age;
public int Age
{
    get { return _age; }
    set {
        if (value < 0) throw new ArgumentException("Age cannot be negative");
        _age = value;
    }
}

// C# 14:
public int Age
{
    get; // Retorna el valor del backing field generado automáticamente
    set
    {
        if (value < 0) throw new ArgumentException("Age cannot be negative");
        field = value; // 'field' es el backing field
    }
}

Para ver esta característica en mayor detalle, podéis consultar el post Propiedades semi-automáticas en C# que publiqué por aquí hace ya casi un año.

Uso de nameof con tipos genéricos

El operador nameof ha sido mejorado para permitir el uso de tipos genéricos sin especificar sus parámetros de tipo, algo que antes no era posible.

Console.WriteLine(nameof(List<>)); // Imprime "List"

Constructores y eventos parciales

También es interesante comentar que ahora se permite la implementación de constructores y eventos parciales. En la práctica, esto quiere decir que podemos definir este tipo de miembros en una parte de una clase parcial, y completarlos en otra.

Como podemos ver en el siguiente ejemplo, en la primera porción de la clase definimos los elementos como partial y en la segunda los implementamos. Fijaos que en el caso del evento, debemos definir los bloques add y remove para gestionar los suscriptores:

// Definición en una parte de la clase parcial:
public partial class MyClass
{
    public partial MyClass(string something); // Partial constructor declaration
    public partial event EventHandler? OnMyEvent; // Partial event declaration
}

// Implementación en otra parte de la clase parcial:
public partial class MyClass
{
    public partial MyClass(string something)
    {
        // Constructor implementation
    };

    private EventHandler? _onMyEvent;

    public partial event EventHandler? OnMyEvent
    {
        add
        {
            _onMyEvent += value; // Subscriber added
        }

        remove
        {
            _onMyEvent -= value; // Subscriber removed
        }
    }
}

Conversiones implícitas para Span<T> y ReadOnlySpan<T>

C# 14 introduce conversiones implícitas para los tipos Span<T> y ReadOnlySpan<T>, facilitando su uso y manipulación sin necesidad de conversiones explícitas.

Por ejemplo, el bloque de código mostrado a continuación no compila en versiones anteriores a C# 14, porque el método StartsWith() es un extensor del tipo ReadOnlySpan<int> y no existe en la clase Array. Sin embargo, en C# 14, gracias a las conversiones implícitas, este código funciona correctamente:

int[] arr = [1, 2, 3];
Console.WriteLine(arr.StartsWith(1)); // Muestra "True" en C# 14

Modificadores simplificados en parámetros lambda

En C# 14, se pueden usar modificadores de parámetros como scoped, ref, in, out, o ref readonly en expresiones lambda sin necesidad de especificar el tipo del parámetro, haciendo el código algo más conciso y legible.

Por ejemplo, observa la sutil diferencia entre C# 13 y 14 en la siguiente definición de un delegado que intenta parsear un valor:

// Definición del delegado
delegate bool TryParse<T>(string text, out T result);

// C# 13 y anteriores
TryParse<int> parser = (string text, out int result) => int.TryParse(text, out result);

// C# 14
TryParse<int> parser = (string text, out result) => int.TryParse(text, out result);

La segunda opción no compilará en versiones anteriores de C# porque en el parámetro out de la expresión lambda no se especifica el tipo int. En C# 14 es perfectamente válido, porque puede ser inferido del delegado.

Punto extra: ¿podemos usar C# 14 en proyectos .NET 9 o Visual Studio 2022?

Pues sí, es posible porque estamos hablando de características del lenguaje y no del framework, así que podremos aprovechar estas novedades en proyectos dirigidos a versiones anteriores de .NET y con Visual Studio 2022.

Para ello, simplemente debemos asegurarnos de configurar la versión del lenguaje en la propiedad LangVersion del archivo de proyecto (.csproj), especificando 14.0, como se muestra a continuación:

<PropertyGroup>
    <LangVersion>14.0</LangVersion>
</PropertyGroup>

Publicado en Variable not found.

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

Variable not found

Enlaces interesantes 628

noviembre 17, 2025 07:03

Desarrolladora rodeada de monitores donde se anuncia la llegada de .NET 10

La semana pasada, sin duda el protagonismo se lo ha llevado el lanzamiento de .NET 10, con multitud de artículos que repasan los cambios y características introducidas en esta versión, aunque hay mucho más.

En primer lugar, destacamos el artículo de CampusMVP: .NET 10 - Informe técnico para desarrolladores: novedades y mejoras importantes, imperdible para ponernos rápidamente al día.

También OpenAI ha publicado interesantes artículos, uno sobre los problemas de seguridad que supone el uso de prompt injection, y otro sobre el uso de una nueva técnica que mejora la interpretabilidad de las redes neuronales, algo difícil de conseguir con los modelos tradicionales. Y bueno, ha aprovechado también presentar su nuevo modelo GPT-5.1.

Por último, destacar el artículo de Sudhir Mangla sobre gestión de memoria en .NET, que repasa conceptos clave como stack, heap, Span<T>, Memory<T>, ArrayPool y otros temas que debemos conocer para crear aplicaciones .NET de alto rendimiento.

El resto de enlaces a contenidos interesantes, a continuación.

Por si te lo perdiste...

.NET

ASP.NET Core / ASP.NET / Blazor

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Machine learning / IA

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

.NET MAUI

Otros

Publicado en Variable not found.

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

Variable not found

Enlaces interesantes 627

noviembre 10, 2025 06:50

Desarrollador con dudas sobre la puerta a la que debe dirigirse: React, Angular, Vue o Svelte

La obsesión por los tipos primitivos es un code smell bastante común en el desarrollo de software. Gerson Azabache nos explica cómo los Value Objects en .NET pueden ayudarnos a combatir este problema.

Decidir qué framework de frontend nos conviene aprender siempre es una tarea complicada. Para ayudarnos con esto, José Manuel Alarcón ha preparado una comparativa detallada entre React, Angular, Vue.js y Svelte.

El servidor MCP de Azure permite que agentes o aplicaciones basadas en IA interactúen con los recursos que gestionamos en la nube. Juan Irigoyen da un vistazo a esta herramienta en su artículo.

Daniel Roth nos cuenta cómo Copilot Studio utiliza .NET y WebAssembly para mejorar el rendimiento en este entorno low-code.

Estos, y muchos más enlaces a contenidos interesantes, a continuación.

Por si te lo perdiste...

.NET

ASP.NET Core / ASP.NET / Blazor

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

.NET MAUI / Cross-platform

Otros

Publicado en Variable not found.

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

Picando Código

A Pizza Delivery - Steam

noviembre 06, 2025 09:01

A Pizza Delivery

A Pizza Delivery es un juego independientes que nos transporta a una aventura con elementos de puzzle y exploración. Hace énfasis en su historia, ambientación y estilo cinematográfico, lo que lo hace una experiencia audiovisual atrapante y entretenida.

Juegas como “B”, una repartidora de pizza en su último pedido del día, que se embarca en un viaje onírico, recorriendo paisajes surrealistas, cambios repentinos de escenario y momentos de introspección. Este no es un lugar común: es un reino liminal donde las personas están atrapadas en un estado de anhelo y reflexión. Comparte pizza con personajes entrañables para descubrir sus historias, y la tuya también. Conduce y explora un mundo extraño y meditativo lleno de secretos.

Vistiendo nuestro uniforme de repartidora, armadas con nuestra Vespa y la preciada carga, nos encontramos en un mundo 3D imaginativo sin mucho contexto. Controlamos a B con vista en tercera persona, con las acciones posible de recoger objetos, manejar la Vespa, agacharse y poco más. Vamos a tener que ir descubriendo qué hacer y dónde ir, siempre avanzando.

Es un juego tranquilo, como dice la descripción, que da lugar a la meditación e introspección. Se va desarrollando lentamente a medida que exploramos y mediante la interacción con personajes. Tiene paisajes donde dan ganas de ir y tirarse a dormir la siesta en el pasto o pasear en una Vespa. Otros son algo más sombríos, a pesar de lo inocuo aparentan esconder cierta oscuridad que no se termina de definir, lo que lo hace cautivador y misterioso. Paisajes o lugares 

Los elementos de puzzle implican usar o tener que encontrar ítems que nos ayuden en nuestra aventura. La exploración también nos lleva a descubrir caminos alternativos que nos permitan avanzar. Nos encontramos con personajes variados, con los que podemos conversar y compartir historias y pizza. También hay objetos que cuentan memorias de B u otros personajes que agregan más al relato.

Jugué una versión Beta para Testing en Garuda Linux. Empecé usando Proton 9.0.4 y una versión anterior del juego, y creía que sólo podía jugar con mouse y teclado. Esto cambió después de una actualización (al momento de escribir esto, terminé el juego en la versión 1.2.2). También me pasé Proton 10.0-2 (beta). Al conectar mi contol de XBox 360, Steam avisa que no está soportado, pero funciona bien de bien. Incluso el juego nos muestra los botones que tenemos que presionar para las distintas interacciones cuando conectamos el control, en vez de las teclas del teclado.

Ya que estaba, intenté también jugar con el Steam Controller. Había tenido éxito jugando a juegos de mouse como Kingdom Rush, pero no se sintió cómodo en este juego. Así que volví al clásico control XBox 360 que uso siempre en Steam. La experiencia con el control fue mucho mejor que con mouse y teclado. En general me gusta más jugar con gamepad, incluso en juegos de primera persona, pero acá en particular creo que funciona mejor que el teclado.

[See image gallery at picandocodigo.net] Tiene algunos detalles técnicos, que estoy seguro se van a poder corregir en eventuales actualizaciones. Generalmente estos juegos reciben una actualización importante antes de su fecha de lanzamiento (mañana). Y como dije, estuve jugando la versión Beta para Testing. 

El rendimiento a veces no es el mejor, cuando hay mucho detalle en la pantalla, baja un poco la cantidad de frames por segundo. También el juego se olvidó de mi preferencia de usar el idioma español, y se pasó al inglés (creo que esto pasó cuando actualicé a 1.2.2). Por último, los achievements de Steam no funcionaron, pero seguro se debe a que es una versión de prueba para reseña.

Es una experiencia corta, pude terminarla en menos de 2 horas. Ahora que sé cómo resolver los puzzles principales, creo que una vez esté la versión definitiva en Steam voy a probar terminar la aventura desde cero otra vez, y en una sentada. De repente puedo encontrar soluciones para alguno de los puzzles que no resolví. Me pareció una duración buena, me entretuvo y me dejó pensando. Es de esos juegos que después de terminado nos deja preguntando si habrá más.

¿Qué más hay sobre las memorias que fuimos adquiriendo? ¿Por qué pasa lo que pasa cuando nos sentamos en un banco? ¿Qué lugar es este? Son preguntas que van surgiendo que no se responden del todo, o sí, o quedan a libre interpretación del jugador. Da para volverlo a jugar.

A Pizza Delivery es una creación del desarrollador independiente español Eric Osuna y la publisher basada en Barcelona Dolores Entertainment. Llega a PC (Steam y Windows), PlayStation 5 y Xbox Series mañana 7 de noviembre. Va a tener un precio de 13,99 €, con un descuento del 20% durante las dos primeras semanas.

Agrego el trailer a continuación. Pero si te interesa el juego, te recomendaría jugarlo sin mirar el trailer, porque muestra muchas cosas que para mí fueron sorpresa mientras jugaba:

YouTube Video

¿Pizza? ¡Siempre! 🍕

El post A Pizza Delivery - Steam fue publicado originalmente en Picando Código.

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

Picando Código

Definir lenguaje a español en los toots con el plugin de WordPress "Share on Mastodon"

noviembre 04, 2025 10:30

En el blog uso el plugin Share on Mastodon para WordPress. Éste permite compartir automáticamente los posts publicados en nuestro blog en una cuenta de Mastodon. En mi cuenta personal de Mastodon uso inglés para la interfaz, pero posteo en español, inglés y a veces gaélico escocés.

Mastodon idiomas

La web y el software en general no parecen estar bien diseñados para ambientes polilingües. La mayoría de las cosas usan un sólo idioma por defecto, y listo. Por suerte ya resolví ese problema para el teclado. Puedo escribir en español, inglés, escocés gaélico, italiano y más con esa distribución.

Cuando posteamos en Mastodon desde su web, podemos especificar el idioma de la publicación desde un menú en el campo de escritura. Esto permite que las interfaces que incluyen traducción automática puedan saber en qué idioma está escrito un toot. Así se hace más fácil la traducción automática en su sistema en caso de ser necesaria.

Viendo las publicaciones automáticas programadas del blog en mi cuenta de Mastodon, notaba que siempre se publicaban "en inglés". A veces me tomaba el trabajo de editarlas o re-postearlas en español. Pero le quita un poco la gracia a todo este proceso automatizado. Generalmente programo posts para que se publiquen sólos y me olvido. Así que me puse a investigar la documentación de Share on Mastodon.

En este enlace encontré "Miscellaneous API Arguments": un filtro que se aplica justo antes de enviar el pedido POST a tu instancia de Mastodon. Permite agregar o eliminar cualquiera de los parámetros soportados por la API de Mastodon. ¿No son geniales las APIs? Mirando en la API de estados de Mastodon, como suponía encontré que language es uno de los parámetros aceptados. Acepta un String con el código ISO 639-1 del lenguaje para un estado.

Para modificar los parámetros del llamado a la API, tenemos que usar el filtro share_on_mastodon_toot_args. Tenemos que escribir una función que recibe el array de parámetros a enviar con el pedido POST, y modificar/agregar/eliminar parámetros de la API. En este caso necesité agregar language y definir su valor como es para español (¡bo!).

Así que para que los toots enviados automáticamente desde mi blog estén definidos en español, agregué el siguiente código PHP al archivo functions.php de mi tema de WordPress:

add_filter( 'share_on_mastodon_toot_args', function( $args ) {
  $args['language'] = 'es'; // Set language to Español bo
  return $args;
});

Ahora los toots programados desde el blog van en español, para que sea más fácil identificarlos por idioma en caso de filtros de lenguaje, traducciones y demás.

Existe también el plugin oficial de ActivityPub. Pero por más que intenté varias veces, no parece funcionar bien con mi servidor. Entrando desde distintas instancias al perfil, veo posts publicados hace meses, o ninguno. Y nunca se actualiza con todos los posts del blog. Alguna vez volveré a intentarlo, pero por ahora me alcanza con tener los posts publicados automáticamente en mi cuenta personal, los feeds RSS, RDFAtom y el Canal de Telegram.

El post Definir lenguaje a español en los toots con el plugin de WordPress "Share on Mastodon" fue publicado originalmente en Picando Código.

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

Variable not found

.SLNX: El nuevo formato de soluciones .NET

noviembre 04, 2025 07:44

SLNX: el nuevo formato de archivo de solución

Si, por curiosidad o necesidad, alguna vez has abierto un archivo de solución de Visual Studio (.sln) con un editor cualquiera, habrás comprobado que se trata de un archivo de texto plano.

Su estructura, aunque no es excesivamente compleja y está bien documentada, es propietaria de Visual Studio y no es evidente a simple vista, resulta demasiado verbosa, tiene mucha información irrelevante duplicada y, en general, no es nada amigable para los desarrolladores que se ven obligados a modificarla, sobre todo cuando se trata de resolver conflictos en sistemas de control de versiones. Y, por supuesto, es algo que se agrava conforme crece el número de proyectos en la solución.

Esto llevó a Microsoft a presentar hace unos meses el nuevo formato de archivo de solución .slnx, una alternativa basada en XML mucho más ligera, concisa y fácil de entender, que será el utilizado por defecto a partir de la llegada de .NET 10.

Lo vemos en profundidad a continuación.

El nuevo formato .slnx

Para comprender bien el enfoque de este nuevo formato, lo mejor es compararlo con el formato tradicional .sln. Partamos de una solución relativamente simple, con la siguiente estructura:

MyApp.sln
|-- MyApp
|-- MyApp.Application
|-- MyApp.Domain
|-- MyApp.Infrastructure
|-- Extra files
    |-- README.txt

El archivo de solución en formato .sln correspondiente a esta estructura sería el siguiente:

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.14.36616.10 d17.14
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyApp", "MyApp\MyApp.csproj", "{BEF31430-FCBF-4293-911F-668D40006860}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyApp.Domain", "MyApp.Domain\MyApp.Domain.csproj", "{B89A57B4-ACC1-42C1-A17E-6A0A8CC9DD6E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyApp.Application", "MyApp.Application\MyApp.Application.csproj", "{1687322D-3B21-448B-83CD-859E4A46A420}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyApp.Infrastructure", "MyApp.Infrastructure\MyApp.Infrastructure.csproj", "{2EF1B4B4-A954-40E6-A7C5-00151236431F}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extra files", "Extra files", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
    ProjectSection(SolutionItems) = preProject
        README.txt = README.txt
    EndProjectSection
EndProject
Global
    GlobalSection(SolutionConfigurationPlatforms) = preSolution
        Debug|Any CPU = Debug|Any CPU
	Release|Any CPU = Release|Any CPU
    EndGlobalSection
    GlobalSection(ProjectConfigurationPlatforms) = postSolution
	{BEF31430-FCBF-4293-911F-668D40006860}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
	{BEF31430-FCBF-4293-911F-668D40006860}.Debug|Any CPU.Build.0 = Debug|Any CPU
	{BEF31430-FCBF-4293-911F-668D40006860}.Release|Any CPU.ActiveCfg = Release|Any CPU
	{BEF31430-FCBF-4293-911F-668D40006860}.Release|Any CPU.Build.0 = Release|Any CPU
	{B89A57B4-ACC1-42C1-A17E-6A0A8CC9DD6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
	{B89A57B4-ACC1-42C1-A17E-6A0A8CC9DD6E}.Debug|Any CPU.Build.0 = Debug|Any CPU
	{B89A57B4-ACC1-42C1-A17E-6A0A8CC9DD6E}.Release|Any CPU.ActiveCfg = Release|Any CPU
	{B89A57B4-ACC1-42C1-A17E-6A0A8CC9DD6E}.Release|Any CPU.Build.0 = Release|Any CPU
	{1687322D-3B21-448B-83CD-859E4A46A420}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
	{1687322D-3B21-448B-83CD-859E4A46A420}.Debug|Any CPU.Build.0 = Debug|Any CPU
	{1687322D-3B21-448B-83CD-859E4A46A420}.Release|Any CPU.ActiveCfg = Release|Any CPU
	{1687322D-3B21-448B-83CD-859E4A46A420}.Release|Any CPU.Build.0 = Release|Any CPU
	{2EF1B4B4-A954-40E6-A7C5-00151236431F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
	{2EF1B4B4-A954-40E6-A7C5-00151236431F}.Debug|Any CPU.Build.0 = Debug|Any CPU
	{2EF1B4B4-A954-40E6-A7C5-00151236431F}.Release|Any CPU.ActiveCfg = Release|Any CPU
	{2EF1B4B4-A954-40E6-A7C5-00151236431F}.Release|Any CPU.Build.0 = Release|Any CPU
    EndGlobalSection
    GlobalSection(SolutionProperties) = preSolution
	HideSolutionNode = FALSE
    EndGlobalSection
    GlobalSection(ExtensibilityGlobals) = postSolution
	SolutionGuid = {7882EB29-7FE2-4245-8787-3CFDC076B009}
    EndGlobalSection
EndGlobal

Aunque no es fácil de leer, pueden distinguirse los distintos proyectos que componen la solución, su configuración, archivos adicionales e identificadores únicos de todos los elementos.

En el nuevo formato .slnx, el mismo archivo de solución se representaría así:

<Solution>
  <Folder Name="/Extra files/">
    <File Path="README.txt" />
  </Folder>
  <Project Path="MyApp.Application/MyApp.Application.csproj" />
  <Project Path="MyApp.Domain/MyApp.Domain.csproj" />
  <Project Path="MyApp.Infrastructure/MyApp.Infrastructure.csproj" />
  <Project Path="MyApp/MyApp.csproj" />
</Solution>

¡Uau! Mucho mejor, ¿eh? Han desaparecido los GUIDs (que no aportaban demasiado), las duplicidades y el contenido se centra exclusivamente en lo relevante: la estructura de la solución y las rutas a los proyectos y archivos adicionales.

Con esto, se consiguen los objetivos que pretendía Microsoft:

  • Es fácilmente legible y editable por humanos. Ha desaparecido la información redundante, los GUIDs y secciones innecesarias, pasando en algunos casos a ser configuraciones implícitas.
  • Al ser mucho más conciso, su carga y guardado son más rápidos.
  • Y al simplificarse su estructura, reduce la probabilidad de conflictos en sistemas de control de versiones.
  • Se abandona el formato propietario en favor de XML, un estándar ampliamente conocido.
  • Está alineado con otros formatos, como los usados por MSBuild.
  • Es compatible con versiones de Visual Studio desde la 17.14 en adelante.

Cómo crear soluciones en formato .slnx

El nuevo formato .slnx será el utilizado por defecto para las nuevas soluciones a partir de .NET 10 y Visual Studio 2026, así que no habrá que hacer nada especial para comenzar a utilizarlo cuando comencemos nuestros proyectos. Desde el IDE, se creará un archivo de este tipo al utilizar el menú File > New > Project/Solution, y desde la línea de comandos, la siguiente orden creará directamente el archivo .slnx:

dotnet new sln

De hecho, si quisiéramos crear la solución en el formato anterior también se podría, pero tendríamos que especificarlo explícitamente:

dotnet new sln --format sln

Migrar soluciones existentes al nuevo formato

Los archivos .slnx serán compatibles con Visual Studio 2022 (actualizado a la versión 17.14 o superior), así como con Visual Studio Code y otros editores y herramientas que trabajen con soluciones de .NET. Sin embargo, dado que el nuevo formato mejora bastantes aspectos del anterior, es recomendable migrar las soluciones existentes, incluso aunque estemos usando proyectos en versiones anteriores de .NET.

Esto podemos conseguirlo fácilmente desde Visual Studio 2026 o 2022 (17.14 o superior) abriendo la solución en el IDE y utilizando el menú File > Save As..., seleccionando el nuevo formato .slnx en el cuadro de diálogo:

Convertir la solución .sln a .slnx

También es posible realizar la conversión desde la línea de comandos utilizando el comando dotnet sln migrate, que generará el archivo .slnx correspondiente en la misma carpeta donde se encuentra el archivo .sln original:

D:\Projects\Stuff\MyApp>dir
 El volumen de la unidad D es Datos
 El número de serie del volumen es: 8CBC-81E3

 Directorio de D:\Projects\Stuff\MyApp

    <DIR>          .
    <DIR>          ..
    <DIR>          MyApp
    <DIR>          MyApp.Application
    <DIR>          MyApp.Domain
    <DIR>          MyApp.Infrastructure
    <DIR>          MyApp.Shared
             3.081 MyApp.sln
                 0 README.txt
  2 archivos          3.081 bytes
  7 dirs  436.579.930.112 bytes libres

D:\Projects\Stuff\MyApp>dotnet sln migrate
.slnx file D:\Projects\Stuff\MyApp\MyApp.slnx generated.

D:\Projects\Stuff\MyApp>dir
 El volumen de la unidad D es Datos
 El número de serie del volumen es: 8CBC-81E3

 Directorio de D:\Projects\Stuff\MyApp

    <DIR>          .
    <DIR>          ..
    <DIR>          MyApp
    <DIR>          MyApp.Application
    <DIR>          MyApp.Domain
    <DIR>          MyApp.Infrastructure
    <DIR>          MyApp.Shared
             3.081 MyApp.sln
               479 MyApp.slnx
                 0 README.txt
  3 archivos          3.560 bytes
  7 dirs  436.579.930.112 bytes libres

D:\Projects\Stuff\MyApp>_

Es importante tener en cuenta que si en la misma carpeta existen ambos archivos, .sln y .slnx, habrá que especificar explícitamente cuál de ellos se desea usar. Por ejemplo, el siguiente comando

D:\Projects\Stuff\MyApp>dotnet build
MSBUILD : error MSB1011: Specify which project or solution file to use because this folder contains more than one project or solution file.

D:\Projects\Stuff\MyApp>dotnet build .\MyApp.slnx
Restore complete (0,8s)
  MyApp.Domain net10.0 succeeded (0,3s) → MyApp.Domain\bin\Debug\net10.0\MyApp.Domain.dll
  MyApp.Infrastructure net10.0 succeeded (0,3s) → MyApp.Infrastructure\bin\Debug\net10.0\MyApp.Infrastructure.dll
  MyApp.Application net10.0 succeeded (0,2s) → MyApp.Application\bin\Debug\net10.0\MyApp.Application.dll
  MyApp net10.0 succeeded (0,5s) → MyApp\bin\Debug\net10.0\MyApp.dll

Build succeeded in 1,6s

D:\Projects\Stuff\MyApp>_

Sinceramente, no veo ningún motivo para mantener el archivo .sln en la carpeta una vez migremos a .slnx, pero bueno. La cuestión es que si los mantenemos ahí, este problema ocurrirá con todos los comandos de .NET CLI que trabajen con soluciones, como los usados para añadir o eliminar proyectos de una solución. Por ejemplo, para añadir un nuevo proyecto a una solución en formato .slnx si tenemos ambas en la carpeta, habría que hacer lo siguiente:

D:\Projects\Stuff\MyApp>dotnet sln .\MyApp.slnx add Utils\Utils.csproj

¡Espero que te resulte útil!

Publicado en Variable not found.

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

Variable not found

Enlaces interesantes 626

noviembre 03, 2025 07:03

Caja fuerte llamada ASP.NET Core con un agujero lateral por el que se puede entrar a ella

Esta semana, en la recopilación de enlaces encontramos el interesante repaso de Ricardo Peres a algunas técnicas para optimizar el trabajo con cadenas de texto en .NET en búsquedas, formateo, extracción de subcadenas y uso de interning.

Vale la pena también echar un vistazo a la notación de objetos orientada a tokens (TOON) propuesta por Johann Schopplich para reducir significativamente el coste en tokens al enviar datos estructurados (como JSON) a modelos de lenguaje.

Isaac Ojeda nos muestra cómo utilizar channels y servicios de .NET para crear un sistema de control de tareas en segundo plano, muy interesante.

E imperdible el detallado análisis de Andrew Lock sobre la grave vulnerabilidad "request smuggling" en ASP.NET Core, calificada con una puntuación de 9.9, la más alta hasta la fecha.

El resto de contenidos, a continuación.

Por si te lo perdiste...

.NET

ASP.NET Core / ASP.NET / Blazor / Aspire

Conceptos / Patrones / Buenas prácticas

Machine learning / IA

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

.NET MAUI

Otros

Publicado en Variable not found.

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

Picando Código

Data Uruguay: Vuelve Datos y Cogollos

octubre 31, 2025 10:30

Porque puedo sacar a Fernando de Uruguay, pero no puedo sacar Urug... o algo así. Para quienes leen desde Uruguay, la gente amiga de Data Uruguay vuelve a organizar este evento que tiene como objetivo dar a conocer lo que se está haciendo en tema de datos abiertos:

Datos y Cogollos es un evento que apunta a juntar a toda la comunidad que trabaja alrededor (o cerca, o con interés) del tema datos abiertos, conocer los proyectos que hay en la vuelta, pero sobretodo conocer a las personas que estamos en esto y compartir una charla informal. Es organizado por la comunidad de datos abiertos local, así que no le pertenece a ninguna organización en particular y está más que abierto a que se sigan sumando nuevas personas y organizaciones.

Datos y Cogollos

¿Es obligatorio presentar/saber de datos abiertos/tomar/fumar porro?
No, para nada. Cada uno viene y hace lo que quiere 😉

Te esperamos el miércoles 5 de noviembre, a las 19:00 hs., en el Café La Diaria, Bacacay 1306. Entrada libre.

Podés inscribirte en la página del evento (también están las presentaciones y más info).

Agenda de la actividad:

  • Bienvenida y presentación de la iniciativa
  • Presentaciones relámpago ⚡ (máx. 5 min.) de proyectos y organizaciones
  • Gustavo Suárez - Agesic - Datacamp 2025
  • Chino Carranza - Data - Proceso participativo ATuServicio.uy
  • Marcelo Ventós - IPRU - Datos y educación financiera
  • Más por venir, y podés ser vos 😉
  • Brindis y convivencia

**Si querés ser uno/a de los/as presentadores/as, sólo tenés que comentar en el evento o escribirnos a contacto@data.org.uy (diciendo el nombre del proyecto / organización / presentación que querés compartir). ¡De 5 minutos máximo, acordate!

Por cierto la página del evento está alojada en el dominio de Data, usando el sistema Gathio:

Y ya que estamos en el Fediverso, no está de más mencionar que estamos usando Gathio para publicar e inscripciones a eventos a partir de ahora, donde cada evento queda federado y permite interacción desde apps del fediverso 💅

Gathio es una plataforma simple,federada y orientada a la privacidad para alojar eventos. Es código software libre liberado bajo la licencia GPLv3. Y Data también se encuentra en Mastodon, así que pueden seguirla por ahí.

Si estuviera en Uruguay, iría. A ver en qué anda la movida de los Datos Abiertos éstos días por allá, y a ver si se me ocurre alguna idea para desarrollo de datos abiertos. Tendría que revisar cómo están mis proyectos de datos abiertos y si los vuelvo a subir a algún lado (desde que Heroku dejó de ser gratis, murieron). O empezar alguno nuevo...

Si van al evento, manden saludos.

El post Data Uruguay: Vuelve Datos y Cogollos fue publicado originalmente en Picando Código.

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

Picando Código

List Category Posts v0.93.0 donde se corrigen "problemas" y re-aprendo PHP

octubre 30, 2025 11:30

A principio de mes recibí otro reporte de seguridad de Wordfence para el plugin de WordPress List Category Posts. Cada vez vienen llegando más seguido. Me pregunto si hay algún negocio por detrás, si alguien se está beneficiando monetariamente con las investigaciones de seguridad, o simplemente con "crédito" por encontrarlas. Los que desarrollamos el plugin definitivamente no nos venimos beneficiando monetariamente, eso lo puedo afirmar.

Fernando versión elefante programando PHP

Fernando versión elefante programando PHP

Estas "vulnerabilidades" son de bastante poco riesgo, y por suerte hasta ahora han sido relativamente sencillas de corregir. Cuando escribí el plugin, lo hice pensando en usuarios que tienen total control de su WordPress y que saben lo que están haciendo. Y si no saben, no deberían poder hacerse demasiado daño mientras usaran las funcionalidades básicas.

No consideré sitios donde hay muchos usuarios con distintos permisos, que es algo que WordPress permite. Entonces alguien con suficiente maña, y malas intenciones, podría llegar a usar el plugin para hacer cosas malas (nunca graves, como obtener contraseñas ni nada del estilo, salvo que tuviera acceso como administrador, que ahí ya lo podría hacer por otros medios). 

Así que llegó un nuevo reporte con dos problemas. Uno era que existía una forma de mostrar contenido de posts que estaban protegidos con contraseña. El arreglo fue relativamente fácil de implementar. A la hora de mostrar contenido, revisar si el post está protegido con post_password_required y si devuelve verdadero, usar get_the_password_form en vez del contenido. Si recuerdo bien los tests que hice, después de ingresar el password sí vemos el contenido en la página/post donde usamos el plugin.

El segundo problema reportado fue que existía una forma en casos bastante borde de mostrar el contenido de un post privado o borrador a usuarios sin los permisos adecuados. WordPress tiene estados para los posts incluyendo private y draft, que sólo deberían ser vistos por usuarios editores y administradores. Así que agregué una función para "desinfectar" los estados y eliminar private y draft a menos que la persona tenga permisos.

Acá fue donde tuve que re-aprender PHP. Este es mi cerebro cuando me encuentro con un problema, por más simple que sea, en un lenguaje que no es Ruby: "Esto no es Ruby, ¿cómo se hace esto en PHP? No tengo ni idea, en Ruby ya lo solucioné en mi cabeza, ¿pero PHP? ¡Seguro son mucho mas líneas de código! AAAAAH!!!". Después de este breve ataque de histeria, me acuerdo que soy un adulto y que Ruby no es el santo grial y podemos programar en varios lenguajes de programación distintos y diversos, y eso está bien.

Quería, teniendo un array de Strings, eliminar cualquier ocurrencia de los Strings private y draft. Salí a buscar en la documentación de PHP y me encontré con array_diff que compara arrays y devuelve los valores de un array que no están presentes en los otros.

Mi array $statuses tiene los estados de post que envían los usuarios, y necesito solamente los que no sean private y draft. Así que quedó solucionado en una sóla línea de código: array_diff($statuses, array('private', 'draft')) devuelve todos los valores de $statuses que no sean los que definí en el segundo array (¿viste Fernando que se puede?). En el momento no me encontré con la función array_filter, pero creo que en algún momento podría actualizar el código para usarla, porque me resulta más claro.

Y quedó la versión que publiqué. Algo interesante es que había aplicado una solución que no funcionaba (porque no tenía en cuenta que alguien podía mandar post_status=private,private). Klemens (otro desarrollador del plugin) comentó una mejora que apliqué, pero al final no quedó, y dejé todo en el historial de commits. Como esto es un proyecto personal al que le dedicamos tiempo voluntario, no necesita tener un historial de commits en Git profesional. Estos commits cuentan una historia de cómo programo (mal) en PHP, así que ahí me quedan, para leer en el futuro...

La vulnerabilidad me fue reportada por privado responsablemente por Wordfence, antes de hacerla pública (para darnos tiempo a solucionarla). Ni bien publiqué la versión nueva del plugin, 0.93.0, respondí a Wordfence avisando. Así que ahora a esperar y ver qué dicen de esta solución, y que caiga la próxima vulnerabilidad en unos meses por alguna razón.

List Category Posts es un plugin para WordPress, es software libre publicado bajo la GPLv2. El código fuente está disponible en Codeberg, en GitHub y en WordPress.org (SVN). Se puede descargar desde el sitio de plugins de WordPress.

El post List Category Posts v0.93.0 donde se corrigen "problemas" y re-aprendo PHP fue publicado originalmente en Picando Código.

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

Arragonán

Viviendo en el péndulo. Hasta la vista Genially

octubre 13, 2025 12:00

Tras casi 4 años, esta fue mi última semana en Genially. Una decisión que me costó mucho tomar tanto por motivos personales, con un ambiente de compañerismo en la compañía que creo que no es habitual en otros lugares que tienen cierta escala y haber construido lazos con un buen puñado de personas; como por motivos profesionales, con unos cuantos desafíos que se venían a corto y medio plazo en los que sé que hubiera disfrutado y aprendido.

En este tiempo, mi rol en la empresa se fue transformando poco a poco casi sin darnos cuenta, primero influenciando solo a nivel de tecnología y terminando haciéndolo a nivel de producto, negocio y operaciones. Hubo algo que fue una constante: vivir en el péndulo entre individual/team contributor y el management.

Incorporación

Entré casi por casualidad. Conocí a Chema, CTO de Genially, unos años antes en una Pamplona Crafters y mantuvimos el contacto vía redes sociales (cuando tuiter aún molaba). Tiempo después, hablamos por otro tema y me propuso explorar si tenía sentido incorporarme. La compañía venía de crecer mucho y vieron que podía asumir un rol que resultase complementario.

Justo era un momento en el que empezaba a sentir que necesitaba un cambio profesional, pero quería estar seguro de mi siguiente paso. Así que tras haber hecho el proceso de selección, nos tomamos un par de meses y tuvimos varias conversaciones hasta que finalmente me incorporé a la compañía.

Plataforma y organización

Entré con un rol de Platform Lead, por lo que mi responsabilidad iba a ser ayudar a los equipos de producto a que pudieran enfocarse en tener impacto, tratando de mejorar la experiencia de desarrollo y la efectividad de los equipos. Como bonus, también iba a echar una mano en cuestiones más organizativas.

Aunque este era un rol de liderazgo, no había un equipo dedicado, de modo que iba a tener que influir, colaborar con distintos equipos y empujar algunas iniciativas por mi cuenta.

Sobre el trabajo de plataforma

Una vez hecho el onboarding, empezar a tener algunas iniciativas ya lanzadas y conociendo más la compañía, traté de aterrizar una serie de metas:

Las iniciativas y actividades de plataforma deben facilitar la Developer Experience y ayudar a los equipos en distintos aspectos:

  • Reducir su carga cognitiva, que se enfoquen en complejidad intrínseca y no en la extrínseca, potenciando la germana (generando aprendizaje, construyendo esquemas o automatizando su uso)
  • Habilitar actitudes data-informed en la toma de decisiones
  • Aportar la mayor autonomía y empoderamiento posibles
  • Tratar de ser lo más eficientes en cuestiones de costes económicos
  • Dar fiabilidad, que las herramientas apenas fallen

Metas que traté de tener en cuenta, definiendo e implantando prácticas, procesos y herramientas, automatizando tareas, creando documentación… Y darles seguimiento unas veces de manera cuantitativa y otras de forma cualitativa.

Algunas de las iniciativas más relevantes fueron estas:

  • Actuar como puente entre el equipo de infraestructura y los equipos de producto para acelerar la implantación del stack de observabilidad de Grafana (Loki, Mimir, Tempo).
  • Formalizar la redacción de postmortems blameless tras incidencias que afecten al servicio junto a CTO y VP de ingeniería, para fomentar el aprendizaje sistémico.
  • Apoyar al equipo de Design System con distintas actividades.
  • Ayudar en la introducción de Snowplow + Amplitude para la instrumentación de producto colaborando con el equipo de data y los primeros equipos de producto involucrados.
  • Introducir el uso de Turborepo en el monorepo para simplificar el proceso de build.

A nivel organizativo

Durante esos primeros meses se estaba planteando una reorganización del área de desarrollo de producto bastante importante. Básicamente era pasar de una estructura de squads que se organizaba de forma recurrente a una basada en líneas de trabajo que tuvieran continuidad en el tiempo.

Esto fue algo en el que me involucraron aún sin tener mucho contexto junto al resto de managers de tech y producto, en parte venía bien tener a alguien con mirada limpia aunque en ocasiones pudiera pecar de ingenuo, para cuestionar supuestos y detectar disonancias.

En aquel entonces me resultó muy útil que alguna de la gente involucrada en la reorganización estuviera hablando en términos de Team Topologies, porque nos ayudaba a tener un lenguaje común. Eso lo terminamos enfrentando con un ejercicio de Context Mapping (en un par de eventos charlé sobre ello), donde representamos buena parte del funcionamiento de la compañía para ver el encaje de equipos respecto a la situación actual.

Este ejercicio sirvió para completar la foto real a la que íbamos, e incluso permitió detectar potenciales problemas de bounded contexts en los que había muchas manos y algunas en las que había muy pocas. Así que cuando con el paso del tiempo surgieron algunos problemas relacionados, no nos pilló tan por sorpresa.

Además, institucionalizamos el dar seguimiento a lo que surgía de las retros de los equipos, ya que se estableció como práctica que se documentase por escrito al menos lo más destacado de ellas y las acciones que surgieran. Esta práctica se mantiene a día de hoy, y resulta muy útil a la capa de management como complemento a los 1:1s para detectar fricciones y puntos de mejora.

Mucho de esto y algunas cosas más, las compartí en la charla Desarrollo de producto en una Scale-Up precisamente en la Pamplona Crafters 2024.

Foto donde hay algo más de 100 personas de pie posando para la foto, fondo con árboles y suelo adoquinado

Volviendo a hacer producto (interno)

Tras un año, mi foco cambió a ser algo más todoterreno. Por un lado, ya tenía bastante controladas las dinámicas en el área de desarrollo de producto y a nivel de plataforma había aún puntos de mejora en los que iba trabajando, pero estábamos ya más en una fase de mejora continua y constante, además el equipo de infraestructura seguía introduciendo grandes mejoras. Mientras tanto, en otras áreas de la compañía se identificaban problemas mayores con los que creíamos que podía aportar más.

Ventas

Uno que terminó surgiendo fue en el equipo de ventas, al que había que buscarle solución de forma prioritaria, ya que ninguna de las líneas de trabajo a priori tenía foco en ese tema. Teníamos un proceso interno ineficiente y propenso a errores que terminaban sufriendo clientes High Touch de gran tamaño: muchos emails y llamadas de coordinación, uso de spreadsheets de gestión, tiempos de espera por parte de clientes y account managers, etc.

Para solventar el problema se decidió montar un squad específico moviendo a algunos perfiles técnicos desde algunas líneas de trabajo, y como no queríamos sacar de foco a product managers terminé involucrado en la fase de discovery para abordarlo.

Así que tocó entender los problemas que el equipo de ventas se estaba encontrando y, junto a compañeras de customer experience, entrevistarnos con clientes y gente del área de ventas. A partir de ahí, documentar el impacto que estaba teniendo, definir un primer alcance del MVP usando un user story map y preparar un kick-off para tener tanto al squad, stakeholders y el resto de la compañía alineados.

El squad trabajó en una herramienta que diera mayor autonomía a esos clientes de gran tamaño, permitiendo a account managers mantener el control y darles seguimiento. Y en mi caso, aunque no tiré ni una línea de código y que luego me quedase acompañando al squad de lejos casi como un stakeholder más, me lo pasé como un enano volviendo a participar en hacer producto en fases iniciales.

Creativo

Tiempo más tarde, nos encontrarnos otro problema importante en el área de creativo. Uno de los valores que ofrece Genially a las personas usuarias son las plantillas diseñadas por este equipo. Nos encontramos con un problema de atasco en la publicación de nuevas plantillas y cambios en las existentes que antaño no ocurría, el producto había evolucionado de un modo en el que terminó afectando en la operativa diaria de este equipo.

Esto es porque una vez diseñadas, existía un cuello de botella en el proceso de publicación tanto en el producto como en la web pública. Esta ineficiencia en el Go to Market provocaba tardar más tiempo en recuperar la inversión, era propenso a errores y frustraba a las personas de ese equipo.

Al final era un problema para el que la perspectiva Lean encajaba como anillo al dedo: Identificar el valor, mapear el flujo de trabajo, mantener un flujo continuo, aplicar sistema pull y buscar la mejora continua. Para lo cual se decidió crear de nuevo un squad que se enfocara en esta área.

Una vez analizado y mapeado el journey principal que queríamos resolver, habiendo identificado los distintos hand-offs y limitaciones de las herramientas, planteamos crear un nuevo backoffice diseñado para habilitar mayor autonomía y que simplificase su proceso. De ese modo podríamos sustituir de forma incremental el backoffice legacy, un CMS y un par de spreadsheets de gestión.

Para acelerar el proceso de publicación introdujimos: soporte i18n, gestión de estados, uso de IA generativa, mejoras en las validaciones… Además de crear un servicio que pudiera consumirse desde el producto y la web, cosa que evitaba el uso de herramientas externas y, a nivel técnico, simplificaba la infraestructura y la mantenibilidad futura.

Una vez eliminado ese cuello de botella, que con la combinación del trabajo del squad con el equipo creativo pasó de un retraso de 4 meses a estar al día, nos centramos en mover y mejorar el resto de procesos que se soportaban aún en el legacy en esta nueva herramienta, y en el camino colaborar con uno de los equipos de producto para introducir algunas mejoras conjuntamente.

Operaciones y tech

Aproximadamente el último año en Genially mi rol terminó pivotando de manera oficial a trabajar con foco en las operaciones internas de la compañía. Esto significaba estar en un punto medio entre tecnología, producto, negocio, organización y su potencial impacto en la operativa diaria de cualquier área de la compañía. Esto implicaba mucha amplitud y normalmente menor profundidad, mucha comunicación, intentar promover iniciativas que tuvieran sentido o frenar las que aparentemente necesitasen reflexionarse más, identificar posibles problemas entre áreas de forma prematura, moverme todavía más habitualmente entre diferentes grados de abstracción, etc.

Siempre con una perspectiva de desarrollo de producto, durante ese tiempo tuve oportunidad de trabajar de nuevo y esta vez mucho más involucrado con el área de ventas y desarrollo de negocio; además de empezar a hacerlo también con las de financiero, personas, soporte y comunidad.

Trabajando con más áreas

Con el área de ventas empezamos a trabajar en hacer crecer el MVP que arrancamos antaño, soportando nuevas casuísticas primero y luego acercando e integrando esta herramienta con el CRM que se usa en la compañía. En mi caso había estado involucrado en el día a día de la línea de trabajo que lo evoluciona, con un rol de facilitador y ocasionalmente de desatascador.

Junto a las áreas de financiero y personas hicimos pequeñas iniciativas coordinadas con líneas de trabajo de producto, aunque algunas más ambiciosas se quedaron en el tintero porque el coste de llevarlas a cabo las hacían inviables al menos en el medio plazo.

Con soporte empecé a trabajar muy de cerca, ya que había una tormenta perfecta de cambios en el producto, iteraciones de negocio, atasco en los tiempos de respuesta y degradación de la experiencia de cliente.

Lanzamos varias acciones para solventar la situación: mejorar flujos en el chatbot, rehacer la integración con la herramienta de soporte para enriquecer la información de clientes y mejorar la gestión de colas, introducir automatizaciones, contratar e integrar a un proveedor para pasar a tener un chat basado en agentes de IA que escalase sólo casos complejos, etc. Una vez recuperada una situación de normalidad pudimos entrar en un modo de mejora continua en soporte, y poder dedicar más tiempo a iniciativas relacionadas con comunidad.

Y en medio de todo esto apoyar en ajustes organizacionales, tanto a nivel de desarrollo de producto como en el resto de áreas; y en iniciativas transversales, por ejemplo y para sorpresa de nadie, últimamente con foco en un par relacionadas con IA generativa.

Foto donde aparecemos 12 personas posando de pie, sucios y con barro en el suelo tras haber estado limpiando una casa en las afueras de Álora, Málaga, tras la DANA 2025 que también afectó a esa zona

Conclusión

Aunque al inicio costó un poco ver cómo gestionar ese tipo de rol pendular en el que a veces era difícil manejar la cantidad de cambios de foco y de niveles de abstracción, se me dio siempre confianza y autonomía. Finalmente me sentí muy cómodo con ese tipo de responsabilidades.

Me permitió poder influir a nivel organizativo como un manager primero a nivel de área y luego tratar de hacerlo a nivel de compañía, aunque sin las responsabilidades de gestión de personas. Y de vez en cuando poder ejecutar trabajo directamente por ejemplo programando, documentando o investigando de forma autónoma o colaborando con más personas en equipo.

Tras tener la oportunidad de trabajar con tanta gente diferente y variedad de áreas en el contexto de una scale-up, cierro esta etapa en Genially habiendo crecido mucho profesionalmente y con un montón de aprendizajes: técnicos, organizativos, de personas, de negocio… y siendo un fan de la marca y la compañía.

Y ahora ¿qué?

Toca arrancar una aventura nueva que no esperaba, uno de esos trenes que sientes que son una gran oportunidad que tienes que tomar, pero eso lo cuento un poco más adelante en otro post.

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

Juanjo Navarro

LinkBridge, gestor de enlaces

septiembre 25, 2025 05:46

¡He creado una extensión para navegador!

Este verano, aprovechando las vacaciones, he creado una extensión para navegador que puedes utilizar si te apetece.

Se llama LinkBridge y es un gestor de enlaces para que puedas acceder fácilmente a las webs que utilizas más frecuentemente.

Te dejo un vídeo de su uso y los enlaces de descarga y después te cuento algo más sobre ella.

Algunos otros navegadores pueden usar las extensiones de Chrome o Firefox.

De dónde viene la idea

Hace tiempo que venía usando como gestor de enlaces frecuentes en el navegador el software Flame.

Software de este tipo hay mucho, algunos muy populares, pero a mi me gusta Flame por su estilo minimalista tanto en lo visual como en lo funcional.

El problema de Flame es que es una aplicación que tienes que instalar en tu propio servidor, lo cual obviamente no está al alcance de muchas personas.

Pensé que estas funciones (especialmente las que yo usaba, que no son todas) se podrían cubrir perfectamente desde una extensión del navegador que no requiriese montar nada en nuestro propio servidor.

Funciones de la extensión

Las funciones que he implementado son las principales (y las que yo uso) del software Flame:

  • Tus enlaces aparecen cada vez que abres una nueva pestaña del navegador
  • Puedes agrupar los enlaces en grupos temáticos con dos niveles: Aplicaciones (de mayor tamaño) y Bookmarks.
  • Soporte de temas, tanto claros como oscuros
  • Exportación e importación de los enlaces

Acceso al código fuente

El código fuente (por si te interesan estas cosas) está disponible en GitHub: LinkBridge en GitHub

¡Espero que te guste y te sirva si te animas a probarla!

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

Blog Bitix

Novedades y nuevas características de Java 25

septiembre 19, 2025 07:00

Habitualmente publicaba un artículo por cada versión de Java, pero teniendo en cuenta que para producción es recomendable usar preferentemente las versiones LTS estoy prefiriendo publicar uno en cada versión LTS con el acumulado de características de la versión anterior. En este caso de Java 25 como nueva LTS que sucede a Java 21.

Continuar leyendo en Blog Bitix

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

Header Files

Mejorando los tiempos de compilación

agosto 28, 2025 09:00

Introducción

Como todos los lectores sabrán, C++ es un lenguaje compilado, lo que implica que su código fuente se transforma en un archivo binario ejecutable. No es necesario que el usuario final realice este proceso, lo hace el desarrollador. Generalmente es un proceso bastante rápido, pero a medida que el proyecto comienza a crecer también lo hace el tiempo requerido para su compilación. No es extraño encontrar proyectos de mediano tamaño que tardan 15-20 minutos en compilarse, y personalmente he trabajado con algunos que requerían unas 13 horas partiendo de cero.

Una de las primeras formas de reducir el tiempo de compilación usadas fue la de evitar compilar cosas que no fuesen necesarias: si un fichero ni ninguna de sus dependencias ni parámetros han sido modificados, una nueva compilación devolverá el mismo fichero objeto, por lo que una comparación de los timestamps entre ficheros fuente y objeto ayudará a centrarnos en aquellos que sí requieren ser procesados nuevamente. Este mecanismo es estándar en cualquier sistema de compilación, bien sean Makefiles, proyectos de MSVC, Xcode, etc.

Aún así, esto no suele ser suficiente, siendo frecuente tener que esperar entre 30 segundos y 5 minutos hasta que la compilación termine. Si sumamos el hecho de que tenemos que compilar multitud de veces cada día, en total supone un montón de tiempo dedicado a ver pasar líneas frente a nuestros ojos. Un día normal para mí conlleva entre 30 y 50 compilaciones, con una duración media de 30s, lo que nos da entre 15 y 25 minutos por jornada.

Créditos: https://xkcd.com/303/

Casi todos aprovechamos estos micro-descansos durante las compilaciones para hacer algo en paralelo. Dos minutos apenas da tiempo para ir al baño, en 4 podemos hacernos un café (contando el tiempo de ir a la cocina, calentar la leche, etc.). Podemos aprovechar para responder un email o mirar el estado del equipo, etc. Pero, salvo contadas ocasiones, la experiencia dicta que es mejor no hacer nada, no sólo porque lo que creíamos que era cosa de 1 minuto se extienda, sino porque además hacer muchas cosas al mismo tiempo significa perder el enfoque: el cambio de contexto penaliza la tarea principal. Lo mejor que podemos hacer es tratar de minimizar ese tiempo muerto (obviamente estoy asumiendo que compilamos cuando lo necesitamos y no como tic nervioso).

En este artículo comentaremos algunas estrategias de optimización (todas centradas en reducir la cantidad de código a compilar, ya veremos por qué), cómo aplicarlas eficientemente, y aspectos a tener en cuenta para que no nos salga el tiro por la culata.

Poniendo a dieta al compilador

El proceso de compilación de proyectos en C++ se lleva a cabo, normalmente, en tres etapas: preprocesado, compilación y enlazado. Simplificando mucho, diremos que en la primera el fichero de código fuente es leído y convertido en un fichero de código fuente intermedio completamente autocontenido (es decir, que toda la información necesaria para compilarse está presente en dicho fichero). En la etapa de compilación, cada uno de estos ficheros autocontenidos es analizado y las sentencias C++ son transformadas en código binario intermedio, aunque aún no es ejecutable. Es en la tercera etapa donde todas las unidades de compilación son enlazadas entre sí junto a las dependencias externas, resultando en el fichero ejecutable.

Primero que nada, es importante mencionar que el preprocesado no hace ningún tipo de análisis de código; más bien podríamos verlo como una serie de operaciones de edición de texto. Por ejemplo, las macros son operaciones de sustitución de bloques de código (buscar/reemplazar), la inclusión de ficheros de cabecera es como un copiar/pegar, y la compilación condicional es como eliminación de código. Es durante la compilación donde se determina si el fichero tiene una sintaxis C++ válida y se identifica qué partes de todo el código compilado son realmente necesarias.

Como podemos intuir, la compilación más rápida será aquella que sólo compile lo que realmente estamos necesitando.

Análisis de la compilación

Es posible que muchos managers no entiendan el beneficio de reducir en 3 segundos el proceso de compilación. La forma más sencilla de justificarlo es, claramente, desde el punto de vista económico: 3 segundos por 30 veces diarias por 20 días al mes es media hora que gano al mes para el proyecto por programador. Lo mismo aplica para las pipelines de CI/CD.

De todas formas, si para conseguir esos 3 segundos hemos necesitado 3 días de trabajo, no hemos mejorado el proceso, ya que el ROI (Return on Investment) será muy bajo. Pero si por el contrario lo hemos hecho en una hora, habrá merecido la pena.

Se ve claramente que, como en toda optimización, lo primero que tenemos que hacer es averiguar dónde necesitamos optimizar, para atacar los problemas que nos den el mayor salto posible. Y es que casi siempre los cuellos de botella están centrados en unos pocos lugares.

Centraré la sección de profiling en clang, aunque el modus operandi en general es análogo en otros compiladores. Lo primero será activar la opción -ftime-trace, con la cual obtendremos un análisis de la compilación con el tiempo dedicado a cada etapa de la compilación y fichero (en MSVC sería /timetrace). El fichero generado se ubicará junto a los ficheros objeto intermedios, con el mismo nombre de la unidad de compilación pero extensión JSON. Podemos abrirlo con la herramienta de tracing incluida en cualquier navegador Chromium (por ejemplo, en Edge es edge://tracing/), o con otras como Perfetto (https://ui.perfetto.dev/).

Identificando objetivos

Hay básicamente dos formas que uso para detectar los cuellos de botella. La primera sería analizar los ficheros JSON manualmente, y lo que suelo hacer en estos casos es ordenarlos por tamaño y centrarme en los más grandes; si bien no siguen un orden estricto, en general un fichero JSON grande significa que el timetrace recolectó muchos datos sobre el mismo. La segunda ya es más elaborada, y pasa por un script que lee los JSON y básicamente ensambla estadísticas globales del proyecto, incluyendo ficheros de mayor tiempo (medio), número de inclusiones y tiempo total.

Personalmente uso una combinación de ambas: con el script obtengo una clasificación (el podio), los ficheros que más impactan la compilación. Luego los analizo manualmente, abriendo cualquiera de los JSON referenciados para su análisis.

La siguiente imagen muestra las trazas para la compilación de un fichero .cpp modesto, de 2K líneas y 68KB de peso (es un ejemplo real, por lo que he ocultado algunos datos). Los resultados se muestran como un flame graph: el eje horizontal es el tiempo desde el inicio del procesado del fichero, donde cada bloque es una fase de la compilación y su ancho es el tiempo invertido en dicha fase; el eje vertical es el call stack.

Vista general de un timetrace

En este artículo nos centraremos en los bloques verdes, que representan la lectura, preprocesado y parseo (análisis sintáctico). Los bloques más grandes son nuestros principales cuellos de botella: ficheros que tardan mucho en ser analizados. Estos ficheros son los que tenemos que optimizar.

Ahora bien, debemos saber si, dado un bloque grande, el fichero asociado es pesado en sí mismo o porque otros (ficheros incluidos por el primero) lo engordan. Podemos obtener esta información en dos formas (complementarias entre sí): la primera es observar el eje vertical y el impacto de las dependencias (véase el bloque central: el primer fichero consume mucho, pero es obvio que es culpa de dos dependencias). La otra es observando los detalles de cada bloque (basta con seleccionar el bloque y ver el pánel inferior):

Detalle de un timetrace

Acá podemos ver que si bien el procesado del fichero toma 325ms (wall duration), el fichero como tal solo toma 2ms (self time).

Esta combinación de acciones permite centrar los esfuerzos en los ficheros más importantes, si no podría pasar que el análisis individual sin ningún tipo de guía nos lleve a optimizar ficheros con un impacto muy bajo en la compilación final del proyecto.

Estrategias a seguir

A continuación comentaremos algunas de las acciones que podemos llevar a cabo para mejorar los tiempos de compilación, aunque podrían resumirse en quitar lo que no hace falta. El orden de exposición corresponde al que uso personalmente, siguiendo como criterio el tiempo que lleva aplicarlas.

  • Incluir únicamente los ficheros de cabecera necesarios. Es normal que, a lo largo de la historia del proyecto, el código haya cambiado mucho, por lo que es posible que haya ficheros de cabecera innecesarios, o que estemos incluyendo un súper fichero que contiene muchas cosas cuando sólo necesitamos una. Cualquier limpieza viene bien, y más aún si es de alguno de nuestros objetivos. Esta estrategia puede no salir siempre bien en el caso de que otro fichero de cabecera del mismo documento esté incluyendo al que hemos borrado, con lo que no estamos ganando nada. Esta estrategia paga mejor cuando se aplica a limpiar los propios ficheros de cabecera de includes innecesarios, ya que el impacto se multiplica. Truco: algunos IDEs y analizadores estáticos proveen esta información de forma directa.
  • Utilizar forward-declarations. Si nuestro fichero de cabecera sólo hace uso de una clase como una referencia o puntero (es decir, no necesita saber detalles), podríamos eliminar el fichero de cabecera que la declara y sustituirla por un forward declaration.
  • Separar ficheros con múltiples declaraciones en ficheros individuales. Existe una regla de oro (aunque algo flexible) que dice que cada declaración debe ir en su propio fichero. Esto permite que incluyamos únicamente lo que necesitamos.
  • Extraer declaraciones anidadas. Es una especie de corolario de la estrategia anterior: si una clase define una clase o enumeración dentro de la misma, cualquier referencia a los segundos obligará a incluir a la primera por completo.
  • Utilizar el patrón Pimpl. No entraré en detalles, pero este patrón permite separar mejor la declaración de la implementación. Así no sólo evitamos incluir ficheros de cabecera que sólo son necesarios en la implementación, sino que además cualquier modificación en la misma no impacta a los ficheros que usan esta clase.
  • Refactorizar las clases grandes en clases más pequeñas y con menos responsabilidades. De nuevo, una forma de incluir sólo lo que se necesita, pero que además mejora enormemente el diseño reduciendo el acoplamiento entre módulos.

Pasándonos de la raya

Consideremos ahora algunas posibles desventajas de las estrategias mencionadas:

  • Un forward-declaration es un segundo lugar al que tenemos que prestar atención si hacemos algún cambio a un tipo de dato (especialmente su nombre) aunque normalmente esto implica únicamente un fallo de compilación.
  • Tener decenas o cientos de mini-ficheros para definir enums puede ser tedioso de mantener; en algunas ocasiones bastará con tener unos pocos ficheros de “tipos” básicos, agrupados por componente o función.
  • El uso del patrón Pimpl implica en la práctica una desreferencia de memoria adicional. En los sistemas modernos esto no suele ser un problema, pero convendría tenerlo presente si lo usamos en secciones de código donde el rendimiento es crítico (y el profiler ya nos ha dicho que el pimpl es el problema; no optimicemos prematuramente, primero el diseño).

Conclusión

Optimizar los tiempos de compilación no es sólo una cuestión de comodidad para el desarrollador, sino una inversión que mejora la productividad del equipo y reduce los costes del proyecto. Las estrategias presentadas en esta primera parte se centran en el principio fundamental de “compilar únicamente lo necesario”, atacando el problema en su origen: la cantidad de código que debe procesar el compilador.

La clave del éxito radica en medir antes de optimizar. Herramientas como -ftime-trace nos permiten identificar los verdaderos cuellos de botella y centrar nuestros esfuerzos donde realmente importa. No todos los ficheros tienen el mismo impacto, y una optimización bien dirigida puede resultar en mejoras significativas con un esfuerzo mínimo.

Recordemos que estas optimizaciones deben aplicarse con criterio: aunque las estrategias mencionadas son generalmente beneficiosas, siempre conviene evaluar el coste de mantenimiento frente a la ganancia obtenida.

El tiempo de compilación perdido nunca se recupera, pero el tiempo invertido en optimizarlo se amortiza cada día.

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

Header Files

5 cosas que puedes hacer al migrar a C++ moderno

agosto 14, 2025 06:00

Si bien C++11 y el resto de versiones del bien llamado C++ moderno llevan ya tiempo entre nosotros, muchos programadores siguen usando C++ a la antigua usanza, en detrimento de la legibilidad, flexibilidad de diseño e incluso del rendimiento que nos ofrecen las versiones más recientes. He aquí algunas cosas que puedes hacer fácilmente para comenzar a disfrutar de las ventajas del C++ moderno.

No hagas nada

Sí, como suena, la primera de ellas es “no hagas nada”. Haz un benchmarking de tu código antes y después de migrar y seguramente te sorprenderás, especialmente si haces un uso intensivo de contenedores de la biblioteca estándar.

Una de las principales razones de esto es la introducción de la semántica de movimiento, con la consecuencia añadida de que muchos de los métodos y constructores ya existentes han recibido soporte para argumentos r-value, lo que significa que, sin mover un dedo, disfrutamos ya de sus beneficios. Además, existen otras optimizaciones que pueden destacar dependiendo del compilador que usemos, tales como el return-value-optimization (RVO, obligatorio a partir de C++17).

El siguiente ejemplo muestra un caso no poco común de creación de un vector de objetos (adjunto también el código del benchmark):

std::vector<std::string> create_vector(size_t n, std::string s)
{
    std::vector<std::string> v;

    for (size_t i = 0; i < n; ++i)
    {
        v.push_back(s + s);
    }

    return v;
}

int main(int argc, char* argv[])
{
    const size_t n = atoi(argv[1]);
    const size_t m = atoi(argv[2]);

    int z = 0;
    for (size_t i = 0; i < m; ++i)
    {
        char c = (rand() % 26) + 'a';
        size_t sn = (rand() % 1000) + 10;
        std::vector<std::string> v = create_vector(n + i, std::string(sn, c));

        const int x = v.front().front();
        z += x; // to prevent compiler to remove code
    }

    std::cout << z << std::endl;

    return 0;
}

El compilador usado ha sido Apple clang version 14.0.3 (clang-1403.0.22.14.1), todos con nivel de optimización -O2 y bajo plataforma ARM. Todas las pruebas se han hecho con n=10000 y m=1000, midiendo el tiempo de ejecución con time 5 veces y promediando los resultados:

  • --std=c++03: real 5.586s, user 3.325s, system 2.010s
  • --std=c++11: real 1.856s, user 1.166s, system 0.689s
  • --std=c++14: real 1.875s, user 1.155s, system 0.692s
  • --std=c++17: real 1.829s, user 1.139s, system 0.689s
  • --std=c++20: real 1.839s, user 1.159s, system 0.674s

Sin hacer nada hemos podido triplicar la velocidad de nuestro programa simplemente compilando con una versión más moderna del lenguaje. Esto obviamente no quiere decir que todo nuestro programa se acelere 3x; este ejemplo está preparado específicamente para mostrar esta mejora, pero da una idea clara de los beneficios que implican las nuevas características del lenguaje.

Auto-matiza la deducción de tipos

C++11 introdujo un nuevo significado para la palabra reservada auto (es el único caso que conozco de cambios en este sentido). Se usa en una declaración para deducir el tipo de la variable a partir de su inicialización; y si el compilador no es capaz de hacer una deducción única, la declaración se considera incorrecta y se genera un error.

El uso de auto permite reducir la cantidad de código a escribir (y leer), simplificando el mismo, y moviendo el nivel de abstracción al qué en lugar del cómo (o con qué).

std::vector<int> generate_ids(int n) { ... }

// Before
const std::vector<int> ids_old_way = generate_ids();

// Now
const auto ids_old_way = generate_ids();

Un caso especialmente útil es a la hora de usar iteradores:

std::map<std::string, std::vector<std::string> > synonyms;

// Before
std::map<std::string, std::vector<std::string> >::iterator it = synonyms.find(word);

// Now
auto it = synonyms.find(word);

Además, se facilitan los refactorings y optimizaciones de código al reducir el número de errores de compilación: si se cambia el tipo de un contenedor, auto se deducirá el nuevo iterador y listo (obviamente, si el nuevo tipo de iterador no es compatible con el anterior sí puede haber problemas, por ejemplo si se pasa de un std::vector a un std::unordered_map). Lo mismo sucedería si se cambia el tipo de retorno de una función: con auto tendríamos ya hecha una parte del pastel.

La única pega (que por otra parte tiene su lado positivo), es que el nombre de la variable cobra más peso ya que no tenemos a la mano (¿ojo?) su tipo. Pero como dije, esto puede incluso mejorar el código al obligarnos a poner nombres descriptivos (cardVector podría ser cardCollection o simplemente cards, y esa enigmática DatabaseConnectionController* dcc = DatabaseConnectionController::create() pasar a ser auto dbConnectionController = DatabaseConnectionController::create()).

Pythoniza tu código

El que diga que C++ moderno no ha copiado se ha inspirando en aspectos de otros lenguajes más jóvenes (especialmente Python), pues simplemente está negando lo obvio. Las nuevas sintaxis introducidas no sólo ayudan a hacer un código más compacto, sino que además permiten mejorar la expresividad del código y elevar el nivel de abstracción.

  • Range-for: seguramente la más conocida de estas pythonizaciones, permite recorrer una colección de elementos, sin necesidad de preocuparse del tipo exacto de contenedor. C++ ya disponía de un par de formas de hacerlo (un for desde begin hasta end, y el std::for_each), pero el range-for es más natural en muchos casos donde solamente queremos recorrer los elementos (pero no modificar el contenedor, por ejemplo).

      for (size_t i = 0; i < container.size(); ++i) { // random-access iterators
          foo(container[i]);
      }
    
      for (std::list<int>::iterator it = list.begin(); it != list.end(); ++it) { // basic iterator version
          foo(*it);
      }
    
      for (auto it = container.begin(); it != container.end(); ++it) { // more generic using 'auto'
          foo(*it);
      }
    
      std::for_each(container.begin(), container.end(), foo); // using an algorithm
    
      for (auto&& c : container) { // range-for
          foo(c);
      }
    

    Como detalle curioso, he visto cómo el uso del range-for puede optimizar código en determinados momentos. Un range-for siempre copiará el iterador end(), por lo que si nuestro contenedor hacía uso de un end() costoso, eso que nos ahorramos.

    Por último, una rápida comparación entre std::for_each y los range-for:

    • Los range-for permiten utilizar las instrucciones break y continue para modificar el flujo.
    • std::for_each puede ser paralelizado (C++17, ver más abajo).
  • Utiliza las listas de inicialización, de esta forma puedes inicializar colecciones de datos en la propia declaración, e incluso hacerlas constantes.

      const std::map<int, std::string> numbers = {
          {1, "one"},
          {2, "two"},
          {3, "three"},
      };
    
  • Mejora la expresividad atando variables. Devolver pares o tuplas es una forma común de evitar crear structs específicamente para devolver varios valores en una función. Ahora bien, el problema surge rápidamente cuando no sabemos qué significan el .first o el .second, y peor aún si comparten el mismo tipo de datos.

      std::pair<int, std::string> get_id_and_name();
    
      // Without binding
      const auto id_and_name = get_id_and_name();
      std::cout << "ID: " << id_and_name.first << ", name: " << id_and_name.second << std::endl;
    
      // With binding
      const auto [id, name] = get_id_and_name();
      std::cout << "ID: " << id << ", name: " << name << std::endl;
    

    También puede usarse al iterar sobre mapas:

      std::map<int, std::string> roman_numbers;
    
      // Without binding
      for (const auto& it : roman_numbers) {
          std::cout << "Number " << it.first << " is " << it.second << std::endl;
      }
    
      // With binding
      for (const auto& [decimal, roman] : roman_numbers) {
          std::cout << "Number " << decimal << " is " << roman << std::endl;
      }
    

Haz uso de los nuevos contenedores y métodos

C++11 introduce nuevas estructuras de datos que mejoran drásticamente el rendimiento bajo determinadas condiciones:

  • Todo std::map cuyas claves sean tipos básicos (char, int, float, enumeraciones, punteros, etc.), y con más de unas pocas decenas de elementos, puede ser reemplazado por std::unordered_map. Es el equivalente de una tabla hash y sus operaciones son mucho más eficientes: O(1) de media para la inserción y la búsqueda, dependiendo de las colisiones que puedan generarse. También puede usarse con otros tipos, tales como std::string pero acá el rendimiento va a depender también del tamaño medio de la clave. Cuidado que con mapas de poco tamaño puede no notarse el rendimiento o incluso disminuir (el coste relativo de calcular la función hash respecto a la comparación del tipo bruto aumenta conforme el número de elementos es más pequeño).
  • De forma análoga tenemos a std::unordered_set como alternativa a std::set. En ambos casos es importante hacer notar que, tal y como indica su nombre, las claves no están ordenadas, por lo que hay que tener cuidado si la implementación actual depende de ello. Esto no debe de ser un impedimento por sí mismo para migrar; por ejemplo, si sólo se requieren las claves ordenadas para un proceso de serialización, y el rendimiento del mismo no es crítico, se podrían extraer las claves, ordenarlas y serializarlas en orden, manteniendo así la compatibilidad con el código anterior.
  • Usar std::string_view (C++17) en los argumentos de funciones que no requieren modificar la cadena de texto. std::string_view es básicamente un wrapper al estilo de las cadenas de texto en C (un puntero al primer caracter y un tamaño) pero de forma segura y compatible con std::string donde haga falta. De esta forma, cuando se requiere un subconjunto de la cadena, se evita pasar copias innecesarias.

Gracias a los r-value y a los variadic templates, C++11 introdujo nuevos métodos para añadir elementos a un contenedor de forma más eficiente. Tradicionalmente usamos push_back para añadir elementos a un std::vector o insert para los std::map. Ahora bien, en ambos casos el método primero reserva e inicializa el espacio para el elemento en el contenedor, y luego es que copia (o mueve) el elemento en sí. En la práctica esto significa que tenemos que llamar a un constructor por defecto y a un constructor de copia (o movimiento). En C++11 tenemos std::vector::emplace_back y std::map::emplace que nos permitirán construir in-place el elemento en su zona de memoria reservada, generando un código mucho más eficiente. Desgraciadamente, y por compatibilidad hacia atrás, los métodos anteriores push_back e insert no pudieron ganar esta mejora y la migración tenemos que hacerla a mano (además de cambiar costumbre de los métodos a usar).

struct my_bag {
    my_bag(int32_t a, int32_t b, int32_t c);
};

std::vector<my_bag> bags;

// Before
bags.push_back(my_bag(1, 3, 3));

// Now
bags.emplace_back(1, 2, 3);

Así, vemos que emplace_back se ha de llamar con los mismos argumentos del constructor. Si hiciese my_bag bag{2, 3, 4}; bags.emplace_back(bag); estaría llamando al constructor de copia, pero in-place, que sería una mejora más no la óptima.

Reduce la dependencia de bibliotecas de terceros

Como extensión del punto actual, y como ya se ha visto, C++11 y posteriores han ido extendiendo la biblioteca estándar con nuevos integrantes, muchas veces inspirándose en populares bibliotecas de terceros, especialmente Boost.

  • std::thread, std::mutex, para gestión de hilos y sincronización. boost::thread no es exactamente igual que std::thread, la de Boost tiene un conjunto mayor de funcionalidades, tales como interrupción de un hilo y manejo de colecciones de hilos.
  • std::chrono, para operaciones con unidades de tiempo.
  • std::optional, std::variant, para tipos opcionales y variantes tipo-seguras.
  • std::filesystem (C++17), para gestión del sistema de ficheros (aunque no es 100% equivalente).
  • std::ranges (C++20), inspirándose en ranges-v3.

Mejora la gestión de recursos

Uno de los puntos que muchos desarrolladores critican a C++ es la gestión de memoria (punteros nulos, colgantes, etc). Es cierto que, tal y como comentaba Bjarne Stroustrup, C hace que sea fácil pegarte un tiro en el pie; C++ lo hace más difícil, pero cuando lo haces te vuela toda la pierna. Pero también es cierto que desde C++11 es aún más difícil ya que la biblioteca estándar provee de muchos mecanismos para evitarlo. Los dos principales son std::unique_ptr y std::shared_ptr. el primero permite expresar que un objeto tiene un único dueño, mientras el segundo distribuye, mediante un contador de referencias, la propiedad entre varios.

Un ejemplo común para unique_ptr son las clases manager, que centralizan el acceso a un determinado recurso. Así, esta clase puede tener un unique_ptr y pasar una referencia a todas las demás. Además, los unique_ptr no pueden ser copiados, sólo movidos, por lo que la transferencia de propiedad es explícita. Los shared_ptr, por otro lado, son más comunes en elementos con un ciclo de vida impredecible o donde los actores creadores del objeto pueden desaparecer antes que el objeto en sí.

Un código de C++ moderno no debería usar punteros raw para almacenar objetos. A la hora de pasar un objeto unique_ptr podemos o bien usar una referencia (que además obliga a no pasar un nullptr); si el objeto puede no estar inicializado podríamos pasar un std::optional<Objeto&> (C++17), pero en este caso no hay una ventaja muy clara respecto a pasar un puntero raw ya que se puede usar mal en ambos casos. El acceso a punteros nulos sigue siendo responsabilidad del programador. Así que cuidado en este caso.

Ambos tipos de punteros inteligentes se basan en un principio muy conocido de C++ y del que ya he hablado en otras ocasiones: el RAII (ver RAII 1 y RAII 2). No me extenderé acá en este tema y refiero a dichas páginas para más información

Bonos

Algunos pequeños cambios adicionales que marcan una gran diferencia:

  • Nuevos literales: ""s para std::string, ""ms para std::chrono::milliseconds, 0x1234_u32 para enteros con tipo específico, etc. Hacen el código más expresivo y evitan conversiones implícitas.
  • Paralelización de algoritmos (C++17): simplemente añade std::execution::par a tus std::sort, std::transform y otros algoritmos para aprovechar múltiples núcleos automáticamente.
  • if constexpr (C++17): cambia el complicado SFINAE por código estructurado más legible en templates. Permite escribir código condicional que se evalúa en tiempo de compilación.
  • Designated initializers (C++20): inicializa estructuras de forma más clara con Point{.x = 10, .y = 20} en lugar de Point{10, 20}.
  • std::optional (C++17): expresa explícitamente cuando una función puede no devolver un valor válido, eliminando la ambigüedad de los valores “especiales” como -1 o nullptr.
  • Fold expressions (C++17): simplifica operaciones en parameter packs con expresiones como (args + ...) en lugar de recursión manual.
  • Lambda expressions mejoradas: desde C++11, pero con mejoras constantes. Usa [&] para capturar por referencia, [=] por copia, o mezcla ambas. En C++14 puedes usar generic lambdas con auto en los parámetros.
  • constexpr everywhere: marca funciones como constexpr siempre que sea posible. El compilador las evaluará en tiempo de compilación cuando pueda, mejorando el rendimiento.
  • std::array vs arrays C: reemplaza int arr[10] por std::array<int, 10>. Obtienes los beneficios de los contenedores STL sin coste adicional.
  • Inicialización uniforme: usa {} en lugar de () para la inicialización. Es más segura (previene narrowing conversions) y más consistente.

Conclusión

Migrar a C++ moderno no es solo cambiar el estándar del compilador; es adoptar una mentalidad que prioriza la expresividad, el rendimiento y la seguridad. Como hemos visto, algunas de estas mejoras llegan prácticamente “gratis” con solo recompilar el código, mientras que otras requieren cambios mínimos que pueden transformar drásticamente la calidad del software.

Los cinco puntos que hemos cubierto —aprovechar las optimizaciones automáticas, usar auto para simplificar el código, adoptar las sintaxis “pythonizadas”, migrar a contenedores más eficientes y mejorar la gestión de recursos— representan solo la punta del iceberg de lo que C++ moderno tiene para ofrecer.

La belleza del C++ moderno radica en que permite escribir código más limpio y expresivo sin sacrificar el rendimiento que siempre ha caracterizado al lenguaje. Al contrario, en muchos casos lo mejora. Así que la próxima vez que inicies un proyecto o tengas la oportunidad de refactorizar código existente, no dudes en darle una oportunidad a estas características. Tu código (y tus compañeros de equipo) te lo agradecerán.

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

Navegapolis

IA en empresas ágiles y en las que dicen ser ágiles

junio 27, 2025 10:36

En la comunidad ágil, documentos como el “Scrum Guide Expansion Pack“ intentan guiar el uso de la IA en las organizaciones ágiles, pero la verdad es que pueden ser cuchillos de doble filo: puede ayudar a usar la IA sobre las bases de la agilidad o pueden ser la coartada perfecta para perpetuar viejos hábitos disfrazados de agilidad. En concreto el «Scrum Guide Expansion Pack» dedica gran esfuerzo a recordar que “lo humano es lo primero” y que la IA debe ser sólo un “aumento cognitivo”, pero al mismo tiempo se explaya en describir con detalle la eficiencia que aporta la inteligencia artificial.

Es inevitable preguntarse si en la práctica, la presión por los resultados no inclinará la balanza hacia la optimización y convertirá las buenas intenciones humanistas en discursos de bien quedar.

La encrucijada oculta en el mismo marco

Para la cultura de la empresa, el «Scrum Guide Expansion Pack» es un test de Rorschach, que refleja la que ya tiene. La empresa puede proyectar sus valores sobre el texto y encontrar, la confirmación que necesita para validar su enfoque.

Una organización centrada en las personas, busca en el texto la inspiración para validar o desarrollar su cultura de confianza, creatividad y desarrollo de personas y , por supuesto, la encuentra:

  • Encuentra un liderazgo que sirve, no que manda: Confirma su creencia en un liderazgo de servicio al leer que su función es «…cultivar el entorno para los equipos Scrum autogestionados…«. Identifica el significado de nutrir y remover obstáculos, no dirigir ni controlar.
  • Encuentra en la autogestión un motor de innovación al encontrar que los «Equipos Scrum autogestionados organizados en torno al valor son cruciales para la resolución creativa de problemas y la captura de la emergencia«. Valida que la autogestión es el camino para desatar el potencial colectivo ante la complejidad.
  • Pone a la IA al servicio del talento humano, afianzando su visión de la tecnología como una palanca para las personas, al leer que la IA debe permitir que «…los miembros humanos del equipo Scrum se centren en consideraciones estratégicas, creativas y éticas«. La IA es un asistente que libera, no un sustituto que optimiza.

Pero al mismo tiempo, una organización con una cultura arraigada en la eficiencia, la predictibilidad y el control de procesos, busca en el mismo documento la justificación para perfeccionar y acelerar su maquinaria productiva y, también la encuentra:

  • Encuentra en el Lean Thinking la coartada para la eficiencia extrema: El pensamiento lean «…reduce el desperdicio en el trabajo y en cómo se lleva a cabo…«. Desperdicio es todo lo que no sea producción directa, osea se pueden eliminar debates «innecesarios» y estandarizar procesos para maximizar el flow.
  • Encuentra en la IA el camino hacia la automatización del control: Se entusiasma al leer que la IA puede «…actualizar y repriorizar los elementos del Product Backlog…» o que sus «…análisis basados en IA mejoran la transparencia, la inspección y la adaptación», viendo aquí un consejo para reemplazar el juicio humano por algoritmos eficientes que optimizan el rendimiento a escala.
  • Encuentra en la responsabilidad un mecanismo de conformidad: Define la profesionalidad de forma rígida al encontrar que los desarrolladores son «…colectivamente responsables de: Instaurar la calidad adhiriéndose y mejorando la definición de resultado terminado (Definition of Output Done)«. La responsabilidad se convierte en conformidad con la norma, y la calidad en una checklist que el sistema debe superar.

Para esta organización, la “agilidad” es un marco adecuado para modernizar el taylorismo, con un barniz vanguardista, sin alterar su núcleo de control y eficiencia.

Las dos organizaciones aplican el “Pack”, y en ambos casos el resultado es la intensificación la cultura que ya poseían.

Cuando el marco deja de ser un mapa y se convierte en un espejo

Un marco ambiguo puede funcionar como un espejo que refleja la cultura y las intenciones preexistentes. A una organización centrada en las personas, el marco valida y refuerza su enfoque, pero a una organización centrada en el producto, también.

Puede ser un eco de lo que ya es la organización y el peligro se hace más profundo cuando no se usa como espejo sino como máscara. Porque la ambigüedad permite adoptar la retórica de la agilidad centrada en las personas —»empoderamiento» , «autogestión» «seguridad psicológica», etc. — para construir una fachada sin una realidad consecuente.

Las consecuencias: cinismo, estancamiento y devaluación

Para las personas, el cinismo de las organizaciones que predican el humanismo pero practican el taylorismo es agotador. Mata la confianza, la motivación y con ello: la creatividad.

Para la organización el resultado es una transformación estancada. Se adoptan los rituales, pero sin cambiar el ADN cultural, confundiendo la actividad con el progreso.

Y para la comunidad, el resultado es la devaluación del término «ágil», que pierde su significado para convertirse en un conjunto de herramientas que cualquier cultura, puede adoptar.

La entrada IA en empresas ágiles y en las que dicen ser ágiles se publicó primero en Navegápolis.

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

Navegapolis

Agilidad en la encrucijada de la IA: ¿valor o personas?

junio 24, 2025 06:04

El Manifiesto Ágil redefinió el trabajo del conocimiento, al valorar a los «individuos y sus interacciones por encima de los procesos y las herramientas«. Durante más de dos décadas, este principio ha sido la estrella polar de los equipos que desarrollan en entornos complejos y volátiles (VUCA), en los que el valor lo aportan las personas. Pero ahora, la inteligencia artificial desafía esta situación y nos pone en una encrucijada.

La nueva realidad plantea una pregunta incómoda: en un mundo en el que la única inteligencia disponible es la humana, las personas, ¿son un fin en sí mismo o el medio para lograr el verdadero fin: el valor?

El Pacto Tácito: valor a través del bienestar

El paradigma de la producción está acotado entre dos extremos

  • los entornos industriales, donde son los procesos y la tecnología los responsables de la calidad y del valor del resultado. Donde las personas actúan como operarios que “ayudan” o supervisan, para que se ejecuten correctamente.
  • En el otro lado, en los entornos VUCA, caracterizados por la ambigüedad y el cambio constante. Donde la calidad y el valor del resultado depende del conocimiento tácito de las personas, ese que no puede ser explicitado en un proceso, y donde son los procesos y la tecnología los que “ayudan” y potencian el valor de las personas.

La agilidad entendió una verdad biológica: el talento y la creatividad humana dependen de factores emocionales como el estado de ánimo y la motivación. Para que “fluyan” es necesaria una cultura ágil en un entorno de trabajo centrado en el bienestar de las personas.

Esto nos lleva al dilema central: ¿Cuál es el objetivo de una empresa al apostar por la agilidad? ¿Las personas, y el valor que aportan es la consecuencia?, ¿o directamente el valor y para lograrlo tiene que desarrollar una cultura de bienestar? Hasta ahora la pregunta ha sido puramente filosófica, porque en los dos casos la respuesta es la misma: céntrate en las personas.

La Disrupción: cuando perdemos el monopolio de la inteligencia

Es probable que en poco tiempo la inteligencia artificial alcance el nivel general (AGI) y de capacidad “agéntica”, que rete lo que hasta ahora ha sido monopolio de la inteligencia humana. Pero no hace falta esperar a que esto ocurra (si finalmente ocurre). La actual generación de IA «estrecha» avanzada ya está ejecutando tareas complejas de conocimiento que eran dominio exclusivo del ser humano. Desde el análisis de datos en tiempo real hasta la generación de código o la validación acelerada de hipótesis. La IA ya puede conseguir eficiencia y optimización a una escala sin precedentes.

Esto convierte la pregunta filosófica de antes en una decisión de negocio crítica. Si el objetivo es maximizar el valor y la IA puede ofrecerlo sin el “coste” que implica la gestión el bienestar humano, la tentación de elegir esta ruta es muy atractiva.

La contradicción ya es palpable. Documentos como el «Scrum Guide Expansion Pack» dedican un gran esfuerzo a recordarnos que «lo humano es lo primero» y que la IA debe ser solo un «aumento cognitivo». Pero al mismo tiempo se explayan con en describir con detalle la eficiencia que aporta la inteligencia artificial. Es inevitable preguntarse si en la práctica, la presión por los resultados no inclinará la balanza hacia la optimización y convertirá las buenas intenciones humanistas en discursos de bien quedar.

La pregunta de si el objetivo es el valor o las personas pone al descubierto la base de las dos formas de entender la agilidad. Lo que se viene denominando “hacer agilidad” o “ser ágil”

Hacer agilidad o agilidad técnica.

Consiste en un desarrollo iterativo e incremental para entregar valor temprano, frecuente y creciente, empleando el conocimiento del sistema, que antes de la IA estaba explicitado en el binomio de procesos y tecnología y ahora se le suma un tercer pilar: la inteligencia artificial. La premisa es la de los entornos industriales: «la calidad del resultado depende de la calidad de los procesos (y la tecnología)».

Aquí las personas tienen un rol de asistencia. «Ayudan» o «asisten» a que el proceso tecnológico, ahora inteligente, se realice correctamente. Son los «humanos en el bucle» que validan las sugerencias de la IA, supervisan los algoritmos y corrigen las desviaciones del sistema optimizado. La agilidad aquí es sinónimo de velocidad, eficiencia y predictibilidad. Es el camino de la optimización, una ruta atractiva, fácil de justificar en un informe de resultados, pero que simplifica la agilidad a su esqueleto mecánico, despojándola del alma humanista.

Ser ágil o Agilidad completa.

Representa una reafirmación consciente de los principios originales. Es una «Agilidad completa» o humanista que, si bien también entrega valor temprano, de forma iterativa e incremental, lo hace desde un punto de partida opuesto.

Esta vía se aferra al principio de que la agilidad prefiere «a las personas y su interacción sobre los procesos y las herramientas», incluso cuando las herramientas son redes neuronales capaces de proezas cognitivas. Aquí, el conocimiento que realmente importa sigue siendo el «tácito humano», aquel que emerge de la experiencia, la colaboración, la intuición y la creatividad de un equipo de personas motivadas.

En este modelo, los procesos, la tecnología y la inteligencia artificial no son los protagonistas del sistema, sino los potenciadores del talento humano. La IA se convierte en un poderoso asistente que automatiza las tareas repetitivas, ofrece datos para enriquecer el debate, libera a las personas de la carga cognitiva trivial y permite al equipo centrarse en lo que los humanos hacemos de forma única: comprender el contexto del mundo físico real, empatizar con el cliente, negociar las complejidades políticas de un proyecto y, sobre todo, innovar de forma disruptiva.

Esta agilidad requiere, ahora más que nunca, «culturas ágiles» que proporcionen el bienestar y desarrollo personal. Porque si la IA puede replicar la inteligencia analítica, la ventaja competitiva humana residirá precisamente en su «naturaleza biológica / emocional»: la motivación, la curiosidad y un estado de ánimo positivo son las claves para que el talento pueda «fluir» y superar las soluciones algorítmicas.

¿Qué agilidad queremos?

La agilidad agilidad no se concreta por los avances de la IA, sino por las decisiones de los líderes. La elección entre un modelo «técnico» y uno «completo» no es una decisión tecnológica, sino una decisión estratégica y filosófica.

Podemos usar la IA para construir un taylorismo digital, una versión eficiente y optimizada de desarrollo de productos o podemos usarla para potenciar la inteligencia colectiva de los equipos.

¿Para qué queremos ser ágiles?

La entrada Agilidad en la encrucijada de la IA: ¿valor o personas? se publicó primero en Navegápolis.

» 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