Mostrando postagens com marcador Tratamento de Exceções. Mostrar todas as postagens
Mostrando postagens com marcador Tratamento de Exceções. Mostrar todas as postagens

segunda-feira, 23 de abril de 2007

Aplicações Robustas IV: Propagando de Exceções


Esse artigo é continuação do post “Aplicações Robustas III

Dando continuidade a seqüência de artigos sobre tratamento de exceções.....

Neste artigo vamos tratar de como acontece a propagação de exceções. Já falamos anteriormente sem detalhamos muito, vamos agora testar alguns exemplos e observar mais de perto como isso acontece. Consequentimente estaremos apurando a tecnica de tratamento de exceções.

Propagando exceções:

Suponha que uma exceção vemha ocorrer dentro de uma função “A”, essa função foi chamada pela procedure “B”, que por sua vez foi executada no evento OnClick de um botão. De cara vc pode perceber o problema envolvido se vc tiver um tratamento já na função “A”. Como propar essa exceção? Algumas vezes, acredite isso contece, vc terá que propagar essa exceção até a achamda mais externa. Vamos para a prática ...

Construindo um exemplo:

Inicie uma nova aplicação no Delphi:

No D7 - Menu, File ►New ► Application.
No BDS - Menu, File ►New ► VCL Forms Application – Delphi for Win 32 (“Alt, n, r”).

Adicione uma nova Unit: Nela vamos copdificar uma função.

No D7
- Menu, File ►New ► Unit.
No BDS - Menu, File ►New ► Unit – Delphi for Win 32 (“Alt, n, t”).

Eu vou salvar a Unit com o nome de “ZnFunctionPropagExcept”, o nome da unit do Form1 vou salvar como “ZnPropagExceptForm.pas”. Veja o Código da Unit:

unit ZnFunctionPropagExcept;

interface
uses DbClient, Db, Classes, SysUtils, Dialogs;

type
ZnException = class(Exception);

function SetDados(Lista: TStringList;
Cds: TClientDataSet; AField: TField): String;

procedure ForcaExcecao(const Value: string);

implementation

procedure ForcaExcecao(const Value: string);
begin
(* força uma exceção. Basta que um dos elementos da lista seja a palavra
exceção. *)
if AnsiUpperCase(Value) = AnsiUpperCase('exceção') then
raise ZnException.Create('ZnException: Buuuuuuuhhhhhhhh!!!!!!');
end;

function SetDados(Lista: TStringList;
Cds: TClientDataSet; AField: TField): String;
var
I: Integer;
begin
try
if Lista.Count <= 0 then
raise
Exception.Create('Nenhum elemento na lista.');
for I := 0 to Lista.Count - 1 do
begin
ForcaExcecao(Lista[i]);
if not Cds.Locate(AField.FieldName, Lista[i], []) then
begin
Cds.Append;
AField.AsString := Lista[i];
Cds.Post;
end;
end;
except
(*essa exceção não vou propagar*)
on E: ZnException do
MessageDlg(E.Message, mtInformation, [mbOK], 0);
(*Propagando a exceçaõ genérica*)
on E: Exception do raise;
end;
Result := Cds.XMLData;// retorna os dados no formato Xml numa string.
end;
end.



Na seção “uses” declaramos as seguintes bibliotecas: DbClient (para usarmos o TClientDataSet), Db (para usarmos o TField), Classes (para usarmos TStringList), SysUtils (onde está definida Exception), Dialogs (para usarmos a função “MessageDlg”).
Na seção “type” declaramos uma exceção “ZnException” aqual usaremos no nosso tratamento. Os dois modulos cdificados nesta Unit são: A função “SetDados” que recupera os dados de uma lista e atribui a um dataset, retorna um string que usaremos como log do que foi adicionado no dataset. O procedimento “ForcaExcecao”, como o próprio nome já indica força um exceção, algo que é impressindível para o nosso exemplo. Após codificar esta Unit, passe para a implentação do form1 na unit “ZnPropagExceptForm”.


No Form1 adicione os seguintes componentes: Um Edit (TEdit), um Button (TButton), um Label (Tlabe) e um ListBox (TListBox), da palheta Standard; Dois DbGrids (TDbGrid), da palheta Data Control; Dois DataSource(TDataSource), Dois ClientDataSet(TClientDataSet) Data access. Configure as propriedades dos componentes segundo listado abaixo:


Form1: TZnPropagExceptFrm
Name = ZnPropagExceptFrm
ClientHeight = 395
ClientWidth = 578
Caption = 'Propagando Exceções'

Label1: TLabel
Left = 16
Top = 8
Width = 31
Height = 13
Caption = 'Digite o Dado'

ListBox1: TListBox
Name = LstDados
Left = 16
Top = 56
Width = 121
Height = 329

Button1 : TButton
Name = BtnAddElemento
Left = 143
Top = 27
Width = 90
Height = 25
Caption = 'Add Elemento'

Edit1: TEdit
Name = EdtDados
Left = 16
Top = 29
Width = 121
Height = 21

ClientDataSet1: TClientDataSet
Name = cdsLog

ClientDataSet2: TClientDataSet
Name = cdsDados

DataSource1: TDataSource
Name = dsDados
DataSet = cdsDados

DataSource: TDataSource
Name = dsLog
DataSet = cdsLog

DbGrid1: TDBGrid
Name = GrdLog
Left = 248
Top = 216
Width = 322
Height = 169
DataSource = dsLog

DbGrid2: TDBGrid
Name = GrdDados
Left = 247
Top = 29
Width = 322
Height = 181
DataSource = dsDados

Button2: TButton
Name = BtnTransDados
Left = 143
Top = 70
Width = 90
Height = 25



A seção type da unit1 deverá estar assim

type
TZnPropagExceptFrm = class(TForm)
LstDados: TListBox;
BtnAddElemento: TButton;
EdtDados: TEdit;
Label1: TLabel;
dsDados: TDataSource;
cdsLog: TClientDataSet;
GrdLog: TDBGrid;
GrdDados: TDBGrid;
cdsDados: TClientDataSet;
dsLog: TDataSource;
BtnTransDados: TButton;
private
{ Private declarations }
public
{ Public declarations }
end;


Campos persistentes:

Vamos adicionar um campo persistente em cada um dos ClientDataSets. O Nome do Campo vai ser “ZnTexto”, o tipo string, e tamanho 200 (quantidade de caracteres). Click com o botão direito do mouse sobre o ClientDataSet, escolha aopção, “Fields Editor”.




Agora botão direito no Field Editor, escolha a opção, “New Field”.



Cofigure conforme a figura abaixo e click “Ok”:



Pronto vc acabou de criar um campo persistente num dataset. Repita a mesma operação para o outro ClientDataSet.



Codificando o evento OnClick do botão “BtnAddElemento”, “BtnAddElementoClick”:


procedure TZnPropagExceptFrm.BtnAddElementoClick(Sender: TObject);
begin
(* Atribuido a lista o valor digitado no Edtdados *)
LstDados.Items.Add(EdtDados.Text);
EdtDados.Clear; // Limpa o Edit
EdtDados.SetFocus;// seta o foco para ele
end;

É chegado o momento de usarmos a Unit contendo os módulos que programamos onde está o tratamento de exceção. Faça use unit, ou seja no Menu, File ►Use Unit (Alt, f, u), ou Alt + F11.



Codificando o evento OnClick do botão “BtnTransDado”, “BtnTransDadosClick”:

procedure TZnPropagExceptFrm.BtnTransDadosClick(Sender: TObject);
var
Lst: TStringList;
begin
Lst := TStringList.Create;
(*Bloco protegido*)
try
try
Lst.Assign(LstDados.Items);
cdsLog.XMLData := SetDados(Lst, cdsDados, cdsDadosZnTexto);
except
(*Capturando a exceção propagada pela rotina "SetDados"*)
on Zn: Exception do
begin
Self.Caption := Zn.Message + '/'+ Zn.ClassName;
MessageBox(0, 'Exceção propagada capturada!!!!',
'Estação Zn', MB_ICONASTERISK or MB_OK);
end;
end;
finally
Lst.Free;
end;

end;


Vamos executar nosso programa para testar. Eu garanto que vai ocorrer uma exceção, não programe uma linha de código além do que foi proposto até aqui. Antes de qualquer coisa, vamos preparar a IDE para não interromper a execução do programa caso aja uma exceção.
No D7:
No menu Tools ► Debuggrer Options ► Aba “Language Exceptions”, desmarque a check “Stop on Delphi Exception” (Dica do Felipe)
No BDS:
No menu Tools ►Options ...Selecione, na treeview a esquerda, Borland Debugger, sub item, language Exceptions, e desmarque a check “Notify on Language Exceptions”. Veja na figura abaixo:



Execute, pressionado F9, adiciono alguns elementos clickando em “add Elemento” e click em “Transfere Dados”. Se vc esta sincronisado comigo vai ocorrer, propositalmente, o seguinte: Uma mensagem será exibida “Exceção propagada capturada!!!!”, e no caption do form deve estar a mensagem da exceção e o tipo da exceção, que será “EDatabaseError”.



Eu deixei o ClientDataSet fechado de propósito para forçarmos uma exceção diferente da “ZnException”, definida na Unit “ZnFunctionPropagExcept”. Pois de acordo com o que codificamos ela será a única que não será propagada. Portanto, nesse primeiro teste experientamos a propagaçao da exceção. Como o DataSet estava fechado, ao tentar executar o metodo “Locate”, foi levantada uma execeção “EDatabaseError”. A prova de que ela foi propagada foi a execução dos comandos da setrutura “on ..do” no except do evento OnClick botão “BtnTransDado”: A atribuição ao caption do Form, e a execução da função “MessageBox”.
Prosseguindo com o nosso exemplo, vamos acrescentar um botão que irá abrir os ClientDataSets. Ficando ao critério do usuário forçar a exceção “EDatabaseError” ou não. Configure o novo botão da seguinte forma: Name = btnOpenCds; Caption = “Ativar Cds”; Top = 108; left =143; Width = 90.



procedure TZnPropagExceptFrm.BtnOpenCdsClick(Sender: TObject);
begin
(*O Método "CreateDataSet nos permite trabalhar com o ClienteDataSet
desconectado do banco de dados. "*)
cdsLog.CreateDataSet;
cdsDados.CreateDataSet;
end;

Vamos executar nosso segundo teste. Pressione F9, ative os datasets clickando no novo botão. Adicione alguns elementos. Lembre-se, se vc não adicionar a palavra “Exceção”, nenhuma exceção será levantada. Portanto, para testar o caso da exceção que não é propagada, adicione a palavra “exceçao”. E Clicke sobre o botão “Transfere Dados”.



Note na figura acima que o processo transferiu alguns elementos, mas quando encontrou a palvra “Exceçao”, foi interrompido e levantou a exceção. Contudo, essa exceção, não foi capturada pelo tratamento do botão “Transfere Dados”.
Verifique o codigo da função “SetDados”, veja que o que diferenciou um caso do outro foi a utilização da paralra reservada “raise” na estutura “on ..do” (que verificava se a exceção era do tipo “ZnException”). Quando vc trata uma exceção, a estrutura “try ... except” provê para vc um manipulador de exceções. Esse manipulador gerencia também o “tempo de existência” dessa exceção. Antes do final da estrutura a exceção “morre”, é desalocada. Assim sendo, se vc deseja que ela seja atribuida, passada, ao próximo manipulador, vc tem que usar a palavra reservadda “raise”.
Note Ainda que, assim que vc clickar no ok da mensagem da exceção, o processo continuará a execução após a estrutura “try ... except” da função “SetDados” e efetuará o log dos elementos transferidos para o “cdsDados”.



Artigo completo (View Full Post)

domingo, 22 de abril de 2007

Aplicações Robustas III: Criação de Exceções

Esse artigo é continuação do post “Robustez no Delphi

Dando continuidade a seqüência de artigos sobre tratamento de exceções.....

Com base em tudo o que foi falado nos artigos anteriores sobre exceções vamos tratar agora sobre como e por que criar suas próprias exceções. Entenda que não me refiro a levantar um exceção, quando uso o termo “criar” estou falando de definir uma classe nova, derivada diretamente de Exception, ou de um de seus herdeiros.

Criando suas próprias exceções:

Imagine uma situação em que a lógica do negócio envolvida no seu programa, defina certas regras que não podem ser quebradas. Você não vai encontrar nas bibliotecas do Delphi nenhuma exceção para o caso de um cliente ser cadastrado com valor para data de nascimento maior que a data corrente, por exemplo. Portanto, quando você precisa garantir que as regras pertinentes ao negócio sejam suportadas pelo seu sistema em forma de exceções é preciso para isso definir classes, tipos, de exceções específicas para isso.

Construindo um exemplo.


Para exemplificar, vamos propor um problema para em seguida implementarmos um pequeno programa, em cima desse problema, de maneira que possamos modelar as regrar contidas no domínio do problema em questão em forma de exceções.

O problema:
O programa que vamos contruir simula um pequeno aspecto da relação de uma farmácia e seus clientes. Vejamos, para compra de qualquer remédio controlado é exigido a apresentação, por parte do cliente, de uma receita válida para o remédio específico desejado. Em nosso exemplo vamos trabalhar com os seguintes atributos da receita: Data de validade e substância (remédio). Criaremos, portanto, duas classes de exceções: EDataInvalida (caso a receita esteja vencida), ESubstanciaErrada. Vamos arbitrariamente definir que a validade de uma receita seja de 4 dias. Já temos algumas regras, agora já podemos construir nosso pequeno programa.

Inicie uma nova aplicação no Delphi
: Menu, File ►New ► Application.

No Form1 adicione os seguintes componentes: Duas ComboBox (TComboBox), um Button (TButton), três Labels (TLabel) e um ListBox (TListBox), da palheta Standard; Um DateTimePicker1 (TDateTimePicker), da palheta Win32. Configure as propriedades dos componentes segundo listado abaixo:


Form1:
Width = 470
Height = 494
Caption = 'Criando Exceções'

Label1:
Left = 12
Top = 12
Caption = 'Receita'

Label2:
Left = 300
Top = 12
Caption = 'Remédio'

Label3:
Left = 12
Top = 58
Caption = 'Data da Receita'

ComboBox1:
Name = CmbReceita
Left = 12
Top = 29
Width = 280

ComboBox2:
Name = CmbRemedio
Left = 300
Top = 29
Width = 150

Button1: TButton
Left = 373
Top = 64
Width = 75
Height = 25
Caption = 'Efetuar venda'

ListBox1:
Name = LstVendas
Left = 12
Top = 104
Width = 445
Height = 353

DateTimePicker1:
Name = DtReceita
Left = 12
Top = 75
Width = 82


A seção type da unit1 deverá estar assim

type
TForm1 = class(TForm)
CmbReceita: TComboBox;
CmbRemedio: TComboBox;
Button1: TButton;
ListBox1: TListBox;
DtReceita: TDateTimePicker;
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
private
{ Private declarations }
public
{ Public declarations }
end;


Logo abaixo da palavra reservada “type” vamos codificar a definição das nossas exceções. Faça conforme exemplo abaixo:



type
EDataInvalida = class(Exception);//Exceção para data inválida
ESubstanciaErrada = class(Exception); // Exceção de substância errada

TForm1 = class(TForm)
CmbReceita: TComboBox;
CmbRemedio: TComboBox;
Button1: TButton;
LstVendas: TListBox;
DtReceita: TDateTimePicker;
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
private
procedure LoadReceitas;
procedure LoadRemedios;
procedure CriticarVenda;
{ após digitar as procedures a cima pressione: “Ctrl+Shift+C”}
public
{ Public declarations }
end;


Codificaremos primeiro o preenchimento da combo com os remédios.

procedure TForm1.LoadRemedios;
begin
(* Montando a combo dos remédios *)
with CmbRemedio do
begin
Items.Add('Frontal');
Items.Add('Tefxel');
Items.Add('Lorax');
Items.Add('Haldol');
Items.Add('Urbanil');
Items.Add('Tagretol');
end;
end;


Em seguida o preenchimento da combo das receitas. Para facilitarmos nosso exemplo vamos ordenar as receitas conforme a ordem dos remédios.


procedure TForm1.LoadReceitas;
const
DescReceita = 'Receita para o remédio: %s';
var
i: Integer;
begin
for i := 0 to CmbRemedio.Items.Count - 1) do
CmbReceita.Items.Add(Format(DescReceita, [CmbRemedio.Items[i]]))

end;


Agora, a rotina que gera as exeções, “CriticarVenda”.

procedure TForm1.CriticarVenda;
begin
(* Caso nehuma opção seja selecionada levantaremos uma exceção genérica*)
if (CmbReceita.ItemIndex < 0) and (CmbRemedio.ItemIndex < 0) then
raise Exception.Create('Selecione a receita, em seguida o remédio.');

(* Caso uma opção não seja selecionada levantaremos a exceção que criamos*)
if (CmbReceita.ItemIndex <= 0) xor (CmbRemedio.ItemIndex <= 0) then
raise ESubstanciaErrada.Create('A receita não corresponde ao remédio.');

(* Caso as opção selecionada sejam diferntes *)
if not (CmbReceita.ItemIndex = CmbRemedio.ItemIndex ) then
raise ESubstanciaErrada.Create('A receita não corresponde ao remédio.');

(* definimos, hipoteticamente, prazo de validade de 6 dias para a receita. *)
if DtReceita.Date < (Date - 6) then
raise EDataInvalida.Create('Prazo de validade da receita expirou.');
end;

No evento OnCreate do From1 faremos a chamada aos procedimentos que preencherão as Combos.

procedure TForm1.FormCreate(Sender: TObject);
begin
LoadRemedios;
LoadReceitas;
DtReceita.Date := Date - 10;
end;

Para finalizar, o evento OnClick do Button1, onde construiremos o tratamento das exceções.

procedure TForm1.Button1Click(Sender: TObject);
const
MsgSucesso = 'Venda efetuada com sucesso: %s';
begin
try
CriticarVenda;
LstVendas.Items.Add(Format(MsgSucesso, [CmbRemedio.Text]));
except
on E: EDataInvalida do
LstVendas.Items.Add(E.Message);
on E: ESubstanciaErrada do
begin
LstVendas.Items.Add(E.Message);
CmbRemedio.SetFocus;
if CmbReceita.ItemIndex < 0 then
begin
MessageBox(0, 'Selecione a receita.',
'Criando Exceções - Estação ZN', MB_ICONERROR or MB_OK);
CmbReceita.SetFocus;
end;
end;

on E: Exception do
begin
MessageBox(0, PChar(E.Message),
'Criando Exceções - Estação ZN', MB_ICONERROR or MB_OK);
CmbReceita.SetFocus;
end;
end;
end;

Veja as imagens abaixo:






Artigo completo (View Full Post)

quarta-feira, 18 de abril de 2007

Aplicações Robustas II: Desenvolvendo a técnica


Esse artigo é continuação do post “Robustez no Delphi

Dando continuidade a seqüência de artigos sobre tratamento de exceções.....

A Estrutura “Try…Finally…End”:

Bloco protegido:
É impressindível garantir que, em tempo de execução, os recursos alocados por vc, na codificação de um módulo, sejam desalocados, mesmo ocorrendo um erro. Que recursos me refiro? Arquivos, memória (alocação dinâmica), recursos do Windows, objetos (instâncias de classes, que entram tb no quisito memória).
Para garantir a desalocação dos recursos, usamos a estrutura abaixo:


{Aqui vc aloca os recursos}
try
{Aqui temos comandos que usam os recursos}
finally
{Libera os recursos de forma gsarantida}
end;



Como já falamos antes, a aplicação sempre executará os comandos inseridos na parte Finally do bloco, mesmo que uma execeção ocorra. Quando um erro ocorre no bloco protegido, o programa pula para a parte “finally”, chamada de código limpo, que é executado. Mesmo que não ocorra um erro, estes comandos são executados.
No código a seguir, alocamos memória e geramos um erro, ao tentarmos uma divisão por 0. Apesar do erro o programa libera a memória alocada:


procedure TForm1.Button1Click(Sender:TObject);
var
ZnMyPointer : Pointer;
ZnInteger, ZnDividend : Integer;
begin
ZnDividend := 0;
GetMem(ZnMypointer, 512);
try
ZnInteger := 10 Div ZnDividend;
finally
FreeMem(ZnMypointer, 512);
end;
end;


A Estrutura ‘Try…Except…End”:

Com ela podemos tratar erros que ocorrem dentro de blocos protegidos. Para definir um tratamento de exceção, utilize a seguinte construção:


try
{Comandos que você deseja proteger}
except
{Comandos de tratamento de erros}
end;

Mais uma vez repassandoo que vimos no post anterior, o progrma irá executar os comandos na parte “except” somente se ocorrer uma exceção. Se na parte “try” você chamar uma rotina que não trata erros, e uma exeção ocorrer, em decorrencia de algum comando dentro do escopo dela, ao voltar para este bloco a parte “except” será executada. Uma vez que a aplicação localiza um tratamento para exceção ocorrida, os comandos são executados, e o objeto exceção é destruído e o fluxo do programa sai do bloco protegido. A execução continua se houver mais comandos após o bloco protegido até o fim do módulo.
Dentro da parte “except” definimos um código a ser executado para manipular tipos específicos de exceção. Um exemplo mais que batido, contudo didático, o código abaixo trata o erro de divisão por zero, através da exceção EDivByZero:


function ZnDivisao (const ZnSoma, ZnNum : Integer) : Integer;
begin
try
Result := ZnSoma div ZnNum;
except
on EDivByZero do Result := 0;
end;
end;

Falamos ainda a respeito da palavra reservada “on”. Ela define respostas para uma exceção. “on” está sempre junto de “do”; eu chamo de estrutura “on ..do, para manipular a exceção.
Para recuperar informações específicas sobre a exceção ocorrida, você usa uma variação da estrutura “on…do”, que provê uma variável temporária cujo o tipo pode ser quaisquer uma das classes derivadas de Exception, inclusive a própria. Nesse caso, você poderá personalizar tanto a string da mensagem da exceção, quanto a janela que exibirá a mensagem:


try
{* Comandos * }
except
on E: EInvalidOperation do
MessageDlg(‘Personalizando a exceção – Minha mensagem é ... a da exceção é:‘+E.Message,
mtinformation,[mbOk],0);
end;


Você deve cuidar para que, havendo mais de uma, as estruturas “on..do” estejam dispostas do tipo de exceção mais específico para o mais genérico como foi exemplificado no post anterior.



Técnicas avançadas

Tipo genéricos de Execções:
1° Caso: você pode utilizar as classes de exceções genéricas para tratar um erro, em vez de uma 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:

function Divisao (const ZnSoma, ZnNum : Integer) : Integer;
begin
try
Result := ZnSoma div ZnNum;
except
on EDivByZero do Result 0;
on EIntError do
MessageDlg('Outro erro com inteiros aconteceu', mtError, [mbOk],0);
end;
end;


2° Caso: Você pode estruturar ainda um tratamento padrão de erro para tratar exceções que não tenham tratamentos especificados. Para isto, adicione uma parte “else” na parte “except” do bloco:



try
{Comandos}
except
on EintOverFlow do
{Código especifico para o primeiro tipo de erro]
on EintError do
{Código específico para o segundo tipo de erro}
else
MessageDlg('Um erro aconteceu, mas com certeza está relacionada com inteiros',
mtError, [mbOk],0);

end;


Toda técnica está fundamentada num conhecimento específico. No que tange o objetivo do artigo em questão, a pesquisa que apresento a seguir, considero ser alicerce para o refinamento da técnica de tratamento de exceções:


Exceções da RTL:
run-time library, as exceções geradas pela RTL estão definidas na unit SysUtils. Há sete tipos de exceções geradas pela RTL:


Exceções de conversão.
Exceções de entrada e saída.
Exceções de Hardware.
Exceções de memória heap.
Exceções matemáticas envolvendo inteiros.
Exceções matemáticas envolvendo pontos flutuantes.
Exceções de typecast.


Exceções de Conversão

Podem ocorrer quando usamos funções de conversão de um tipo para outro. A exceção chamada EConvertError é lançada quando isto ocorre, avisando que a conversão não pode ser feita. Exemplo:


var
ZnNumInteriro: Integer;
begin
ZnNumInteriro := StrToInt(Edit1.text);
end;


Dependendo do valor que possuir a propriedade “Text” do Edit1, poderá ocorrer uma exceção de conversão. Na vida real, posso garantir que o código exemplificado acima certamente irá, em algum momento, sofrer uma. Basta que não seja digitado nada no “Edit”.


Exceções de Entrada e Saída

Quando seu código tenta acessar arquivos ou dispositivos de entrada e saída.
A exceção genérica de entrada e saída, EinOutError, que contém um atributo, ErrorCode que indica o erro ocorrido.


Exceções de Hardware

Ocorrem em dois tipos de situação: quando o processador detecta uma falha, ou quando a aplicação gera uma interrupção intencional.
A Unit SysUtils define uma exceção genérica chamada EprocessorException, com as exceções derivadas:

 
Efault (Exceção Base),
EGPFault (Falha Geral de Proteção),
EstackedFault (acesso ilegal ao seguimento stack do processador),
EpageFault (O windows não está conseguindo realizar o swap),
EinvalidOpCode (O processador encontra uma instrução indefinida),
EbreakPoint (A aplicação gerou uma interrupção breakpoint),
Esinglestep (A aplicação gera uma interrupção single-step).


Exceções de Memória Heap

Quando alocamos memória dinamicamente podem ocorrer exceções. Para essa situação existem dois tipos de exceção: EoutofMemory (indica que não há memória suficiente) e EinvalidePointer (Ponteiro Inválido).


Exceções Matemáticas para Inteiros

Podem ocorrer quando se realiza operações com números inteiros. Está definida na SysUtils a exceção genérica EintError, com três tipos de exceção derivadas: EdividByZero (Divisão por zero), ErangeError (Número fora do intervalo, fora de faixa), e EintOverFlow (Estouro na operação com inteiro).


Exceções de Pontos Flutuantes

Quando se realiza operações com dados do tipo real. A SysUtils define uma exceção genérica chamada EMathError, e as seguintes exceções derivadas: EinvalidOp , EZeroDivide, EOverFlow , EUnderFlow.


Exceção de TypeCast

Para proteger seu código quando em tempo de execução vc precisa assumir que um determinado objeto possa ser de um tipo de classe. Caso ocorra um cast de tipo, ou seja, conversão de tipo, inválido, usando o operador de typecast “as” é gerada a exceção EinvalidCast. Exemplo:


(* para esse exemplo coloquei um edit e um button, palheta standard, num form. *)
procedure TForm1.Button1Click(Sender: TObject);
begin
(Sender as TBitBtn).Glyph.LoadFromFile('C:\Img.bmp');
end;


Basta que vc confunda a classe do botão, faça um cast de um TButton para um TBitBtn, pronto: “Invalid typecast”.



Exceções da VCL

Alguns exemplos de exceções definidas na VCL:

EDBEditError: Definida na unit “Mask”. Diz respeito a erros ocorridos com o uso do componente TDBEdit.


EDataBaseError: Gerado sempre que ocorre um erro no Banco de Dados

EDBEngineError: Gerado sempre ocorre um erro no BDE.

EPrinter: Gerado quando ocorre um erro com a impressora.

EDDEError: Gerado a aplicação não consegue encontrar um servidor DDE.

EMCIDeviceError: Gerado quando ocorre um erro ao tentarmos
acessar um dispositivo multimídia.

Exceções Silenciosas

A procedure Abort gera uma exceção do tipo Eabort. Isso, é claro, “abortará” a operação, sem exibir mensagem alguma.
O exemplo abaixo aborta a operação de inclusão de itens em um Listbox:
Inicie uma nova aplicação no Delphi:
Adicione os seguintes componentes da palheta standard: um Button (Button1: TButton) e um ListBox (ListBox1: TListBox).
Na seção “private” do Form1 declare um procedimento como no trecho de código abaixo:


type
TForm1 = class(TForm)
Button1: TButton;
ListBox1: TListBox;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
procedure ExcecaoSilenciosa;// Ao terminar de digitar, pressione “Ctrl+ Shift + C”
public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

{$R *.dfm}


A seção “type” do seu forme deve estar igual ao trecho acima. Conforme o comentário ao lado da procedure “ExcecaoSilenciosa”, quando vc pressionou “Ctrl+ Shift + C”, em resposta, o Delphi automaticamente criou o corpo da procedure. Portanto, vamos codificar, continue conforme o trecho a seguir:


procedure TForm1.ExcecaoSilenciosa;
const
ATexto = '%d° Elemetnto';
begin
Listbox1.Items.Add(‘Preparando para adicionar’);
//Estrutura de repetição, utilizada para adicionar os itens
Repeat
// adicionado os elementos
Listbox1.Items.Add(Format(ATexto, [ListBox1.Items.Count]));
// um teste louco pra forçar a exceção silenciosa
if (ListBox1.Items.Count mod 21 = 0) then Abort;
until ListBox1.Items.Count > 50;
end;


Cada vez que um item é adicionado na ListBox, a propriedade Count, da propriedade itens é incrementada. Ou seja, a propriedade “Count” retorna quantidade de itens que existem na ListBox.
Agora vamos codificar a chamada a esse procedimento e obviamente fazer o tratamento de exceção que vai dar o groove no nosso exemplo.

 
procedure TForm1.Button1Click(Sender: TObject);
begin
try
ExcecaoSilenciosa;
except
on Exception do
begin
Self.Color := clRed;
Self.Caption := 'Exceção Silenciosa!'
end;
end;

end;




Continuamos falando sobre este assunto no Estação ZN.

Artigo completo (View Full Post)

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.

Artigo completo (View Full Post)

 
BlogBlogs.Com.Br