Programación defensiva: Crea código más estable



Esta idea se basa en el manejo a la defensiva (de vehículos), por ejemplo cuando manejas a la defensiva, debes estar consciente que el otro conductor puede hacer cualquier locura como invadir tu carril, pasarse un semáforo en rojo, etc. Entonces en la programación defensiva, debes estar consciente que cuando haces una función, con un parámetro llamado edad, tú sabes que la función espera recibir la edad en años (y números) por ejemplo 45, pero cualquier otro programador por increíble que parezca podría intentar enviarte la edad en meses por ejemplo 540 o en letras “45 años”.

Entonces con la programación defensiva, si otro programador intenta alguna locura, al menos tu código hará todo lo posible por prevenir y minimizar el impacto, incluso evitar serios daños.

Antes de seguir voy a contarte algo que me paso hace algunos años: Yo hice una función, hacía todo bien, su único error es que confiaba en que los parámetros que recibía eran correctos (cero programación defensiva), un día otro programador encontró esa función y decidió usarla. Semanas después su programa fallo, y vio que el error sucedió en mi función y entonces dijo que era mi culpa por que el error estaba ahí (en mi función), cuando le dije que el error era por que le estaba mandando mal un parámetro, su respuesta fue: “Entonces es tu error, por que la función falla si no le mandas lo que espera”. Obviamente esto me molesto y al final tuve que agregar una validación a mi función, y dejar de confiar en que otros programadores no iba a usar mis funciones y que iban a enviarles siempre los parámetros que esperaba.

Bueno después de esta breve anécdota veamos algunos principios que debes de seguir.

Protégete de cualquier entrada.

Como lo dije antes, si tu programa, función, clase, etc. recibe una entrada o parámetro no importa si viene de una base de datos, de algo que el usuario ingresa en un formulario, si lo recibe de otro programa, API, etc. A esto se le llama entrada externa y debemos desconfiar de ella a toda costa, especialmente si programas en JS, porque ahí ni siquiera existe la validación de tipos de datos, tu función puede esperar un número y recibir un texto.

Tu programa debería de validar cada punto que estimes conveniente, aquí hay algunos ejemplos:

  • Validar el tipo de datos, en caso de lenguajes como JS
  • Validar si el dato está en un rango correcto, por ejemplo una fecha de nacimiento no puede ser mayor que la fecha actual, en algunos casos ni siquiera cerca, por ejemplo si alguien se está registrando en tu aplicación no puede tener un año de vida. Otro ejemplo es si puedes admitir números negativos o no.
  • Validar si el dato existe, por ejemplo si recibes un código de cliente, debes validar que el código esté creado en la base de datos.
  • Validar si la entrada puede dañar el sistema, por ejemplo prevenir sql injection y code injection.

Utiliza Assertions (Aserciones, Afirmaciones)

Esta es una función que se usa durante el desarrollo para que el código se pruebe a si mismo mientras lo ejecutamos, cuando la aserción es verdadera es por que el programa esta funcionando como debería y cuando es falsa significa que hemos encontrado un error. Estas funciones usualmente tiene dos parámetros uno es la condición, y el otro es un mensaje que se muestra en caso de que la condición sea falsa. Si tu lenguaje de programación no tiene una función Assert, puedes crearla tu mismo.

Algo que debes de tener en cuenta es que las aserciones se usan para detectar situaciones que no deben de pasar, y el control de errores se usa para que el programa siga funcionando cuando ocurren errores que esperamos que puedan suceder. También podemos usar las aserciones para documentar las condiciones bajo las cuales debe trabajar nuestro programa, veamos un ejemplo en PHP.

<?php

function sumaEnteros($a, $b)
{
  assert( is_int($a), 'Parametro a, debe ser un numero entero'  );
  assert( is_int($b), 'Parametro b, debe ser un numero entero'  );

  $c = $a + $b;

  assert( is_int($c), 'El resultado de la suma debe ser un numero entero'  );
  return $c;
}

echo "<br>" . sumaEnteros(1,2);
echo "<br>" . sumaEnteros(1,"2");
echo "<br>" . sumaEnteros(1,null);

 ?>

Como ven la función sumaEnteros, recibe dos parámetros, obviamente debería de ser utilizada únicamente con dos parámetros que sean enteros, pero un programador defensivo, no se confía de nadie, hemos colocado unas aserciones para verificar que los parámetros que se reciban sean ambos enteros, y adicionalmente colocamos una aserción para comprobar que nuestro código genero un valor entero.

Si ejecutas este código sin las aserciones, no verás ningún error y parecerá que todo funciona bien, sin embargo, en la tercera llamada, el segundo parámetro es un null, esto significa que has encontrado un error lógico, alguna parte del programa no funciona y está enviando un parámetro en null, posiblemente crees que has sumado dos números, pero eso no es cierto. Esto podría hacer que tu programa realice cálculos o presente datos equivocados.

Nota: En el caso de php, este puede ser configurado para que no muestre estos mensajes en pantalla, lo cual es algo que debe hacerse en cualquier servidor de producción. Sin embargo también puede configurarse para que estos mensajes se guarden en un archivo de log, el cual puedes monitorear regularmente para detectar estos errores.

Manejo de errores.

Usamos las aserciones para situaciones que no deberían de pasar “nunca”, pero cuando esperamos que suceda un error, usamos técnicas para manejo de errores.

Programacion defensiva - manejo de errores

Cuando detectas un error en un valor puedes:

  1. Regresar un error
  2. Intentar sustituirlo por un valor predeterminado
  3. Tomar el valor más próximo en el rango permitido, por ejemplo si estás esperando una lectura de velocidad, y recibes -0.5, sabes que la velocidad se mide iniciando en 0, entonces tomas el valor 0.

Además puedes tomar alguna acción por ejemplo:

  1. Detener la ejecución del programa.
  2. Detener el programa y regresar todo al estado inicial, por ejemplo si estás haciendo una transferencia bancaria, no querrás dejar el proceso a medias y solo sacar dinero de una cuenta dejándolo perdido.
  3. Mostrar un error o advertencia, pero ten cuidado de no revelar información importante como una contraseña o información de la base de datos. Algunas veces los hackers prueban las aplicaciones intentando hacerlas fallar para que revelen información mediante los mensajes de error.
  4. Grabar un mensaje en un archivo de log
  5. Intentar recuperarse del error y continuar con la ejecución.

Excepciones (Exceptions)

Las excepciones son un medio por el cual se pueden manejar los errores en los que el sistema no tiene forma de recuperarse o de responder ante el error. Básicamente no sabe qué hacer, sin embargo la pieza de código que lo llamó podría saber que hacer y puede capturar la excepción y hacer que el programa logre recuperarse y continuar su ejecución.

Tú puedes hacer una función que regrese un código de error en caso de que se presente algo inesperado y esperar que el programa que lo llame, revise ese código de error y haga algo al respecto, pero ¿Puedes estar seguro de que lo harán?. Bueno si generas una excepción puedes estar seguro de que harán algo, de lo contrario el programa termina con un error. Usa excepciones siempre que desees que se tome una acción correctiva ante una situación inesperada, solo ante errores críticos y solamente si tú no puedes encargarte de ese error.

Veamos este ejemplo en PHP

<?php

function procesarPago($cliente, $monto){
  // ... validar datos

  // ... procesar pago....

  // ... verificar si se registro el pago...

  // ... si el pago falla, enviar excepcion
  throw new Exception("El pago no pudo ser procesado", 1);
}

try {
    $cliente = 1;
    procesarPago($cliente, 300);
    //El siguiente codigo no se ejecuta al encontrar una excepcion
    echo "Procesando envio....";
} catch(Exception $e) {
    if ($e->getCode() == 1)
    {
      echo 'Ocurrio un error: ' . $e->getMessage();
    }
    else {
      echo 'Ocurrio un error al procesar su pago, por favor intente de nuevo';
    }
}

?>

En el código anterior, se llama a una función para procesar el pago, en caso de fallar, se emite una excepción con un mensaje y un código de error. Luego de invocar la función, se continua con el código para procesar el envío de la compra, pero si se genera una excepción, todo el flujo se interrumpe y de acuerdo al código de error, se puede tomar una acción que en este caso es informar al usuario.

Si la función para procesar el pago no generará una excepción, no tendríamos forma de asegurarnos que la persona que utilice esta función tome alguna acción en caso de que el pago no haya podido procesarse.

Espero que hayas encontrado útil este articulo que fue escrito para personas que saben algo de programación, por eso no doy mayores explicaciones sobre el código que muestro. Si tienes alguna duda puedes escribir un comentario en el artículo.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *