jueves, 2 de octubre de 2008

Errores y excepciones (II).

Errores y excepciones (I)

Errores y excepciones (II)

Errores y excepciones (III)

Ya definimos error y excepción. Todo para llegar a La Teoría (con mayúscula) que es bastante simple. Voy a tratar de enunciarla como yo la pienso:

Un procedimiento es un pequeño sistema: tiene entradas definidas, salidas definidas, y un objetivo, al que usualmente le decimos responsabilidad. Preferentemente una y sólo una responsabilidad (que puede delegar en otros procedimientos, pero que debe poder enunciarse fácilmente y de la que de todas maneras sigue siendo responsable, eso ya lo sabemos).

Vamos a la responsabilidad: una función, un procedimiento, un método o una propiedad o todo el sistema, debe tener sólo dos respuestas posibles: o hace lo que tiene que hacer y devuelve lo que tiene que devolver, o no hace absolutamente nada y de alguna manera avisa.

Ésa es la ley primera, y es inapelable. Ahora las sutilezas.

Avisar no es necesariamente explotar, y el motivo no es necesariamente un error de programación. No estamos hablando necesariamente de errores, sino de cualquier situación o estado que le impide a una porción de código cumplir con su responsabilidad.

Simplemente tiene que explicitar que algo le impidió hacer su tarea, diferenciando ésa respuesta de los otros resultados válidos. Puede explotar de mala manera con un mensaje incomprensible (algo es algo), puede ser un mensaje amable al usuario (más bonito), puede ser una entrada en un log de errores (más escondido y sutil), o puede ser un código especial de retorno, lo que sea, pero algo tiene que decir.

En un lenguaje orientado a objetos la herramienta natural que cualquier porción de código tiene a mano para decir que no pudo hacer lo que tenía que hacer (y que no hizo nada) es arrojar una excepción (throw en C#). Pero no es la única forma de implementación, lo importante es seguir el principio teórico, la forma variará según la ocasión.

Ahora, ya que una porción de código tiene que decir que no hizo lo que tenía que hacer, puede que sea relevante el por qué. Ojo, digo puede y no debe... la información que devolvamos tiene que ser acorde a la situación.

En la primera parte vimos que la excepción de negocio sólo lleva como información el mensaje que hay que mostrar al usuario. Aparte, el hecho de ser una excepción de negocio la diferencia, por ejemplo, de una excepción por un error de código.

El resto del sistema no hace nada especial con la excepción generada. Simplemente muestra el mensaje, y entiende que para el usuario es relevante (por lo que lo colocará en algún lugar donde pueda verlo, pero eso es responsabilidad del front-end).

En C#, si el resto del código tiene que tomar alguna decisión basada en detalles de la excepción pueden agregarse propiedades. En el ejemplo, si el código tiene la posibilidad de generar un aviso de necesidad de compra por la cantidad faltante, el dato puede viajar como valor de la propiedad "FaltanteDeStock" de la excepción junto con el mensaje. Si no es necesario, no. Lo más simple es lo mejor.

La responsabilidad de avisar también puede delegarse, como cualquier otra. En el caso del error por referencia nula es el framework el que avisa al procedimiento de negocio que no puede consultar el valor de una propiedad. El método de negocio, al dejar pasar la excepción, avisa al que lo llamó que esa excepción le impidió completar la operación.

Y llegamos a que un error de programación es una excepción del componente que hayamos invocado, de alguno interno de éste, o del framework, o del sistema operativo. En algún lugar hubo una validación y el equivalente a un throw. Algún algoritmo no pudo completar su tarea, y está avisando. Y debemos pensarlo como cualquier otra excepción. Un error de programación es un tipo especial de excepción.

La decisión de interceptar o no determinado tipo de excepción tendrá que ver con:

  1. No impide realizar correctamente la operación, por ejemplo codificando algún curso de acción alternativo. En este caso decimos que la estamos resolviendo o manejando. El código que la resuelve no arrojará una excepción, sino otro de sus retornos esperados.
  2. Hay que ejecutar algún tipo de código para cancelar la operación en forma prolija (por ejemplo haciendo un Rollback). En este caso se la intercepta, se codifican las instrucciones para la cancelación y se la deja seguir (throw a secas en C#) o lanzamos una nueva excepción (creando una nueva y especificando la excepción original en el InnerException, en C#), si es que tenemos información propia que agregar.

Llegamos ahora a la segunda parte importante de la teoría: una excepción indica que el algoritmo que la arroja no hace absolutamente nada. Esto implica que si inicié una operación de base de datos tengo que poder deshacerla ante cualquier excepción, si abrí un archivo tengo que cerrarlo, si modifiqué alguna variable o propiedad compartida tengo que dejarla como estaba, y así con todo. Donde haya un uso de un recurso compartido (léase base de datos, archivo o variable) tendrá que haber un control de excepciones.

El ejemplo más sencillo es el de la transacción de base de datos:

        Datos.BeginTransaction();
        try
        {
            //...(operaciones)...
            Datos.Commit();
        }
        catch
        {
            Datos.RollBack();
            throw;
        }

Aquí, desde que el código comienza a utilizar el recurso persistente (la base de datos) tiene que asegurarse de que cualquier excepción (error de programación o no, no importa) cancele todas las modificaciones. Una vez que se cancela pasa la información sobre los motivos (la excepción) a los procedimientos superiores.

El único objetivo del bloque try...catch es cancelar la transacción. Si la excepción se produce antes del inicio de la operación (por ejemplo en la llamada a BeginTransaction) no hay motivo para atraparla.

Si ningún algoritmo puede manejar una excepción (por ejemplo en el caso de un error de programación en algún lado) llegará al front-end. Y la decisión de mostrarla o no al usuario y de qué manera dependerá del caso. Si es un error de negocio se mostrará un mensaje, si es de conexión tal vez se muestre un mensaje genérico y se guarden los detalles en un log, si es un error interno de un componente de terceros, tal vez se muestre información específica... las posibilidades son infinitas.

Pero lo importante es que el front-end, más allá de lo que muestre o no, preserve la información relevante. Esto es especialmente importante en el caso de los errores, donde toda la información contenida en la excepción ayuda (en C# es importante utilizar el método ToString de la excepción, que devuelve todo su contenido).

3 comentarios:

Martín dijo...

¿Lo qué?

AcP dijo...

Ya sé que es críptico para el mundo en general. Algunas entradas son exclusivas para programadores de verdad.

Pero son las menos, es para despuntar el vicio, nomás.

Martín dijo...

Me lo imaginaba, igual está muy bueno el blog, por lo menos muchisimo mejor que el de ameba.