viernes, 14 de agosto de 2009

Verificando el uso de "Buenas Practicas" en código PL/SQL

Para verificar si se aplican "Buenas Practicas" de programación y para contribuir a realizar código mas robusto y menos propenso a que se generen errores en tiempo de ejecución, a partir de 10g R1, se introdujo un nuevo mecanismo que permite advertir en tiempo de compilación sobre potenciales problemas (WARNINGS), que si bien dejan compilada la unidad de código, pueden darnos dolores de cabeza y conducir a que las aplicaciones que utilizan dicho código generen errores imprevistos o peor aún, que no se obtengan los datos correctos alterando la semántica pretendida y siendo, en muchas ocasiones, muy complicados de detectar.

Existe 4 categorias de WARNINGS:

SEVERE : Pueden causar acciones inesperadas, errores que hagan
cancelar una operatoria o resultados erroneos.

PERFORMANCE : Pueden causar problemas de rendimiento

INFORMATIONAL: No afectan el rendimiento ni altera los resultados pero
advierte sobre complicaciones en el mantenimiento del
codigo a futuro.

ALL : Contempla todos los casos anteriores.


Para activar los mensajes de warning se puede usar el parametro PLSQL_WARNINGS a nivel sesion o a nivel instancia (cosa que no recomiendo), tambien se puede usar el paquete DBMS_WARNING para setear el nivel de warning deseado a nivel de código PL en procedures, packages, triggers, etc. Consultando la vista [USER | ALL | DBA]_WARNING_SETTINGS se puede saber que objetos tienen activado el warning y consultando la vista [USER | ALL | DBA]_ERRORS, filtrando por el campo ATTRIBUTE='WARNING' se ven todos los warnings generados.

Ahora que ya hice una introduccion rapida al tema, vayamos a los ejemplos:

Habilito para detectar todas las categorias:

rop@DESA10G> alter session set plsql_warnings='ENABLE:ALL';

Sesión modificada.

Creo una tabla sencila

rop@DESA10G> create table t (x int,y varchar(5));

Tabla creada.

rop@DESA10G> insert into t
2 select rownum,
3 trunc(dbms_random.value(1,99999))
4 from dual
5 connect by rownum <= 100000;

100000 filas creadas.

rop@DESA10G> commit;

Confirmación terminada.

Voy a crear un procedimiento P_PRUEBA1 de forma tal de que se detecte un warning:


rop@DESA10G>ed
1 create or replace procedure p_prueba1 (p_val int)
2 is
3 l_cnt int;
4 begin
5 select count(1) into l_cnt
6 from t
7 where y = p_val;
8 if (l_cnt > 0) then
9 dbms_output.put_line ('El valor existe en la tabla');
10 else
11 dbms_output.put_line ('El valor NO existe en la tabla');
12 end if;
13* end;
rop@DESA10G> /

SP2-0804: Procedimiento creado con advertencias de compilación

rop@DESA10G> select text from user_errors where name = 'P_PRUEBA1';

TEXT
----------------------------------------------------------------------------------------------------
PLW-07204: puede que la conversión que no sea de tipo de columna dé como resultado un plan de consulta subóptimo


En el caso de arriba detectó un potencial problema de performance, ya que al comparar la columna "y" de tipo varchar2 con el parametro "p_val" de tipo number se hace una conversión implicita TO_NUMBER() de la columna "y". Oracle siempre pasa a number cuando se comparan los tipos number y char/varchar2.
Veamos otros ejemplos:

rop@DESA10G> ed
Escrito file afiedt.buf

1 create or replace procedure p_prueba2 (p_val int)
2 is
3 l_cnt int;
4 begin
5 select count(1) into l_cnt
6 from t
7 where y = to_char(p_val);
8 if ( 0 = 0) then
9 if (l_cnt > 0) then
10 dbms_output.put_line ('El valor existe en la tabla');
11 else
12 dbms_output.put_line ('El valor NO existe en la tabla');
13 end if;
14 else
15 null;
16 end if;
17* end;
rop@DESA10G> /

SP2-0804: Procedimiento creado con advertencias de compilación

rop@DESA10G> select text from user_errors where name = 'P_PRUEBA2';

TEXT
----------------------------------------------------------------------------------------------------
PLW-06002: Código inaccesible

Este es una advertencia informativa. Ahora voy a crear una función:


rop@DESA10G> ed
Escrito file afiedt.buf

1 create or replace function f_prueba1
2 return int
3 is
4 l_val int;
5 begin
6 l_val := dbms_random.value(1,10);
7* end;
rop@DESA10G> /

SP2-0806: Función creada con advertencias de compilación

rop@DESA10G> select text from user_errors where name = 'F_PRUEBA1';

TEXT
----------------------------------------------------------------------------------------------------
PLW-05005: la función F_PRUEBA1 se devuelve sin valor en la línea 7

al no retornar valor se podria generar un problema mas grave


rop@DESA10G> ed
Escrito file afiedt.buf

1 create or replace procedure p_prueba3 (p_val varchar2)
2 is
3 begin
4 insert into t (x) values (p_val);
5* end;
rop@DESA10G> /

SP2-0804: Procedimiento creado con advertencias de compilación

rop@DESA10G> select text from user_errors where name = 'P_PRUEBA3';

TEXT
----------------------------------------------------------------------------------------------------
PLW-07202: el tipo de enlace daría como resultado una conversión lejos del tipo de columna

rop@DESA10G>


El warning para el procedimiento P_PRUEBA3, aunque la traducción al español no es muy clara, da un posible problema en la conversión de tipos. Asi podriamos seguir probando otros tantos casos.
La idea fue mostrarles que con esta herramienta se puede mejorar la calidad del software pl/sql y detectar en forma automatica y proactiva posibles problemas en tiempo de ejecución.

1 comentario:

  1. Hola y gracias de antemano por la yuda. Para hacer estas prácticas, ¿qué Oracle usas? Yo tengo un Oracle 8 Personal que usé en su momento pero hoy he ido a descomprimirlo y no he podido porque se había corrompido el RAR.
    Un saludo.

    ResponderEliminar