Diretivas de Compilação


Diretivas de Compilação podem lhe ajudar a tornar seu código multiplatform ou até mesmo cross-compiled.

Unsplash image Photo by Mathyas Kurmann on Unsplash

Introdução

Diretivas de Compilação são comandos poderosos que o desenvolvedor pode utilizar para customizar a compilação.

Essas Diretivas passam parâmetros para o compilador, informando os argumentos da compilação, como deve ser compilado e o que deve ser compilado.

Existem basicamente 3 tipos de Diretivas de Compilação:

  • Switch directive
  • Parameter directive
  • Conditional compilation directive

Os dois primeiros tipos alteram os parâmetros de compilação, enquanto o último altera o que o compilador irá executar.

Nesse artigo iremos tratar do último tipo: Condicionais.

Condicionais

Apesar de serem comandos poderosos, não devem ser utilizados levianamente.

Com apenas alguns comandos condicionais, seu código Free Pascal ou Delphi pode ser compilável em várias plataformas.

Entretanto, a medida que vamos adicionando mais e mais diretivas, o código irá ficar mais complexo.

Vejamos o exemplo abaixo:

No código acima apenas a 1º e 3º chamada da função Writeln serão executadas.

Todas as diretivas e também a 2º chamada de Writeln não irão fazer parte do executável final.

Bem legal.

Entretanto, veja que o código ficou bem “poluído” e também temos um acoplamento temporal, pois as constantes precisam ser definidas numa ordem específica.

Diretivas e Definições de constantes que serão utilizadas em apenas numa única Unidade podem até ser gerenciáveis, mas e se tivermos trabalhando com dezenas ou até centenas de Unidades que irão utilizar as mesmas diretivas e definições, ainda acha que essa abordagem é a melhor escolha para a arquitetura do seu projeto com a finalidade de construí-lo como multiplatform ou cross-compiled?

Eu acho que não, e é por isso que no Projeto James estamos utilizando uma abordagem diferente.

Encapsulando Diretivas

No James estamos codificando num estilo que chamo de Encapsulamento de Diretivas.

Um dos objetivos desse projeto é ser cross-compiled, ou seja, ele irá compilar em Free Pascal e Delphi. No entanto não queremos que nem os usuários (desenvolvedores) ou os autores desse projeto se preocupem com diretivas de compilação.

Atualmente não há diretivas condicionais no código de implementação dos métodos — com exceção da classe TXMLComponent que deverá ser refatorada em breve.

O motivo dessa abordagem é que não queremos nos preocupar em quebrar um código que é utilizado por 2 compiladores diferentes.

Não utilizamos arquivos “include” com pedaços de código.

Não utilizamos definições globais de condicionais.

Diretiva condicional é uma técnica procedural. Não gostamos.

Ao invés disso, utilizamos apenas Objetos.

Implementação

Imagine uma Unidade que contenha classes para representar a criptografia MD5.

No Free Pascal já temos uma Unidade md5 que tem funções que fazem esse trabalho — e é claro que temos que fazer Objetos para encapsular essas funções.

No Delphi a Unidade que faz o mesmo trabalho é denominada hash.

Não queremos “reinventar a roda”. Queremos utilizar o que já está pronto em ambas as plataformas.

Então, como fazer essa implementação sem utilizar diretivas condicionais no código de implementação ou arquivos de inclusão?

Bem, no James temos a Unidade James.Crypto.MD5.Clss com algumas classes que representam MD5.

Essa é a única Unidade (até a data desse artigo) que os usuários devem utilizar para trabalhar com MD5.

Nós, autores do projeto, poderíamos separar alguns desenvolvedores para trabalhar na implementação Free Pascal e outros para trabalhar na implementação Delphi, se assim o desejarmos, pois as implementações estão separadas em Unidades distintas.

Primeiro criamos mais duas Unidades que serão utilizadas pela James.Crypto.MD5.Clss: Uma para Free Pascal e outra para o Delphi.

São elas James.Crypto.MD5.FPC e James.Crypto.MD5.Delphi, respectivamente.

Veja abaixo como implementamos isso:

Ambas as Unidades possuem a definição da Classe TMD5Hash (sim, mesmo nome em ambas). Então bastou criar um alias (novamente, com o mesmo nome) direcionando TMD5Hash para a classe correta (dependente da plataforma) e, voilá!, temos uma Unidade “limpa” e sem condicionais na implementação dos métodos.

Agora temos duas Classes distintas, em Unidades diferentes, que podem evoluir independentemente sem receio de quebrar o código entre plataformas.

A clase TMD5Stream não tem nenhuma diferença entre os compiladores, então é implementada diretamente na Unidade James.Crypto.MD5.Clss.

Conclusão

Diretiva de Compilação é uma boa ferramenta para customização do código, porém deve ser utilizada com parcimônia.

No código Orientado a Objetos, dê preferência aos Objetos para resolver seus problemas.

Para cada diretiva condicional que você queira adicionar no código de implementação, sugiro implementar um novo Objeto que encapsule a diretiva.

Seu código ficará mais limpo e sustentável.

Até logo.


EDIT 2019-04-15:

  • Não estamos mais utilizando essa abordagem — procure o artigo “Usando Paths ao invés de Diretivas de Compilação”.
  • Classes de XML não fazem mais parte do James — veja Xavier.

Posts Relacionados

  • Memória Segura Utilizando Instâncias de Interfaces

  • Classes Mutáveis vs Objetos Imutáveis

  • Implementando Interfaces Utilizando Diferente Assinaturas de Métodos

  • Usando Paths ao invés de Diretivas de Compilação

  • Trabalhando com Exceções em Requisições HTTP

  • Tipo object Continua Vivo

  • Array de Objetos

  • Variáveis Locais Deveriam ter Nomes Curtos

  • Como Dividir e Organizar o Código em Formulários com Muitos Widgets

  • Pascal Deveria ser Modernizado?