Tecnologia além do código.
Régys Borges da Silveira

Estendendo o Delphi: Como Organizar seu Código usando Class Helpers e Record Helpers

helpers
Neste artigo 18 tópicos

Introdução: O Fim da uFuncoesUteis.pas

Todo desenvolvedor Delphi já encontrou (ou criou) a infame unit uFuncoes.pas ou uUtils.pas. É aquele arquivo gigantesco, repleto de procedimentos globais e funções soltas, que serve como um “canivete suíço” para o projeto.

Embora funcional, essa abordagem tem problemas arquiteturais:

  1. Baixa Coesão: Mistura validação de documentos, formatação de datas e manipulação de arquivos no mesmo lugar.
  2. Programação Procedural: Obriga você a escrever ValidaCNPJ(Cliente.CNPJ) em vez de uma abordagem orientada a objetos fluida.
  3. Poluição do Namespace: Inúmeras funções globais disponíveis o tempo todo.

Desde o Delphi 2005 (para .NET) e posteriormente para Win32, a Embarcadero introduziu os Helpers (Class Helpers e Record Helpers). Este recurso permite estender classes e tipos existentes sem a necessidade de herança, permitindo que você escreva códigos como Cliente.CNPJ.IsCNPJ ou Query.ToJSON.

Neste artigo, vamos explorar a fundo como utilizar Helpers para limpar seu código, analisando exemplos práticos, limitações técnicas e as melhores práticas.

O Que São Helpers?

Tecnicamente, um Helper é uma construção do compilador que permite adicionar métodos (procedures e functions) e propriedades a uma classe ou record (struct) existente.

Eles funcionam como “syntactic sugar” (açúcar sintático). Quando você chama MeuObjeto.MetodoDoHelper, o compilador traduz isso internamente para uma chamada estática passando a instância do objeto como parâmetro.

Sintaxe Básica

type
  TNomeDoHelper = class helper for TClasseAlvo
  public
    procedure NovoMetodo;
  end;

Cenário 1: Limpando Validações com Record Helpers

No Delphi moderno, tipos nativos como string, Integer e TDateTime são tratados como Records em muitos contextos, permitindo o uso de Record Helpers.

Vamos transformar a validação de documentos em uma extensão natural do tipo string.

Atenção: O Delphi já possui um TStringHelper nativo em System.SysUtils. Ao declarar um helper para string, você pode ocultar o helper padrão (veja a seção de Limitações).

Implementação: TStringValidationHelper

unit Lib.Helpers.Strings;

interface

uses
  System.SysUtils, System.RegularExpressions;

type
  // Usamos 'record helper' pois string é um tipo intrínseco/record
  TStringValidationHelper = record helper for string
  public
    function OnlyNumbers: string;
    function IsCNPJ: Boolean;
    function IsCPF: Boolean;
  end;

implementation

{ TStringValidationHelper }

function TStringValidationHelper.OnlyNumbers: string;
begin
  // Remove tudo que não for dígito (0-9)
  Result := TRegEx.Replace(Self, '[^0-9]', '');
end;

function TStringValidationHelper.IsCNPJ: Boolean;
var
  LInput: string;
begin
  LInput := Self.OnlyNumbers; // Chamando outro método do helper

  if LInput.Length <> 14 then
    Exit(False);

  // Lógica simplificada de validação (apenas para exemplo)
  // Adicione aqui o algoritmo de módulo 11 real
  Result := True; 
end;

function TStringValidationHelper.IsCPF: Boolean;
begin
  Result := Self.OnlyNumbers.Length = 11;
  // Implementar algoritmo de CPF
end;

end.

Como usar:

uses
  Lib.Helpers.Strings;

procedure TForm1.btnValidarClick(Sender: TObject);
var
  CNPJ: string;
begin
  CNPJ := '12.345.678/0001-99';

  // Leitura fluida e limpa
  if CNPJ.IsCNPJ then
    ShowMessage('Válido!')
  else
    ShowMessage('Inválido: ' + CNPJ.OnlyNumbers);
end;

Cenário 2: Turbinando Componentes com Class Helpers

Imagine exportar o conteúdo de um TDataSet (FireDAC, dbExpress, ou ClientDataSet) para JSON sem criar classes intermediárias ou usar componentes visuais pesados.

Implementação: TDataSetHelper

unit Lib.Helpers.DataSet;

interface

uses
  Data.DB, System.JSON, System.SysUtils;

type
  TDataSetHelper = class helper for TDataSet
  public
    function ToJSON: TJSONArray;
  end;

implementation

{ TDataSetHelper }

function TDataSetHelper.ToJSON: TJSONArray;
var
  LRow: TJSONObject;
  LField: TField;
  LBookMark: TBookmark;
begin
  Result := TJSONArray.Create;
  
  // Evita mover o cursor do dataset original permanentemente
  if Self.IsEmpty then Exit;
  
  Self.DisableControls;
  LBookMark := Self.GetBookmark;
  try
    Self.First;
    while not Self.Eof do
    begin
      LRow := TJSONObject.Create;
      
      for LField in Self.Fields do
      begin
        // Exemplo simplificado. Em produção, tratar tipos (Date, Blob, etc)
        case LField.DataType of
          ftInteger, ftLargeint: 
            LRow.AddPair(LField.FieldName, TJSONNumber.Create(LField.AsLargeInt));
          ftFloat, ftCurrency: 
            LRow.AddPair(LField.FieldName, TJSONNumber.Create(LField.AsFloat));
          else
            LRow.AddPair(LField.FieldName, LField.AsString);
        end;
      end;
      
      Result.AddElement(LRow);
      Self.Next;
    end;
    
    // Restaura posição original
    if Self.BookmarkValid(LBookMark) then
      Self.GotoBookmark(LBookMark);
      
  finally
    Self.FreeBookmark(LBookMark);
    Self.EnableControls;
  end;
end;

end.

Como usar:

uses
  Lib.Helpers.DataSet;

procedure TForm1.ExportarDados;
var
  LArray: TJSONArray;
begin
  // QueryPadrao é um TFDQuery ou TClientDataSet
  LArray := QueryPadrao.ToJSON;
  try
    Memo1.Lines.Text := LArray.ToString;
  finally
    LArray.Free;
  end;
end;

Detalhes Técnicos Críticos

Embora poderosos, os Helpers possuem regras estritas de escopo e limitações que todo arquiteto de software deve conhecer.

1. A Regra de Visibilidade (O “Último Vence”)

Esta é a regra mais importante: Apenas um Helper pode estar ativo para um tipo em determinado escopo.

Se você tem duas units, UnitA (com THelperA para TEdit) e UnitB (com THelperB para TEdit), e faz isso:

uses UnitA, UnitB;

Apenas os métodos de THelperB estarão visíveis para TEdit. O compilador não mescla os helpers. O helper definido na unit mais à direita da cláusula uses tem prioridade.

Dica de Ouro: Para tipos nativos como string, criar um helper próprio pode esconder os métodos padrão do Delphi (como ToString, Split, Join que residem em System.SysUtils.TStringHelper).

2. Sem Estado (No State)

Helpers não podem adicionar novos campos (variáveis de instância) à classe ou record.

  • ❌ FCount: Integer; (Proibido)
  • ✅ class var FCount: Integer; (Permitido, mas é estático/global para o tipo)
  • ✅ property Count: Integer read GetCount; (Permitido se usar métodos existentes)

3. Acesso a Private/Protected

Curiosamente, class helpers podem acessar membros private e protected da classe que estão estendendo (desde que estejam na mesma unit ou em versões do Delphi que permitam o “backdoor”). Observação: Embora útil para corrigir bugs em componentes de terceiros sem código fonte, usar helpers para violar encapsulamento deve ser evitado em design de código limpo.

4. Herança de Helpers

Um helper pode herdar de outro helper. Isso é útil para estender o TStringHelper padrão sem perder suas funcionalidades originais (dependendo da versão do Delphi, a herança de record helpers tem comportamentos específicos).

type
  TMeuHelper = class helper(TParentHelper) for TMinhaClasse
  ...
  end;

Prós e Contras

✅ Vantagens

  • Intellisense (Code Completion): Seus métodos aparecem ao digitar o ponto (.), facilitando a descoberta de funcionalidades por novos desenvolvedores.
  • Legibilidade: Data.ToISO8601 é mais legível que DateToISO8601(Data).
  • Extensibilidade: Permite adicionar funcionalidades a componentes da VCL/FMX ou bibliotecas de terceiros sem criar subclasses (ex: TEditEx).

❌ Desvantagens

  • Ambiguidade de Escopo: O desenvolvedor pode não saber de qual unit vem aquele método ToJSON se houver múltiplos helpers.
  • Ocultação acidental: Risco de esconder métodos padrão da linguagem se não gerenciar a cláusula uses corretamente.
  • Acoplamento Oculto: O código parece pertencer à classe, mas depende de uma unit externa.

Conclusão

Helpers são uma ferramenta formidável para modernizar bases de código Delphi. Eles permitem migrar de uma arquitetura procedural (baseada em units utilitárias globais) para uma abordagem mais orientada a objetos e fluida.

Ao substituir uFuncoes.pas por TStringHelper, TDataSetHelper ou TFileHelper, você não apenas organiza seu projeto, mas torna a escrita do código mais intuitiva. Contudo, use com sabedoria: mantenha o escopo controlado e evite criar “God Helpers” que fazem tudo.

Referências Validadas

  1. Embarcadero DocWiki – Class and Record Helpers: http://docwiki.embarcadero.com/RADStudio/Sydney/en/Class_and_Record_Helpers
  2. Object Pascal Handbook (Marco Cantù): Consulte a seção sobre Type Helpers para detalhes sobre a evolução do recurso desde o Delphi 2007.
  3. Clean Code no Delphi: A utilização de helpers alinha-se aos princípios de “Extension Methods” encontrados em outras linguagens modernas como C# e Kotlin.
Compartilhar por e-mail

0 comentário(s)

Participe da conversa

Seu e-mail não será publicado.

Este site utiliza o Akismet para reduzir spam. Saiba como seus dados em comentários são processados.

Um próximo passo para cada interesse.

Continue estudando com os livros, acompanhe as videoaulas ou consulte as soluções para o seu projeto.

Descubra mais sobre Régys Borges da Silveira

Assine agora mesmo para continuar lendo e ter acesso ao arquivo completo.

Continuar lendo