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.
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.
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.
“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:
raise:
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:
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;
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;
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;
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:
1.2.2. Propriedades
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.
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!
ResponderExcluirPrezado George Queiroz,
ResponderExcluirAcho 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.