Orientações sobre parâmetros de um método

Orientações sobre parâmetros de um métodoFala, pessoal, tudo certo?
Sabemos que refatoração, extração de métodos e subrotinas nos ajudam bastante na programação, principalmente quando o assunto é reutilização de código. Na declaração de novos métodos, algumas vezes é necessário criar alguns parâmetros para que o método execute adequadamente as operações internas. O artigo de hoje é justamente relacionado com estes parâmetros! Acompanhe!

 

Como parte das técnicas de melhoria da expressividade no código, os parâmetros de métodos também devem receber uma atenção especial. Durante o desenvolvimento de um projeto, estaremos em um constante trabalho de criação e consumo de vários métodos e gostaríamos que a interpretação de cada um deles fosse simples. Desejamos que, ao acessar a declaração ou implementação de um método, seja fácil compreendê-lo. Por isso, a seguir, apresento 5 dicas sobre como aplicar as técnicas de Clean Code na escrita e utilização de parâmetros de métodos.

 

1) Nomenclatura
Essa dica é primordial, claro! Quando declaramos variáveis internas, empregamos prefixos e nomes que sejam condizentes com o que elas armazenarão, não é? Para os parâmetros de métodos, devemos seguir o mesmo critério, porém, com um detalhe: nomeá-los de uma forma particular para indicarmos que são parâmetros ao invés de variáveis. Uma sugestão é adicionar o prefixo “p” (de parâmetro), acompanhado do prefixo do tipo e, finalmente, do nome. Veja o exemplo abaixo:

procedure VerificarPedidosPendentes(
  pdtDataInicial, pdtDataFinal: TDateTime; pnCodCliente: integer);

Na implementação do método, saberemos que as variáveis que começam com o prefixo “p” são, na realidade, parâmetros. Saberemos também que, se o prefixo “dt” vem logo em seguida, significa que o parâmetro é do tipo data. Uma das vantagens dessa dica é permitir uma melhor orientação durante a leitura do método, caso seja necessário realizar uma manutenção ou interpretar a regra de negócio.

 

2) Parâmetros constantes
Opa, tem algo faltando na dica anterior! Na implementação do método, não gostaríamos que as datas inicial e final recebam valores diferentes, já que podem comprometer a funcionalidade ou mesmo o retorno. Devemos “proteger” os valores que vieram por parâmetro para que não sejam modificados. Para isso, podemos adicionar a palavra-chave const na declaração dos parâmetros, indicando que só a leitura é permitida:

procedure VerificarPedidosPendentes(
  const pdtDataInicial, pdtDataFinal: TDateTime; const pnCodCliente: integer);

No exemplo acima, os valores de “pdtDataInicial”, “pdtDataFinal” e “pnCodCliente” não poderão ser alterados dentro do método.

 

3) Parâmetros default
Algumas vezes, pode ser necessário definir um valor padrão para um método, caso ele não seja informado. Dessa forma, o método irá funcionar como esperado, sem apresentar erros ou inconsistências devido à falta de algum dado. Bom, talvez você tenha pensado na seguinte solução:

procedure CalcularIPI(const pnCodProduto: integer; pnPorcentagemIPI: real);
begin
  if pnPorcentagemIPI = 0 then
    pnPorcentagemIPI = 5;
 
  ...
end;

Porém, há uma forma mais “limpa” de definir este valor padrão. Basta indicá-lo na própria assinatura do método:

// declaração
procedure CalcularIPI(const pnCodProduto: integer; pnPorcentagemIPI: real = 5);

No método chamador, por sua vez, nem precisaremos informar um valor para este parâmetro, já que, em sua ausência, o valor “5” será assumido!

var
  nCodProduto: integer;
  nValorIPI: real;
begin
  nCodProduto := StrToInt(Edit1.Text);
  nValorIPI := CalcularIPI(nCodProduto); // apenas um parâmetro foi informado
end;

 

4) Quantidade de parâmetros
Já comentei essa dica na segunda parte do artigo sobre expressividade no código. Vale dar uma passadinha por lá!
Quando o número de parâmetros começa a crescer (diga-se, mais de três parâmetros), o recomendado é criar um objeto que contenha todos esses valores e seja passado como um parâmetro único, dispensando o uso excessivo de vírgulas na chamada do método. Sendo assim, ao invés da chamada abaixo:

if EnviarEmailCliente(Edit1.Text, Memo1.Lines.Text,
   RadioGroup1.ItemIndex, ComboBox1.Text, ListBox1.Items.Text, OpenDialog1.FileName) then
    ShowMessage('E-mail enviado com sucesso!');

Eu recomendaria a forma a seguir:

var
  objParametros: TParametros;
begin
  objParametros := TParametros.Create;
  try 
    objParametros.Assunto := Edit1.Text;
    objParametros.Mensagem := Memo1.Lines.Text;
    objParametros.TipoFormatacao := RadioGroup1.ItemIndex;
    objParametros.ContaEmail := ComboBox1.Text;
    objParametros.Destinatarios := ListBox1.Items.Text;
    objParametros.Anexo := OpenDialog1.FileName;
 
    if EnviarEmailCliente(objParametros) then
      ShowMessage('E-mail enviado com sucesso!');
  finally
    FreeAndNil(objParametros);
  end;
end;

O número de linhas de código pode ser relativamente maior, mas, por outro lado, o código definitivamente se torna mais autoexplicativo.

 

5) Parâmetros de saída
A diferença entre procedimentos e funções é que essas retornam um valor para o método chamador, como a função abaixo, que retorna um valor do tipo integer:

// declaração
function SomarQtdeComprasClientes: integer;
 
// chamada do método
var
  nQtdeCompras: integer;
begin
  nQtdeCompras := SomarQtdeComprasCliente;
  ...
end;

Porém, em algumas ocasiões, é necessário que um método retorne mais de um valor! É aqui que os parâmetros de saída entram em ação como uma alternativa. No exemplo abaixo, duas variáveis são criadas no método chamador e passadas como parâmetros de saída para outro método (utilizando a palavra reservada out do Delphi), no qual irá preenchê-las. Tecnicamente, então, podemos dizer que o método “retorna” dois valores.

// declaração
procedure ConsultarDadosComprasCliente(out pnQtdeCompras: integer; out pnTotalCompras: real);
 
// chamada do método
var
  nQtdeCompras: integer;
  nTotalCompras: real;
begin
  ConsultarDadosComprasCliente(nQtdeCompras, nTotalCompras);
  // após a chamada, as duas variáveis "voltam" preenchidas
end;

A minha dica será controversa. Muitos desenvolvedores provavelmente irão discordá-la, mas acho interessante compartilhar a minha visão. De modo geral, devo dizer que sou bastante cético quanto ao uso de parâmetros de saída. Um dos motivos é que, parâmetros, por si só, já são interpretados como entrada. Quando utilizamos um método, assumimos que os parâmetros que informaremos serão subsídios para que ele funcione, e, convenhamos, é a maneira lógica de se pensar. Na minha opinião, ao invés de criar um método que retorne dois valores, é melhor criar dois métodos que retornem apenas um valor.
Além disso, parâmetros de saída também podem confundir o desenvolvedor em alguns casos. Considere a chamada abaixo, baseada no livro de Robert Martin:

AdicionarTexto(sTexto);

Como você a interpreta? A variável “sTexto” é uma entrada (será adicionada a um texto já existente), ou uma saída (um texto será adicionado à variável “sTexto”)? A solução, claro, é verificar a declaração do método. Se estiver em outra classe, será necessário acessá-la. Embora imperceptível, esse esforço consome um tempo e interrompe o raciocínio. A minha sugestão é evitar parâmetros de saída sempre que possível.

 

E você? Tem mais alguma recomendação?
Deixe um comentário!

Abraço!


 

4 comentários

  1. Excelente Artigo, muito bom mesmo, Parabéns! Continue escrevendo que está ajudando muitos desenvolvedores como eu. Obrigado!

    1. Opa! Muito obrigado!
      Pode deixar. Continuarei escrevendo mais artigos!
      Abraço!

  2. E aí André, tudo bem?
    Muito bom o seu artigo, como de costume. Parabéns!
    Entendi bem a solução para o excesso de parâmetros em métodos, o que eu estou em dúvida é quanto ao excessos de parâmetros em construtores. Pois passo como parâmetros no construtor os atributos da classe que dependendo da quantidade de atributos que essa classe possuir, ficará inviável passar todos como parâmetros no construtor. No caso poderia usar um objeto como parâmetro, mas se for do tipo da classe ele será inútil, porque assim que eu popular o objeto que é parâmetro do construtor, já estaria passando esses valores para os atributos via property´s e não precisaria passá-lo para o construtor. Desculpe se ficou confuso a minha dúvida.

    1. Olá, Aleandro, bom dia!
      Obrigado pelo feedback sobre o artigo.
      Construtores são casos um pouco particulares nessa questão de parâmetros. Uma das formas é passar um objeto no construtor de uma classe, usando-o para alimentar as variáveis internas:

      construtor TClasse.Create(Parametros: TParametros);
      begin
        inherited;
        
        FVariavel1 := Parametros.Variavel1;
        FVariavel2 := Parametros.Variavel2;
        FVariavel3 := Parametros.Variavel3;
      end;

      Outra forma – talvez mais adequada por deixar bem explícito os valores que a classe exige – é usar propriedades, preenchendo-as no chamador:

      var
        Objeto: TClasse;
      begin
        Objeto := TClasse.Create;
        Objeto.Variavel1 := Variavel1;
        Objeto.Variavel2 := Variavel2;
        Objeto.Variavel3 := Variavel3;
      
        Objeto.ExecutarAcao;
        Objeto.Free;
      end;

      O que deve-se evitar sempre que possível é criar objetos dentro do construtor, já que aumenta o acoplamento entre classes.

      Abraço!

Deixe uma resposta

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