July 21, 2021

A funny bug

Ayer mismo estaba intentando analizar unos data_types para completar una exportación al backend. La idea era sencilla, buscar para cada usuario la ultima información disponible en una tabla que representaba actualizaciones sobre su perfil.

Como a su perfil podías “añadir”, “actualizar” y “eliminar” cosas, pues existian los tres tipos en la tabla. Para que la imaginemos mejor sería tal que así:

user_id favorite_stuff operation metadata
A Chocolate Add
A Chocolate Update
B Milk Remove
B Cornflakes Add

De tal manera que habría que combinar todos los eventos para saber cual es el perfil actual del usuario y aplicar cierta lógica. Sin embargo los eventos que habían llegado realmente eran así:

user_id favorite_stuff operation metadata
A Chocolate Add
A Chocolate Update
A Chocolate Remove
B Milk Add
B Milk Update
B Milk Remove
B Cornflakes Add
B Cornflakes Update
B Cornflakes Remove

Y así para toda combinación de user_id / favorite_stuff.

Y como buen “golifión” que soy, me puse a intentar encontrar el problema en backend porque el comportamiento era realmente anómalo. Está hecho en C# y aún me acuerdo de algo :-)

Y encontré el método que buscaba.

(El except es el equivalente a la resta de conjuntos, el minus, vaya).

private IEnumerable<DataEvent> PrepareUpdatesEventsToSend(IEnumerable<FavoriteStuff> favoriteStuffUpdate, IEnumerable<FavoriteStuff> previousFavoriteStuff)
{
    var events = new List<DataEvent>();

    var addedElements = favoriteStuffUpdate.Except(previousFavoriteStuff).ToList();
    addedElements.ForEach(x => events.Add(CreateFavoriteStuffUpdateEvent(x, "Add")));

    var removedElements = previousFavoriteStuff.Except(favoriteStuffUpdate).ToList();
    addedElements.ForEach(x => events.Add(CreateFavoriteStuffUpdateEvent(x, "Remove")));

    var updatedElements = favoriteStuffUpdate.Except(addedElements).ToList();
    addedElements.ForEach(x => events.Add(CreateFavoriteStuffUpdateEvent(x, "Update")));

    return events;
}

La lógica es bastante buena y la idea promete. Sin embargo (aunque aquí no se pueda determinar), este código no funciona a menos de que añadamos algo más, ¿por qué? Con números simples la idea funciona perfecta…

using System;
using System.Collections.Generic;				
using System.Linq;
public class Program
{
	public static void Main()
	{
		var a = new List<int>(){1,2,3,4};
		var b = new List<int>(){2,3,4};
		
		var c = a.Except(b);
		Console.WriteLine(String.Join(" ", c));
	}
}
1

Pero aquí estamos comparando dos enumerables de FavoriteStuff usando el comparador por defecto, que en este caso es el equals y busca la identidad de los objetos.

De hecho con un ejemplo muy simple:

using System;
using System.Collections.Generic;				
using System.Linq;
public class Program
{
	public static void Main()
	{
		var a = new List<MyFakeInt>(){new MyFakeInt(){MyNumber = 1},new MyFakeInt(){MyNumber = 2},new MyFakeInt(){MyNumber = 3},new MyFakeInt(){MyNumber = 4}};
		var b = new List<MyFakeInt>(){new MyFakeInt(){MyNumber = 2},new MyFakeInt(){MyNumber = 3},new MyFakeInt(){MyNumber = 4}};
		
		var c = a.Except(b);
		Console.WriteLine(String.Join(" ", c));
	}
}

class MyFakeInt {
	public int MyNumber {get; set;}
}

MyFakeInt, MyFakeInt, MyFakeInt, MyFakeInt

Nos devuelve toda la lista de a porque sus identidades son diferentes.

Ya está reportado y se arreglará, pero siempre es curioso ver comos las bases son vitales para encontrar problemas :)

2017-2022 Adrián Abreu powered by Hugo and Kiss Theme