.comment-link {margin-left:.6em;}

Tuesday, July 05, 2005

Boas Práticas de Programação - WHEN OTHERS

Um colega teve uma dúvida em uma lista de discussão e me motivou a escrever sobre o assunto aqui, já que acredito que seja de interesse comum. Trata-se do uso indevido e indiscriminado da cláusula WHEN OTHERS nos códigos PL/SQL.

Assim como em qualquer linguagem de programação, no PL/SQL também podemos tratar as exceções. O problema é que, muitas vezes, por "falta de tempo" ou preguiça mesmo, muitos codificadores (DBAs, analistas, etc), simplesmente para que seus códigos não apresentem erro, jogam a cláusula EXCEPTION WHEN OTHERS then NULL para evitar que o programa seja abortado. Isso em 99% dos casos é um BUG. A cláusula mascara qualquer exceção no programa e torna muito difícil a depuração do código.

Vamos a um exemplo hipotético (porque isso nunca acontece na vida real) faltando 5 dias para o fechamento do mês, o gerente resolve atender a uma premiação que a diretoria autorizou e pede aos desenvolvedores que codifiquem uma função para aumentar em 10% o salário dos funcionários. Como o desenvolvedor é um analista sagaz, ele não pode deixar que seu código aborte, então ele vai tratar TODAS as exceções através do WHEN OTHERS. Veja o prejuízo dos funcionários.

ops$marcio@ORA10G> create table func as select empno, ename, sal from scott.emp;

Table created.

ops$marcio@ORA10G>
ops$marcio@ORA10G> create or replace function
2 aumenta_sal( sal in number ) return number
3 is
4 l_novo_sal number default 0;
5 begin
6 l_novo_sal := sal / 0; -- ops! certamente um erro!
7 end;
8 /

Function created.

ops$marcio@ORA10G> show error
No errors.

Sem erros! Porém, quando o desenvolvedor que implementou o roda_folha começa a testar seu programa, ele não quer saber de problemas, assim já lança mão da cláusula when others e NULL para realmente não ter surpresas.

ops$marcio@ORA10G>
ops$marcio@ORA10G> create or replace procedure
2 roda_folha
3 is
4 begin
5 update func
6 set sal = aumenta_sal(sal);
7 exception
8 when others then
9 null;
10 end;
11 /

Procedure created.

ops$marcio@ORA10G> show error
No errors.

E nossos heróis esperançosos com o aumento!

ops$marcio@ORA10G> select * from func;

EMPNO ENAME SAL
------------- ---------- -------------
7369 SMITH 800
7499 ALLEN 1600
7521 WARD 1250
7566 JONES 2975
7654 MARTIN 1250
7698 BLAKE 2850
7782 CLARK 2450
7788 SCOTT 3000
7839 KING 5000
7844 TURNER 1500
7876 ADAMS 1100
7900 JAMES 950
7902 FORD 3000
7934 MILLER 1300

14 rows selected.

ops$marcio@ORA10G> exec roda_folha

PL/SQL procedure successfully completed.

Nenhum problema quando rodamos a folha. Os operadores felizes, não acionaram ninguém aquela noite!
Mas no outro dia! ;)

ops$marcio@ORA10G>
ops$marcio@ORA10G> select * from func;

EMPNO ENAME SAL
------------- ---------- -------------
7369 SMITH 800
7499 ALLEN 1600
7521 WARD 1250
7566 JONES 2975
7654 MARTIN 1250
7698 BLAKE 2850
7782 CLARK 2450
7788 SCOTT 3000
7839 KING 5000
7844 TURNER 1500
7876 ADAMS 1100
7900 JAMES 950
7902 FORD 3000
7934 MILLER 1300

14 rows selected.

O que deveria ser:

ops$marcio@ORA10G>
ops$marcio@ORA10G> select func.*, sal * 1.10 novo_sal
2 from func;

EMPNO ENAME SAL NOVO_SAL
------------- ---------- ------------- -------------
7369 SMITH 800 880
7499 ALLEN 1600 1760
7521 WARD 1250 1375
7566 JONES 2975 3272,5
7654 MARTIN 1250 1375
7698 BLAKE 2850 3135
7782 CLARK 2450 2695
7788 SCOTT 3000 3300
7839 KING 5000 5500
7844 TURNER 1500 1650
7876 ADAMS 1100 1210
7900 JAMES 950 1045
7902 FORD 3000 3300
7934 MILLER 1300 1430

14 rows selected.

Este pequeno exemplo tentou ilustrar o que ocorre com a má prática de programação quando se utiliza o artifício da cláusula WHEN OTHERS.

A dúvida do colega é se há alguma maneira de bloquear o uso da cláusula WHEN OTHERS em código fonte de triggers. O recomendável é prevenir em todos os códigos!
Se é para ser drástico, vou matar o mal pela raiz. O método usado foi: criei uma trigger de evento de DDL (create e alter), verifiquei se o CREATE ou ALTER era para trigger e busquei no código fonte o WHEN OTHERS; encontrado, devolvo um erro de usuário com uma mensagem "Uso indesejável do WHEN OTHERS"!
Demonstrando:

ops$marcio@ORA10G> create or replace trigger prevent_when
2 after create or alter on schema
3 declare
4 sql_text ora_name_list_t;
5 stmt varchar2(2000);
6 n number;
7 begin
8 if ( ora_dict_obj_type = 'TRIGGER' ) then
9 n := ora_sql_txt(sql_text);
10 for i in 1 .. n
11 loop
12 stmt := stmt || sql_text(i);
13 end loop;
14 if ( instr(upper(stmt), 'WHEN OTHERS') > 0 ) then
15 raise_application_error( -20001, 'Uso indesejavel do WHEN OTHERS');
16 end if;
17 end if;
18 end;
19 /

Trigger created.

ops$marcio@ORA10G>
ops$marcio@ORA10G> create table t ( x int );

Table created.

ops$marcio@ORA10G> create or replace trigger t_bi_fer
2 before insert on t for each row
3 declare
4 n number;
5 begin
6 n := 10/0;
7 exception
8 when others then
9 null;
10 end;
11 /
create or replace trigger t_bi_fer
*
ERROR at line 1:
ORA-00604: error occurred at recursive SQL level 1
ORA-20001: Uso indesejavel do WHEN OTHERS
ORA-06512: at line 13

Obviamente, o exemplo acima deve ser aperfeiçoado, já que é possível separar WHEN OTHERS em duas linhas ou colocar mais espaços entre eles. A demonstração serve apenas para uma idéia de como implementar o bloqueio.

No texto todo, nota-se claramente o uso excessivo da cláusula WHEN OTHERS. Isso foi proposital, para frisar. Assim, quando a virem em algum código lembrem-se do que foi discutido aqui.
Comments:
mandou bem ... isso é muito importante pra quem está desenvolvendo em PL/SQL ... é comum ver programadores fazerem isso, com um bloqueio desses a probalidade de "bugs" é minimizada.

falow márcio.
 
Porém, é impossível desligar o WHEN OTHERS da linguagem. Como complemento da solução, poderia ser criado uma tabela de log e a cada OTHERS encontrado, um registro com o autor e o código seria gravado nesta tabela.
Depois é só conscientizar os analistas que fizeram. Na maioria das vezes as pessoas fazem por ignorância. Treinamento neles!
 
Bem, to aqui só para agradecer sua ajuda nas listas de discussões de oracle. Sou teu fã!
 
Obrigado! Sempre que possivel vejo as mensagens da lista - mais a noite. Porém, dou prioridade as dúvidas enviadas aqui no blog.
 
fã anonimo ... é isso aí tio ... :-)
 
Você usou o pior exemplo possível.
Usou uma função completamente errada para tentar mostrar seu ponto de vista.
Não sei de onde vc tirou os 99%....
 
Sobre o exemplo, voce tem TODA razão, eu usei uma função completamente errada para mostrar meu ponto. Obrigado por reforçar isso. Didaticamente, isso funciona perfeitamente!
Eu deduzi os 99% porque de 99 em 100 procedures ou packages que vejo e tenho que corrijir, elas usam o WHEN OTHERS sem o RAISE e, portanto, escondem um BUG que pode acontecer a qualquer momento.
 
Post a Comment



<< Home

This page is powered by Blogger. Isn't yours?