segunda-feira, 19 de fevereiro de 2007

Robustez no Delphi


O usuário, após preencher os campos de uma tela, clica no botão “Confirmar”. Inesperadamente o sistema trava, a máquina congela. Todo o trabalho foi perdido, diante de uma mensagem propagada com o seguinte conteúdo: “Ocorreram erros”, ou “Erro fatal, catastrófico”.
Quem não se deparou com essa situação? Acho que, infelizmente, isso ocorre com tanta freqüência que muitos nem dão a devida importância para este tipo de situação.
Considero que desenvolver aplicações robustas é uma questão que transcende valores como “capricho”, “elegância” e “cuidado com a qualidade”. Para mim, a robustez de um software está intrinsecamente relacionada à ética profissional. Principalmente porque o custo que esse cuidado demanda não justifica sua ausência. O que, na minha opinião, torna mais grave esse tipo de falta.
Nenhum argumento vai ter validade para o usuário, quando ele descobrir que determinada ação, equivocadamente, por ele efetuada, poderia não ter causado tanto estrago ao seu trabalho, se o programador tivesse protegido o bloco de código executado naquela funcionalidade.
Sabendo trabalhar com exceções, desenvolvedores e analistas podem otimizar seu trabalho tanto no que se refere ao custo de codificação, quanto no que tange a robustez da aplicação desenvolvida. As Exceções tornam os sistemas mais robustos porque favorecem uma padronização ao notificar e manipular possíveis erros, fluxos alternativos e situações inesperadas. Quanto ao esforço de codificação, uma boa prática de programação através das exceções, pode tornar seu programa mais simples, por isso fácil de escrever, ler, entender, e conseqüentemente, de depurar. Ou seja, o melhor dos mundos! O Desenvolvedor ganha, o projeto ganha e o usuário do produto final também ganha. Portanto, vamos gastar um tempinho pra falar sobre como tornar seus programas mais robustos.

Tentarei mostrar também como isso pode reverter pra vc com vários benefícios decorrentes das técnicas que discutiremos a seguir.

Um recurso muito importante, que é subutilizado pelos programadores no Delphi ,é o suporte a exceções. De maneira objetiva, o suporte a exceções, tanto no Delphi , quando nas demais linguagens de programação, permite que você separe o código de tratamento de erros do código que compõe o fluxo principal, no módulo que você esta programando. Melhorando a organização do seu código, conseqüentemente tornando a manutenção do mesmo muito mais fácil.
O que são Exceções? (Conceito)
Exceções são o que chamamos de erros ocorridos durante a execução de algum programa. Ou melhor, erros que podem o correr no programa, fazendo com que ele falhe ao executar uma determinada tarefa. Esses erros podem comprometer tanto o funcionamento do programa, quanto o funcionamento do sistema opercional, aumentando o transtorno sofrido pelo usuário do computador.
Pequenos detalhes que por ventura deixem de ser considerados podem ocasionar problemas de grandes proporções para uma corporação. Uma data inválida digitada por engano, pode comprometer o processamento de uma folha de pagamento se um determinado trecho de código não tiver sido preparado para evitar tal engano, por exemplo.

Evitá-las, ou tratá-las?

Em outras palavras, devemos evitar esses “erros”, essas exceções, ou devemos usá-las? Quando uma exceção é propagada, isso deve ser encarado com positivo ou negativo para o programador? Ao contrário do pensamento predominante, o mecanismo de disparo de exceções não é um inimigo, ele é aliado do desenvolvedor. De fato, em algumas situações, erros de programação podem originar exceções. Contudo, de forma alguma é correto concluir que a causa, de toda e qualquer exceção propagada, seja sempre essa. Será que em reflexo a prática de evitar erros em seus códigos, algum programador possa, por indução, ao associar a idéia do substantivo “erro”, julgar adequado evitar exceções ao ponto de evitar também trabalhar com exceções? Esse tipo de confusão não deveria acontecer. Erros de programação não são o que chama-se de “exceções”. Nem todos os erros que acontecem em programação são erros de programação. Os erros de programação não devem ser evitados. Eles não podem existir. As “exceções” também não devem ser evitadas, pois elas trabalham a seu favor. As exceções devem sim ser prevenidas e tratadas.

Como podemos, então, prevenir estes “erros” quando estamos desenvolvendo o programa, se eles só acontecerão quando o sistema for executado? Em tempo de execução, o Delphi gera mensagens de exceções quando ocorrem erros. As bibliotecas geram exceções quando algo sai errado, seja no código em execução, num componente, ou no sistema operacional. Quando isso acontece, se a linha de código, onde ocorreu o problema, não está preparada para manipular a exceção a própria VCL irá se encarregar disso exibindo uma mensagem de erro padrão. Mensagem essa quase sempre incompreensível para o usuário. Em seguida o Delphi tenta continuar o programa, sem se “preocupar” em eliminar a causa do problema. Ele tentará isso por meio da manipulação da próxima mensagem de sistema, ou solicitação do usuário. Contudo, as chances de fracasso na tentativa de continuidade do programa são consideráveis. Ainda assim, se axceção, eventualmente, não comprometer totalmente, são enormes as chances de pelo menos parte do sistema não voltar a funcionar de forma adequada.

No Delphi existem duas estruturas pelas quais podemos proteger e tratar exceções, evitando que processos sejam interrompidos de forma brusca ou resultem em operações danosas:

     
“try..finally..end;” - Para proteger um bloco de código.

“try..except..end;” - Para tratar exceções.


Elas podem ser usadas separadamente ou juntas. Vejamos agora o mecanismo de emprego dessas estruturas no intuito de tratarmos as exceções, protergermo blocos de ccódigo e com isso alcançarmos um nível de robustez satifsfatório:

try:
 
Delimita o início de um bloco de código protegido.


except:

1° Delimita o final de um bloco de código protegido
2° Delimita o inicio do bloco de código responsável por
manipular as possíveis exceções ocorridas no código
disposto entre o “try” eo “except”.
Ou seja, o except introduz as instruções de manipulação das exceções.


finally:
Garante a execução do bloco de código sempre. Ou seja, especifica o bloco de código que deverá ser executado, mesmo que ocorram exceções. A palavra reservada finally é usada para garantir finalização de processos que dispõe de recursos de sistema, como conexões abertas com SGBDs, fechamentos de arquivos ou tabelas, liberação de objetos, memória, e outros recursos alocados no mesmo fluxo de programa.


raise:
Gera uma exceção. Por meio dessa palavra reservada o desenvolvedor pode chamar o construtor da classe "Exception". para, em tempo de execução, comunicar que algo que não deveria acontecer aconteceu. Para um melhor entendimento construiremos nosso primeiro exemplo.


Exemplificando

No Delphi inicie uma nova aplicação: Menu file New Application.
No Form1 adicione os seguintes componentes e os configure conforme listado abaixo, da palheta Standard:

Edit (TEdit):
propriedade - Name = Edit1
propriedade - Text =
(vazio, sem valor algum. Apague qqr valor que esteja nela.)

Memo (TMemo):
propriedade - Name = Memo1
propriedade - Lines =
(vazio, sem valor algum. Apague qqr valor que esteja nela.)

Button (TButton):
propriedade - Name = Button1
propriedade - Caption = "Processar"





Codifique os seguintes procedimentos:
Na Seção "private" declare: procedure ProcessaNum(Numero: Integer);



Pressionte as teclas: “Ctrl+Shift+C” e codifique o corpo do procedimento.



Codifique o evento OnClick do Button1:



Neste exemplo, podemos observar que se por acaso o usuário não digitar nenhum valor e clickar no botão "Processar" será exibida uma exceção, como conseqüência o cursor do mouse ficará com a ampulheta pra sempre.



Se protegêssemos o código utilizando o “try..finally...end;” poderíamos garantir a normalização do cursor, entretanto, não evitaríamos a mensagem da VCL que não é nada amigável para o usuário. Esse é um exemplo de bloco de código protegido, contudo, sem tratamento de exceção. Veja abaixo:



O código acima não trata a exceção. O que ele faz é tornar o programa mais robusto, de modo que uma exceção não torne o funcionamento do programa irregular. Podemos acrescentar ao nosso código a estrutura “try..except” a fim de manipularmos uma eventual exceção. Neste caso, teremos que usar a palavra reservada “raise para personalizarmos uma mensagem de exceção mais amigável. Para isso teremos de utilizar “finally e “except aninhados.




Embora o código acima já apresente algum nível de proteção ele ainda está longe do ideal. Todavia, ele atende ao objetivo de entendermos como funciona esse mecanismo:

Ocorrendo qualquer exceção no fluxo de código contido no “try” o Delphi interrompe desviado para o “except”. Note que no fluxo do except, a palavra reservada “on” funciona como um desvio condicional onde podemos definir respostas para uma exceção. A estrutura “on .. do”, permite o desenvolvedor manipular a exceção podendo, em tempo de execução obter informações específicas sobre o erro ocorrido. Ora, se o recurso existente no Delphi para tratarmos as exceções é uma classe, logo, podemos concluir que, se pretendemos evocar metodos, acessar propriedades dessa classe, é lógico que precisamos instanciar um objeto dessa classe. Por isso mesmo declaramos uma variável “E” tipada da classe Exception, por isso foi possível acessar o conteúdo da propriedade “message” para exibirmos para o usuário com a função “MessageDlg”. Como isso funciona?

Uma variação da estrutura “on…do”, permite definirmos uma variável, local, temporária para criarmos uma instância da exceção. Portanto, na linha do “on..do”, testa o tipo da exceção, caso verdadeiro, cria uma instância, ou seja, aloca memória, para que possamos acessar a informação que precisamos. No nosso caso, exibindo o valor da propriedade “message” do objeto “E” (E.Message). Conseqüentemente, obtemos tratamento exclusivo para cada tipo de exceção na clausula “on do”. Por exemplo, o código abaixo trata o erro de divisão por zero, através da exceção EDivByZero:


function Divisao (Soma, Numero : Integer): double;
begin
try
Result := Soma / Numero;
except
on EDivByZero do
result 0;
end;
end;

Nesse caso, será exibido para o usuário uma mensagem padrão da VCL. Você pode tornar seu programa mais amigável criando uma mensagem específica de tratamento:


try
If Edit1.Text = ‘’ then
raise Exception.Create(‘Digite um valor.’);
A := StrToInt(Edit1.Text);
except
on Msg: EConvertError do
MessageDlg(‘Erro: ‘+Msg.Message, mtinformation,[mbOk],0);
end;


Além disso, você pode utilizar as exceções genéricas para tratar um erro, em vez de uma exceção específica. Por exemplo, se você quer tratar um erro relacionado a uma operação com inteiros, mas não sabe exatamente o erro, poderá utilizar a exceção EIntError, que é a exceção genérica da qual derivam outras exceções relacionadas a inteiros:

Dissecado a classe Exception

1.1. Seção private

A classe Exception possui dois campos privados:

  • FMessage: String; Armazena o conteúdo da mensagem que será exibida pela exceção.
  • FHelpContext: Integer; Armazena o valor que identifica o tópico do arquivo de help on-line associado a classe.

1.2. Seção public


Uma classe pode definir mais de um construtor, e esta classe possui doze métodos “Create” diferentes. Os construtores são métodos responsáveis pela alocação de memória necessária aos objetos da classe.


constructor Create(const Msg: string);


Recebe como parâmetro uma string que comporá a mensagem da caixa de diálogo exibida quando a exceção ocorrer.


constructor CreateFmt(const Msg: string; const Args: array of const);


Utiliza internamente a função “Format” para compor a mensagem com os valores passados no parâmetro “Args” e o texto passado para o parâmetro “Msg”.

constructor CreateRes(Ident: Integer); overload;


Recebe como parâmetro um inteiro que identifica uma string armazenada no arquivo de recursos (.res) do seu aplicativo. Ou seja, a string que será armazenada no campo FMessage é obtida pela função “LoadStr” que retorna um texto armazenado no arquivo de extesão “.res”.


constructor CreateRes(ResStringRec: PResStringRec); overload;


Este é a versão sobrecarregada do método anterior. O Parâmetro “ResStringRec” é um ponteiro para o Resource String. Os chamados “Resources” são arquivos que não pertencem, não fazem parte do seu projeto, entretanto, eles são ligados a aplicação quando ela é compilada.


constructor CreateResFmt(ResStringRec: PResStringRec; const Args: array of const); overload;


Este construtor é uma composição dos construtores “CreateFmt” mais o “CreateRes”. Ou seja através do valor, inteiro, passado para “Ident” carrega um texto armazenado num arquivo “.res”, e utliza a função “Format” para formatar a mensagem com os valores contidos no array “Args”.

constructor CreateResFmt(ResStringRec: PResStringRec; const Args: array of const); overload;


Versãp sobrecarregada do construtor anterior cujo aúnica diferença é: “ResStringRec” é um ponteiro para o resource string. Veja, a idéia é essa:
const sMyNewErrorMessage = 'Valor inválido: %d';

Exception.CreateResFmt(@sMyNewErrorMessage, [‘– 1’]);

O que acontece nos bastidores é isso:

constructor Exception.CreateResFmt(Ident: Integer; const Args: array of const);
begin
FMessage := Format(LoadResString(ResStringRec), Args);
end;

Ou seja: Format(LoadResString(@sMyNewErrorMessage), [‘- 1’]);

constructor CreateHelp(const Msg: string; AHelpContext: Integer);


Recebe como parâmetro uma string que comporá a mensagem da caixa de diálogo exibida quando a exceção ocorrer, além de um valor numérico inteiro, “AHelpContext”, que identifica o tópico do arquivo de Help on-line associado a classe.


constructor CreateFmtHelp(const Msg: String; const Args: array of const; AHelpContext: Integer);


Recebe como parâmetro uma string que comporá a mensagem da caixa de diálogo exibida quando a exceção ocorrer. Além de um valor numérico inteiro, “AHelpContext”, que identifica o tópico do arquivo de Help on-line associado a classe. Utiliza internamente a função “Format” para compor a mensagem com os valores passados no parâmetro “Args” e o texto passado para o parâmetro “Msg”.

constructor CreateResHelp(Ident: Integer; AHelpContext: Integer); overload;


Recebe como parâmetro um inteiro que identifica uma string armazenada no arquivo de recursos (.res) do seu aplicativo. Semelhante ao construtor “CreateRes” e um valor numérico inteiro, “AHelpContext”, que identifica o tópico do arquivo de Help on-line associado a classe.


constructor CreateResHelp(ResStringRec: PResStringRec; AHelpContext: Integer); overload;


Recebe como parâmetro um ponteiro para a resource string. Semelhante ao construtor “CreateRes” e um valor numérico inteiro, “AHelpContext”, que identifica o tópico do arquivo de Help on-line associado a classe.

constructor CreateResFmtHelp(ResStringRec: PResStringRec; const Args: array of const; AHelpContext: Integer); overload;


Recebe como parâmetro um ponteiro para a resource string. Semelhante ao construtor “CreateRes” e utliza a função “Format” para formatar a mensagem com os valores contidos no array “Args”. um valor numérico inteiro, “AHelpContext”, que identifica o tópico do arquivo de Help on-line associado a classe. Além disso, um valor numérico inteiro, “AHelpContext”, que identifica o tópico do arquivo de Help on-line associado a classe.


constructor CreateResFmtHelp(Ident: Integer; const Args: array of const; AHelpContext: Integer); overload;


Através do valor, inteiro, passado para “Ident” carrega um texto armazenado num arquivo “.res”, e utliza a função “Format” para formatar a mensagem com os valores contidos no array “Args”. Além disso, um valor numérico inteiro, “AHelpContext”, que identifica o tópico do arquivo de Help on-line associado a classe. Veja o código abaixo:






property HelpContext: Integer read FHelpContext write FHelpContext;


Como pode ser observado no código exibido na figura anterior, o campo interno “FHelpContext” armazena o valor inteiro que identifica o tópico do arquivo de Help on-line associado a classe.

property Message: string read FMessage write FMessage;


Através da propriedade “Message” podemos ler o valor do campo interno “FMessage” o qual contém o texto com a mensagem informativa sobre a exceção, a mesma que foi passada como parâmetro no construtor.


Continua no próximo artigo.

2 comentários:

  1. Suponha que um determinado método possa lançar uma exceção. Existe alguma forma de forçar ao usuário deste método que trate a possível exceção? Em outras linguagens isso existe e é extremamente útil. Se puder me ajudar, eu agradeço!

    ResponderExcluir
  2. Prezado George Queiroz,
    Acho que não entendi sua pergunta ...
    Deixa eu tentar te responder, vou ter que deduzir o que você deseja saber.
    Vou imaginar que você esta querendo saber como fazer no Delphi para lançar uma exceção durante a execução de um método de uma classe qualquer. Feito isso, capturar e tratar essa exceção em outra parte do programa, ou em outro método de outra classe. É isso? Se for isso nos artigos sobre exceção que postei aqui no blog certamente você encontrar como fazer isso. Escrevi sobre isso aqui no Estação ZN, tem um artigo que fala especificamente de propagação de exceções: http://estacaozn.blogspot.com/2007/04/aplicaes-robustas-iv-propagando-de.html

    Contudo,
    Não entendi o que você quer dizer com “forçar o usuário deste método”. Ficaria muito mais fácil pra mim te responder se você me explicar melhor o que seria o “usuário deste método”.
    Espero ter ajudado.

    ResponderExcluir

 
BlogBlogs.Com.Br