lunes, 18 de abril de 2016

Programación defensiva y consultas en diferido.

   Como somos la mar de profesionales y nunca nos fiamos de la información que se nos pasa en los parámetros de una función, dedicamos parte de nuestro tiempo en la programación defensiva. Cosas que comúnmente, en la  programación defensiva, se ponen al principio de la llamada de una función pueden ser:
  1. Comprobar la existencia de valor.
  2. Comprobar el tipo en caso de herencias.
  3. Comprobar el rango del valor.
  4. Comprobar la plausibilidad del valor, si es posible.
   Esto son solo 4 ejemplos de la cantidad de cosas que se pueden controlar.




   Un triste ejemplo sería éste, en el que controlamos que la entrada no sea nula y que nuestro proceso produce una salida esperada:

public int Archive(PersonData user, bool persistent)
{
    if (user == null)
    {
        throw new StorageException("null parameter");//caller can catch this, ask for PersonData and call me again
    }

    // Do some processing
    int resultFromProcessing = …

    Debug.Assert(resultFromProcessing >= 0,
        "resultFromProcessing is negative. There is a bug!");//this is bug. No catch and continue execution.

    return resultFromProcessing;
}

   Con .NET también disponemos de CodeContracts, que es lo mismo pero queda como más elegante.


   Ahora pongámonos en el caso de consultas en diferido con LINQ. Partiendo de este método:

static public int Q()
{
    return Enumerable.Range(0, 100)
        .Select(i => i)
        .First();
}
podemos agregarle código defensivo de esta forma:

static public int Q()
{
    var e = Enumerable.Range(0, 100)
        .Select(i => i);

    Contract.Assume(e.Any()); //check for non empty list
    return e.First();
}
y el lector avispado se dará cuenta de que la hemos liado bien parda. Al llamar a la función Any() estamos materializando la consulta y ejecutándola; por lo que se seleccionarán y se traerán a memoria todos lo elementos para luego solo utilizar el primero. Imagínense esto en una consulta con EF en una tabla de base de datos con 100k elementos.

    Hay que tener esto en cuenta cuando se realiza programación defensiva y buscarse las vueltas para poder separar la consulta sin cargarnos el rendimiento/capacidad de la máquina que ejecutará el código. Algo tal que así funciona al pelo:

var res = Enumerable.Range(0, 100).Select(i => i).Take(1); //execute one query with TOP 1 and store in memory
Contract.Assume(res.Any()); //or res.Count() > 0 //query already in memory
return res.First(); //query already in memory
   Así que recordad, paraos a pensar 2 veces cuando añadáis código defensivo a métodos que utilicen consultas en diferido o la factura de tu compañía de Cloud despegará como un cohete.





No hay comentarios:

Publicar un comentario