10 dic 2015

Excepciones y más Excepciones (o el manejo de Errores)

Como sabemos las excepciones son el mecanismo por medio del cual el .Net Framework maneja los errores, éstas se “lanzan” cuando hay algo que produce un error, una excepción, un comportamiento indeseado y no manejado en nuestro código. Sabemos también que debemos darle tratamiento, no podemos ignorarlas, ya que harán que una aplicación deje de funcionar, se cuelgue, se cierre, o muestre horribles mensajes o páginas con las excepciones “crudas” al usuario final.

El esquema que solemos utilizar para manejar las excepciones son los bloques try{} catch{} finally{}, donde lo que deseamos ejecutar va dentro del bloque try, el tratamiento del error, si se produjese, va en el bloque cacth y lo que ejecutaremos siempre al final independientemente si se produjeron o no errores (liberar recursos por ejemplo) irá en el bloque finally

Exception Bubbling


Supongamos que tenemos varios (muchos) métodos o funciones que se llaman entre sí, de manera que el método 1 llama al método 2 que a su vez llama al método 3  y así consecutivamente hasta “n” métodos. ¿Qué sucede si en el método “n” se produce un error? Entra a jugar el mecanismo que se conoce como “exception bubbling” el cual consiste en que el error es “lanzando hacia arriba”, hacia el método que llamó al que produce el error, se añade información a la pila de seguimiento (StackTrace) y el error es nuevamente “lanzado hacia arriba” reproduciendo el mismo comportamiento hasta llegar al primer método de la cadena o hasta alguno en el que se dé un tratamiento apropiado. El siguiente código ilustra este comportamiento:

using System;

namespace BubblingExceptions
{
    internal static class Program
    {
        private static void Main(string[] args)
        {
            errorbubbling();
        }

        private static void errorbubbling()
        {
            try
            {
                Console.WriteLine("Inicia ejecución >> Llamando a Método 1");
                Metodo1();
                Console.WriteLine("Ejecución exitosa de Método 1");
            }
            catch (Exception ex)
            {
                Console.WriteLine("\nHa ocurrido una excepción:\n\n" + ex);
            }
            finally
            {
                Console.WriteLine("\nEjecutado bloque finally.");
                Console.ReadKey();
            }
        }

        private static void Metodo1()
        {
            Console.WriteLine("Método 1 >> Llamando al Método 2");
            Metodo2();
            Console.WriteLine("Ejecución exitosa de Método 2");
        }

        private static void Metodo2()
        {
            Console.WriteLine("Método 2 >> Llamando al Método 3");
            Metodo3();
            Console.WriteLine("Ejecución exitosa de Método 3");
        }

        private static void Metodo3()
        {
            Console.WriteLine("Método 3 >> Llamando al Método 4");
            Metodo4();
            Console.WriteLine("Ejecución exitosa de Método 4");
        }

        private static void Metodo4()
        {
            Console.WriteLine("Método 4 >> Llamando al Método 5");
            Metodo5();
            Console.WriteLine("Ejecución exitosa de Método 5");
        }

        private static void Metodo5()
        {
            Console.WriteLine("Método 5");
            throw new Exception("Exception de prueba de bubbling");
        }
    }
}

Como puede verse la excepción es lanzada desde el método más profundo, el número 5, hasta el que inicia la ejecución,  errorbubbling, mientras que en el camino recolecta información que es añadida a la pila de seguimiento. 

Por esto es que resulta imprescindible aprender a leer el StackTrace, el cual nos brindará información valiosa cuando estamos depurando nuestro código. Por ejemplo, en el resultado anterior, podemos saber que el error originalmente se produjo en la línea 62 del metodo5, y que este se llamó desde la línea 55 del método4, recorremos los llamados hasta encontrarnos que el error inicia en la línea 17 del método que comenzó la ejecución.

Aquí es importante reafirmar una regla de oro en el tratamiento de las excepciones: solo se captura una excepción (usando el catch)  cuando vamos a hacer algo productivo con ella, que en la mayoría de los casos debería ser tratarla adecuadamente impidiendo el bubbling (detener la propagación de la excepción) y, probable e idealmente, registrar el error en una bitácora (log).  Esto se ilustra de la siguiente manera:

private static void errorbubbling()
{
    try
    {
        Console.WriteLine("Inicia ejecución >> Llamando a Método 1");
        Metodo1();
        Console.WriteLine("Ejecución exitosa de Método 1");
    }
    catch (Exception ex)
    {
        Console.WriteLine("\nHa ocurrido una excepción:\n\n" + ex);
    }
    finally
    {
        Console.WriteLine("\nEjecutado bloque finally.");
        Console.ReadKey();
    }
}

private static void Metodo1()
{
    Console.WriteLine("Método 1 >> Llamando al Método 2");
    Metodo2();
    Console.WriteLine("Ejecución exitosa de Método 2");
}

private static void Metodo2()
{
    Console.WriteLine("Método 2 >> Llamando al Método 3");
    Metodo3();
    Console.WriteLine("Ejecución exitosa de Método 3");
}

private static void Metodo3()
{
    Console.WriteLine("Método 3 >> Llamando al Método 4");
    Metodo4();
    Console.WriteLine("Ejecución exitosa de Método 4");
}

private static void Metodo4()
{
    Console.WriteLine("Método 4 >> Llamando al Método 5");
    Metodo5();
    Console.WriteLine("Ejecución exitosa de Método 5");
}

private static void Metodo5()
{
    try
    {
        Console.WriteLine("Método 5");
        throw new Exception("Exception de prueba de bubbling");
    }
    catch (Exception ex)
    {
        Console.WriteLine("\nHa ocurrido una excepción en el Método 5:\n\n" + ex);
        // Manejo del error impiendo el bubbling, idealmente log del error
    }
    finally
    {
        Console.WriteLine("\nEjecutado bloque finally del método 5.");
    }
}

Como puede verse el StackTrace se reduce considerablemente pues el bubling de la excepción se detiene en el mismo método en el que se produce el error (siendo claro, en este caso, del todo, no habría bubbling), además se constata que los temas métodos completan su ejecución, finalmente el catch del método errorbubbling no se ejecuta, pero si todos los bloques finally.

El costo de los errores (malas prácticas)


¿Hay una penalización de rendimiento por el manejo de los errores? Por supuesto, que desde luego, que claro, que obviamente que sí…

El manejo de los errores tiene un costo significativo, por ejemplo una práctica que puede volverse común es utilizar el try…cacth de la siguiente manera:

            try
            {
               // Código a ejecutar
            }
            catch (Exception ex)
            {
                throw ex;
            }      
O bien esta otra variante:

            try
            {
               // Código a ejecutar
            }
            catch (Exception ex)
            {
                throw;
            }     
Lo que provocará es que el error sea relanzado, no por el .Net Framework, si no, explícitamente por el  código del desarrollador. Utilizando la primer práctica el código de nuestro ejemplo queda de la siguiente manera:

private static void errorbubbling()
{
    try
    {
        Console.WriteLine("Inicia ejecución >> Llamando a Método 1");
        Metodo1();
        Console.WriteLine("Ejecución exitosa de Método 1");
    }
    catch (Exception ex)
    {
        Console.WriteLine("\nHa ocurrido una excepción:\n\n" + ex);
    }
    finally
    {
        Console.WriteLine("\nEjecutado bloque finally.");
        Console.ReadKey();
    }
}

private static void Metodo1()
{
    try
    {
        Console.WriteLine("Método 1 >> Llamando al Método 2");
        Metodo2();
        Console.WriteLine("Ejecución exitosa de Método 2");
    }
    catch (Exception ex)
    {
        throw ex;
    }
}

private static void Metodo2()
{
    try
    {
        Console.WriteLine("Método 2 >> Llamando al Método 3");
        Metodo3();
        Console.WriteLine("Ejecución exitosa de Método 3");
    }
    catch (Exception ex)
    {
        throw ex;
    }
}

private static void Metodo3()
{
    try
    {
        Console.WriteLine("Método 3 >> Llamando al Método 4");
        Metodo4();
        Console.WriteLine("Ejecución exitosa de Método 4");
    }
    catch (Exception ex)
    {
        throw ex;
    }
}

private static void Metodo4()
{
    try
    {
        Console.WriteLine("Método 4 >> Llamando al Método 5");
        Metodo5();
        Console.WriteLine("Ejecución exitosa de Método 5");
    }
    catch (Exception ex)
    {
        throw ex;
    }
}

private static void Metodo5()
{
    try
    {
        Console.WriteLine("Método 5");
        throw new Exception("Exception de prueba de bubbling");
    }
    catch (Exception ex)
    {
        throw ex;
    }
}

Usando la segunda practica queda de la siguiente manera:

private static void errorbubbling()
{
    try
    {
        Console.WriteLine("Inicia ejecución >> Llamando a Método 1");
        Metodo1();
        Console.WriteLine("Ejecución exitosa de Método 1");
    }
    catch (Exception ex)
    {
        Console.WriteLine("\nHa ocurrido una excepción:\n\n" + ex);
    }
    finally
    {
        Console.WriteLine("\nEjecutado bloque finally.");
        Console.ReadKey();
    }
}

private static void Metodo1()
{
    try
    {
        Console.WriteLine("Método 1 >> Llamando al Método 2");
        Metodo2();
        Console.WriteLine("Ejecución exitosa de Método 2");
    }
    catch (Exception ex)
    {
        throw;
    }
}

private static void Metodo2()
{
    try
    {
        Console.WriteLine("Método 2 >> Llamando al Método 3");
        Metodo3();
        Console.WriteLine("Ejecución exitosa de Método 3");
    }
    catch (Exception ex)
    {
        throw;
    }
}

private static void Metodo3()
{
    try
    {
        Console.WriteLine("Método 3 >> Llamando al Método 4");
        Metodo4();
        Console.WriteLine("Ejecución exitosa de Método 4");
    }
    catch (Exception ex)
    {
        throw;
    }
}

private static void Metodo4()
{
    try
    {
        Console.WriteLine("Método 4 >> Llamando al Método 5");
        Metodo5();
        Console.WriteLine("Ejecución exitosa de Método 5");
    }
    catch (Exception ex)
    {
        throw;
    }
}

private static void Metodo5()
{
    try
    {
        Console.WriteLine("Método 5");
        throw new Exception("Exception de prueba de bubbling");
    }
    catch (Exception ex)
    {
        throw;
    }
}


Como puede verse, no es inocua la elección entre un método u otro. Si usamos throw ex; el StackTrace se reinicia con cada llamada, perdiéndose la información previa del error, cosa que no sucede si únicamente usamos throw; por lo que, generalmente, es preferible usar throw; en lugar de throw ex;.

Llegados a este punto nos preguntamos ¿y el costo? Pues bien realizamos algunas pruebas. En los siguientes pruebas se eliminaron todos los console.write dentro de los métodos para controlar solo el manejo de errores, los resultados son los siguientes:





 

Conclusiones

- El costo del manejo de las excepciones es bastante elevado.
- Es preferible manejar el error en el método en el que se produce el error y evitar el bubbling.
- Si el punto anterior no es viable, la regla es no usar catch a menos que se vaya a tratar el error apropiadamente, es mejor evitar el try catch si el error se va a manejar en el método que llama al actual, es decir, si solo se va usar throw dentro del catch, es mejor no hacerlo.
- Entre throw; y throw ex; es mejor elegir thow; ya que mantiene el StackTrace.
- No olvidar registrar la excepción en una bitácora o en el visor de eventos para examinar apropiadamente el problema que lo produce.
- Siempre es preferible no lanzar un error.

Gracias a nuestro"kohāi" Brayan por señalarnos que estábamos reproduciendo un código ineficiente en alguno de los manejos de excepciones. Siempre es importante hacer retrospectivas y atender a las sugerencias para mejorar.

29 nov 2015

Listas y Linq

Linq definitivamente es una herramienta que necesitamos dominar, son muchos los escenarios en los que podemos aprovechar su potencia para simplificar las tareas que otrora nos tomaría varias líneas de código. Aquí unas pequeñas pinceladas con arreglos y listas.

Supongamos que tenemos dos arreglos de enteros y necesitamos saber si ambos se intersecan y cuáles son los números que comparten. Con Linq sería algo así:

static void Main(string[] args)
{
    int[] x = { 1, 2, 3, 4, 5 };
    int[] y = { 2, 4, 6 };
    int[] z = x.Intersect(y).ToArray();
    System.Diagnostics.Debug.WriteLine("Resultado: " + 
    string.Join(",", z.Select(s => s.ToString())));
}
¡Simple!
Otro ejemplo
Supongamos que tenemos la siguiente clase

public class Empleado
{
    public int idEmpleado { get; set; }
    public string nomEmpleado { get; set; }
    public decimal mtoSalario { get; set; }
}
Tenemos un array de enteros y queremos inicializar una lista de empleados únicamente con los id a partir del array, procedemos así:

static void Main(string[] args)
{
    int [] ids =  { 1, 2, 3, 4, 5 };
    List empleados = ids
        .Select(i => new Empleado() { idEmpleado = i })
        .ToList();
    System.Diagnostics.Debug.WriteLine("Cantidad: " + empleados.Count);
}
Una más. Supongamos que adicionalmente a la clase anterior de tenemos otra correspondiente a clientes

public class Cliente
{
    public int idCliente { get; set; }
    public string nomCliente { get; set; }
}
Supongamos que comparten un mismo origen, de manera que un cliente y empleado con la misma id, son la misma persona, teniendo dos listas: una de empleados y otra de clientes, queremos saber cuáles empleados también son clientes, entonces:

static void Main(string[] args)
{
    //Clientes
    List clientes = new List (){
        new Cliente() {idCliente=1, nomCliente= "María"}, 
        new Cliente () {idCliente=2, nomCliente="Jose"},
        new Cliente() {idCliente=3, nomCliente="David"}, 
        new Cliente() {idCliente=4,nomCliente="Ana"} 
    };

    //Empleados
    List empleados = new List(){
        new Empleado() {idEmpleado=2, nomEmpleado="Jose", mtoSalario=3500},
        new Empleado() {idEmpleado=4, nomEmpleado="Ana", mtoSalario=5500},
        new Empleado() {idEmpleado=6, nomEmpleado="Carlos", mtoSalario=3500}
    };

    //ids de clientes
    List idsClientes = clientes.Select(s => s.idCliente).ToList();

    //Empleados que son Clientes
    List empleadosClientes = empleados
        .Where(e => idsClientes.Contains(e.idEmpleado))
        .ToList();

    System.Diagnostics.Debug.WriteLine("Empleados y Clientes: " + 
        string.Join(", ", empleadosClientes.Select(c => c.nomEmpleado.ToString())));
}
Como podemos ver son muchas las posibilidades que nos ofrece Linq, o sus Extension Methods para ayudarnos en nuestra labor.

24 oct 2015

RPAD y LPAD en TSQL

Son un par de funciones que los que algún día estuvimos trabajando con Oracle estrañamos en SQL Server, Básicamente permiten rellenar a la izquierda o derecha con un caracter dado. Aquí una implementación de ambas en T-SQL

--Permite rellenar con un caracter dado a la izquierda hasta completar
-- la longitud deseada
CREATE FUNCTION [dbo].[fnPADL](
 @strOrigen VARCHAR(50), --String original
 @intlen INT,            --longitud a completar
 @chrCaracter CHAR)      --Caracter con el que se rellena
 RETURNS VARCHAR(50)
 AS 
 BEGIN
    DECLARE @len INT = LEN(@strOrigen)
    RETURN (
        CASE 
            WHEN @len < @intlen THEN REPLICATE(@chrCaracter, @intlen - @len) + @strOrigen 
            ELSE  @strOrigen
        END)
END

--Permite rellenar con un caracter dado a la derecha hasta completar
-- la longitud deseada
CREATE FUNCTION [dbo].[fnPADR](
 @strOrigen VARCHAR(50), --String original
 @intlen INT,            --longitud a completar
 @chrCaracter CHAR)      --Caracter con el que se rellena
 RETURNS VARCHAR(50)
 AS 
 BEGIN
    DECLARE @len INT = LEN(@strOrigen)
    RETURN (
        CASE 
            WHEN @len < @intlen THEN  @strOrigen + REPLICATE(@chrCaracter, @intlen - @len)
            ELSE  @strOrigen
        END)
END
Ambas funciones se aseguran que si la longitud de la palabra es mayor o igual a la longitud a cubrir devuelva la misma palabra.

Su forma de utilización sería

SELECT DBO.fnPADL ('HOLA',6,'X')
--devuelve: XXHOLA

SELECT DBO.fnPADR ('HOLA',6,'X')
-- devuelve: HOLAXX

SELECT DBO.fnPADL ('HOLA',2,'X')
-- devuelve: HOLA
Es muy muy importante a tener en cuenta que aunque estén disponibles las UDF (User-Defined Function) que devuelven escalares (como las que acabamos de definir) no es muy recomendable su uso, principalmente con grandes conjuntos de datos, En otro post intentaré ahondar mas en el por qué, quizás con algunas comparativas pero de momento el consejo sería: siempre que sea posible usar la lógica "in line" o dentro de la misma sentencia SQL, por ejemplo es más eficiente esto

SELECT REPLICATE('X',6 - LEN(miColumna)) + miColumna
FROM miTabla
en lugar de esto:

SELECT  DBO.fnPADL (miColumna,6,'X')
FROM miTabla
Obviamente con una cantidad de datos significativa, Además para usar la primera sentencia tenemos que estar seguros que la longitud de miColumna no es mayor a 6.

En todo caso recordar que si vamos a usar UDF escalares debemos realizar pruebas con buenos volúmenes de datos.

18 sept 2015

Error en instalación de SQL Server 2014 en Windows 10


Hace ya un tiempo que migré de Windows 8.1 a Windows 10 pro. En el proceso instalé Visual Studio  2013 sin ningún problema pero al tratar de instalar el SQL server 2014 comenzaron los problemas:




Intenté varias soluciones, todas sin ningún efecto. Luego de revisarlo incluso con técnicos de Microsoft y a pesar de que ellos tampoco lograron resolver el problema, si me pusieron en la dirección correcta.

Aquí dejo los pasos con los que logré solventar la situación:

Primero se intentó instalar Visual C++ 2005, sin embargo este a su vez producía un error similar al que producía la instalación de SQL Server solo que en lugar de Microsoft.VC80.CRT era Microsoft.VC80.ATL por lo que determinamos que el problema era más profundo.

Lo siguiente fue levantar un command promp con privilegios de administrador y ejecutar el siguiente comando:

sfc /scannow

Este arrojó lo siguiente:
There is a system repair pending which requires reboot to complete. Restart Windows and run sfc again.”

Fue aquí cuando relacioné con el problema una circunstancia que no había tomado en cuenta anteriormente: cada vez que apagaba o reiniciaba mi laptop el Windows me indicaba que habían actualizaciones pendientes por descargar y/o instalar, yo simplemente dejaba que terminase siempre sin darle mayor relevancia.

Lo que pasaba en realidad es que en alguna de estas instalaciones el sistema operativo no había podido finalizar el proceso adecuadamente y el archivo pending.xml en la carpeta del sistema, que es el cargado de manejar estos aspectos, se había corrompido.

Para arreglar este problema lo que se debe hacer es ir a la consola de recuperación (recovery console) de Windows y ejecutar cierto comando. Hago un paréntesis aquí para explicar cómo llegar a la consola re recuperación: 

Lo más sencillo es, usando la búsqueda de Windows (tecla de Windows + q), digitar “Recovey Settings”. Seleccionando la opción correspondiente nos llevará a una pantalla como la siguiente

Aquí seleccionamos “Restart Now”. El sistema se iniciará e ingresa a la consola de recuperación


 En esta seleccionamos la opción de "Troubleshoot"


Y en esta la opción de “Advanced options


Finalmente seleccionamos “Command Prompt


Ya estando en la ventana de comandos ejecutamos la siguiente línea:


dism.exe /image:C:\ /cleanup-image /revertpendingactions

Cuando nos indique que el proceso se ejecutó satisfactoriamente reiniciamos el equipo. 
Para asegurarnos que todo esté correcto volvemos a ejecutar, en un command prompt con permisos de administrador, el comando:

sfc /scannow

Esta vez nos debe indicar, una vez finalizado, que no hay ningún problema con la integridad del sistema.

Entonces procedemos a instalar el Visual C++ 2005 x64, en mi caso, ya sin ningún problema. Antes de proceder a la instalación de SQL SERVER vamos a la opcion“Turn Windows Features On of Off” y nos aseguramos que la casilla correspondiente a ."Net Framework 3.5 (Includes .Net 2.0 and 3.0)” este marcada (si no lo esta la marcamos y reiniciamos la máquina).


Finalmente podemos instalar SQL Server sin ningún problema