Blog – FAQ 8

Blog - FAQ 8Olá, pessoal!
Após 7 meses, volto com mais um conjunto de perguntas e respostas dos leitores do blog. Fiz questão de selecionar as perguntas mais relevantes como meio de fornecer ajuda para outros leitores e visitantes. Se você tem alguma dúvida, talvez ela já possa estar respondida neste artigo!

 

Tenho uma tabela no banco de dados que contém uma chave primária auto-incremento e gostaria de representar essas características em uma classe no Delphi. É possível?
Quando criamos uma classe que representa uma tabela do banco de dados, todos os campos são declarados da mesma forma, independentemente se são chaves primárias, auto-incremento ou obrigatórios. Logo, esse tipo de regra deve ser implementado manualmente pelo próprio desenvolvedor.
Suponha que temos uma tabela no banco de dados chamada CLIENTE com os seguintes campos:
– Código (chave primária, auto-incremento)
– Nome

No Delphi, a classe que representa essa tabela (através de um conceito conhecido como ORM) ficaria dessa forma:

type
  TCliente = class
  private
    FCodigo: integer;
    FNome: string;
 
    function GetCodigo: integer;
 
    procedure SetNome(const Value: string);
    function GetNome: string;
 
    function VerificarCodigoJaCadastrado(const pnCodigo: integer): boolean;
  public
    property Codigo: integer read GetCodigo;
    property Nome: string read GetNome write SetNome;
  end;

Observe que ambos os campos são declarados da mesma forma, porém, o campo “Codigo” só disponibiliza o getter, já que, como é auto-incremento, o próprio banco de dados se encarregará de atribuir um valor e, portanto, o setter não é necessário. Essa estrutura já é o suficiente para que o usuário dessa classe (leia-se: outro desenvolvedor) descubra que o campo “Codigo” é somente leitura.
A respeito do comportamento de chave primária, é possível implementar uma validação que faça essa verificação na própria classe. Por exemplo, ao gravar um novo cliente, poderíamos criar o seguinte método:

function TCliente.VerificarCodigoJaCadastrado(const pnCodigo: integer): boolean;
var
  bCodigoJaExiste: boolean;
begin
  result := True;
 
  Query1.SQL.Clear;
  Query1.SQL.Add('Select 1 from CLIENTE where Codigo = :Codigo');
  Query1.ParamByName('Codigo').AsInteger := pnCodigo;
  Query1.Open;
  bCodigoJaExiste := not Query1.IsEmpty;
  Query1.Close;
 
  if bCodigoJaExiste then
  begin
    ShowMessage('O código informado já está cadastrado!');  
    result := False;
  end;
end;

Este método evita que um código já existente seja persistido no banco de dados. Na prática, estamos “aplicando” o comportamento de chave primária em um atributo da classe.
Embora essa dica seja viável, existem mecanismos mais robustos de ORM para Delphi, como o InstantObjects e DORM, que mapeiam as tabelas do banco de dados em classes e permitem, inclusive, a criação de novos campos via programação. Vale a pena conferir!

 

Como posso exibir a porcentagem de envio do e-mail no Delphi, por exemplo, utilizando um componente TProgressBar?
Uma vez já fui questionado sobre TProgressBar no envio de e-mails. Ao contrário de conexões FTP, que nos permitem “acompanhar” a transferência de dados, as conexões SMTP não retornam o andamento do envio para que possamos exibi-lo em uma barra de progresso.
Alternativamente, podemos preencher a barra de progresso, manualmente, a cada passo: no comando Connect, preenchemos uma porcentagem da barra; em seguida, no comando Authenticate, aumentamos a porcentagem; nas configurações da mensagem, mais uma quantia; na inserção de anexos, preenchemos mais um pouco; e, por fim, completamos a porcentagem ao enviar a mensagem. Dessa forma, controlamos o preenchimento da barra conforme as operações que são realizadas no código.
Em uma das minhas aplicações, optei por criar uma tela de espera enquanto o e-mail é enviado. A tela apresenta a frase “Processando” com um GIF animado e só é fechada quando o envio é concluído.

 

Tenho uma consulta SQL na qual faço um agrupamento pela data, mas aparentemente não está funcionando. Os registros com datas iguais aparecem em linhas diferentes.
O agrupamento por data pode ser um pouco “trapaceiro”. Um dos motivos da falha do agrupamento é que a maioria dos SGBDs também armazenam a hora nos campos de data, resultando em valores diferentes. Para exemplificar, considere os três registros abaixo:

DataPedido       |  TotalPedido
08/05/2015 09:15 |     1.200,00
08/05/2015 11:32 |     2.350,00
08/05/2015 16:05 |       890,00

Agora, considere também a consulta a seguir:

SELECT sum(Total) GROUP BY DataPedido

Ao executá-la, o resultado exibirá as três linhas separadas, e não apenas uma linha com o total do dia. Como a informação de hora é diferente em cada registro, o engine do banco de dados considera as informações como distintas, separando-as. Para agrupar estes registros, é necessário converter a coluna “DataPedido” para o tipo de data sem o horário. No Firebird, por exemplo, basta utilizar o CAST:

SELECT sum(Total) GROUP BY CAST(DataPedido AS DATE)

Além disso, também recomendo que as colunas de datas, quando forem utilizadas para agrupamentos, sejam criadas no banco de dados com o tipo DATE para evitar o armazenamento de horários.

 

(referente ao artigo “Diferença entre software e sistema“)
Pelo que você disse, quando temos um hardware envolvido, já podemos considerar como um sistema, certo? Vamos supor que exista um sistema de gerenciamento de Ordenha, no qual possui um hardware específico (uma pulseira) que gera informações sobre o gado. A empresa que desenvolve o sistema é a mesma que fabrica esse hardware ou é uma empresa terceirizada? Se for uma terceirizada, os desenvolvedores da pulseira disponibilizam uma API para que os desenvolvedores possam implementar a comunicação com o hardware?

Ótima pergunta! Sim, estes dispositivos geralmente são fabricados por uma empresa específica e vendidos para a empresa de desenvolvimento de software. Essa, por sua vez, os revende para os clientes como parte da solução proposta. As máquinas registradoras de cupons fiscais (ECFs) que encontramos em supermercados são um exemplo desse contexto. Estes equipamentos são fabricados por uma empresa e o software é desenvolvido por outra. Juntos, eles formam um sistema de frente de caixa.
Respondendo a segunda pergunta, sim, os fabricantes de hardwares devem disponibilizar bibliotecas (DLLs) e APIs, acompanhados de um manual de integração, para que os desenvolvedores possam implementar a comunicação através do software.

 

(referente ao artigo “[Delphi] Envio de e-mail com componentes Indy“)
Tentei adaptar o exemplo do artigo para enviar mensagens no formato HTML, mas não obtive sucesso.
Para enviar e-mails em formato HTML, é preciso configurar algumas propriedades do objeto TIdMessage, como abaixo:

IdMessage.CharSet := 'ISO-8859-1';
IdMessage.Encoding := meMIME;
IdMessage.ContentType := 'text/html';

 

(referente ao artigo “[Delphi] Envio de e-mail com componentes Indy“)
Quando tento enviar um e-mail formatado em HTML contendo um arquivo em anexo, o e-mail é recebido sem formatação, além de exibir a mensagem “This is a multi-part message in MIME format…”.
Este é um caso específico de envio que exige uma “separação” entre o corpo da mensagem e o anexo. A solução é utilizar um novo objeto da classe TIdText, conforme o exemplo a seguir:

var
  ...
  // novo objeto declarado
  IdText: TIdText;
begin
  ...
  IdText := TIdText.Create(IdMessage.MessageParts);
  IdText.Body.Add('<html>');
  IdText.Body.Add('<body>');
  IdText.Body.Add('<h1><font color = "red"> Teste Formatação </font></h1>');
  IdText.Body.Add('</body>');
  IdText.Body.Add('</html>');
  IdText.ContentType := 'text/html; charset=iso-8859-1';
 
  CaminhoAnexo := 'C:\Anexo.txt';
  if FileExists(CaminhoAnexo) then
    TIdAttachmentFile.Create(IdMessage.MessageParts, CaminhoAnexo);
 
  ...
end;

De qualquer forma, clique aqui e confira o artigo do blog que trata exclusivamente desse tema!

 

(referente ao artigo “[Delphi] Envio de e-mail com componentes Indy“)
Segui os passos do artigo, mas quando tento enviar o e-mail, recebo a mensagem “Could not load SSL Library”.
Para desenvolvedores que estão trabalhando com Delphi 7, recomendo utilizar uma versão específica das bibliotecas do Indy, que podem ser baixadas neste link.
Em adição, vale lembrar que as DLLs devem ser copiadas para a mesma pasta em que está o executável. As versões mais recentes do Delphi criam, por padrão, um subdiretório chamado “Debug” na pasta principal do projeto para gerar o executável compilado. Neste caso, é nesse subdiretório que as DLLs devem ser copiadas.

 

(referente ao artigo “[Delphi] Parametrizando o comportamento de um software“)
Se o desenvolvedor optar por parametrizar o software gravando os valores dos parâmetros no banco de dados, deve levar em consideração que a tabela será compartilhada com todos os usuários do software, portanto, o comportamento será o mesmo para todos eles. Talvez isso não seja o ideal.

Isso mesmo. Em um sistema multiusuário, se os parâmetros estiverem armazenados no banco de dados, eles serão compartilhados com todos os usuários da aplicação, ao menos que a tabela possua uma coluna para diferenciar os parâmetros de cada usuário. Por exemplo, além dos campos que mencionei do artigo, teríamos a coluna “CodigoUsuario” para que fosse possível gravar valores do mesmo parâmetro para usuários diferentes. Ao iniciar a aplicação, carregaríamos apenas as configurações do usuário conectado.

 

Qualquer pessoa que tenha o Firebird instalado pode ter acesso ao meu arquivo FDB com as credenciais padrão?
O usuário e senha padrão de bancos de dados Firebird são SYSDBA e masterkey, respectivamente. Porém, com o utilitário gsec.exe, é possível alterar estes dados e definir novas credenciais de administração do banco de dados. Sendo assim, mesmo que o arquivo FDB seja transportado para outro computador que tenha o Firebird instalado, não será possível acessá-lo com as credenciais padrão. Esse procedimento garante que somente o criador do banco de dados tenha acesso ao conteúdo das tabelas.

 

Para realizar o backup de um banco de dados Firebird, é preciso somente copiar o arquivo com extensão FDB?
Para fazer uma cópia de segurança (backup), você pode, sim, apenas copiar o arquivo FDB para outro diretório ou dispositivo. Porém, à medida que o banco de dados armazena mais informações, aumentando de tamanho físico, a cópia pode se tornar demorada e, talvez, inconsistente. Por esse motivo, recomendo o uso de um utilitário chamado gbak, disponibilizado na pasta bin do próprio Firebird. Além de confiável, este utilitário é exclusivo para essa finalidade e reduz o tamanho do arquivo em aproximadamente 70%.
Confira abaixo um exemplo de backup e restauração do banco de dados:

Backup:
gbak -b -v -user SYSDBA -password masterkey C:\Aplicativo\Banco.fdb C:\Backup\Banco.fbk
 
Restauração:
gbak -r -v -user SYSDBA -password masterkey C:\Backup\Banco.fbk C:\Aplicativo\Banco.fdb

 

(referente ao artigo “Escalabilidade e sustentabilidade em um ambiente corporativo“)
Na sua opinião, quais seriam os primeiros passos para tornar o código Delphi de um ERP de 7 anos sustentável, considerando um cenário atual sem testes unitários, sem padrões de projeto, fracamente orientado a objetos, com uma equipe SCRUM extremamente enxuta?
Ótima pergunta! Diga-se de passagem: esse é um cenário relativamente comum em algumas empresas de software. O projeto cresce sem uma arquitetura definida e a sustentabilidade infelizmente acaba sendo negligenciada.
Pois bem, a iniciativa é a parte mais difícil. No entanto, já me deparei com uma situação semelhante e talvez eu possa orientá-lo com base na sequência de procedimentos que executamos.
Em primeiro momento, padronizamos o código em geral. Utilizamos Notação Húngara em nome de variáveis, ajustamos os nomes de métodos (de “Consultar” para “ConsultarPedidosPorPeriodo”, por exemplo), declaramos constantes para letras e números (como “NUMERO_ITENS = 30”) e empregamos tipos enumerados. Em seguida, começamos a refatorar alguns métodos, iniciando pela aplicação do princípio SRP do SOLID. Pensamos que, com métodos pequenos, ficaria mais fácil declarar novas classes e estendê-las. Além disso, durante a refatoração, identificamos muita duplicação de código, principalmente de funções básicas, e buscamos centralizar as regras de negócio de acordo com seus respectivos conceitos.
Quando atingimos esse estágio, já era possível partir para a criação e divisão de algumas classes e, com isso, trabalhar também com heranças.
Embora tenha funcionado, devo dizer que levou um bom tempo. É importante realçar que nada impede que você, assim como outras empresas, inicie de uma forma diferente. O que importa é movimentar-se em direção à sustentabilidade do código!

 

Espero que não demore mais 7 meses para o próximo FAQ! Haha!
Abraço! 


Confira também os outros FAQs:

FAQ 01 | FAQ 02 | FAQ 03 | FAQ 04 | FAQ 05 | FAQ 06 | FAQ 07 | FAQ 08 | FAQ 09


 

Compartilhe!
Share on FacebookTweet about this on TwitterShare on LinkedInShare on Google+Pin on PinterestEmail this to someone

2 comentários

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Preencha o campo abaixo * Time limit is exhausted. Please reload CAPTCHA.