lunes, 7 de julio de 2014

Excepciones en C# - Parte 2: Uso de Excepciones

Tabla de Contenido

0. Introducción
1. El Patrón de Diseño de Métodos TryXXX
1.1 Métodos TryXXX en la biblioteca base de clases (BCL)
1.1.1 Ejemplos
1.1.1.1 Int32.TryParse
1.1.1.2 Dictionary.TryGetValue
1.1.2 Patrón TryXXX vs excepciones
1.2 Consideraciones sobre el diseño de métodos TryXXX
2. Otras Alternativas a las Excepciones
3. Conclusiones
4. Glosario
5. Literatura & Enlaces

0. Introducción

En esta segunda parte de la serie de artículos de excepciones en C# exploraremos dos temas importantes: el patrón de diseño de métodos TryXXX como alternativa al lanzamiento de excepciones (conoceremos varios de los métodos de la biblioteca base de clases de .NET Framework que implementan este patrón y su utilidad directa en el manejo de tipo de datos numéricos); y la implementación de una técnica alternativa al uso de excepciones, que será otro de los temas importantes en esta segunda parte.

1. El Patrón de Diseño de Métodos TryXXX

El patrón TryXXX [1] se presenta como una alternativa al manejo o manipulación de excepciones. Se trata de un modo semántico alternativo para el tratamiento de errores en la ejecución de una función o de un elemento de un programa. Podríamos pensar en este patrón como una formalización al clásico enfoque de codificación de errores a través de caracteres alfanuméricos, por ejemplo:
  • 0: el programa ha finalizado satisfactoriamente.
  • 1: ha un corrido un error y el programa debió cerrarse sin terminar su ciclo de ejecución.
  • 2: ocurrió un desbordamiento de memoria.
  • 3: no hay memoria de sistema suficiente para localizar los datos del programa.
empero, con este nuevo patrón sólo recurrimos a dos posibles estados de correctitud (calidad de correcto):
  • true: la ejecución de la función (i.e., un método) finalizó correctamente.
  • false: la función ha fallado.
Ya mencionamos que este enfoque para el diseño de métodos se presenta como una alternativa ante las excepciones; sin embargo, podemos diseñar nuestros tipos usando ambos enfoques. Por ejemplo:
  1. public int Convertir (string valor);
  2. public bool TryConvertir (string valor, out int valorRetorno);
Para el caso (1), en caso de que la conversión falle con la invocación a Convertir se lanzará una excepción relacionada con el problema ocurrido (e.g., FormatException [5]). Mientras que con el método TryConvertir si algo falla, este retorna false.

1.1 Métodos TryXXX en la biblioteca base de clases (BCL)

En la biblioteca base de clases del Framework .NET encontramos un sinnúmero de estructuras y clases que implementan este patrón para ofrecer la alternativa de manejo de errores. En el caso de las estructuras básicas que representan tipos de datos numéricos podemos mencionar:
  • Int32.TryParse [6] / Int32.Parse
  • Boolean.TryParse [7] / Boolean.Parse
  • Decimal.TryParse [8] / Decimal.Parse
  • Int64.TryParse [9] / Int64.Parse
  • Dictionary<TKey,TValue>.TryGetValue [8] / Versión con indexer
  • Uri.TryCreate [10] 
En la lista previa solo he mencionado hasta 6 métodos que implementan el patrón TryXXX, pero como ya mencioné existen otros tipos de datos numéricos que también lo implementan, al igual que un sinfín de clases. Ahora veamos un par de ejemplos de uso de estos métodos.

1.1.1 Ejemplos

1.1.1.1 Int32.TryParse


Veamos este primer ejemplo en el que usaremos el método TryParse de la estructura Int32 para intentos de conversión de cadenas de caracteres que posiblemente representan números enteros.

Archivo C# UsoInt32TryParse.cs [enlace alternativo]:

En las líneas 20-41 declaramos el método IntentoDeConversion para demostrar el uso del método TryParse para intentar convertir una cadena de caracteres en un valor entero. En la línea 26 ocurre la invocación al método TryParse al que se le pasa como argumentos:
  • numero: valor a tratar de ser convertido, y 
  • out numero: resultado del retorno de la conversión.
Con la sentencia if (línea 27) se pregunta si la conversión fue satisfactoria y se muestra el mensaje generado por la invocación al método Console.WriteLine: línea 30. En caso de no ser así, se muestra el mensaje que anuncia que la conversión ha fallado: línea 39.

Compilación:

csc /target:exe UsoInt32TryParse.cs

Ejecución assembly:


  1. .\UsoInt32TryParse.exe

> Prueba de ejecución (ideone.com).

> Prueba de ejecución (local).
Ejecución assembly UsoInt32TryParse.exe
Figura 1. Ejecución assembly UsoInt32TryParse.exe.

1.1.1.2 TryGetValue


Ahora pasemos a conocer a otros de los métodos de una de las colecciones genéricas, que también implementa el patrón TryXXXDictionary<TKey,TValue>.TryGetValue.

Archivo C# UsoTryGetValue.cs [enlace alternativo]:

En la línea 11 creamos un diccionario genérico para los tipos string. Agregamos claves para representar los nombres de los extensiones, y los nombres de las aplicaciones asociadas a estas extensiones para su apertura: líneas 14-18). Inicializamos con la cadena vacía a la variable string aplicacion (línea 22) que nos servirá de valor de retorno para el método TryGetValue.


Continuando, con la sentencia if validamos que el método TryGetValue ha realizado la recuperación del valor satisfactoriamente: línea 24. Más adelante, línea 39, recurrimos al uso del indizador (indexer) de la clase Dictionary<TKey,TValue>. En caso de que la recuperación del valor falle a partir de la llave o clave abrirCon[".tif"] se generará la excepción KeyNotFoundException [10].



Con este ejemplo se pretende demostrar la diferencia de estos dos enfoques (i.e., patrón TryXXX, y el uso de excepciones). (Más adelante veremos que con el patrón TryXXX, obtenemos mejor desempeño que la generación y tratamiento de excepciones.)

Compilación:


  1. csc /target:exe UsoTryGetValue.cs

Ejecución assembly:


  1. .\UsoTryGetValue.exe

> Prueba de ejecución (ideone.com).

> Prueba de ejecución (local).
Ejecución assembly UsoTryGetValue
Figura 2. Ejecución assembly UsoTryGetValue.exe.

1.1.2 Patrón TryXXX vs excepciones

Remodelemos el contenido del archivo UsoTryGetValue.cs para medir cuál de los dos enfoques posee el mejor desempeño.

Archivo C# TryXxxVsExcepciones.cs [enlace alternativo]:

En la línea 12 creamos un diccionario genérico para los tipos string. Agregamos claves para representar los nombres de los extensiones, y los nombres de las aplicaciones asociadas a estas extensiones para su apertura: líneas 15-19). Más adelante, línea 25, creamos una instancia de la clase Stopwatch para representar un cronómetro, el cual nos servirá para medir el tiempo que tarda la ejecución 1000 invocaciones del método TryGetValue y el uso del indizador (indexer) de Dictionary<TKey, TValue> dentro del bloque try-catch de la líneas 49-56.

Compilación:


  1. csc /target:exe TryXxxVsExcepciones.cs

Ejecución assembly:


  1. .\TryXxxVsExcepciones.exe

> Prueba de ejecución (ideone.com).

> Prueba de ejecución (local) x 10.
Ejecución del assembly TryXxxVsExcepciones.exe
Figura 3. Resultado de la ejecución del assembly TryXxxVsExcepciones.exe.


Resultado de las diez (10) ejecuciónes del assembly TryXxxVsExcepciones.exe:
Resultado de la ejecución del assembly TryXxxVsExcepciones.exe
Figura 4. Resultado de la ejecución del assembly TryXxxVsExcepciones.exe.

1.2 Consideraciones sobre el diseño de métodos TryXXX

Quiero incluir el siguiente listado de consideraciones acerca del diseño de métodos TryXXX extraídas desde diferentes fuentes fiables y relevantes:
  • El uso de excepciones resulta costoso (y lo acabamos de ver), en su lugar podemos recurrir al uso métodos TryXXX [11].
  • Uso de generación notificación de fallas basada en un dos estados: true o false [11].
  • El diseño de un tipo (e.g., una clase o una estructura) puede requerir ambas representaciones (versiones sobrecargadas) del método [1].
  • Dentro de un método TryXXX también pueden ocurrir excepciones, como:
  • Además, el uso del patrón TryXXX no garantiza la generación de otro tipos de excepciones, por ejemplo:

    var dic = new Dictionary<string, string>() { {"Llave", "Valor"}
    };
    string valor = String.Empty; string llave = null;

    dic.TryGetValue (llave, out valor); // ArgumentNullException
[Nota: Recomiendo la lectura de esta entrada [11] en StackOverflow para saber más acerca de este patrón. Igualmente la lectura de esta guía de diseño [3] de métodos TryXXX.]

2. Otras Alternativas a las Excepciones

Existen otros métodos alternativo al uso de excepciones; como por ejemplo la generación de un código de error a partir del tipo de retorno o parámetro de una función invocada. Como nos advierten en [1]:
«...this can work with simple and predictable failures, it becomes clumsy when extended to all errors, polluting method signatures and creating unnecesary complexity and clutter.»
Sencillamente, identificar todos los errores que podría generar la implementación (de un método, por ejemplo) recae en aumento de complejidad y extensión (líneas de código fuente).

Esta alternativa tampoco se puede aplicar (generalizar) a otro tipo de funciones (e.g., operadores o propiedades); de ahí que en [1] nos vuelvan advertir de la siguiente manera:
«...cannot generalize to functions that are not methods, such as operators (...) or properties. An alternative is to place the error in a common place where all functions in the call stack can see it (e.g., a static method that stores the current error per thread). This, though, requires each function to participate in an error-propagation pattern that is cumbersome and, ironically, itself error-prone.»

3. Conclusiones

Hemos aprendido acerca de otros enfoques viables y alternativos al uso de excepciones. En particular hicimos un recorrido medianamente extenso acerca del patrón TryXXX, el cual nos permite obviar el uso de excepciones para realizar alguna operación (e.g., conversión entre distintos tipos de datos, recuperación de un valor de una colección genérica, creación de la instancia de un tipo). Vimos en una prueba de rendimiento (sección 1.1.2) que el uso de métodos que implementan este patrón son varias más rápidos frente al lanzamiento y captura de excepciones (las cuales son muy costosas). Al final, hicimos una introducción (muy breve) acerca de otra alternativa basada en la generación de códigos de error que para casos muy concretos puede resultar útil, pero en programas complejos se vuelve más difícil su codificación y mantenimiento.

4. Glosario

  • BCL
  • Código de error
  • Conversión
  • Excepciones
  • Método
  • Patrón
  • TryXXX

5. Literatura & Enlaces

[1]: C# 5.0 in a Nutshell by Joseph Albahari and Ben Albahari. Copyright 2012 Joseph Albahari and Ben Albahari, 978-1-449-32010-2.
[2]: return value - "TryParse / Parse like" pattern: what is the best way to implement it - Stack Overflow - http://stackoverflow.com/questions/182440/tryparse-parse-like-pattern-what-is-the-best-way-to-implement-it
[3]: The OldWood Thing: Out vs Ref For TryXxx Style Methods - http://chrisoldwood.blogspot.com/2011/05/out-vs-ref-for-tryxxx-style-methods.html
[4]: Dictionary(TKey, TValue).TryGetValue Method (System.Collections.Generic) - http://msdn.microsoft.com/en-us/library/bb347013(v=vs.110).aspx
[5]: FormatException Class (System) - http://msdn.microsoft.com/en-us/library/system.formatexception(v=vs.110).aspx
[6]: Int32.TryParse Method (System) - http://msdn.microsoft.com/en-us/library/system.int32.tryparse(v=vs.110).aspx
[7]: Boolean.TryParse Method (System) - http://msdn.microsoft.com/en-us/library/system.boolean.tryparse(v=vs.110).aspx
[8]: Dictionary(TKey, TValue).TryGetValue Method (System.Collections.Generic) - http://msdn.microsoft.com/en-us/library/bb347013(v=vs.110).aspx
[9]: Uri.TryCreate Method (System) - http://msdn.microsoft.com/en-us/library/system.uri.trycreate.aspx
[10]: KeyNotFoundException Class (System.Collections.Generic) - http://msdn.microsoft.com/en-us/library/system.collections.generic.keynotfoundexception(v=vs.110).aspx
[11]: .net - Should TryFoo ever throw an exception? - Stack Overflow - http://stackoverflow.com/questions/1166880/should-tryfoo-ever-throw-an-exception
[12]: Receta Multithreading en C# No. 1-4: Abortar un Thread en Ejecución | OrtizOL - Experiencias Construcción Software (xCSw) - http://ortizol.blogspot.com/2014/06/receta-multithreading-en-csharp-no-1-4-abortar-un-thread-en-ejecucion.html


J

No hay comentarios:

Publicar un comentario

Envíe sus comentarios, dudas, sugerencias, críticas. Gracias.