miércoles, 28 de mayo de 2008

Cómo poner constraints en método genéricos

Una de las cosas que me olvidé de nombrar en el post sobre generics es cómo poner un constaint para un método, bueno, por ahora eso sólo así que acá está

Como poner un constraint a un método

public void Probar<T>(T param) where T : IBase
{
}

En este código simplemente decimos que el tipo T del parámetro param debe implementar la interfaz IBase, esto hasta la próxima.

Actualización

Como indica mi colega Eduardo el ejemplo que puse es muy malo, en realidad casi no sirve para nada, en casos muy raros que si bien se me ocurren son muy confusos, veámos casos en los que un constraint es útil en un método

Tipos de restricciones

Vamos a ver tres casos en los que las restricciones pueden ser útiles en los métodos

/// <summary>
/// new() indica que T tiene que tener un constructor sin parámetros
/// </summary>
public void Probar2<T>(T param) where T : new()
{
    //Si quito la restrucción no puedo hacer esto 
    //porque T no tiene implícitamente un constructor sin parámetros
    T var = new T();
}

/// <summary>
/// class indica que T será un tipo por referencia
/// </summary>
public void Probar3<T>(T param1, T param2) where T : class
{            
}

/// <summary>
/// struct indica que T tiene que se un tipo no Nullable
/// </summary>
public void Probar4<T>(T param1, T param2) where T: struct
{
    //Si quito la restricción no puedo hacer esto
    //porque el método Nullable.Compare espere dos tipos nos nullables (underlying types)
    //sin embargo se pueden pasar estructuras
    Nullable.Compare<T>(param1, param2);
}

Bueno, espero que les sirva y gracias Eduardo por la observación

jueves, 22 de mayo de 2008

Pruebas manejadas por datos con VIsual Studio o Data Driven Test

Pruebas manejadas por datos con Visual Studio

Un utilidad interesante del Visual Studio es la posibilidad de que un método de prueba (TestMethod) se ejecute múltiples veces a partir de un origen de datos, qué quiero decir con esto, teniendo un origen de datos (SqlServer, Access, Oracle, Excel, cualquier cosa que tenga un conector .net) podemos hacer que el IDE de Visual Studio ejecute un método de prueba (un TestMethod) una vez por cada registro de un origen de datos.
Por ejemplo, podemos tenes un método para probar un cierto algorítmo pero como no podemos tener todos los casos de prueba claro de entemano (quiero decir los posibles datos de entrada) podemos hacer que el método los tome del origen de datos y se ejecute una vez por cada registros, esto nos permite dos cosas:

-Poder dejar para más adelante todos los casos posibles, no pensarlo ahora, o porque no los sabemos o porque no queremos o lo que sea.
-Delegar a otra persona (funcional tal vez) la tarea de crear los datos de entrada e incluso ejecutar la prueba unitaria

Por supuesto, no podemos saber de antemano que van a poner, así que una forma astuta (que obviamente no se me ocurrió a mí) es poner una columna adicional con el resultado esperado, pero bueno, basta, vamos al código

/// <summary>
/// Este método se ejecuta una vez por cada fila de la tabla
/// </summary>
[TestMethod]
[DataSource(
  "System.Data.Odbc", 
    @"Dsn=Excel Files;
    dbq=C:\\Pruebas\\datosPruebaDB\\origenDatos.xls;
        defaultdir=C:\\Pruebas\\datosPruebaDB;
    driverid=790;
    maxbuffersize=2048;
    pagetimeout=5", 
    "Datos$", 
    DataAccessMethod.Sequential)]
public void TestMethod1()
{
   //recuperados los valores del origen de datos
    string valor = this.Context.DataRow["Valor"].ToString();
    bool resultado = Convert.ToBoolean(this.Context.DataRow["Resultado"]);

    //nuestra función de validación de largos
    bool largoCorrecto = (valor.Length>6);

    //assert para verficar si coincide con el resultado esperado
    Assert.AreEqual<bool>(largoCorrecto, resultado);

}

Y listo es todo, es este caso mi archivo Excel tiene esta forma

Valor Resultado
Leonardo 1
Gabriel 1
Hernán 0
Gerardo 1
Norberto 1
Adrián 0
Angel 0
Albino 0

Lo hice con Excel para que sea más visual, por supuesto, la hoja donde se encutran estos datos se llaman "Datos" y me conecto por ODBC. La columna con el encabezado valor es el valor de entrada y la que se llama resultado es el resultado esperado, como puede verse simplemente agrego un atriburo en el método indicando el origen de datos y después recupero la fila actual con el TestContext.

Enjoy,

sábado, 17 de mayo de 2008

Primeras Jornadas Ágiles de Latinoamérica 2008

El proyecto del cual participo como organizador (realmente mi participación es más que escueta) http://www.agiles.2008.org/ ya es Agile Alliance Compliant http://www.scrumalliance.org/events/10-giles-, felicitaciones al equipo, a Juan, Ricardo y todos los demás. Saludos.

viernes, 16 de mayo de 2008

Sobre excepciones en .net

Excepciones

El framework de .net utiliza un mecanismo para informar de situaciones que impiden el flujo normal del código como un error, se trata de las excepciones.

 Las excepciones se pueden producir por muchas razones, en general situaciones inesperadas, por ejemplo querer acceder a un archivo que no existe, esto produce una horrible excepción, veamos un poco de código

using (StreamReader reader = new StreamReader(@"c:\archivoQueNoExiste.txt"))
{
 reader.ReadLine();
 reader.Close();
}

 Si lo ejecutamos recibimos una fea fea excepción del tipo FileNotFoundException, ok esto es lo que queríamos, ahora bien, qué hacemos ahora?

Alternativa ante las excepciones

Inicialmente podemos hacer dos cosas cuando nos encontramos con una porción de código que puede lanzar una excepción

-Atraparla en un bloque catch

-No hacer nada

Comencemos por el primer caso

try 
{ 
 using (StreamReader reader = new StreamReader(@"c:\archivoQueNoExiste.txt")) 
 { 
  reader.ReadLine();
  reader.Close();
 } 
}catch 
{ 
 throw;
} 

Qué pasa acá, bien, lo mismo que no hacer nada, porque en el bloque volvemos a lanzar la excepción por lo tanto es lo mismo que no hacer nada

try 
{ 
 using (StreamReader reader = new StreamReader(@"c:\archivoQueNoExiste.txt")) 
 { 
 reader.ReadLine();
 reader.Close();
 } 
}catch(Exception ex) 
{ 
 throw ex;
} 

Este ejemplo es peor que el anterior porque no sólo no hacemos nada sino que además al colocar Exeception ex y luego hacer throw borramos toda la información que se recopiló hasta el momento del error (el stack trace) por lo tanto, no sólo no la manipulamos sino que además perdemos valiosa información.

Enseñanza:

-Si atrapamos una excepción y la relanzamos haciendo referencia al objeto Exception borramos el stack trace.

try 
{ 
 using (StreamReader reader = new StreamReader(@"c:\archivoQueNoExiste.txt")) 
 { 
  reader.ReadLine();
  reader.Close();
 } 
}catch(Exception ex) 
{ 
 GuardarInformacionDelError(ex);
} 

Esto es bastante más elegante, ocurre un error y guardamos la información para un análisis posterior, sin embargo esto nos lleva a una enseñanza mejor, por qué no hacer mejor esto:

string nombre = @"c:\archivoQueNoExiste.txt";
if (File.Exists(nombre))
{ 
 using (StreamReader reader = new StreamReader(nombre)) 
 { 
  reader.ReadLine();
  reader.Close();
 }
}

 Nada de nada, no ocurre la excepción porque estamos verificando de antemano la existencia del archivo, la enseñanza es que es mejor evitar las excepciones, porque son costosas a nivel recursos y son feas también.

 Manejando excepciones

En caso de querer manejar la excepción o que ocurra una excepción inesperada por ejemplo:

string nombre = @"c:\archivoQueNoExiste.txt";
if (File.Exists(nombre))
{
 using (StreamWriter writer = new StreamWriter(nombre))
 { 
  writer.Write("hola mundo");
  writer.Close();
 }
}

 y que el archivo sea de sólo lectura o algo así (obviamente también podemos verificarlo antes, es un ejemplo), entonces tenemos un bloque de código en el catch para manipular la excepción, pero qué hacemos? Primero que nada podemos guardar información de error, segundo lanzar otra excepción, una personalizada, indicando un problema con información más "amigable"

try 
{
 string nombre = @"c:\archivoQueNoExiste.txt";
 if (File.Exists(nombre)) 
 {
  using (StreamWriter writer = new StreamWriter(nombre))
  {
   writer.Write("hola mundo"); 
   writer.Close();
  }
 }
}catch (Exception ex) 
{
 GuardarInformacionDelError(ex);
 throw new ExcepcionPersonalizada("Ocurrión un error sentimos las molestias");
 }

 Un lujo asiático, vamos a hacer un resumen de lo que aprendimos

-Siempre es mejor verificar para evitar las excepciones que esperar que ocurran

-Si ocurren y vamos a atraparlas hay que manipularlas correctamente, de muy poco sirve relanzar la misma excepción

Tipificando excepciones

Las excepciones son objeto de una clase, todas heredan de la clase base Exception y las podemos diferenciar por el tipo, nada de códigos de error ni cosas raras

Esta tipificación nos permite poder hacer cosas simpáticas filtrando las excepciones, veámos el siguiente ejemplo:

try 
{ 
 string nombre = @"c:\archivoQueNoExiste.txt";
 if (File.Exists(nombre)) 
 {
  using (StreamWriter writer = new StreamWriter(nombre))
  {
   writer.Write("hola mundo"); 
   writer.Close();
  }
 }
}catch (UnauthorizedAccessException)
{
 throw new ExcepcionPersonalizada("No se puede acceder al archivo");
}catch (Exception ex) 
{ 
 GuardarInformacionDelError(ex);
 throw new ExcepcionPersonalizada("Ocurrió un error inesperado"); 
}

 En este caso en caso de ocurrir una excepcion del tipo UnauthorizedAccessException vamos a lanzar otra indicando que no se puede acceder al achivo, ahora cuando ocurra cualquier otra excepción se va a guardar la información y se va a lanzar una excepción personalizada indicando que pasó algo raro. Ahora, sabemos que UnautorizedAccessException hereda de SystemException qué pasa si hacemos esto?:

try 
{ 
 string nombre = @"c:\archivoQueNoExiste.txt";
 if (File.Exists(nombre)) 
 {
  using (StreamWriter writer = new StreamWriter(nombre))
  {
   writer.Write("hola mundo"); writer.Close();
  }
 }
}catch (UnauthorizedAccessException)
{
 throw new ExcepcionPersonalizada("No se puede acceder al archivo"); 
}catch (SystemException)
{
 throw new ExcepcionPersonalizada("Error de sistema");
}catch (Exception ex)
{
 GuardarInformacionDelError(ex);
 throw new ExcepcionPersonalizada("Ocurrió un error inesperado");
}

 Bien, se ejecuta el bloque que tiene UnauthorizedAccessException porque siempre se ejecuta primero el bloque con el tipo más especializado, es decir, como la excepción es del tipo UnauthorizedAccessException por más que esta herede de SystemException se ejecuta su bloque para que podamos hacer algo con ese tipo en particuar de excepción, otra cosa con todas las otras excepciones que hereden de systemException y por útlimo otra cosa con el resto.

Otra enseñanza

-Cuando tenemos varios bloque catch se ejecuta el que tiene el tipo que mejor encaja con el de la excepción que se produjo y sólo ese.

 Ahora bien, qué pasa si hacemos esto:

try 
{
 string nombre = @"c:\archivoQueNoExiste.txt";
 if (File.Exists(nombre))
 {
  using (StreamWriter writer = new StreamWriter(nombre))
  {
   writer.Write("hola mundo"); writer.Close();
  }
 }
}catch (ApplicationException)
{
 throw new ExcepcionPersonalizada("No se puede acceder al archivo");
}

 Nadie atrapa la excepción porque estamos sólo teniendo en cuenta las del tipo ApplicationException y deribadas y no es el caso de UnauthorizedAccessException, entonces la excepción "burbujea" al método que invocó al actual y seguirá haciendo esto hasta que algún bloque maniuple la excepción, si esto nunca ocurre la aplicación pincha, o más técnicamente, el CLR la finaliza con un carte muy feo que habla acerca de nuestro descuído.

Otra enseñanza

-Si no atrapamos una excepción esta burbujea a la capa superior y así sucesivamente hasta que alguien la manipula o el CLR interrumpe la aplicación

No utilizar excepciones como control de flujo para reglas de negocios

Un tema polémico, no es recomendable utilizar excepciones personalizadas para controlar el flujo de la aplicación por dos razones:

-Son costosas a nivel recursos

-Y se llaman excepciones porque deben ocurrir sólo en ocasiones excepcionales

Auque puede haber casos en los que se utilicen, por ejemplo, escribimor un servicio web para que consuma alquien a través de internet, poco seguro sería si la validación de los datos del usuario devolviese un valor booleano si la autenticación del mismo fuera errónea, creo que más de uno pensaría en que es sencillo saltarla, en este caso lo más recomendable es lanzar una excepción para que se interrumpa la comunicación directamente.

Crear excepciones personalizadas

Bueno, no hay mucho para decir, simplemente hay que seguir una regla, que nuestras excepciones hereden de ApplicationException porque Exception está reservada para excepciones del CLR, esto nos va a permitir un mejor filtrado, más aún si definimos nuestra propia excepción base y hacemos heredas todas nuestras excepciones de esta.

Hasta la próxima.

martes, 6 de mayo de 2008

Cómo saber si un objeto es de un tipo, o si se puede "castear" o si implementa una interfaz.

Cómo saber si un objeto implementa una interfaz o hereda de una clase, o se puede castear

En ocasiones, sobre todo cuando la arquitectura de una aplicación comienza a hacerse compleja, recibimos objetos que necesitamos "castear" o hacer "boxing" contra un tipo base o una interfaz, esto puede pasar en muchos casos y nos es indispensable hacer el cast del estilo

Tipo objeto = (Tipo) otroObjeto;

Para que otroObjeto se convierta en Tipo y poder utilizar sus  métodos, etc. Bueno, vamos a ser brever y vamos directamente al código, no sin antes contarle que esto se logra verificando si nuestro objeto implementa o hereda de algo con el operador is, del modo

if(objeto is Tipo)

using System;
using System.Collections.Generic;
using System.Text;

namespace saberUnTipo
{
    class Program
    {
        static void Main(string[] args)
        {
            ClaseQueImplementaYHereda();
            ClaseSola();
            Console.ReadLine();
        }

        public static void ClaseQueImplementaYHereda()
        {
            ClasePrueba clasePrueba = new ClasePrueba();
            Console.WriteLine("Probando ClasePrueba");
            Console.WriteLine("Implementa MiInterfase {0}", (clasePrueba is MiInterfase));
            Console.WriteLine("Hereda de ClaseBase {0}", (clasePrueba is ClaseBase));
            Console.WriteLine("-------------------------------------------------");
        }

        public static void ClaseSola()
        {
            ClaseSola clasePrueba = new ClaseSola();
            Console.WriteLine("Probando ClaseSola");
            Console.WriteLine("Implementa MiInterfase {0}", (clasePrueba is MiInterfase));
            Console.WriteLine("Hereda de ClaseBase {0}", (clasePrueba is ClaseBase));
            Console.WriteLine("-------------------------------------------------");
        }
    }

    public class ClasePrueba: ClaseBase, MiInterfase
    {
    }

    public class ClaseSola
    {
    }

    public class ClaseBase
    {
    }

    public interface MiInterfase
    {
    }
}

Simple, hasta la próxima.