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!