Blog Bitix

Calendario de Arch Linux, cubo de comandos y Tux en papel

marzo 23, 2023 06:00

En este artículo rescato, actualizo y comparto de nuevo unos elementos de papiroflexia que ofrecen información y son decorativos. Unos calendarios con la temática de Arch Linux uno de pared, de mesa y otro con forma de cubo de doce caras, un cubo con comandos de Arch Linux y el Tux en papel. Son relativamente simples de montar con un poco de tiempo y además de ofrecer información decoran.

Continuar leyendo en Blog Bitix

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

Variable not found

Patrones de listas (list patterns) en C# 11

marzo 21, 2023 07:05

C#

El pattern matching de C# proporciona la capacidad de analizar expresiones para ver si cumplen determinados "patrones" o presentan características determinadas. Podéis ver algunos ejemplos interesantes en el post Un vistazo a los patrones relacionales y combinadores.

Aunque ya los tengo bastante interiorizados y hago uso de ellos cuando toca, todavía no se me ha dado el caso de necesitar los patrones de listas, introducidos hace unos meses en C# 11. Así que no está de más echarles un vistazo para cuando se dé la ocasión 😉

Qué son los list patterns o patrones de listas

Esta funcionalidad de C# nos permite comprobar si los elementos de una secuencia siguen el patrón que indiquemos, con la posibilidad adicional de poder extraer valores o rangos de valores cuando nos interese.

Esto se entiende mucho mejor con ejemplos, así que vamos a ver algunos. Probablemente el escenario más sencillo que podemos encontrar es algo como lo siguiente:

int[] numbers = { 1, 2, 3, 4, 5};

Console.WriteLine(numbers is [1, 2, 3, 4, 5]); // true
Console.WriteLine(numbers is [1, 2, 3]); // false

Seguro que podéis entender el código a simple vista. Usamos el operador is sobre el array [1, 2, 3, 4, 5] para preguntar si sus valores coinciden exactamente con los patrones que andamos buscando. En el primer caso el matching es completo, pero no ocurre lo mismo con el segundo patrón, donde buscamos un array de tres elementos con valores [1, 2, 3].

Ojo, es importante tener en cuenta que los valores que usamos en el patrón deben ser constantes. Por tanto, el siguiente código fallaría en compilación:

int one = 1, two = 2;
int[] numbers = { one, two, 3, 4, 5};
Console.WriteLine(numbers is [one, two, 3, 4, 5]); // Constant value expected (CS0150)

Por esta razón, podemos usar este tipo de pattern matching únicamente con tipos de datos que podamos representar en el código como una constante, como char, string, bool, o todos los numéricos:

string[] letters = { "a", "b", "c" };
Console.WriteLine(letters is ["a", "b", "c"]); // true

También es importante saber que podemos usar list patterns con cualquier tipo de colección, siempre que sea contable (es decir, que tenga una propiedad Length o Count) y troceable (que permita la obtención de slices o porciones basadas en rangos). Por tanto, el siguiente código será válido:

var numberList = new List<int>(){ 1, 2, 3 };
Console.WriteLine(numberList is [1, 2, 3]);

Pues seguro que estás pensando que lo que hemos visto hasta ahora no es demasiado útil, pero claro, solo hemos arañado muy ligeramente la superficie de los list patterns. Vamos a seguir viendo otros escenarios mucho más potentes, y a los que probablemente veréis más utilidad.

El patrón slice ".."

Los ejemplos anteriores buscaban el matching en el conjunto completo de elementos, pero tenemos forma de comprobar exclusivamente inicios o finales de la secuencia usando el patrón slice "..".

Por ejemplo, fijaos qué forma más curiosa e interesante de comprobar los primeros y últimos elementos de la lista:

int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

Console.WriteLine(numbers is [1, .., 9]); // True
Console.WriteLine(numbers is [1, 2, .., 8, 9]); // True

Básicamente, usamos la sintaxis .. para indicar que nos da igual lo que vaya ahí dentro, siempre que se cumpla la parte constante del patrón que buscamos al principio y al final.

Esto podríamos usarlo también usarlo para buscar exclusivamente inicios o finales:

int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

Console.WriteLine(numbers is [.., 8, 9]); // True
Console.WriteLine(numbers is [1, 2, ..]); // True

Console.WriteLine(numbers is [.., 8]); // False
Console.WriteLine(numbers is [2 ,..]); // False

Hay tres cosillas interesantes que añadir en este punto.

Primero, los dos puntos del slicing solo se pueden introducir una vez en cada patrón. Es decir, el siguiente código fallaría en compilación:

int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

// Error CS8980: Slice patterns may only be used once
// and directly inside a list pattern.
Console.WriteLine(numbers is [1, .., 5, .., 9]);

Segundo, y muy importante, la porción comprobada por el patrón slice podría estar vacía y aún así seguiría cumpliendo el patrón. Es decir, cuando usemos ".." para comprobar elementos de una secuencia, el matching se producirá aunque no haya nada dentro, como podemos ver a continuación:

int[] numbers = { 1, 2, 3 };
Console.WriteLine(numbers is [1, 2, 3]); // True
Console.WriteLine(numbers is [1, .., 3]); // True
Console.WriteLine(numbers is [1, 2, .., 3]); // True
Console.WriteLine(numbers is [1, 2, 3, ..]); // True
Console.WriteLine(numbers is [.., 1, 2, 3]); // True

Tercero, el patrón slicing permite el uso de subpatrones para restringir los valores que pasarán el filtro. En el siguiente ejemplo podemos ver cómo utilizar esta característica para detectar secuencias que comiencen con 1, acaben con 5, y entre medios tengan entre 2 y 5 elementos:

int[] numbers = { 1, 2, 3, 4, 5 };
Console.WriteLine(numbers is [1, .. { Length : >=2 and <=5}, 5]); // True

int[] moreNumbers = { 1, 2, 5 };
Console.WriteLine(moreNumbers is [1, .. { Length : >=2 and <=5}, 5]); // False

El patrón de descarte "_"

El carácter de descarte _ podemos usarlo como placeholder para representar un único elemento, cuyo valor no nos interesa.

Por ejemplo, a continuación vemos que el matching se produce cuando una secuencia de cinco elementos exactos comienza con 1 y 2, y finaliza con 5:

int[] numbers = { 1, 2, 3, 4, 5 };
Console.WriteLine(numbers is [1, 2, _, _, 5]);

Obviamente, el patrón de descarte "_" puede combinarse con el de slicing. En los siguientes ejemplo, buscamos una lista que comience por cualquier valor seguido del número 2, acabando en 5:

int[] numbers = { 1, 2, 3, 4, 5 };
Console.WriteLine(numbers is [_, 2, .., 5]); // True

Patrones relacionales y combinadores

Como vimos en un post anterior, los patrones relacionales permiten usar el conjunto de operadores relacionales <, <=, >, y >= para detectar los valores deseados, en lugar de usar simples constantes.

Pues bien, esta capacidad está también disponible en en los patrones de lista, como podemos ver a continuación. En el primer ejemplo, buscamos una secuencia de cualquier número de elementos que comience y acabe por un número mayor que cero:

int[] numbers = new[] { 1, 2, 3, 4, 5 };
Console.WriteLine(numbers is [> 0, .., > 0]); // True

Los operadores relacionales podemos usarlo en el lugar que introduciríamos un valor constante. El siguiente ejemplo complica algo más el patrón, con relacionales especificando valores posibles en cada entrada de la secuencia:

int[] numbers = new[] { 1, 2, 3, 4, 5 };
Console.WriteLine(numbers is [> 0, <= 2, <= 3, < 5, > 4]); // True

También en el post al que hacíamos referencia vimos que los combinadores permitían usar las palabras clave and y or para evaluar condiciones compuestas, lo que permite expresar patrones aún más complejos.

Por ejemplo, a continuación vemos el patrón que encajaría con una lista que comience por un valor entre 1 y 5, y acabe con el valor 5 u 8:

int[] numbers = new[] { 1, 2, 3, 4, 5 };
Console.WriteLine(numbers is [>= 1 and <= 5, .., 5 or 8]); // True

Extracción de valores: el patrón var

Cuando el matching se produce, podemos aprovechar la ocasión para extraer valores de la secuencia.

int[] numbers = new[] { 1, 2, 3, 4, 5 };
if (numbers is [var first, .., var last])
{
Console.WriteLine($"{first}, {last}");
}

Pero ojo, porque los valores de first y last solo serán establecidos cuando cuando se detecte la coincidencia entre la secuencia de entrada y el patrón especificado. Por tanto, el ámbito de las variables estará restringido al bloque de código de la condición donde se realice la comprobación de matching, lo que provocará que el siguiente código no compile:

if (numbers is [var first, .., var last])
{
// first y last son visibles aquí:
Console.WriteLine($"{first}, {last}");
}
// Pero no aquí:
Console.WriteLine(first); // CS0165: Use of unassigned local variable 'first'

También podemos usar el patrón var para capturar el contenido de rangos completos. En el siguiente ejemplo buscamos secuencias que comiencen y acaben con unos valores determinados, y extraemos al mismo tiempo elementos centrales:

int[] numbers = { 1, 2, 3, 4, 5 };

if (numbers is [1, .. var elems, 5])
{
Console.WriteLine(string.Join(", ", elems)); // 2, 3, 4
}

Bueno, pues creo que hemos visto lo más interesante de los patrones de lista, así que deberíamos tener suficiente para aplicarlos cuando sea necesario. Espero que os haya resultado interesante :)

Publicado en Variable not found.

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

Variable not found

Enlaces interesantes 520

marzo 20, 2023 07:05

Enlaces interesantes

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

Por si te lo perdiste...

.NET Core / .NET

ASP.NET Core / ASP.NET / Blazor

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Machine learning / IA / Bots

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

.NET MAUI / Xamarin

Otros

Publicado en Variable not found.

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

Blog Bitix

Cifrar y descifrar datos usando algoritmos de clave asimétrica con Java

marzo 16, 2023 06:30

El lenguaje de programación Java ofrece clases e implementa varios algoritmos relacionados con la criptografía y seguridad. Con unas pocas líneas de código es posible listar los algoritmos soportados, generar claves, cifrar datos y descifrar datos. Soporta tanto criptografía de clave simétrica donde se usa la misma clave tanto para cifrar como para descifrar y como en este artículo se muestra criptografía asimétrica en la que se utiliza dos claves una la clave pública para cifrar datos y la clave privada para descifrar los datos.

Continuar leyendo en Blog Bitix

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

Variable not found

¡No uses List<T> si siempre vas a buscar por clave en los elementos en una colección!

marzo 14, 2023 01:16

C#

A veces, los problemas de rendimiento de las aplicaciones, o determinadas funcionalidades de ellas, vienen derivados del uso de estructuras incorrectas para almacenar los datos, ya sea en memoria, base de datos o en cualquier tipo de almacén.

En este post vamos a centrarnos en un caso específico que me he encontrado demasiadas veces en código real: el uso indebido del tipo List<T> cuando sólo nos interesa buscar en esta colección por una propiedad que actúa como identificador único del objeto T.

Imaginad que tenemos una colección de objetos Friend y queremos buscar aquél (en singular) cuyo identificador sea, por ejemplo, 1906. Esto podríamos implementarlo de la siguiente manera:

List<Friend> friends = ... // Obtenemos una colección de amigos desde donde sea
var friend = friends.FirstOfDefault(f=> f.Id == 1906);

El código es válido y funciona, pero tiene un problemilla que se pondrá especialmente de manifiesto si el número de elementos de la colección es alto, y más aún si realizamos continuamente búsquedas sobre la misma colección: el método FirstOrDefault(), en el por de los casos, tendrá que recorrer obligatoriamente los N elementos hasta encontrar el ítem que buscamos o retornar un nulo para indicar que no fue posible localizarlo. Y obviamente, el tiempo necesario para esto será proporcional al número de elementos, un O(N) en toda regla.

Por esta razón, si tenemos una lista de objetos a los que queremos acceder mediante una clave que los identifica de forma única, la estructura de datos deberíamos usar normalmente será un diccionario Dictionary<TKey, TItem>, donde TKey es el tipo de la clave de búsqueda y TItem el tipo de objetos a guardar. Esta clase utiliza un algoritmo que asegura que el tiempo de respuesta será constante independientemente del número de elementos (O(1)), por lo que las consultas serán, la mayoría de veces, mucho más rápidas y eficientes.

Dictionary<int, Friend> friends = ... // Cargar el diccionario de amigos de donde sea
friends.TryGet(friends, out var friend);

Y para demostrarlo, nada como el amigo BenchmarkDotNet. Vamos a utilizarlo para realizar un becnhmarking probando la búsqueda por identificador de un elemento existente en:

  • un lista List<Friend> de N elementos, usando LINQ,
  • una lista List<Friend> de N elementos, usando el bucle for de toda la vida,
  • un diccionario Dictionary<int, Friend> de N elementos

El código básico de las pruebas es el siguiente:

BenchmarkRunner.Run<ListVsDictBenchmarks>(); // Go!

public record Friend(int Id, string Name);

public class ListVsDictBenchmarks
{
static int N = 10; // Items de la colección
static int IdToFind = N / 2; // Identificador a buscar

static List<Friend> FriendList = Enumerable.Range(0, N)
.Select(id => new Friend(id, "Friend #" + id))
.ToList();
static Dictionary<int, Friend> FriendDict =
FriendList.ToDictionary(f => f.Id, f => f);

[Benchmark]
public Friend? FindInDictionary()
{
FriendDict.TryGetValue(IdToFind, out var friend);
return friend;
}

[Benchmark]
public Friend? FindInListUsingLinq()
{
return FriendList.FirstOrDefault(item => item.Id == IdToFind);
}

[Benchmark]
public Friend? FindInListUsingFor()
{
for (var i = 0; i < N; i++)
{
if (FriendList[i].Id == IdToFind)
return FriendList[i];
}
return null;
}
}

Primero, probamos con N=10, para comprobar el rendimiento cuando el número de elementos es muy  pequeño:

MethodMeanErrorStdDev
FindInDictionary3.485 ns0.0470 ns0.0440 ns
FindInListUsingFor7.497 ns0.0258 ns0.0229 ns
FindInListUsingLinq66.175 ns0.2474 ns0.2193 ns

En este caso, con tan solo diez elementos, ya vemos que el diccionario es el doble de rápido que la búsqueda en la lista mediante el bucle for y veinte veces más rápido que la alternativa usando LINQ. Eso sí, tened en cuenta que hablamos de nanosegundos, por lo que aunque la diferencia sea grande, hay muchos escenarios en los que sería imperceptible para el usuario.

Vamos a subir la apuesta haciendo N=5000. En este caso, el resultado pone de manifiesto claramente la superioridad del diccionario respecto a las listas para este tipo de búsquedas por identificador:

MethodMeanErrorStdDev
FindInDictionary3.478 ns0.0317 ns0.0265 ns
FindInListUsingFor3,245.731 ns33.0878 ns30.9504 ns
FindInListUsingLinq21,278.690 ns115.3831 ns102.2841 ns

Es decir, con 5.000 elementos, la búsqueda directa en el diccionario es 1.000 veces más rápido que usar el for sobre la lista y siete mil veces mejor que el uso de LINQ.

Una última prueba: subiremos a 50.000 elementos, y esta vez buscaremos elementos que no existan en el diccionario para ponernos en el peor de los casos. El resultado se muestra en la siguiente tabla:

MethodMeanErrorStdDev
FindInDictionary2.939 ns0.0476 ns0.0422 ns
FindInListUsingFor141,258.966 ns2,544.6197 ns4,036.0365 ns
FindInListUsingLinq483,093.548 ns7,052.4853 ns6,596.8991 ns

¡Uau! Buscando sobre 50.000 elementos, en el peor de los casos el diccionario llegó a ser 50.000 veces más rápido que el bucle for en la lista, y sobre 150.000 veces más rápido que el uso de LINQ. Vemos además que la promesa del O(1) es cierta: da igual cuántos elementos tengamos en la colección, el rendimiento va a ser muy similar.

Bueno, creo que a la vista de estas pruebas creo que, cuando queramos almacenar en memoria datos para buscarlos luego usando un identificador único, el uso un diccionario está más que justificado.

Fijaos que esto no quita que podamos contar o recorrer secuencialmente los elementos o las claves, por lo que el uso del diccionario no nos privará de ninguna de las ventajas de usar un tipo List<T>. Para comprobar que no existe ningún tipo de penalización por realizar este tipo de operaciones, añadimos un par de benchmarks que recorren la lista completa en ambos casos:

[Benchmark]
public double GetAverageFromDictionary()
{
return FriendDict.Values.Average(f => f.Id);
}

[Benchmark]
public double GetAverageFromList()
{
return FriendList.Average(f => f.Id);
}

Realizando la prueba para 5.000 elementos, el resultado no deja lugar a dudas: podríamos utilizar la propiedad Values del objeto diccionario para acceder a los valores de forma secuencial, sin apenas penalización:

MethodMeanErrorStdDev
GetAverageFromDictionary44.69 us0.522 us0.462 us
GetAverageFromList41.90 us0.372 us0.348 us

Por otra parte, también es interesante saber qué diferencia de rendimiento existe a la hora de insertar elementos en la colección, porque, de forma intuitiva, podría pensarse que las estructuras internas y algoritmos de hashing del diccionario, así como la detección de duplicados, podría penalizar las inserciones. De nuevo, lo llevamos a BenchmarkDotNet, a ver qué opina:

[Benchmark]
public void AddToDictionary()
{
var d = new Dictionary<int, Friend>();
for (int i = 0; i < N; i++)
{
d.TryAdd(i, new Friend(i, "Friend #" + i));
}
}

[Benchmark]
public void AddToList()
{
var l = new List<Friend>();
for (int i = 0; i < N; i++)
{
l.Add(new Friend(i, "Friend #" + i));
}
}

Ejecutamos la prueba insertando 10.000 elementos y, a la vista de los resultados, resulta que la inserción de ítems en el diccionario es incluso más eficiente que en la lista

MethodMeanErrorStdDev
AddToDictionary689.0 us6.69 us8.94 us
AddToList1,579.3 us28.09 us39.37 us

Por tanto, si tenemos una colección de elementos en la que queremos buscar repetidamente valor por algún tipo de identificador único, debemos considerar seriamente el uso de un diccionario sobre una lista.

Publicado en Variable not found.

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

Variable not found

Enlaces interesantes 519

marzo 13, 2023 07:05

Enlaces interesantes

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

Por si te lo perdiste...

.NET Core / .NET

ASP.NET Core / ASP.NET / Blazor

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Machine learning / IA / Bots

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

.NET MAUI / Xamarin

Otros

Publicado en Variable not found.

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

Blog Bitix

Los 3 clientes de Spring para hacer peticiones REST

marzo 09, 2023 06:00

El proyecto Spring ofrece hasta 3 clientes o formas diferentes para realizar peticiones a servicios REST. La ventaja de estos clientes es que no requieren de dependencias adicionales si se usa Spring y están integradas con el ecosistema de Spring y Spring Boot.

Continuar leyendo en Blog Bitix

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

Variable not found

Prueba APIs fácilmente con el nuevo cliente HTTP integrado en Visual Studio 2022

marzo 07, 2023 07:05

Visual Studio

Si desarrollas APIs, probablemente utilizarás Postman, Fiddler o herramientas similares para probarlas. Cualquier utilidad capaz de lanzar peticiones y analizar la respuesta, incluso basadas en línea de comandos como el desconocido Dotnet HTTP REPL, viene de perlas para ponernos en el lugar del cliente y comprobar cómo responde nuestra API ante determinados parámetros de entrada, o qué se siente al consumirla.

Tiempo atrás, el descubrimiento de la extensión REST client para Visual Studio Code supuso una bocanada de aire fresco en la forma de lanzar peticiones y examinar respuestas, para mi gusto mucho más cómoda que las que estaba acostumbrado a utilizar. Esta extensión permite definir peticiones HTTP en archivos de texto con extensión .http o .rest y ejecutarlas de forma realmente sencilla, sin abandonar el IDE.

Pues bien, la última revisión de Visual Studio 2022 (17.5) ha incluido el soporte nativo para este tipo de archivos, así que cada vez lo tenemos más sencillo. Y como es posible que alguno de vosotros aún no lo conozca, vamos a echarle un vistazo ;)

¿En qué consiste esta nueva funcionalidad de VS?

Como decía algo más arriba, la gracia está en que ya no tendremos que usar herramientas externas para definir y ejecutar peticiones a un servicio HTTP, sino que lo haremos desde dentro del propio entorno de desarrollo.

Usando el menú Add > New item podemos añadir fácilmente archivos con extensión .http a cualquier proyecto, como Test.http o PaymentsAPI.http. El contenido más simple que podemos tener en este tipo de archivos es una línea como la siguiente:

GET https://www.google.es

¡Eso es todo! Basta con indicar el método o verbo de la petición y la URL de destino. Cuando lo abrimos con Visual Studio, veremos que el propio editor ya detecta que la línea del GET es una petición, e inserta un botón de "play" para que la ejecutemos directamente:

Visual Studio con un archivo .http abierto y el resultado de la petición visible

Obviamente, en esa línea podríamos añadir parámetros de query string o debajo de ella agregar encabezados, o incluso el cuerpo completo de la petición, algo como lo siguiente:

POST https://localhost:48374/api/friends
content-type: application/json

{
"name": "John Doe",
"age": 43
}

Visual Studio con otro archivo .http abierto y el resultado de la petición visible

Pero bueno, visto así no parece que sea para dar saltos de entusiasmo... pero la cosa mejora cuando vemos que podemos añadir distintas peticiones en el mismo archivo separándolas con tres o más caracteres "#", o usar "//" para introducir comentarios:

// Obtener el contenido de la home de Google
GET https://www.google.es

###

// Obtener el contenido de la home de Bing
GET https://www.bing.es

En este caso, Visual Studio mostrará un botón para cada petición definida, de forma que podremos lanzarlas cómodamente:

Visual Studio mostrando un archivo .http con la definición de dos peticiones distintas

Otra de las funcionalidades que hacen de esta característica algo indispensable es su capacidad para utilizar variables.

Las variables se definen directamente en el mismo archivo mediante una sintaxis bastante previsible, usando asignaciones de la forma @varName=valor. Luego, podemos referenciarlas en cualquier parte de la petición o peticiones definidas posteriormente usando la expresión {{varName}}. Vemos a continuación un ejemplo donde vemos cómo definir y usar variables:

@host = localhost:7194
@token = test-1s890udiuh3iuhsio8uhkjdsekh3
@firstName = John
@lastName = Doe
@age = 42
@fullName = {{firstName}} {{lastName}}

POST https://{{host}}/api/friends
Authorization: {{token}}
Content-Type: application/json

{
"name": "{{fullName}}",
"age": {{age}}
}

Seguro que podéis intuir la utilidad que puede suponer el tener en el código fuente de nuestros proyectos API un conjunto completo de llamadas definidas de esta forma para nuestros endpoints, muy fáciles de leer por un humano, y además directamente ejecutables sin necesidad de instalar nada.

¿Significa esto que herramientas como Postman o Fiddler ya no son necesarias?

Pues en mi opinión, estas dos magníficas herramientas, así como otras más que pululan por ahí, aportan funcionalidades avanzadas que van más allá de definir o efectuar llamadas HTTP, por lo que no creo que vayan a desaparecer o que vayamos de dejar de utilizarlas totalmente.

Pero lo que es cierto es que en muchos casos la solución incluida en Visual Studio será más que suficiente, así que, ¡a disfrutarla!

Eso sí, tened en cuenta que se trata de una primera versión de la característica y podríamos echar en falta funcionalidades, sobre todo si hemos usado herramientas similares en otros entornos como VS Code o en IDEs de JetBrains.

Publicado en Variable not found.

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

Variable not found

Enlaces interesantes 518

marzo 06, 2023 07:05

Enlaces interesantes

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

Por si te lo perdiste...

.NET Core / .NET

ASP.NET Core / ASP.NET / Blazor

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Machine learning / IA / Bots

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

.NET MAUI / Xamarin / Mobile

Otros

Publicado en Variable not found.

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

Blog Bitix

Cifrar y descifrar datos usando algoritmos de clave simétrica con Java

marzo 02, 2023 07:30

Algunos datos son sensibles y necesitan especial protección como los datos personales, bancarios o relacionados con la seguridad como contraseñas. Para minimizar los riesgos de seguridad en caso de un fallo se suele cifrar los datos al persistirlos en la base de datos de modo que en caso de la base de datos sea filtrada los datos sigan protegidos siempre y cuando la clave que permite descifrarlos no se ha filtrado también. Java ofrece clases en su JDK que implementan los principales algoritmos para cifrar y descifrar datos.

Continuar leyendo en Blog Bitix

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

IOKode

El software open-source funciona gracias al egoísmo

marzo 02, 2023 02:04

Esta es una de esas entradas que quizás sea polémica, pero este blog nunca fue para hacer amigos.

He utilizado mucho software tanto open-source como software libre, aunque no me termina de gustar esa denominación ya que, en mi opinión, Richard Stallman y la FSF, quienes acuñaron el término, tienen un concepto equivocado de lo que es la libertad. Sin embargo, este tema queda fuera del alcance de esta entrada y, posiblemente, del alcance de este blog.

A lo largo de mi carrera profesional, además de usar software open-source, también he sido testigo en GitHub de como cientos de proyectos open-source tienen un desarrollo activo con contribuciones de código de cientos de personas que, a simple vista parece que están trabajando gratis y regalándolos a la comunidad por amor.

Yo mismo he contribuido a varios proyectos open-source, por ejemplo, RavenDB he reportado varios bugs y contribuído con aportaciones al código, también estoy desarrollando el proyecto OpinionatedFramework que también es open-source.

Soy egoísta

A pesar de que la palabra “egoísta” tiene una connotación negativa, ser egoísta es buscar el propio interés personal. Cualquier acción realizada por una persona que busque su propio interés es una acción egoista.

Teniendo esto en cuenta, todas las contribuciones que yo mismo he hecho, son por motivos egoístas. La contribución que hice al cliente .NET de RavenDB fue por necesidad para un proyecto de VADAVO. Es cierto que podría haber hecho el cambio y utilizar el cliente modificado sin enviar la PR, pero también hay motivos egoístas para ello. El primero es que una vez el cambio ha sido mergeado en el proyecto original, me quita el quebradero de cabeza de tener que mantener el fork. El segundo motivo es que además, me hace sentir bien por ayudar a la comunidad, pero la búsqueda de ese sentimiento es una acción egoísta.

Otros motivos egoístas para contribuir a proyectos open-source

He hablado de que el cambio que hice respondía a una necesidad en un proyecto de mi empresa, pero pueden haber otros motivos como buscar aprender o mejorar tu propia reputación.

Antes de escribir esta entrada, tuve una interesante conversación con ChatGPT sobre este tema y me enumeró algunos posibles motivos de naturaleza egoísta para contribuir con proyectos open-source:

  1. Mejora de la reputación profesional: Las contribuciones al software de código abierto pueden ser vistas como una forma de aumentar la visibilidad y reputación de un desarrollador, lo que puede ser beneficioso para futuras oportunidades de empleo o proyectos.
  2. Desarrollo de habilidades: Contribuir al software de código abierto puede ser una forma de aprender nuevas habilidades o mejorar las habilidades existentes, lo que puede ser beneficioso en el futuro en el trabajo o en proyectos personales.
  3. Acceso temprano a nuevas tecnologías: Al contribuir al desarrollo de nuevas tecnologías de código abierto, un desarrollador puede tener acceso temprano a esas tecnologías, lo que puede ser beneficioso para su propio trabajo o proyectos.
  4. Generación de ingresos: La contribución al software de código abierto puede generar ingresos a través de la venta de servicios o productos relacionados con el software de código abierto.
  5. Aumento de la calidad del propio trabajo: Al contribuir al software de código abierto, un desarrollador puede mejorar la calidad de su propio trabajo al aprender de otros desarrolladores y participar en mejores prácticas.
  6. Mejora del trabajo en equipo y habilidades de colaboración: Contribuir al software de código abierto puede mejorar las habilidades de trabajo en equipo y colaboración, lo que puede ser beneficioso en el futuro en el trabajo o en proyectos personales.
  7. Ampliación de la red de contactos: La contribución al software de código abierto puede proporcionar la oportunidad de conectarse con otros desarrolladores y líderes de la industria, lo que puede ser beneficioso en el futuro para oportunidades de trabajo o proyectos.

La número 4 me llama la atención porque cuando empecé a desarrollar OpinionatedFramework, pensé que si algún día el proyecto crece, desde IOKode podría ofrecer servicios de developer advocate sobre el framework. Dado el estado temprano del mismo es difícil saber si algún día esto ocurrirá, pero es una motivación más para su desarrollo.

Haciendo negocio con el open-source

Dejando de lado el “sentirse bien” como un posible motivo egoísta, lo cierto es que lanzar tu propio software (o una base) como open-source es algo que puede tener sentido incluso desde una perspectiva empresarial. Tanto es así, que hay empresas que tienen personal a tiempo completo contribuyendo a proyectos open-source.

Esta visión puede parecer chocante porque tradicionalmente las empresas no hacían público el código de sus productos para evitar que otros vendiesen su trabajo con pequeñas modificaciones como un simple cambio de nombre o para evitar la piratería. Sin embargo, véamos un ejemplo real donde lanzar un producto como open-source ha generado beneficios a la empresa.

Google lanzó como un proyecto open-source el navegador web Chromium, que es la base a la que una vez añadida la integración con los servicios de Google, da forma al famoso navegador web Google Chrome.

El hecho de que Google haya lanzado Chromium no es un regalo a la comunidad motivado porque esta empresa sea un hermanito de la caridad, si no que viene motivado por el hecho de que gracias a ello, otras personas (y empresas) mejorarán Chromium, cada uno movido por sus propios motivos y ello repercutirá en que Google Chrome será un mejor navegador. Practicamente otros le están haciendo el trabajo gratis a Google. Lo mismo aplica para muchos grandes proyectos open-source como .NET o Kubernetes.

Volviendo al inicio, yo he trabajado gratis para Hibernating Rhinos Limited al mejorar su producto RavenDB, lo he hecho movido por mi propio interés y he salido beneficiado de ello.

Conclusión

Muchos de nosotros hemos visto como grandes proyectos open-source, algunos nacidos dentro de la comunidad, reciben contribuciones que parecen desinteresadas de cientos de personas. Pero que parezcan desinteresadas no quiere decir que lo sea. Cada uno de estas personas que contribuyen a estos proyectos lo hace por su propio interés y es gracias a ese egoísmo que algo tan bonito como la colaboración comunitaria y el software open-source funciona.

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

IOKode

La comunicación como experiencia de usuario

febrero 21, 2023 10:57

En relación a los recientes cambios de Twitter sobre 2FA de los cuales hablé en mi última entrada, afirmé rotundamente que el problema que desencadenó la ola de reacciones de los usuarios fue una muy mala comunicación con el usuario.

El cambio que se llevó a cabo no me parece algo malo, es más, me parece un cambio razonable dentro del objetivo de Elon Musk de reducir costes en Twitter, sin embargo la forma de comunicarlo me parece demencial.

Muchas veces cuando hablamos de experiencia de usuario, nos centramos en las UI de las aplicaciones y, aunque estas existen para comunicar la aplicación con el usuario, en esta entrada me centraré en otro tipo de comunicación: los mensajes emitidos por la aplicación.

Y es que, una aplicación puede tener una UI impecable, con elementos bien utilizados, correctamente alineados, etc. y luego mostrar un texto deficiente en un cuadro de diálogo, haciendo que la UX pueda ser un infierno.

El caso de Twitter

El siguiente mensaje ha estado apareciendo a los usuarios que tienen activida la 2FA sin una subscripción activa de Blue.

Solo los suscriptores de Twitter Blue pueden usar el método de autenticación en dos fases por mensaje de texto. La eliminación de este método solo tardará unos minutos. Puedes seguir usando otros métodos, como la app de autenticación y la llave de seguridad.

Para evitar perder acceso a Twitter, elimina la autenticación en dos fases por mensaje de texto antes del 19 mar. 2023.

En mi opinión, la ola de reacción negativa que vivimos durante los últimos días no se hubiese dado si el mensaje hubiese sigo algo así:

El método de autenticación en dos fases se considera obsoleto y será eliminado a partir del 19 de mar. 2023. Se recomienda encarecidamente utilizar otro método como la app de autenticación o la llave de seguridad.

Este nuevo mensaje, a diferencia del anterior, elimina el segundo párrafo con todo amenazante, pues a mi me da la sensación de leer algo tipo “si no desactivas 2FA vía SMS, perderás para siempre el acceso por desobediencia”. Simplemente se limita a avisar de que será desactivado automáticamente llegada la fecha límite. Por otro lado, no utiliza los menús de configuración de seguridad para hacer marketing de una suscripción de pago, algo que da muy mala imagen.

Cabría destacar que esta propuesta, además del mensaje requiere cambios de comportamiento, pues la característica en concreto pasa de ser sólo para suscriptores a desaparecer y la acción llegada la fecha límite pasa de ser perder la cuenta a desactivarse, sin embargo, me parecen cambios de comportamiento menores.

Otros casos

El caso de Twitter no es un caso aislado. Por desgracia, me he topado con alguna que otra aplicación que, o bien como en el caso de Twitter, se va de las ramas promocionando cosas donde no debe, o bien tienen algunos “mensajes creativos”. He utilizado aplicaciones que tras rellenar un formulario y pulsar un botón, me ha saltado una alerta que rezaba algo así:

¿Viene usted del futuro?

Tras leer este mensaje, mi cara fue 🫤 pues no fui capaz de relacionar que el error se debía a que la fecha de nacimiento era igual o superior a la fecha actual.

Este mensaje puede ser ingenioso y, no creo que este mal incluirlo, pero siempre debe de estar acompañado de un mensaje que indique de forma clara donde está el problema.

–No es que yo fuese tonto y no sepa cuando he nacido, es que ese formulario venía con la fecha de nacimiento preseleccionada con el día de hoy, por lo que vi un campo rellenado y pasé directamente al siguiente, lo cual dice mucho (y no para bien) de esa UX, pero eso ya es algo que se sale del ámbito de esta entrada.–

Otro ejemplo clásico son los mensajes de error indescifrables. ¿Qué significa error 4ea3fb? ¿qué me aporta a un usuario un número exadecimal en la ventana de un error?

Parafraseando a @jmhdez en su entrada Lo peor de desarrollar software, no hace falta ser un experto redactor, con aplicar un poco de sentido común y poner algo de interés basta.

Logs y excepciones

Por último, a modo de campaña, me gustaría hacer incapié que de la misma forma que hay que escribir buenos mensajes al usuario, los mensajes de las excepciones y los logs deberían ser exactamente igual de claros. Más de una vez me he vuelto loco analizando archivos de logs porque “total, esto no lo ve el usuario final, puede estar de cualquier manera”.

Sobre las excepciones, un buen indicador de que un mensaje está bien escrito es si se lo puedes pasar directamente al usuario sin reescribirlo.

Imaginemos este fragmento de código en un controlador de interfaz de usuario:

try
{
    createUser(inputData);
}
catch (UserAlreadyExists)
{
    showAlert("The username already exists.");
}
catch (InvalidBirthDate)
{
    showAlert("The user must be over 18 years old.");
}
catch (Exception)
{
    showAlert("An unexpected error was happened.");
}

Si el mensaje que hay dentro de los excepciones está bien escrito, deberiamos de poder pasárselo directamente al usuario sin mayor problema:

try
{
    createUser(inputDate);
}
catch (Exception ex)
{
    showAlert(ex.Message);
}

Esto incluso se puede reducir a una única línea si tu framework posee un manejador global de excepciones que muestre un mensaje al usuario de todas aquellas excepciones no capturadas en el controlador de interfaz de usuario:

createUser(inputDate);

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

Header Files

Cómo cambiar una bombilla

febrero 21, 2023 07:00

Introducción

Llevo más de 20 años desarrollando software y durante muchos otros he impartido o colaborado en diversas asignaturas relacionadas con la programación: Informática I (en diversas modalidades, pero siempre como ayudante), Diseño de Sistemas Operativos (tanto en Venezuela como en España), y Seguridad de Redes.

En todas ellas he visto el mismo patrón: la mayoría de los estudiantes (incluso algunos de los brillantes) les costaba pasar de un simple caletreo en lo que a programación se refería: aprendían muy bien los conceptos teóricos de las instrucciones de control de flujo, sabían lo que estaban haciendo los programas que veíamos en clase y muchas veces salían de los atolladeros de errores de compilación de C++ por cuenta propia. Pero cuando tocaba realizar un programa desde cero o incluso modificar (sustancialmente) un programa dado, no hacían más que comenzar a poner bucles “for” acá y allá sin razón, o a preguntar si debían usar un “if” o una función. Parecía que todo lo demás hubiese sido una farsa. Con el tiempo he llegado a ver ese comportamiento no sólo en alumnos, sino en “profesionales” del sector.

Después de muchas reflexiones y de comentarlo con colegas de la academia y de la industria, he concluido que el problema radica en que se han saltado un paso en su formación. Me explico. Cuando entré en la facultad que me di cuenta de que había algo raro, y que además le pasaba a casi la totalidad de los que llevaban un tiempo programando por cuenta propia. Pasados unos meses, noté que eso se apoderaba de todos mis compañeros de estudios. Los que más contagiados estaban solían ser los que lograban que sus proyectos funcionasen más rápidamente, los que destacaban en los maratones de programación. Y lo mismo he observado con el tiempo en otras escuelas de informática y en las diferentes empresas por donde he pasado.

Pero, ¿qué era eso que se propagaba como una epidemia? Creo que cualquiera que haya tenido un mínimo trato con un desarrollador de software lo ha podido oler y me sabrá entender. Sencillamente nuestro cerebro estaba sufriendo un daño irreparable, permanente y significativamente visible; y no, no es que no pudiésemos pensar, es que lo hacíamos diferente, ya no como un ser humano, sino como una máquina.

Cambiar una bombilla

Había un ejercicio que se solía proponer en muchos cursos de Algoritmos I y, que si bien tiene sus variantes, en esencia es el mismo. Digo solía porque hasta donde he visto ya no se expone en muchas facultades ni cursos de programación. Lo dejaré escrito y daré unos momentos para que reflexionen sobre ello:

Diseñe un algoritmo para cambiar una bombilla. (Para los no iniciados, un algoritmo es un conjunto de pasos para hacer algo, el plan de trabajo).

⌛️ Tiempo de reflexión…

Muy bien. A ver vuestros trabajos, veamos, tomemos el primero que tenemos acá:

  1. Comprar bombilla nueva
  2. Poner una escalera debajo de la lámpara
  3. Subir la escalera
  4. Desenroscar bombilla vieja
  5. Enroscar bombilla nueva
  6. Bajar escalera
  7. Tirar bombilla vieja
  8. Guardar escalera

Revisión

Bien, ahora veamos lo que podría decir un ordenador sobre la línea 2

  • Ordenador: Fenomenal, ¡gracias! a ver ¿qué es escalera?
  • Programador: Una escalera es un conjunto de peldaños o escalones que enlazan dos planos a distinto nivel, y que sirven para subir y bajar.
  • O: Vale, ¿qué es peldaño?
  • P: Un peldaño es un trozo de madera, hierro, plástico, cemento, en el que se apoya el pie para subir o bajar.
  • O: Muy bien, ¿qué es subir? ¿qué es madera? ¿qué es hierro? ¿que es bajar? ¿qué es pie?…

¿Y sobre la línea 5?

  • O: ¡Me encanta! Antes de seguir, ¿me explicas qué es eso de enroscar?
  • P (ya en alerta después de la experiencia con la línea 3): Consiste en cuatro pasos: primero sujetar la bombilla con la mano dominante con la fuerza suficiente para que no se caiga y que podamos vencer el rozamiento de la rosca en el sócate, pero sin ser demasiada como para romperla y hacernos daño; segundo, ubicar la rosca de la bombilla en la entrada del sócate; tercero, realizar un movimiento repetitivo de unos 170° cada uno en dirección antihoraria de la bombilla (ayudarse con la otra mano mientras la bombilla aún no esté sujeta por el sócate); cuarto, repetir el paso tres hasta que la bombilla esté firme en el sócate.
  • O: ¡Estupendo! ¿Qué es un sócate?
  • P: 😒😒😒

Y así podríamos continuar hasta que el ordenador ya lo tuviera todo claro. Veríamos entonces que nuestro algoritmo es realmente un tratado completo acerca de la anatomía de la mano y el brazo, de la estructura de una bombilla y de la lámpara, un inventario de herramientas y utensilios, y toda una orquesta de movimientos humanos de sujeción y desplazamiento, por no decir un glosario de los términos más básicos que cualquier niño de 3 años conoce.

El tonto más rápido del condado

Creo que queda claro el punto nuclear: el ordenador no es más que una pieza tonta de silicio al que hay que explicárselo todo. Eso sí, es el tonto más rápido del lugar. De la misma forma que nuestro ejemplo anterior, el más simple programa de ordenador puede terminar siendo bastante complejo desde el punto de vista del usuario.

Cuando uno empieza a programar descubre que uno tiene el poder de hacer que el ordenador haga lo que uno quiera, que sólo protestará en la medida de si puede hacerlo o no, pero no tendrá pereza, ni dirá que ya ha hecho mucho, ni criticará la decisión que uno ha tomado y, si uno ha metido la pata, el ordenador no dirá nada y lo hará, siendo uno el responsable de ello. De hecho, se suele decir que los ordenadores siguen un modelo GIGO (garbage in, garbage out): si les damos la orden correcta, harán lo que uno pretendía, pero si uno da la orden equivocada, el ordenador no hará lo que uno quería. El ordenador no tiene telepatía, sólo sigue órdenes concretas y precisas.

Evolucionando

El día que un aspirante a desarrollador cae en la cuenta de todo esto, automáticamente se hace mejor, ¡evoluciona!, ya que entenderá que no debe esperar ni por asomo que el ordenador haga mágicamente lo que él quería, sino que sabrá que debe dar todas y cada una de las instrucciones de una forma detallada y ordenada. Su mente dejará de funcionar como la de un humano provisto de un alma inteligente y libre, con experiencia, iniciativa, curiosidad, y empezará a contar ciclos de reloj, a no asumir nada, a no dar nada por sabido de antemano, a ser muy explícito y cuadriculado.

En estos últimos días hemos sido testigos del gran avance en materias de deep learning, con los modelos de procesamiento de lenguaje GPT-3 (y pronto GPT-4), generación de imágenes stable diffusion, y su aplicación en prácticamente cualquier ámbito profesional y artístico. Además, desde hace años incluso los ordenadores más sencillos cuentan con una potencia de cálculo bastante superior a la de un cerebro humano. Cada segundo se procesa una cantidad inimaginable de datos. Las herramientas cada vez hacen más cosas que antes hacían las personas (bueno, es lo que ha pasado siempre desde la invención de la rueday la palanca, la domesticación de caballos, el motor de vapor, la electrónica y así hasta la IA). Hay quienes ven amenazas, otros oportunidades, otros un cambio de paradigma.

Pero incluso con todo esto, el ordenador no ha cambiado en sus fundamentos: no piensa, no tiene voluntad, no es libre, sólo sigue instrucciones, aunque éstas sean complejísimas, se nutran de toda la información mundial y se retroalimenten continuamente.

El tonto del condado es cada vez más rápido y tiene mejores instrucciones y datos sobre los que trabajar, pero sigue siendo el tonto y necesita de seres racionales -personas- que entiendan esto y que puedan pensar (procesar sería una mejor palabra) como lo hace un ordenador para poder progresar.

Pensar como un ordenador es, a su vez, un término que varía con el tiempo en el cómo, mas no en el qué: ya lo hizo del paso de ensamblador a lenguajes de alto nivel y luego a las aplicaciones web y móviles, lo hizo durante el cambio de programación mono-hilo a software altamente concurrente, de las tarjetas perforadas a las interfaces gráficas y a la realidad aumentada / virtual. Pero siempre necesitaremos saber que el ordenador no es más que eso, una máquina de cómputo, por muy rápido y complejo que sea.

Encendamos la luz

Volvamos al ejercicio inicial y dediquemos unos momentos a pensar cómo le explicaríamos a un ordenador que cambie una bombilla, sin asumir nada, sin dejar cabos sueltos… Es un ejercicio sin fin, y es su razón de ser. Realmente pienso que si este ejercicio se volviese a exponer en los cursos de programación veríamos un cambio sustancial de calidad; y que, independientemente del lenguaje de desarrollo, framework, tecnología, entenderíamos que no hay magia, no hay intuición, no hay libre albedrío en la informática, sólo instrucciones explícitas, sin dobles sentidos, con todos los datos, lógicos, binarios (hace una cosa o no la hace).

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

IOKode

Dejemos de usar SMS para hacer 2FA

febrero 19, 2023 10:55

Ayer Twitter anunció que la autenticación en dos pasos a través de SMS pasaba a estar disponible únicamente para usuarios con una subscripción de pago Blue activa, mientras que el resto de usuarios podrían utilizar autenticación en dos pasos únicamente mediante aplicación (TOTP) y dispositivos de hardware.

Esta decisión despertó una ola de reacciones en su contra debido a una muy mala comunicación por parte de la empresa que da la sensación de que quieres hacer de la seguridad una feature de pago.

Personalmente no creo que sea el caso, ya que la opción 2FA basada TOTP y hadrware sigue disponible para el resto de usuarios. Yo creo simplemente que Twitter quiere cubrir el coste de enviar SMS a través de la suscripción Blue. Desde que Elon Musk está a la cabeza de la compañía, una de sus prioridades siempre ha sido reducir los costes y los SMS no son precisamente baratos.

Dejando de lado la decisión de Twitter y su mala comunicación, creo que esta es una buena oportunidad para que la industria cambie de rumbo deje de utilizar SMS como medio para hacer 2FA por otras opciones más seguras, como puede ser los tokens TOTP. Por desgracia, tal como dijo @eiximenis, no creo que esto vaya a darse, al menos a corto plazo.

Los problemas de los SMS

Todo lo anterior no tendría validez sin una explicación que lo justifique, por lo que creo necesario explicar cuales son los principales problemas de usar SMS como medio para hacer 2FA.

Costes

Si echamos un vistazo a varias páginas web de servicios que ofrecen API para enviar SMS, podemos observar que el precio de cada mensaje ronda entre los 0,05 € y 0,10 € por mensaje.

Si tu aplicación tiene 100 usuarios que se autentican una vez al día y cada SMS te cuesta 0,06 €, esto es un coste de 6 € al día, 180 € al mes y 2.160 € al año.

Ausencia de cifrado

Los SMS viajan por la red GSM sin ningún tipo de cifrado. Haciendo uso de técnicas de falsificación de estaciones base, es posible interceptar estos mensajes y por lo tanto robar una clave 2FA.

SIM Swapping

Este es un conocido ataque de ingeniería social que consiste en llamar a tu compaía teléfonica haciendose pasar por el cliente de la misma y solicitar un clon de la tarjeta SIM. Una vez el atacante se hace con el clon, puede recibir en nombre de su víctima los SMS con las claves 2FA.

Desde VADAVO hemos hecho una capacitación a nuestro equipo de atención al cliente para evitar estos ataques a nuestros clientes en la medida de lo posible, sin embargo un error humano lo puede tener cualquiera, en cualquier compañía.

El SMS es mejor que nada

Si estás utilizando un servicio que por desgracia únicamente acepta SMS como medio 2FA, deberías de utilizarlo, siempre acompañado de tu contraseña y no como sustituto de esta.

En conclusión

Me gustaría aprovechar mi blog para hacer campaña a favor de dejar de utilizar SMS para enviar claves 2FA.

Como usuarios deberíamos de configurar una aplicación como Authy, Google Authenticator, Microsoft Authenticator o similar y nunca SMS.

Como desarrolladores deberíamos de ofrecer siempre 2FA a sus usuarios, pero nunca a través a SMS.

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

Blog Bitix

Los algoritmos de hashing criptográficos, cálculo de hashes con comandos de GNU/Linux y Java

febrero 15, 2023 10:00

Los algoritmos de hashing criptográficos son fundamentales en la firma digital y criptografía, pero también tienen su utilidad por sí mismos para la comprobación de la integridad. Se basan en un algoritmo y funciones matemáticas que transforman un conjunto de bytes en un número binario de longitud fija que constituye el hash digital del contenido. Hay varios algoritmos de hashing criptográficos y en GNU/Linux varios comandos que permiten calcular y comprobar el hash de un archivo. En los lenguajes de programación como en el caso de Java se ofrecen clases y métodos para la generación y cálculo de hashes en los algoritmos soportados.

Continuar leyendo en Blog Bitix

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

Metodologías ágiles. De lo racional a la inspiración.

Primer Agile Open Navarra

febrero 15, 2023 09:01

Se ha celebrado el primer Agile Open Navarra, como ya comentaba, con el tema Agilismo y Negocio. He tenido la suerte de facilitar otro Open Space, que ciertamente es un formato fabuloso para este tipo de eventos. El evento empezó un poco frio, quizás demasiado madrugón para ser un sábado. Sin embargo, enseguida se completó la parrilla de los temas. Había bastante diversidad en los perfiles, gente

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

Metodologías ágiles. De lo racional a la inspiración.

Open Space, para un roto o para un descosido

febrero 15, 2023 08:59

NOTA: Estoy escribiendo la guia de facilitación de Open Space. Hemos hablado varias veces aquí ya de los Open Space como un formato increible para la organización y facilitación de conferencias. En Agile-Spain hemos organizado ya tres a nivel nacional, y han surgido multitud de pequeños "opens" para tratar muchos temas alrededor del agilismo. Si no sabes qué es un Open Space, echa un ojo a esta

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

IOKode

OpinionatedFramework: Contratos y fachadas

febrero 14, 2023 01:19

Esta entrada forma parte de una serie:


Los contratos son una serie de interfaces en el core de OpinionatedFramework que pueden ser localizadas desde el propio Service Locator estático de OpinionatedFramework y están pensados para optimizar el desarrollo de aplicaciones.

Generalmente, cuando se inicia el desarrollo de una nueva aplicación, se escriben en capa de dominio una serie de interfaces que casi toda aplicación tiene que serán implementadas en capa de infraestructura con diversas funcionalidades comunes: envíar notificaciones, lanzar tareas en segundo plano, lanzar eventos, escribir logs, etc.

El framework al incluir todos estos contratos, evita la aburrida tarea de tener que crear tus propias interfaces para ello al inicio del desarrollo de una aplicación

La implementación de los mismos es una cuestión de infraestructura y no de dominio.

Fachadas

Las fachadas son un acceso estático para hacer más cómodo el uso de servicios. No están directamente relacionadas con los contratos, ya que cualquier servicio puede ser utilizado desde una fachada, sin embargo el framework proporciona fachadas de todos sus contratos.

Por ejemplo, la interfaz IEmailSender puede ser utilizada desde la fachada estática Email, haciendo que ambos fragmentos de código sean equivalentes (excepto porque la fachada añade una llamada extra el stack trace):

await Locator.Resolve<IEmailSender>().SendAsync(new CustomerCreatedEmail());
await Email.SendAsync(new CustomerCreatedEmail());

La fachada internamente simplemente hace esto:

public static class Email
{
    public static async Task SendAsync(Email email, CancellationToken cancellationToken = default)
    {
        await Locator.Resolve<IEmailSender>().SendAsync(email, cancellationToken);
    }
}

Creando fachadas

Reescribir todas las firmas de un método de una interfaz puede ser un trabajo muy aburrido, es por ello que el framework proporciona un souce generator que se encarga de crear la fachada a partir de una interfaz. Simplemente hay que añadir el atributo AddToFacade a la interfaz y la magia del generador se encarga de todo lo demás.

Por volver al ejemplo del contrato anterior, tiene este atributo, por lo que la fachada que se genera se llama Email:

[AddToFacade("Email")]
public interface IEmailSender
{
    public Task SendAsync(Email email, CancellationToken cancellationToken);
}

Fachadas multiservicio

El generador de fachadas está diseñado para poder generar una misma fachada para varios servicios, siempre y cuendo la interfaces de tengan un método con la misma firma (tipo de retorno + nombre + parametros) exacta.

En el core del framework esto se da con los contratos IJobEnqueuer e IJobScheduler, dos contratos que se añaden a la fachada Job.

En caso de conflicto de firma, un analizador de Roslyn genera un error y el proyecto no compila. ¡Incluso te lo marca con un subrayado rojo en el IDE!

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

IOKode

OpinionatedFramework: Resolviendo y registrando servicios

febrero 14, 2023 01:16

Esta entrada forma parte de una serie:


Toca hablar de como en OpinionatedFramework se registrar y resuelven servicios, algo importantisimo ya que todos los contratos que el propio framework trae consigo mismo son servicios registrados y las fachadas son clases que tienen que resolver estos servicios.

Pero antes de ello, vamos a hablar de distintas formas de resolver servicios.

La inyección de dependencias por constructor

Este es, probablemente, el patrón de diseño más extendido cuando se trata de resolver servicios. Estos se inyectan directamente en el constructor de la clase asegurandote que cuando la clase sea construída, posea todos los servicios de los que esta depende (dependencias).

La principal ventaja de usar este patrón es que tu clase queda totalmente desacoplada del mecanismo que utilices para instanciarla. Esta clase no es consciente ni tiene porque ser consciente de que existe (o no) un contenedor que se encargue de crear las instancias.

Además permite crear la instancia directamente usando la palabra new, lo que puede ser útil para utilizarla en un proyecto sencillo que no requiera de un contenedor de dependencias.

Si estás escribiendo un componente de una librería que requiera de un servicio, esta debería de ser la forma por defecto de hacerlo y, así es como lo hice cuando diseñé el componente [WordGenerator](https://github.com/iokode/inake/blob/main/IOKode.Inake/WordGenerator.cs#L19) de la librería Inake.

¿Qué problemas tiene la inyección de dependencias por constructor?

La inyección de dependiencias por constructor no está exenta de problemas. Bajo mi punto de vista esta tiene cuatro problemas principales:

  • Dificultad de instanciación de objetos: Aunque se pueden crear las instancias manualmente con la plabara new, la dificultad de instanciar una clase con sus dependencias (y las dependencias de las dependencias) hace que de forma prácticamente obligatoria tengamos que hacer uso de un contenedor que se encargue de crear las instancias mediante técnicas de reflexión.
  • Imposibilidad de uso en clases estáticas: Las clases estáticas no tienes constructores, lo que prácticamente hace imposible que esta pueda depender de servicios. Ello te obliga a hacer dinámicas y necesitar una instancia de una clase que por su funcionalidad tendría sentido que sea una clase estática, como puede serlo una factoría.
  • Dificultad de inyección en entidades: Las entidades siempre son un dolor de cabeza cuando requieren de un servicio. Por lo general están pensadas para ser creadas directamente utilizando new y, cuando requieren de un servicio, toca buscar alternativas como construirlas a través de un factoría. Este problema se agrava cuando toca escribir toda una capa de mappers para que tu ORM pueda deserializar datos en una entidad haciendo uso de la factoría que le inyecta los servicios.
  • Expone detalles de implementación: A través del constructor se exponen los servicios que la clase requiere para funcionar, lo que puede dar una serie de pistas de como funciona. Si uso un servicio que me tiene que dar leche, no quiero tener que proporcionarle la nevera de donde sacará la leche.

Sobre el último punto, hay opiniones contrarias. Aunque entiendo el razonamiento, personalmente no termino de compartirlo del todo. Recomiendo encarecidamente leer el hilo completo y que cada uno saque sus propias conclusiones.

Service Locator

Otro de los patrones (o antipatrón, según a quién le preguntes) más famosos para resolver servicios es el llamado Service Locator. Este patrón es bastante sencillo, simplemente consiste en llamar a una función que se encargue de resolver un servicio.

Muchas implementaciones de este patrón no son estáticas, teniendo una forma similar a esta:

public class Controller : IController
{
    private readonly IServiceLocator _serviceLocator;
    public Controller(IServiceLocator serviceLocator)
    {
        _serviceLocator = serviceLocator;
    }
 
    public void Execute<TContext>(TContext context)
    {
        var command = _serviceLocator.GetInstance<ICommand<TContext>>();
 
        if (!command.IsNull())
        {
            command.Execute(context);
        }
 
        var disposable = command as IDisposable;
 
        if (disposable != null)
            disposable.Dispose();
    }
}

Esta aproximación tiene los mismos problemas que he comentado sobre la inyección de dependencias (ya que en esencia, se está haciendo DI para inyectar el service locator). En su lugar, yo preferiría utilizar un locator estático para esto:

public class Controller : IController
{ 
    public void Execute<TContext>(TContext context)
    {
        var command = ServiceLocator.GetInstance<ICommand<TContext>>();
 
        if (!command.IsNull())
        {
            command.Execute(context);
        }
 
        var disposable = command as IDisposable;
 
        if (disposable != null)
            disposable.Dispose();
    }
}

Esta aproximación soluciona los problemas de arriba además de hacer la clase más pequeña al eliminar el constructor.

Los problemas del patrón Service Locator.

Este patrón tampoco está exento de problemas. Si muchas personas lo consideran un antipatrón no es precisamente porque les haya dado una cruzada irracional contra él.

El principal problema del patrón Locator es que obliga a tu aplicación a referenciar al contenedor (o al menos al Locator si se hace una buena segregación de interfaces). Esto rompe la idea de que tu aplicación no debe de depender del framework, si no que este debe de ser parte de la infraestructura. Esto aquí no es un problema ya que todo OpinionatedFramework está diseñado para actuar sobre las capas de dominio y aplicación.

Otro de los problemas es que oculta las dependencias de una clase. Esto yo es algo que a mi me sigue pareciendo casi más una ventaja que un inconveniente, pues pienso que viola el principio tell, don’t ask. Sin embargo, hay quién opina que hacer las dependencias explícitas es algo positivo. Recomiento leer el hilo de Twitter completo para profundizar más sobre ello.

¿Y en OpinonatedFramework?

Pues para OpinionatedFramework he optado por incluir un contenedor de depdencias que pueda localizar servicios utilizando un Service Locator estático. Si utilizas un framework como ASP.NET Core en capa de insfraestructura que incluye su propio contenedor de dependencias, este contenedor está aislado y es ajeno al mismo. Esto permite registrar aquellos servicios que tienen que ver con la aplicación en un contenedor y aquellos del framework en otro.

El framework tiene dos clases estáticas, IOKode.OpinionatedFramework.ConfigureApplication.Container que es la encargada de configurar los servicios y IOKode.OpinionatedFramework.Foundation.Locator, que es la encargada de localizar los servicios previamente registrados.

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

Header Files

febrero 13, 2023 08:49

Introducción

Desde que comencé a usar ordenadores allá por 1992 (😱), siempre me pareció que el lado más oscuro e incomprensible de la informática se llamaba impresoras. Esta opinión cambió ligeramente desde que añadimos soporte para webcams en nuestras aplicaciones; ¡ahora hay un segundo lado oscuro e incomprensible! Cada webcam es, como se dice coloquialmente, de su padre y de su madre, con ajustes y especificaciones propios y casi únicos. Para poner peor las cosas, OpenCV (la biblioteca por antonomasia para procesamiento de imágenes) es bastante básica, o mejor dicho, genérica, en cuanto a captura de vídeo se refiere. En este artículo nos ocuparemos de un aspecto en específico: las resoluciones soportadas, o mejor dicho, la máxima resolución que una webcam puede soportar y cómo obtenerla usando OpenCV.

Solución

Para comenzar, OpenCV trata las cámaras y los vídeos como una fuente de imágenes genérica, bajo el objeto cv::VideoCapture, y como un vídeo no tiene resolución máxima, solamente tiene resolución, de forma que no podemos saber la resolución máxima que permite nuestra cámara consultando una propiedad del vídeo.

Para solucionar este problema, puede hacerse uso de un comportamiento colateral: cuando se asigna una resolución inválida la cámara decae a la resolución válida más cercana (640 x 481 pasa a ser 640 x 480), por lo que el truco consiste en solicitar una resolución descomunalmente alta y luego simplemente mirar la resolución a la que quedó fijada la cámara:

std::tuple<int, int> query_maximum_resolution(cv::VideoCapture* camera)
{
  // Save current resolution
  const int current_width = (int)(camera->get(cv::CAP_PROP_FRAME_WIDTH));
  const int current_height = (int)(camera->get(cv::CAP_PROP_FRAME_HEIGHT));

  // Get maximum resolution: set a out-of-range resolution and let the camera set itself to the maximum allowed
  camera->set(cv::CAP_PROP_FRAME_WIDTH,  10000);
  camera->set(cv::CAP_PROP_FRAME_HEIGHT, 10000);
  const int max_width = (int)(camera->get(cv::CAP_PROP_FRAME_WIDTH));
  const int max_height = (int)(camera->get(cv::CAP_PROP_FRAME_HEIGHT));

  // Restore resolution
  camera->set(cv::CAP_PROP_FRAME_WIDTH, current_width);
  camera->set(cv::CAP_PROP_FRAME_HEIGHT, current_height);

  return {max_width, max_height};
}

Créditos

La técnica la vi por primera vez en Stack Overflow.

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

Header Files

febrero 13, 2023 08:49

Introducción

En algunos ejemplos mostrados en entradas anteriores ha aparecido el uso del atributo [[maybe_unused]] que igual no os suena aún. Los atributos son una característica del C++ moderno (C++11 en adelante) que permiten indicar al compilar información acerca del código, con el fin de optimizar determinados fragmentos, introducir restricciones, evitar warnings o generar el código de una forma específica. Los atributos vienen a unificar alternativas ya existentes pero que eran propias de cada compilador, lo que generaba código no portable u obligaba a usar macros y detección del compilador. Una lista completa de los atributos de C++ puede encontrarse en cppreference.com.

En nuestro caso, [[maybe_unused]] es un atributo introducido en C++17, que indica al compilador que no genere warnings de no-uso para el identificador asociado. Esto es especialmente útil si se ha indicado al compilador que convierta los warnings en errores de compilación (/WX para el compilador ‘cl’ de Visual Studio, -Werror en gcc) pero el código en sí es correcto (al final de la entrada hablo un poco más sobre la utilidad de este warning).

Utilidad de los warnings

Para muchos programadores los warnings no son más que una gran molestia del compilador, que resulta ser quisquilloso y no nos deja en paz. Esto es cierto en algunos escenarios, pero normalmente tienen su razón de ser: el código potencialmente puede tener un problema de lógica y el compilador nos avisa de ello, pudiendo muchas veces solucionarlos incluso antes de depurar el código.

Sin desviarme mucho del tema del atributo, mencionaré algunos warnings genéricos que me suelen ayudar:

  • Conversión implícita: se está convirtiendo un tipo en otro para ser usado, normalmente, en la construcción de un objeto. Esto algunas veces lleva a un comportamiento no esperado, como el que describo en esta pregunta de Stack Overflow.
  • Código no alcanzable: no hay combinación de parámetros posible que haga que un código se ejecute, por lo que es lo mismo a no haberlo puesto nunca. Por ejemplo, si tenemos un condicional (con su contraparte) que evalúa siempre a verdadero, por lo que nunca se ejecutaría el else.
  • Se usa una variable no inicializada: en C++ las variables no tienen un valor por defecto salvo que el constructor por defecto así lo haga, por lo que si el compilador detecta que una variable se está usando sin haber sido inicializada nos lanza un warning para informarnos. Desafortunadamente hay al menos un escenario en el que el compilador no puede saber si esto es realmente un problema: cuando la variable se usa como argumento de salida de una función (pasada bien por referencia o como puntero); en este caso tendremos que ignorar el warning si sabemos que el código es correcto.
  • Una declaración local oculta a una de nivel superior: por ejemplo, un parámetro de un método con el mismo nombre de una variable miembro, o una variable local respecto a una de un bloque padre; potencialmente podríamos estar queriendo usar la variable original en lugar de la nueva.
  • Parámetro o variable no usada: se ha definido una variable que no se usa en ningún momento. Puede ser, por ejemplo, debido a código antiguo o por un problema similar al anterior: tienen nombres semejantes y lo hemos escrito mal, usando la variable equivocada. A continuación detallo algunos casos en los que el warning nos es útil:

Hemos metido la pata al escribir

Básicamente es código que compila correctamente, pero que tiene un error de lógica: estamos usando la variable que no es. Suele pasar cuando hacemos copy-paste, en funciones con variables de nombre similar, al actualizar código antiguo, etc. Por ejemplo:

int computeDistance(float x, float y)
{
   const auto x2 = x * x;
   const auto y2 = x * x; // <
   return sqrt(x2 + y2);
}

Código basura

Suele ser producto de algún refactoring, actualización de código para ser compatible con una nueva API, o limpieza después de algunas pruebas temporales. Por ejemplo:

bool checkFileIntegrity(const std::filesystem::path &file_path, const std::string &checksum)
{
  std::ifstream file(file_path);
  if (!file.is_open()) return false;

  const auto filename_length = file_path.filename().string().size(); // <

  return computeChecksum(file) == checksum;
}

Probablemente filename_length se usó durante una prueba o en una versión vieja del código pero ya no es necesaria.

¿Cuándo el warning no es útil?

Ahora que conocemos el aviso que nos concierne, vamos a ver por qué nos interesaría ignorarlo, o lo que es mejor, indicarle al compilador que sabemos lo que estamos haciendo mediante el uso del atributo [[maybe_unused]].

Argumento de función no utilizado

Éste es el escenario más frecuente (e importante) donde uso el [[maybe_unused]]. En un primer momento parece que la solución es obvia (eliminar el argumento que no se usa, ya que posiblemente la interfaz se ha complicado). Un ejemplo sería una aplicación de dibujo que define una serie de herramientas que heredan todas de la misma clase. Cuando el usuario hace clic sobre el lienzo, se llama al método mouseClicked de la herramienta activa, pasándole el botón del ratón presionado y la posición del cursor en coordenadas del lienzo:

class BaseDrawingTool {
  // ...
protected:
  virtual void mouseClicked(int button, const std::tuple<int, int> &pos_xy) = 0;
}

class ClearWholeCanvas : public BaseDrawingTool {
protected:
  virtual void mouseClicked(int button, const std::tuple<int, int> &pos_xy) override
  {
    switch (button) {
    case LEFT: clearCanvas(m_foreground_color); break;
    case RIGHT: clearCanvas(m_background_color); break;
    }
  }

  void clearCanvas(const Color &color) { ... }
}

Claramente, la herramienta ClearWholeCanvas no necesita conocer la posición exacta del cursor, únicamente qué botón se ha presionado. Como no se usa pos_xy el compilador generará un warning (o un error si hemos activado la opción correspondiente).

Solución a argumento no utilizado

La solución más obvia sería comentar el parámetro o simplemente dejarlo sin nombre

virtual void mouseClicked(int button, const std::tuple<int, int> &) ...
virtual void mouseClicked(int button, const std::tuple<int, int> & /*pos_xy*/) ...

El problema acá es que se pierde la información semántica del parámetro: ¿qué significa?, ¿por qué está comentado?, si necesito la posición del cursor en el futuro ¿me acordaré que ya la tengo disponible?. El caso del comentario es algo mejor pero muchos ayudantes de código (como el IntelliSense) no interpretan estos comentarios cuando presentan los prototipos de las funciones, por lo que perdemos esa ayuda extra in-situ.

En todos estos casos tendríamos que referirnos a la clase padre para saber estos datos pero, además de tedioso, ¿y qué pasa si la clase padre también los tiene borrados? Situación típica en clases que dejan una implementación vacía por defecto:

virtual void mouseClicked(int, const std::tuple<int, int> &) {}

De nuevo, los comentarios serían de utilidad pero no tendríamos esa información en el ayudante contextual (IntelliSense, p.e.)

Podríamos también generar un NOOP (no-operation), que en general es de las mejores opciones y de hecho es implementado por muchas bibliotecas, como Qt con su Q_UNUSED.

virtual void mouseClicked(int button, const std::tuple<int, int> &pos_xy) override
{
  (void)(pos_xy);
}

Sin embargo, la solución usando [[maybe_unused]] es más sencilla y explícita:

virtual void mouseClicked(int button, [[maybe_unused]] const std::tuple<int, int> &pos_xy) override { ... }

Variable o argumento de función no utilizad (a veces)

Una variante del caso anterior es cuando el argumento (o variable local) es usada sólo bajo determinados escenarios de compilación. Pongamos como ejemplo una función que verifica la validez de un fichero de licencia, pero sólo si se está compilando para despliegue (las versiones de desarrollo se ejecutarían sin licencia):

bool checkLicense(const std::filesystem::path &license)
{
#ifdef PROJECT_IN_DEPLOYMENT_MODE
  std::ifstream file(license);

  // ...
#else
  return true;
#endif
}

Otras variantes de este ejemplo implicarían determinados parámetros que se usan sólo en un determinado sistema operativo, o en una arquitectura hardware específica.

Solución a variable no utilizado (a veces)

El caso de checkLicense es diferente al anterior, ya que hay situaciones en las que sí se usa el argumento, por lo que la variable debe tener nombre.

La única solución hasta ahora ha sido generar un NOOP:

bool checkLicense(const std::filesystem::path &license)
{
#ifdef PROJECT_IN_DEPLOYMENT_MODE
  std::ifstream file(license);

  // ...
#else
  (license);

  return true;
#endif
}

La solución usando [[maybe_unused]] es, de nuevo, muy explícita:

bool checkLicense([[maybe_unused]] const std::filesystem::path &license)
{
#ifdef PROJECT_IN_DEPLOYMENT_MODE
  std::ifstream file(license);

  // ...
#else
  return true;
#endif
}

Cumplir con un [[nodiscard]]

El [[nodiscard]] es otro atributo de C++17 que indica al compilador que genere un warning si el valor de retorno de una función no es tenido en cuenta (por ejemplo, para verificar que se comprueba la validez de una operación, evitar resource-leaks, etc). Se puede cumplir con esta restricción simplemente asignando el valor de retorno a una variable, aunque obviamente ahora el compilador nos dirá que dicha variable no está siendo usada; parafraseando a Obi-Wan: se suponía que debías destruirlos, no unirte a ellos.

Como el ejemplo es muy directo y ya hemos expuesto bastante el warning, pasaré directamente a la solución:

[[nodiscard]] bool foo() { ... }

void bar()
{
  [[maybe_unused]] const auto ret = foo();
}

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

Header Files

febrero 13, 2023 08:49

Introducción

Como mencioné en una entrada anterior, uno de los aspectos que más me gusta de C++ es el RAII (Resource Acquisition Is Initialization). Introducida por Bjarne Stroustrup (creador de C++), esta técnica hace uso de los constructores y destructores para la correcta gestión de recursos. El RAII tiene como bases las siguientes premisas:

  • Un constructor siempre se ejecuta antes de que el objeto pueda ser usado, por lo que es un lugar seguro para reservar, inicializar, preparar los recursos a ser utilizados posteriormente.
  • Los destructores son llamados implícitamente cuando el objeto, bueno, se destruye, y es lo último que hace el objeto antes de liberar su propia memoria. Es el momento adecuado de liberar otros recursos usados.
  • Lo único que está garantizado que se ejecutará después de una excepción son los destructores de los objetos ya creados.

Además, recordar que:

  • Los miembros de una clase se destruyen automáticamente al destruir un objeto (y a su vez los miembros de los miembros, etc).
  • Las variables locales y parámetros, por estar en el stack, son destruidos automáticamente al finalizar su alcance.

Lo que implica que podemos automatizar la gestión de gran parte de los recursos de nuestra aplicación únicamente con esta técnica. Casi todo C++ hace uso de la misma; acá algunos ejemplos:

Colecciones dinámicas (arrays dinámicos)

std::vector<T> permite sustituir el duo new[] / delete[] cuando se necesitan colecciones contiguas de elementos homogéneos, por no decir que añade características adicionales como poder añadir/quitar elementos, consultar su tamaño, etc.

Usando new[] / delete[]

C* foo(size_t n) {
  auto old_style = new C[n];

  size_t count = 0;
  for (size_t ii = 0; ii < n; ++ii) {
    if (!old_style[ii].is_valid()) {
      delete[] old_style;
      return nullptr;
    }

    if (old_style[ii].bar()) {
      ++count;
    }
  }

  if (count < n / 2) {
    delete[] old_style;
    return nullptr;
  }

  return old_style;
}

int main() {
  auto c = foo(100);

  delete[] c;
}

He acá los principales defectos de este código:

  • El programador es el encargado de gestionar la memoria para cada punto de salida de la función foo (3 en este caso).
  • La función que llame a foo hereda esta responsabilidad.
  • El nuevo responsable de destruir foo no sabe si tiene que usar delete o delete[].
  • Adicionalmente, es fácil perder el contexto y no saber cuántos elementos tiene c, teniendo que guardarlo para evitar errores de acceso fuera de límites.

RAII con std::vector<T>

std::vector<C> foo(size_t n) {
  std::vector<C> vector_style(n);

  size_t count = 0;
  for (const auto& c : vector_style) {
    if (!c.is_valid()) { return {}; }

    if (old_style[ii].bar()) {
      ++count;
    }
  }

  if (count < n / 2) { return {}; }

  return old_style;
}

int main() {
  auto c = foo(100);
}

Como se ve, gracias al RAII hemos podido simplificar muchísimo nuestro código, automatizando (delegando) la gestión de los recursos al propio lenguaje.

Propiedad del recurso

Uno de los principales dolores de cabeza que suelo tener al diseñar software es el reparto de responsabilidades entre los diferentes componentes del software: el quién hace qué. Entre estas responsabilidades está quién es dueño del recurso, es decir, quién se encarga de inicializarlo y liberarlo.

Gestión manual

Imaginemos un gestor de cámaras de vídeo:

class CameraManager {
public:
  Camera* create_camera(int device_id) const {
    auto camera = new Camera();
    if (!camera->init(device_id)) {
      delete camera;
      return nullptr;
    }

    if (!camera->check_hardware()) {
      delete camera;
      return nullptr;
    }

    return camera;
  }
};

void foo(Camera* cam) { /* ... */ }

int main() {
  auto cam = CameraManager.create_camera(1);

  foo(cam);

  delete cam;
}

Vale, nada del otro mundo pero, como en el ejemplo anterior vemos que hay muchos delete debido a la gestión de errores. Además, una vez creada la cámara, es responsabilidad del programador gestionarla, saber en todo momento quién es el dueño de la cámara y, por ende, quién es el encargado de destruirla. Esto no siempre es fácil cuando la cámara va pasando de mano en mano, terminando con múltiples copias del puntero.

Centralización

Igual un primer pensamiento es que el gestor sea el dueño de las cámaras:

class CameraManager {
  std::map<int, Camera*> m_cameras;
public:
  ~CameraManager() {
    for (auto cam : m_cameras) {
      delete cam.second;
    }
  }

  Camera* create_camera(int device_id) const {
    auto it = m_cameras.find(device_id);
    if (it != m_cameras.end()) { return it->second; }

    auto camera = new Camera();
    if (!camera->init(device_id)) {
      delete camera;
      return nullptr;
    }

    if (!camera->check_hardware()) {
      delete camera;
      return nullptr;
    }

    m_cameras[device_id] = camera;

    return camera;
  }
};

Bajo un correcto contrato entre clases esto podría resolver el problema de la gestión de memoria siempre que no se necesita que las cámaras sean destruidas en mitad del proceso. Podríamos entonces agregar un método destroy_camera al gestor, pero ¿no estaríamos complicando el diseño demasiado ya?

RAII usando punteros inteligentes

Desde C++11 el lenguaje ofrece varias soluciones a este problema, siguiente la técnica del RAII. Aunque me centraré en el std::unique_ptr<T>, otra posible opción es std::shared_ptr<T>.

Como su nombre indica, con std::unique_ptr<T> sólo existe un dueño del recurso; cualquier otro puntero tiene un rol de usuario del mismo, no de dueño. Además, std::unique_ptr<T> se encarga automáticamente de liberar la memoria al destruirse.

class CameraManager {
public:
  std::unique_ptr<Camera> create_camera(int device_id) const {
    auto camera = std::make_unique<Camera>();

    if (!camera->init(device_id)) { return nullptr; }
    if (!camera->check_hardware()) { return nullptr; }

    return std::move(camera);
  }
};

void foo(Camera* cam) { /* ... */ }

int main() {
  auto cam = CameraManager.create_camera(1);
  foo(cam.get());
}

Podemos ver cómo nos hemos quitado las destrucciones manuales en la gestión de errores (simplicando el código enormemente) así como la liberación de recursos por parte del dueño. Además, el propio código foo(cam.get()); grita a voces “oye, te estoy prestando el objeto, pero es mío”.

Otros ejemplos clásicos

Enumeraré otros casos en los cuales se usa el RAII ampliamente, para que los tengáis en cuenta en vuestros desarrollos:

Próximamente

En el siguiente artículo utilizaremos el RAII para automatizar otro tipo de acciones en nuestro código. Mientras tanto, os dejo con una reflexión al respecto de parte de Jonathan Boccara, autor de Fluent C++, To RAII or not to RAII.

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

Header Files

febrero 13, 2023 08:49

Introducción

Es normal que, para muchos, al hablar de Qt vengan a la cabeza dos palabras: signals y slots. Y es que Qt usa ampliamente este mecanismo de comunicación, muy similar al patrón observador, especialmente en lo referente a interfaces gráficas.

La documentación de Qt es muy extensa en el uso de este mecanismo, pero me ha parecido interesante resaltar algunos aspectos básicos y otros un poco menos obvios. Por su extensión, dividiré este artículo en dos entregas. En esta primera parte introduciré el uso más común y tradicional (el usado hasta Qt 4, aunque sea dentro del contexto de Qt 5), y en la segunda entrega pasaré a explicar las novedades introducidas en Qt 5, una comparativa entre ambas versiones, y algunos tópicos más avanzados o al menos no tan frecuentes.

He creado un pequeño proyecto de ejemplo para ilustrar las principales ideas de esta entrada; está disponible en GitHub.

Conceptos generales

Una señal (signal) es emitida por un objeto para informar acerca de algo. El slot no es más que una función encargada de recibir ese mensaje (callback). Al proceso de unir una señal con un slot se le llama conexión. Las conexiones son del tipo uno-a-muchos, es decir, una señal puede estar conectada a múltiples slots (aunque no hay ninguna garantía del orden de ejecución de las mismas). Un slot puede invocarse desde varias señales, pero es un uso menos frecuente.

El ejemplo más obvio es, seguramente, poder asociar una acción a un evento de la interfaz gráfica, como la pulsación de un botón:

connect(ui.btnButton, SIGNAL(clicked()), SLOT(buttonHasBeenPressed()));

Los signals - slots basan una buena parte de su funcionamiento en el meta-objeto de Qt. En una entrada futura (igual muy futura) hablaré sobre él, pero por ahora resumir tres aspectos:

  • Es el encargado de casi toda la comunicación basada en señales (luego aclararé el casi toda), información del objeto en tiempo de ejecución y de las propiedades.
  • Su uso requiere un pre-proceso de compilación mediante la herramienta moc (meta-object compiler). Este paso lo gestiona automáticamente el plugin de Qt para Visual Studio o la herramienta qmake, dependiendo de cómo se compile el proyecto. Para incluir el meta-objeto es necesario que ésta herede de QObject (directamente o de una clase padre) y marcar la clase:
class MyObject : public QObject {
  Q_OBJECT // la marca: debe ser privada y no hace falta ; al final

public: // ...

public slots: // ...

protected slots: // ...

private slots: // ...

signals: // ...
};

Los slots no dejan de ser métodos normales de la clase, y como tales pueden ser públicos, protegidos o privados, se pueden heredar, y marcar como virtuales, abstractos y finales. Las señales por su parte, son algo más especiales: son siempre públicas y su implementación corre por cuenta del proceso moc (es decir, no les debemos definir un cuerpo).

Notas de interés

En caso de usar herencia múltiple, la clase QObject (o heredada, si es el caso) debe ser la primera en la lista de clases:

class MyWidget : public QWidget, public MandatoryInterface, protected UIDelegate {
};

Así mismo, no es necesario moc’ear toda clase que herede de QObject, únicamente aquellas para las que necesitemos hacer uso de señales y slots, u otras características del meta-objeto. Algo más sobre esto en Stack Overflow.

Sintaxis básica

Esta es la forma antigua (hasta Qt 4) y es la más sencilla de usar. Es importante conocerla no sólo para poder entender código legado, o por tener una forma corta de hacer la conexión, sino porque aún en Qt 5 hay ciertas conexiones que no son posibles sino mediante este mecanismo. De hecho, Qt no indica que esta forma esté obsoleta, simplemente han añadido nuevas (con sus ventajas, pero ya lo veremos en otra entrega).

La sintaxis es como sigue:

connect(objeto_emisor, SIGNAL(la_señal(parámetros)),
        objeto_receptor, SLOT(el_slot(parámetros)));

Ambos objetos, el emisor y el receptor deben heredar de QObject y deben coincidir en los parámetros indicados, aunque se puede emitir el indicador const y el de referencia &. Si el objeto receptor es el mismo objeto donde se está haciendo la conexión (es decir, this), se puede omitir.

En caso de que el slot o la señal no exista, el runtime de Qt reportará un error por consola en tiempo de ejecución a la hora de realizar la conexión. Como por lo general las aplicaciones con interfaz gráfica no tienen una consola asociada, tengo como norma crearla en al menos una configuración del proyecto, normalmente la Debug y, en los proyectos que lo ameriten, la ReleaseWithDebug. Como he dicho, la configuración de producción (Release) rara vez tiene consola.

Qt Signal Slots no existen

Como nota: muchas veces es necesario mantener actualizado el estado entre diferentes componentes de la interfaz. La forma tradicional de hacerlo en Qt es conectando recíprocamente las señales de un objeto con los slots del otro. Esto puede hacer por código o bien desde el Qt Designer, aunque no soy muy amigo de esta última forma ya que prefiero tener a la vista en el código las conexiones que se realizan, y dentro del fichero UI quedan escondidas y no es sencillo dar con ellas al realizar búsquedas.

Desconexión

La conexión se mantiene viva mientras ambos objetos, emisor y receptor, existan. En cuanto uno de los dos es destruido, la conexión es destruida también.

Es posible también realizar esta desconexión manualmente mediante el método disconnect. Un par de escenarios para hacer la desconexión manual pueden ser:

  • Existe una señal emitida por la clase padre y que está conectada a un slot de una clase hija. Si la señal es emitida durante la destrucción de la clase padre, ya la clase hija ha sido destruida, generando un comportamiento indefinido. En este caso se puede desconectar la señal en el destructor de la clase hija.
  • Alternar entre destinatarios de una señal dependiendo del estado actual de la aplicación.

Existen muchas formas de desconexión manual, pero igual las más interesantes son:

  • Usar el valor de retorno del método connect, un objeto QMetaObject::Connection que representa la conexión, y el cual puede pasarse al método disconnect para destruirla. Este objeto no puede ser usado para reconectar la señal con el slot.
  • Desconectar por completo una señal: ui.btnButton->disconnect(SIGNAL(clicked()));.

Es posible prevenir, de forma temporal, que un objeto emita señales usando QObject::blockSignals.

Exponer una señal

Otro uso frecuente de las conexiones es para rebotar una señal dentro de una composición de objetos o en un wrapper: uno de los miembros de la clase emite una señal a la que deben reaccionar usuarios de la clase. Para ello, la clase define su propia señal (con el mismo nombre, u otro) y simplemente se conecta la señal del objeto privado a la señal pública de la clase:

connect(ui.btnButton, SIGNAL(clicked()), SIGNAL(buttonClicked()));

El método sender()

Como se dijo, las señales y los slots deben pertenecer a objetos que hereden en algún momento de QObject. QObject tiene un método protegido llamado QObject* sender() el cual devuelve un puntero al objeto emisor de la señal, o nullptr si en ese momento no se está respondiendo a una señal.

Este método permite obtener información adicional y simplifica el diseño de la clase (particularmente de los slots). Por ejemplo, si estamos diseñando un teclado virtual, podemos conectar el clicked() de todos los botones al mismo método y usar sender() para obtener el caracter a mostrar por pantalla (en este caso habría que usar las propiedades de QObject o bien hacer un casting al tipo de botón usado). También permite añadir asserts para comprobar que determinado slot sólo está siendo invocado por un tipo específico de objeto, o sólo mediante una señal (sender() != nullptr).

Algunas señales y slots interesantes

Cabe decir que, pese al título, esta lista no es ni mucho menos exhaustiva y ni siquiera amplia; simplemente representa una pequeña muestra de un par de señales y slots propios de Qt que conviene conocer. La documentación de Qt, de nuevo, es rica en ejemplos de conexiones y detalla perfectamente las señales y slots de cada clase (estando atentos a aquellos que puedan estar siendo heredados).

Slots

  • QObject::deleteLater(): como es sabido, es necesario destruir los objetos que no se usen a fin de evitar memory leaks. Los objetos de Qt suelen usar un esquema jerárquico de propiedad (padre-hijo), y cuando el padre se destruye los hijos también. Aún así, hay muchos casos donde no se asocia un objeto a un padre, por lo que es responsabilidad del programador el liberar esa memoria. Por otro lado, es posible que queden señales pendientes de procesar y a las que el objeto está conectado, en cuyo caso no se podría garantizar que la señal ha sido procesado antes de borrar el objeto. deleteLater() marca el objeto para su destrucción en una próxima iteración del bucle de eventos, por lo que cualquier señal pendiente es correctamente despachada. Un ejemplo puede ser destruir objetos cuando un hilo termine su ejecución (connect(the_thread, SIGNAL(finished()), the_object, SLOT(deleteLater()))). Como todo slot, éste puede ser llamado como un método normal.
  • QCoreApplication::quit(): finaliza la ejecución de la aplicación de forma inmediata. Seguramente el ejemplo más común es asociar la entrada Salir del menú a este slot (connect(ui.actionQuit, SIGNAL(triggered()), qApp, SLOT(quit()))).

Señales

  • QAction::triggered(bool), QAction::toggled(bool): emitidas cuando la acción es activada (el parámetro booleano sólo aplica si la acción es chequeable). La diferencia básica está en que la primera sólo es emitida cuando la acción cambia por intervención del usuario, mientras que la segunda se emite también cuando el estado cambia programáticamente.
  • QAbstractButton::clicked(bool), QAbstractButton::toggled(bool): análogas a las de las QAction pero para los botones: clicked es emitida sólo si el botón es pulsado por el usuario, mientras que toggled se emite también si el estado cambia programáticamente. Esta diferenciación es importante cuando actualizamos el estado de la interfaz desde nuestro código, tanto por si queremos que se ejecute un determinado código como si no.
  • QThread::started(): emitida por el hilo cuando ya está preparado para ejecutar código. Conectar con esta señal es de hecho la forma recomendada para usar hilos en Qt, y no heredando de QThread.
  • QThread::finished(): análoga a la anterior, es emitida por el hilo cuando ha finalizado la ejecución del código asociado y está a punto de destruirse.
  • QObject::destroyed(): emitida por un objeto justo antes de destruirse. Puede usarse para concatenar la destrucción de objetos no-hijos (cuidado con los punteros inválidos que quedan).

Siguiente entrega

En el siguiente artículo de esta serie estudiamos la nueva sintaxis introducida en Qt 5.

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

Header Files

febrero 13, 2023 08:49

Introducción

El sistema de traducciones de Qt es muy simple: usar el método QObject::tr() para definir los textos a traducir, crear un fichero .ts para cada idioma, ejecutar el comando lupdate para actualizar esos ficheros con las nuevas frases del proyecto, traducirlas y ejecutar lrelease para generar el fichero que usa Qt con las traducciones. El sistema es muy potente, por ejemplo, no hace falta nada especial para cambiar a idiomas orientales, simplemente funciona, además de proveer ayudas a la traducción como libros de frases para tener traducciones “pre-hechas”, o detectar incoherencias en parámetros o signos de puntuación distintos entre traducciones los muestra.

Como siempre, no quiero dar un curso, sólo recopilar algunos tips nacidos del día a día.

Frases dinámicas

Igual es de los consejos más importantes y “no opcionales”. Veamos.

“El carro de Pepe”, “Pepe’s car”. Un ejemplo rápido de que cada idioma tiene construcciones diferentes. ¿Y qué? Imaginemos que queremos que nuestra aplicación pregunte el nombre al usuario y muestre el mensaje anterior. Nuestro primer código podría ser algo como:

QString msg = tr("El carro de ") + strNombre;

El problema viene al traducirlo: la cadena a traducir es únicamente “El carro de “, mientras que en nuestro código concatenaremos el nombre a continuación. No es posible traducir la frase al inglés usando este método.

Qt ofrece una solución basada en parámetros: construimos una única frase indicando el lugar de los elementos. En tiempo de ejecución sustituimos esos parámetros con el método QString::arg. Así:

QString msg = tr("El carro de %1").arg(strNombre);

La cadena a traducir en inglés tendrá la forma “%1’s car”, y funcionará perfectamente.

Cambios en los textos originales

Un proyecto es un ser vivo, y es normal que los textos mostrados cambien. Cada vez que eso ocurre hay que ejecutar lupdate para actualizar el fichero .ts. Ahora bien, al cambiar textos o quitar frases, el fichero .ts guarda las viejas entradas (muy bueno por una parte para re-traducir, especialmente si son cambios menores como un signo de puntuación o una falta de ortografía en el texto original). Conforme crece el proyecto, las viejas entradas pueden molestar más que ayudar. Para eliminarlas basta con ejecutar lupdate -noobsolete (si se usa el plugin de Visual Studio se puede especificar este parámetro en las opciones del proyecto de Qt).

Propiedades personalizadas

Es posible definir en el Qt Designer propiedades personalizadas a los QWidget (por ejemplo, para distinguir grupos de objetos de forma sencilla). No entraré en detalle ahora de esto, prometo un post otro día. ¿Qué tiene que ver con las traducciones? Que si el valor asignado es una cadena de texto, por defecto Qt Designer lo marca como “traducible”, por lo que nuestro fichero .ts podría verse desbordado de propiedades que no queremos traducir. Para evitarlo basta con desmarcar la casilla “Traducible” de la propiedad.

One more thing

El último consejo no es directamente propio de las traducciones sino de la clase QString y del Visual C++. El editor de código usa una codificación diferente a la que usa Qt por defecto, lo que puede llevar a que algunos caracteres se muestren mal al ejecutar la aplicación. Un caso típico es el símbolo de grado (º). El problema se magnifica si ese símbolo está en una traducción, ya que entonces Qt no asociará los textos ‘origen’ y ‘traducción’ correctamente. La solución más cómoda que he encontrado es usar un parámetro para ello e insertar el carácter Unicode correspondiente:

QString degreeMsg = tr("Ahora mismo hacen 25%1").arg(QChar(0260));

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

IOKode

OpinionatedFramework: Validaciones

febrero 11, 2023 01:59

Esta entrada forma parte de una serie:


La validación de precondiciones e invaraintes es un requisito básico en el diseño de cualquier clase o estructura.

Existen varios paquetes en NuGet para realizar esta validación de forma sencilla, pero prácticamente todos pecan del mismo problema: no permiten personalizar el tipo de excepción que se lanzará cuando la validación no se cumple.

OpinionatedFramework incluye una librería de validación que refleja lo que para mi es el API perfecta para validaciones.

Uso

El uso de esta librería es simple:

Ensure.Exception(new Exception()).Category.Validation(args);

La librería lanzará la excepción proporcionada si no se cumple la validación. Para hacer su uso más cómodo, se incluyen algunos métodos que seleccionan ciertas excepciones:

Ensure.Argument(nameof(param)).String.NotEmpty(value); // ArgumentException
Ensure.Argument(nameof(param)).NotNull(value); // ArgumentNullException
Ensure.Base("Exception message.").String.NotEmpty(value); // Exception
Ensure.Exception(new EmailException("Invalid email address.")).String.EmailAddress(value); // EmailException
Ensure.InvalidOperation("Cannot add an empty string to the error bag.").String.NotEmpty(value); // InvalidOperationException

Estos métodos son:

  • Argument: Lanza una ArgumentException o una ArgumentNullException
  • Base: Lanza una Exception con el mensaje especificado.
  • Exception: Lanza la instancia de la excepción proporcionada.
  • InvalidOperation: Lanza una InvalidOperationException con el mensaje especificado.

Categorías de validaciones (ensurers)

En cuanto a las validaciones, esta librería incluye validaciones de las siguientes categorías:

  • Assertion: Valida booleanos a true o false.
  • Enumerable: Contiene validaciones sobre IEnumerable, por ejemplo, si está vacío, al menos tantos elementos, si contiene un elemento concreto…
  • Number: Contiene validaciones sobre valores númericos, por ejemplo si es valor está entre dos números, si es decimal...
  • Object: Contiene validaciones sobre objetos, por ejemplo, si es nulo.
  • Stream: Contiene validaciones sobre streams, por ejemplo, si se puede leer, se puede escribir…
  • String: Contiene validaciones sobre cadenas de carácteres, por ejemplo si está vacía, si es de tal tamaño, si contiene tal subcadena, si es un email válido, si cumple una expresión regular...
  • Type: Contiene validaciones sobre tipos, por ejemplo, si es un tipo valor o referencia, si es asignable a otro tipo…

Puntos de extensión

A cada una de estas categorías se les llama Ensurers. Escribir un ensurer nuevo es tan sencillo como crear una clase estática con las validaciones y añadirle el atributo [Ensurer]:

[Ensurer]
public static class CustomerEnsurer
{
    public static bool IsIndividual(Customer customer)
    {
        return customer is IndividualCustomer;
    }

    public static bool IsBusiness(Customer customer)
    {
        return customer is BusinessCustomer;
    }
}

Tras escribir esta simple clase, un source generator se encarga de hacer toda la magia necesaria y a partir de este momento ya pueden ser usados (muchas gracias a @Angeling3 por escribir los source generators):

Ensure.Exception(new CustomerException()).Customer.IsIndividual(customer);

Actualmente no lo permite, pero me gustaría permitir poder añadir nuevos métodos que seleccionan la excepción, de forma que la validación de arriba pueda ser escrita se la siguiente forma:

Ensure.Customer().Customer.IsIndividual(customer);

Desactivación por motivo de rendimiento

Otro aspecto que aún no está disponible pero que pretendo añadir es la opción de poder desactivar las validaciones sobre ciertas condiciones. Esto permitiría poder ignorarlas evitando así un impacto en el rendimiento cuando, por ejemplo, la aplicación se compila en modo release.

Aislado del resto del framework

Esta librería será lanzada en un paquete NuGet aislado del resto de OpinionatedFramework, de forma que pueda ser utilizada sin necesidad de tener una dependencia sobre el framework completo. Sin embargo, el resto de OpinionatedFramework depende de ella, ya que es usada para validar las invariantes de las propias clases que proporciona el framework.

¿Quieres colaborar?

OpinionatedFramework es un proyecto open source alojado en GitHub. Si quieres colaborar en el desarrollo, puedes enviar una pull request.

Ensuring es el proyecto dentro de OpinionatedFramework encargado de las validaciones.

» 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