You are on page 1of 10

Asseres

Todo Analista de Sistemas sabe da importncia das pr-condies e das ps-condies que devem ser atendidas antes ou depois da realizao de uma determinada operao. Estas condies em geral so usadas para impor as regras de negcio, como por exemplo, no vender dez sacos de farinha quando s h trs em estoque, no vender uma garrafa de pinga para um cliente menor de dezoito anos ou no permitir a incluso de um segundo volante em um objeto do tipo TAutomovel... Um Analista gasta boa parte de seu tempo adicionando estas restries em seus diagramas de classe, e o(s) programador(es) deve(m) garantir que elas sejam implementadas fielmente no cdigo sob pena de invalidar o modelo ou ate mesmo, todo o projeto. A esta tcnica de projeto/programao d-se o nome de projeto/programao por contrato. O projeto por contrato foi brilhantemente discutido por Bertrand Meyer em seu clssico Object-oriented Software Construction, alguns autores atribuem a ele a criao do conceito. Meyer introduziu os recursos necessrios implementao desta tcnica na linguagem de programao por ele desenvolvida (Eiffel). As pr/ps-condies so introduzidas nas linguagens de programao em forma de asseres, uma assero uma suposio a respeito de algo que deve ser verdadeiro no momento do teste, se esta condio no for encontrada sinal de que algo errado aconteceu. Um resumo sobre a programao por contrato do prprio Meyer: Se voc promete chamar a rotina r com a pr-condio pre atentida, ento eu, em contrapartida, prometo lhe entregar um resultado final no qual a ps-condio pos atendida.1 Em geral, devemos usar asseres para garantir que o nosso cdigo vai bem obrigado e no para tratar entradas errneas do usurio ou erros esperados, para esse tipo de situao devemos usar o tratamento de exceo. A rigor, deveramos considerar uma exceo, quando uma operao realizada em situao totalmente legal, ou seja, com sua prcondio plenamente atendida, e mesmo assim no obtemos o resultado final esperado, ou seja, no conseguimos atender a ps-condio. Na pratica, entretanto, comum acabar misturando as coisas. Meu objetivo demonstrar o uso de asseres no Delphi para que voc possa aumentar seu repertorio de rotinas de tratamento e tirar o mximo proveito delas. Asseres no Delphi so implementadas atravs do procedimento Assert, este procedimento uma das funes embutidas no compilador e voc no conseguir encontrar seu cdigo em nenhuma unit do Delphi (estes procedimentos internos ao compilador em geral so listados como se fossem declarados na unit System). O uso do Assert possui algumas diferenas fundamentais em relao ao tratamento de exceo tradicional do Delphi e eles no devem ser vistos como mutuamente exclusivos, no pretendemos fazer uma discusso completa sobre as diferenas entre erros, asseres, excees e falhas, apenas para ajud-lo, citarei aqui uma breve definio de Meyer:
1

If you promise to call r with pr satisfied then I, in return, promise to deliver a final state in wich post is satisfied.

Uma EXCEO a ocorrncia de uma condio anormal durante a execuo de um elemento de software; Uma FALHA a inabilidade do elemento de software satisfazer seu propsito; Um ERRO a presena no software de algum elemento que no satisfaz suas especificaes. Eu aprecio muito o Assert por trs motivos, o primeiro que eles podem ser desabilitados com o uso de uma simples diretiva de compilao, o que permite que voc desenvolva seu software usando as asseres e desative-as na hora de gerar o executvel de distribuio, a segunda, que ao contrrio dos outros tratamentos de erro, o Assert permite que seu cdigo continue a ser executado (caso voc escreva uma rotina de tratamento), e a terceira que j me poupou muitas horas de debuger, que quando a assero falha, a mensagem mostrada na tela indicando o nome do arquivo e o nmero da linha onde o problema ocorreu, como isso soa pra voc? Ate mesmo em verses de distribuio em perodo de testes j utilizei asseres para resolver problemas, e acredite, maravilhoso quando o usurio te manda um e-mail com aquele print screen da tela com o nome da unit e a linha exata onde o erro ocorreu. S pra voc entender minha paixo pelo Assert: Situao 1: Usuario: , o programinha que voc fez aqui pra gente ta dando pau cara; Programador: mas o que voc fez que provocou o erro?; Usurio: eu no fiz nada, s cliquei naquele boto que fica no canto esquerdo da tela de traz do cadastro, saqual?. Situao 2: Usuario: Prezado, conforme combinado envio abaixo captura da tela aps o erro ocorrido:

Isso se voc no for muito sofisticado, seno, podemos gerar um arquivo de log e envialo automaticamente para nosso e-mail! Crie uma aplicao e monte o form conforme a figura:

Componente Edit Button Button Button

Propriedade Name Name Caption Name Caption Name Caption

Valor edtValor btDepositar Depositar btRetirar Retirar btTratar Tratar

Criaremos uma situao bastante grosseira apenas para demonstrar o uso do procedimento Assert no Delphi. Simularemos um caso em que um cliente possui um saldo inicial de 200 Reais e ele poder fazer depsitos e retiradas. A pr-condio para efetivarmos o depsito de que o valor depositado seja maior que 0, e a ps-condio que o saldo aps o depsito seja maior que o saldo anterior operao, simples mas bastante lgico no? Para a retirada teremos como pr-condio que o valor a ser retirado no seja maior que o saldo, e como ps-condio, que o saldo final seja maior que 0. Comece declarando duas variveis globais do tipo Currency, uma chamada valor e outra chamada saldo inicializada com o valor de 200: var valor: Currency; saldo: Currency = 200; No evento OnClick do boto btRetirar digite o cdigo a seguir: procedure TForm1.btRetirarClick(Sender: TObject); begin valor:= StrToCurr(edtValor.Text); Assert(saldo > valor, 'Tentativa de retirada invlida'); saldo:= saldo - valor; Assert(saldo >= 0, 'O saldo estava negativo'); ShowMessage('Saldo atual: ' + CurrToStr(saldo)); end;

Comeamos capturando o valor digitado no edtValor na varivel valor, na linha a seguir, inferimos a nossa pr-condio, saldo deve ser maior que valor para que a retirada possa ser feita, caso essa condio no seja atendida (o chamador no cumpriu sua parte do contrato), um erro de assero ser lanado conforme a figura abaixo:

A execuo do cdigo subseqente ento abortada e a retirada nunca concretizada. Isso foi um erro de assero. Caso consegussemos chegar penltima linha deste cdigo (Assert(saldo >= 0, 'O saldo estava negativo');) ai sim teria ocorrido uma verdadeira exceo, j que pelas regras impostas o saldo nunca conseguiria ser menor que 0, portanto, em algum momento o software teria falhado em impor estas regras. Porque ento colocamos esta linha que nunca ser executada? Guarde sua curiosidade, ela ser satisfeita em breve. Vamos agora programar o click do boto btDepositar: procedure TForm1.btDepositarClick(Sender: TObject); var SaldoInicial: Currency; begin SaldoInicial:= saldo; valor:= StrToCurr(edtValor.Text); Assert(valor > 0); saldo:= saldo + valor; Assert(saldo > SaldoInicial, 'Saldo incorreto aps um depsito'); ShowMessage('Saldo atual: ' + CurrToStr(saldo)); end; Como a ps-condio exige que comparemos o saldo ao fim da operao com seu prprio valor antes da operao criamos uma varivel local para guardar o saldo inicial (j que saldo ser alterada no decorrer da rotina), comeamos armazenando nesta varivel o valor de saldo antes da execuo da rotina, logo depois capturamos o valor digitado no edtValor na varivel valor, a seguir garantimos nossa pr-condio, o valor a ser depositado maior que 0, caso a condio no seja satisfeita ser gerado o erro de assero mostrado na tela:

Observe que como no passamos uma string aps a condio booleana no Assert o procedimento usar a string padro Assertion failure. Bem, se houve um deposito e o valor foi maior que 0 (isso est garantido), o saldo final deve ser maior que o saldo inicial, isso foi o que prometemos e devemos garantir com nossa ps-condio (Assert(saldo > SaldoInicial, 'Saldo incorreto aps um depsito');), mais uma vez esperamos que essa assero nunca seja violada, pois isso indicaria uma falha em nosso cdigo (ou no hardware ou no SO).

Sofisticando o uso do Assert


O que vimos at aqui o uso padro do procedimento e j satisfaz a maioria das necessidades do dia a dia dos programadores, mas o Assert guarda seus segredos, e um deles que podemos escrever rotinas de tratamento para a violao da assero, e quando fazemos isso, ganhamos a capacidade de continuar a executar o cdigo subseqente dentro do procedimento onde estava a assero violada. A varivel AssertErrorProc declarada na unit System um ponteiro para uma rotina com a seguinte assinatura: procedure (const Message, Filename: string; LineNumber: Integer; ErrorAddr: Pointer); Portanto, tudo que temos a fazer criar uma rotina com esta assinatura e atribu-la varivel AssertErrorProc, quando uma assero for violada o Delphi chamar esta rotina para tratar a violao. Adicione uma nova unit ao projeto e salve-a como utilidades.pas, na interface declare as rotinas a seguir: function AdicionarTextoEmArquivo(const NomeArquivo: string; var Texto: string; Quebra: boolean = false; Recriar: boolean = false): boolean; procedure EnviarMail(const FileName: string); procedure OnAssertFailure(const Message, Filename: string; LineNumber: Integer; ErrorAddr: Pointer); Na clausula uses da implementation adicione as seguinte units: uses Classes, SysUtils, Dialogs, Controls, StdCtrls, ShellApi, Windows, unit1;

A rotina OnAssertFailure a rotina que usaremos para manipular as violaes, e como pretendemos gravar um arquivo de log criamos a rotina AdicionarTextoEmArquivo que ser chamada pela rotina de tratamento, a rotina EnviarMail ir, surpreendentemente, enviar um e-mail com o log de erros (supondo que o Outlook Express ou outro programa qualquer de e-mail que suporte a extenso .eml esteja instalado). Implemente a rotina OnAssertFailure da forma a seguir: procedure OnAssertFailure(const Message, Filename: string; LineNumber: Integer; ErrorAddr: Pointer); var S: String; begin S := Format('%s (%s, linha %d, endereo $%x)', [Message, Filename, LineNumber, Pred(Integer(ErrorAddr))]); S:= S + #13#10 + FormatDateTime('DD/MM/YY HH:MM:SS', Now); if not FileExists(LogFileName) then AdicionarTextoEmArquivo(LogFileName, S, True, True) else AdicionarTextoEmArquivo(LogFileName, S, True, False); case MessageDlg('A aplicao detectou o seguinte erro: ' + #13 + Message + #13 + 'deseja prosseguir?', mtConfirmation, mbYesNo, 0) of mrYes: ; mrNo: Raise EAssertionFailed.Create(S); end; end; Essa rotina ser chamada automaticamente pelo Delphi quando uma assero for violada e receber como parmetro a mensagem programada, a unit e o nmero da linha onde se encontra o procedimento Assert que ocasionou a violao. Comeamos atribuindo esses parmetros string S, depois adicionamos data e hora ao contedo de S, depois adicionamos o contedo de S ao arquivo de log atravs da chamada AdicionarTextoEmArquivo. Aqui existe uma situao muito peculiar, quando uma assero violada todo o cdigo aps a instruo Assert abortado e no deve ser executado (assim como quando uma exceo ocorre), s que, quando atribumos uma rotina de tratamento de violao varivel AssertErrorProc este comportamento muda, a rotina ser chamada pelo delphi e o processamento continuar normalmente aps a linha onde se encontrava o Assert que ocasionou a violao. Este comportamento pode ser extremamente til em determinadas situaes, porem pode ser muito perigoso, j que, se uma assero foi violada o processamento no deveria prosseguir, a menos que na rotina de tratamento voc j faa os consertos necessrios, no nosso exemplo, decidimos deixar a cargo do usurio a deciso de prosseguir ou abortar o processamento do cdigo subseqente ao Assert, para

isso exibimos uma mensagem informando o erro encontrado e questionando se o usurio deseja que o processamento prossiga, caso a resposta seja afirmativa no precisamos fazer nada pois este o comportamento padro agora, por isso a instruo vazia no caso da resposta a caixa de dialogo ser mrYes. No caso do usurio optar por abortar o processamento recriamos a exceo passando as informaes que recebemos dela mesma de volta, isso far com que o processamento seja abortado. Implemente a rotina EnviarMail da forma a seguir: procedure EnviarMail(const FileName: string); const cr = #13 + #10; MailFileName = 'Log.eml'; var memo: TMemo; begin memo:= TMemo.Create(Form1); with memo do begin Parent:= Form1; Visible:= False; Lines.LoadFromFile(FileName); Lines.Insert(0, 'To: ' + InputBox('Log', 'E-Mail do destinatrio', 'teste@bol.com.br') + cr + 'Subject: Log de Erros' + cr + 'X-Unsent: 1' + cr); Lines.SaveToFile(MailFileName); end; ShellExecute(0, 'open', pchar(MailFileName), nil, nil, SW_NORMAL); end; Essa rotina ser chamada ao encerrarmos a aplicao para que ela tente enviar um e-mail com o contedo do arquivo de log. Ela comea com a criao dinmica de um memo para que possamos manipular o contedo do arquivo de log acrescentando a ele as caractersticas especificas de um e-mail (no precisvamos usar um componente para fazer isso, uma outra tcnica usando streams ser mostrada no prximo passo). Aps criarmos o memo, fazemos com que ele leia o contedo do arquivo de log, depois inserimos o cabealho do e-mail seguindo o padro do Outlook Express, logo aps, salvamos o contedo do memo em disco com a extenso .eml. A ultima instruo (ShellExecute) tentar lanar o programa registrado no Windows para trabalhar com a extenso .eml. Implemente a rotina AdicionarTextoEmArquivo da forma a seguir: function AdicionarTextoEmArquivo(const NomeArquivo: string; var Texto: string; Quebra: boolean = false; Recriar: boolean = false): boolean;

const CRLF = #13 + #10; var pch: pchar; fSt: TFileStream; begin Result:= false; if Quebra then Texto:= Texto + CRLF; pch:= pchar(Texto); try if (not FileExists(NomeArquivo)) or (Recriar) then fSt:= TFileStream.Create(NomeArquivo, fmCreate) else begin fSt:= TFileStream.Create(NomeArquivo, fmOpenReadWrite); fSt.Seek(fSt.Size, 0); end; finally fSt.Write(pch^, length(Texto)); fSt.Free; end; Result:= true; end; Essa rotina ir gravar no arquivo NomeArquivo o texto enviado em Texto, caso o parmetro Quebra seja definido como True ser inserida uma quebra de linha (CRLF) aps o texto, o parmetro Recriar determina se o arquivo deve ser sobrescrito (true) ou apenas acrescentado o novo texto ao contedo j existente (false). Esta funo retornar True se conseguir gravar os dados no arquivo e False em caso de falha. O que falta agora atribuirmos nossa rotina varivel AssertErrorProc, poderamos fazer isso no create do form ou mesmo em uma seo initialization, porem, como desejamos poder ver os comportamentos se alternarem, faremos isso no click do boto btTratar: procedure TForm1.btTratarClick(Sender: TObject); begin AssertErrorProc:= OnAssertFailure; end; Para completar o exemplo precisamos enviar o e-mail ao encerrar a aplicao, no evento OnClose do form faa a chamada a rotina EnviarMail: procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction); begin EnviarMail(LogFileName); end;

Execute a aplicao e teste a insero de valores invlidos e veja os resultados, depois, clique no boto Tratar e teste novamente para observar a diferena de comportamento. Vamos experimentar fazer um deposito com valor invalido: Execute a aplicao, digite -1 no edit e clique em Depositar, observe a mensagem padro do Assert (j que no h uma string neste Assert):

Observe tambm que o restante do cdigo no ser executado, ou seja, o processamento abortado. A seguir, sem remover o valor -1 do edit clique em Tratar, depois clique em Depositar e observe que ser apresentada a caixa de dialogo que programamos solicitando uma escolha do usurio:

Clique em Yes e observe que as linhas subseqentes sero executadas o que far com que o segundo Assert seja violado e dispare nova mensagem na tela:

Clique em No para que a exceo seja apresentada na tela:

Observe que agora o processamento no seguiu, seno teramos a mensagem com o saldo atual exibido. Feche a aplicao, ser apresentada uma tela solicitando que voc digite o e-mail do destinatrio (InputBox):

Aps fechar esta tela, caso voc possua um programa de e-mail registrado no Windows para tratar arquivos com a extenso .eml este programa ser aberto j totalmente configurado, bastando clicar em Enviar para que o e-mail siga. Finalizando, caso voc deseje desativar os Assert, insira a diretiva {$ASSERTIONS OFF} no inicio do seu cdigo. Clique aqui para fazer o download do projeto.

You might also like