Nenhum comentário Download


Dicas para melhorar suas aplicações cliente/server

No primeiro momento, ao ler o título deste artigo, o leitor pode entender que falaremos de padrões de projetos com suas siglas e algo mais (fique claro, que não tenho nada contra os padrões, pelo contrário, indico a utilização dos mesmos), mas na verdade teremos dicas de como utilizar da forma produtiva e com desempenho os projetos client/server que desenvolvemos.

 

Acredito que desenvolvedores iniciantes, ou até com mais experiência, fiquem em dúvidas quando precisam desenvolver alguma funcionalidade, do tipo: "Essa técnica é a que tem mais desempenho?" "Posso melhorar esse código?".

 

Pretendo mostrar neste artigo algumas dicas para que essas dúvidas possam ser respondidas, mas como sempre gosto de dizer, cada caso é um caso, portanto o que vou mostrar aqui não é regra e sim dicas adquiridas no desenvolvimento de projetos cliente/server ao longo dos anos.

Banco de dados

É preciso identificar e criar corretamente seus objetos para que no decorrer do desenvolvimento Delphi, não seja preciso retornar ao mesmo para criar tabelas, campos etc. Claro que temos ocasiões onde isso é necessário, a obrigação (pedido do cliente) de criar objetos ultrapassa o desejo do programador. Vamos mostrar aqui, exemplos em Firebird, mas acredito que a utilização dos exemplos em outros bancos, seja algo fácil de fazer.

 

Procure criar views para massa de dados muito grandes ou onde as consultas possuam muitos relacionamentos, para que no projeto Delphi não precisamos criar em vários locais o mesmo relacionamento.

 

Um exemplo, a ficha do cliente, que possui relacionamento com outras informações (dependentes, telefones, compras etc.) deve/pode ter uma view que retorne todos esses dados, assim caso precisamos alterar a consulta, não precisamos modificar em vários locais (chamada do cadastro, chamada de relatórios etc.) na aplicação Delphi.

 

Para Stored Procedures, procure utilizar apenas um objeto para inserção e atualização, assim teremos apenas um objeto no banco e apenas um componente vinculado ao objeto, no Delphi.

 

Nota: Não indico essa dica para tabelas onde a quantidade de registros seja muito grande.

 

Procure (sempre na medida do possível e com cuidado) deixar as regras de negócio no banco de dados usando Triggers. Para exemplificar melhor, imagine a seguinte situação: temos a tabela de Parcelas e uma de lançamento do Caixa.

 

Quando o cliente pagar a parcela, a mesma deve “quitada” e o valor de pagamento deve ser lançado no Caixa. Podemos, através de triggers, executar essa inserção diretamente no banco, não precisando utilizar métodos ou funções na aplicação Delphi.

Veja na Listagem 1 a criação dos objetos que ilustram esse exemplo:

 

Listagem 1. Código da Stored Procedure e Trigger

CREATE PROCEDURE CAIXA_INS (

    DESCRICAO VARCHAR(250),

    TIPO CHAR(1),

    VALOR NUMERIC(9,2))

AS

BEGIN

    INSERT INTO CAIXA (ID, DATA, DESCRICAO, TIPO, VALOR)

    VALUES (

        GEN_ID(SEQ_CAIXA, 1), 'NOW', :DESCRICAO, :TIPO, :VALOR);

END

 

CREATE TRIGGER PARCELAS_BIU0 FOR PARCELAS

ACTIVE BEFORE INSERT OR UPDATE POSITION 0

AS

BEGIN

  if (NEW.PAGO = 'T') then

  begin

     /* Lançamento no Caixa */

     EXECUTE PROCEDURE CAIXA_INS ('Recebimento da parcela nº: '|| OLD.NR_PARCELA || ' do Contrato nº: ' || OLD.id_contrato, 'E', NEW.VALOR_PAGTO);

  end

end

 

Temos uma Stored Procedure cujo objetivo é inserir dados dos parâmetros na tabela Caixa. Na trigger, para os eventos ao incluir e ao alterar, executamos a Stored Procedure, incluindo na tabela Caixa os valores que foram utilizados. Colocamos nesses dois eventos, pois um lançamento pode ser criado, já com o pagamento efetuado (valor da entrada, por exemplo).

 

Assim, na aplicação Delphi, precisamos apenas realizar a "baixa" da parcela, sem nos preocuparmos com o lançamento no Caixa. Esse é um exemplo simples, mas que poderíamos (sempre lembrando, com cuidado e boa análise) executar em outras situações. A seguir, mostraremos como utilizar essa dica na prática.

 

Outro objeto do banco de dados que podemos usar para facilitar o desenvolvimento, são os Domains. Com eles, podemos criar campos personalizados para os tipos de campos que usamos, adicionando a validação necessária. Por exemplo: costumo cria rum campo Blob para campos onde a quantidade de texto é grande, assim, basta criar um Domain com o tipo e utilizá-lo onde for necessário.

 

Outro exemplo, para campos com dados monetários, onde basta criar o objeto e caso seja necessário mudar alguma característica do campo (por exemplo, casas decimais), vamos alterar apenas no Domain. Veja na Listagem 2 alguns Domains que podem ser usados no seus projetos.

 

Listagem 2. Exemplos de Domains

CREATE DOMAIN DM_BOOLEAN AS

CHAR(1);

 

CREATE DOMAIN DM_DINHEIRO AS

NUMERIC(9,2);

 

CREATE DOMAIN DM_FLOAT AS

NUMERIC(7,2);

 

CREATE DOMAIN DM_IMAGEM AS

BLOB SUB_TYPE 0 SEGMENT SIZE 80;

 

CREATE DOMAIN DM_OBS AS

VARCHAR(250);

 

CREATE DOMAIN DM_STATUS AS

CHAR(1)

NOT NULL

CHECK (VALUE in ('A', 'I'));

 

CREATE DOMAIN DM_TIPOPESSOA AS

CHAR(1)

NOT NULL

CHECK (VALUE in ('F', 'J'));

 

Veja que em alguns Domains, colocamos algumas características como não pode ser nulo (Not Null) e verificamos o valor (Check) do campo.

Delphi – Data Modules

Acredito que todo desenvolvedor Delphi sabe o que é um Data Module, mas sabe usar o mesmo corretamente? Ainda hoje, vejo alguns desenvolvedores utilizando componentes de dados (Table, Query, Connection) em formulários, o que não é o correto. A finalidade do Data Module e concentrar os componentes de dados e as regras de negócio da aplicação.

 

Com certeza, o componente que possui os dados do cliente, não será apenas usado no respectivo cadastro, poderá ser usado em “n” formulários. Assim se precisar utilizar o componente, terá de declarar o uses desse em outros formulários, podendo acarretar problemas de referencia cruzada circular de uses (um bom problema para você resolver).

 

É no Data Module que você vai criar, por exemplo, o método para Salvar os dados do Cliente e não no formulário de cadastro do cliente. No formulário, você apenas irá fazer a chamada do método, assim, fica mais fácil dar a manutenção quando necessária. Alguns desenvolvedores, colocam no Data Module o DataSource.

 

Não vou questionar se é certo ou errado, acredito que nas duas opções (Data Module ou formulário) existem seus acertos, até por que quase sempre precisamos acessar o componentes de dados (DataSet) e não o DataSource.

 

Veja na Figura 1 um Data Module criado para o nosso artigo.

 

Figura 1. Data Module de exemplo

Vamos fazer um exemplo, baseado no que foi apresentado até agora. No evento AfterPost do ClientDataSet vamos repassar para uma Stored Procedure (utilizando o SQLStoredProc) os valores da baixa de uma parcela. Veja na Listagem 3 o código.

 

Listagem 3. Evento AfterPost do ClientDataSet

procedure TDM.cdsParcelasAfterPost(DataSet: TDataSet);

begin

  with spParcelas do

  begin

     Params[0].AsInteger := cdsParcelasID_PARCELA.AsInteger;

     Params[1].AsInteger := cdsParcelasID_CONTRATO.AsInteger;

     Params[2].AsInteger := cdsParcelasNR_PARCELA.AsInteger;

     Params[3].AsDate := cdsParcelasDATA_VENC.AsDateTime;

     Params[4].AsCurrency := cdsParcelasVALOR_PARCELA.AsCurrency;

     Params[5].AsDate := cdsParcelasDATA_PAGTO.AsDateTime;

     Params[6].AsCurrency := cdsParcelasVALOR_PAGTO.AsCurrency;

     Params[7].AsString := cdsParcelasPAGO.AsString;

     ExecProc;

  end;

 

Ao salvar o registro, no banco de dados será dado baixa na parcela e o valor lançado no Caixa. Veja na Figura 2 os lançamentos no Caixa.

 

Figura 2. Consulta com o lançamento, sem o uso de código na aplicação Delphi

Como criamos a trigger, ela faz o lançamento do caixa com os valores passados quando da baixa da parcela. Evitamos assim, um bom número de linhas de código.

Herança Visual de formulários

Uma das teclas que sempre bati no desenvolvimento, foi na parte de produtividade e não poderia deixar de falar em produtividade, sem falar em herança visual. Imagine um projeto de médio a grande porte, onde a quantidade de formulários de cadastro, seja em um número grande. Imagine agora, que a maioria desses formulários possua características muitas semelhantes, como os botões de Salvar, Incluir, Excluir, Pesquisar etc.

 

O trabalho de criar cada um desses formulários, nem gostaria de imaginar ou calcular. Então, o Delphi possui uma vantagem presente desde a versão 1, a herança visual. Precisamos criar um formulário base com características comuns (botões, imagens etc.) de cadastro e criar um formulário descendente.

 

Nesse formulário criado, vamos adicionar as características do cadastro, como as caixas de textos, validações etc. Precisamos trabalhar o mais genérico possível, para que possamos colocar no formulário base a maior quantidade de código possível. Assim, no formulário herdado, vamos apenas implementar as características do cadastro (nome dos campos daquele cadastro etc).

 

Vamos a um exemplo, veja na Figura 3 um formulário de cadastro para ser base.

 

Figura 3. Formulário base de cadastro

Imagine que teremos um código que deve ser executado em todos os formulários de cadastro, como, por exemplo, o botão Fechar, clicando em um menu/botão ou usando a tecla ESC. Se não usássemos herança, teríamos que implementar a chamado ao Close do formulário, em todos os controles e no evento correspondente ao ESC.

 

Isso seria muita duplicação de código e perda de tempo. Com o formulário base, vamos implementar isso no mesmo e quando herdarmos um formulário baseado no base, o código também será herdado, não precisando ser implementado novamente.

 

Esse é um exemplo simples, mas imagine a seguinte situação: preciso implementar um funcionalidade de permissões/restrições do usuário na aplicação. Esse tipo de funcionalidade depende muito do que o cliente precisa, mas de maneira genérica é bem fácil de implementar.

 

Na edição 56 da Active Delphi, desenvolvi um artigo com essa funcionalidade, usando as vantagens da herança. Veja neste artigo, como podemos criar um método no formulário base genérico, onde podemos dar permissão ao usuário, de acordo com o formulário aberto. Veja na Listagem 4 como ficou o método do referido exemplo.

 

Listagem 4. Exemplo para uso genérico de código em formulários base de cadastro

procedure TfrmCadastro.funcionalidades;

begin

  {: verifica as funcionalidades existentes para os botões }

  DM.VerificaFuncionalidades(Self.Name);

  {: habilita ou desabilita botões e itens de menu }

  actNovo.Enabled := (DM.cdsFuncionalidades.Locate(

    'NOME_FUNCIONALIDADE', 'Novo',

    [loPartialKey, loCaseInsensitive]));

  actSalvar.Enabled := (DM.cdsFuncionalidades.Locate(

    'NOME_FUNCIONALIDADE', 'Salvar',

    [loPartialKey, loCaseInsensitive]));

  actExcluir.Enabled := (DM.cdsFuncionalidades.Locate(

    'NOME_FUNCIONALIDADE', 'Excluir',

    [loPartialKey, loCaseInsensitive]));

  actRelatorio.Enabled := (DM.cdsFuncionalidades.Locate(

    'NOME_FUNCIONALIDADE', 'Relatorio',

    [loPartialKey, loCaseInsensitive]));

  actLocalizar.Enabled := (DM.cdsFuncionalidades.Locate(

    'NOME_FUNCIONALIDADE', 'Localizar',

    [loPartialKey, loCaseInsensitive]));

end;

 

Veja que graças a herança, não precisamos implementar em cada formulário a chamada ao método passando o nome do mesmo, fizemos isso, usando a propriedade Name do formulário (Self.Name), que mudará, de acordo com o formulário atual.

Biblioteca de funções

Uma pratica muito boa para produtividade e reaproveitamenteo de código, é criar uma unit (que chamamos de Biblioteca de código), invariavelmente denominada Lib, contendo métodos e funções que podem ser usadas em vários locais do projeto.

 

Assim, facilitamos a vida do desenvolvedor ou dos desenvolvedores, para não duplicar código, pois o método criado por você para, por exemplo, remover os caracteres acentuados de uma string, pode ser útil para outro desenvolvedor da sua equipe, em outro local do projeto.

 

Procure sempre criar métodos e funções genéricas, que não usem, por exemplo, componentes de formulários ou Data Modules. Se for preciso, coloque parâmetros para que o código não fique preso ao projeto e possa ser usado em outros.

 

Na Listagem 5 temos alguns exemplos de métodos e funções que podem ser adicionados em uma biblioteca para ser compartilhado entre uma equipe de desenvolvimento ou em mais de um projeto.

 

Listagem 5. Métodos e funções para uso em equipe ou mais de um projeto

{ Abrir formulário }

procedure AbreForm(aClasseForm: TComponentClass; aForm: TForm);

begin

  Application.CreateForm(aClasseForm, aForm);

  try

    aForm.ShowModal;

  finally

    aForm.Free;

  end;

end;

 

{ Remove os espaços em branco à direita da string }

function RTrim(Texto: string): string;

var

  i: integer;

begin

  i := Length(Texto)+1;

  while true do

  begin

    Dec(i);

    if i <= 0 then

      break;

    if Texto[i] <> #32 then

      break;

  end;

  Result := Copy(Texto, 1, i);

end;

 

{ Remove os espaços em branco à direita da string }

function LTrim(Texto: string): string;

var

  i: integer;

begin

  i := 0;

  while true do

  begin

    inc(i);

    if i > Length(Texto) then

      break;

    if Texto[i] <> #32 then

      break;

  end;

  Result := Copy(Texto, i, Length(Texto));

end;

 

{ Valida uma data }

function DateValidate(const aData: string): boolean;

begin

  try

    StrToDate(aData);

    Result := true;

  except

    Result := false;

  end;

end;

 

{ Remove caracter passado como parâmetro }

function RemoveChar (const Ch: Char; const S: string): string;

var

  Posicao: integer;

begin

  Result := S;

  Posicao := Pos(Ch, Result);

  while Posicao > 0 do

  begin

    Delete(Result, Posicao, 1);

    Posicao := Pos(Ch, Result);

  end;

end;

 

{ Remove acentos da string passada como parâmetro }

function RemoveAcento(Str: string): string;

const

  ComAcento = 'àâêôûãõáéíóúçüÀÂÊÔÛÃÕÁÉÍÓÚÇÜ';

  SemAcento = 'aaeouaoaeioucuAAEOUAOAEIOUCU';

var

  x: integer;

begin

  for x := 1 to Length(Str) do

    if Pos(Str[x], ComAcento) <> 0 then

      Str[x] := SemAcento[Pos(Str[x],ComAcento)];

  Result := Str;

end;

 

{ Pesquisa um caractere na string, retornando se achou }

function BuscaTexto (Busca, Text: string): boolean;

begin

  Result := (Pos(Busca, Text) > 0);

end;

 

{ Gera caracteres aleatórios para senha }

function GeraSenha (aQuant: integer): string;

var

  i: integer;

const

  str = '1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';

begin

  for i:= 1 to aQuant do

  begin

    Randomize;

    Result := Result + str[Random(Length(str))+1];

  end;

end;

 

{ Retorna o valor do Generator onde você pode incrementar o valor do mesmo }

function GeneratorID (aName: string; Connection: TSQLConnection;

  aInc: Boolean): integer;

var

  Qry: TSQLQuery;

begin

  Qry := TSQLQuery.Create(nil);

  try

    Qry.SQLConnection := Connection;

    if aInc then

      Qry.SQL.Add('SELECT GEN_ID('+aName+', 1) FROM RDB$DATABASE')

          else

      Qry.SQL.Add('SELECT GEN_ID('+aName+', 0) FROM RDB$DATABASE');

    Qry.Open;

   

    Result := Qry.Fields[0].AsInteger;

  finally

    FreeAndNil(Qry);

  end;

end;

 

Veja na Listagem 6 a utilização desses métodos em exemplos.

 

Listagem 6. Exemplo da utilização dos métodos e funções

{ Abrir formulário }

AbreForm(TForm1, Form1);

 

{ Remove os espaços em branco à direita da string }

Edit1.Text := RTrim(Edit1.Text);

 

{ Remove os espaços em branco à direita da string }

Edit2.Text := LTrim(Edit2.Text);

 

{ Valida uma data }

if not DateValidate(Edit3.Text) then

  ShowMessage('Data inválida!');

 

{ Remove caracter passado como parâmetro }

Edit4.Text := RemoveChar('@', Edit4.Text);

 

{ Remove acentos da string passada como parâmetro }

Edit5.Text := RemoveAcento(Edit5.Text);

 

{ Pesquisa um caractere na string, retornando se achou }

if BuscaTexto('luc', Edit6.Text) then

  ShowMessage('Texto encontrado!');

 

{ Gera caracteres aleatórios para uma senha }

Edit7.Text := GeraSenha(6);

 

Veja na Figura 4 os exemplos em execução.

 

Figura 4. Formulário com exemplos da biblioteca

 

Observação: Os códigos mostrados neste artigo foram coletados da internet e sofreram alguma modificação. Não tenho o nome do autor para adicionar os devidos créditos aos mesmos.

Conclusão

Vimos neste artigo várias dicas e padronizações para aplicações client/server, mostrando assim como é possível criar aplicações mais rápidas, com performance e de fácil manutenção. Um grande abraço a todos e sucesso em seus projetos!