Melhor prática para passar muitos argumentos para o método?

90

Ocasionalmente, temos que escrever métodos que recebem muitos argumentos, por exemplo:

public void doSomething(Object objA , Object objectB ,Date date1 ,Date date2 ,String str1 ,String str2 )
{
}

Quando encontro esse tipo de problema, geralmente encapsulo os argumentos em um mapa.

Map<Object,Object> params = new HashMap<Object,Object>();
params.put("objA",ObjA) ;

......

public void doSomething(Map<Object,Object> params)
{
 // extracting params 
 Object objA = (Object)params.get("objA");
 ......
 }

Esta não é uma boa prática, encapsular parâmetros em um mapa é totalmente um desperdício de eficiência. O bom é que a assinatura limpa é fácil de adicionar outros parâmetros com o mínimo de modificação. qual é a melhor prática para esse tipo de problema?

Serrador
fonte

Respostas:

137

Em Effective Java , Capítulo 7 (Métodos), Item 40 (Projete as assinaturas do método com cuidado), Bloch escreve:

Existem três técnicas para encurtar listas de parâmetros excessivamente longas:

  • divida o método em vários métodos, cada um exigindo apenas um subconjunto dos parâmetros
  • criar classes auxiliares para conter grupos de parâmetros (normalmente classes de membros estáticos)
  • adapte o padrão Builder da construção do objeto à invocação do método.

Para mais detalhes, encorajo você a comprar o livro, vale muito a pena.

JRL
fonte
O que são "parâmetros excessivamente longos"? Quando podemos dizer que um método tem muitos parâmetros? Existe um número ou intervalo específico?
Red M
2
@RedM Eu sempre considerei qualquer coisa mais do que 3 ou 4 parâmetros como "excessivamente longo"
jtate
1
@jtate é uma escolha pessoal ou você está seguindo um documento oficial?
Red M de
1
@RedM preferência pessoal :)
jtate
2
Com a terceira edição do Effective Java, este é o capítulo 8 (métodos), item 51
GarethOwen
71

Usar um mapa com chaves de corda mágicas é uma má ideia. Você perde qualquer verificação de tempo de compilação e realmente não está claro quais são os parâmetros necessários. Você precisaria escrever uma documentação muito completa para compensar isso. Você vai se lembrar em algumas semanas o que são essas Strings sem olhar para o código? E se você cometeu um erro de digitação? Use o tipo errado? Você não descobrirá até que execute o código.

Em vez disso, use um modelo. Faça uma classe que será um recipiente para todos esses parâmetros. Dessa forma, você mantém a segurança de tipo do Java. Você também pode passar esse objeto para outros métodos, colocá-lo em coleções, etc.

Obviamente, se o conjunto de parâmetros não for usado em outro lugar ou transmitido, um modelo dedicado pode ser um exagero. Há um equilíbrio a ser alcançado, então use o bom senso.

tom
fonte
24

Se você tiver muitos parâmetros opcionais, pode criar uma API fluente: substitua o método único pela cadeia de métodos

exportWithParams().datesBetween(date1,date2)
                  .format("xml")
                  .columns("id","name","phone")
                  .table("angry_robots")
                  .invoke();

Usando a importação estática, você pode criar APIs fluentes internas:

... .datesBetween(from(date1).to(date2)) ...
Ha.
fonte
2
E se todos os parâmetros forem obrigatórios, não opcionais?
emeraldhieu
1
Você também pode ter parâmetros padrão desta forma. Além disso, o padrão do construtor está relacionado a interfaces fluentes. Esta realmente deveria ser a resposta, eu acho. Além de quebrar um construtor longo em métodos de inicialização menores que são opcionais.
Ehtesh Choudhury
13

É chamado de "Introduzir objeto de parâmetro". Se você passar a mesma lista de parâmetros em vários lugares, basta criar uma classe que contenha todos eles.

XXXParameter param = new XXXParameter(objA, objB, date1, date2, str1, str2);
// ...
doSomething(param);

Mesmo que você não passe a mesma lista de parâmetros com tanta frequência, essa fácil refatoração ainda melhorará a legibilidade do código, o que é sempre bom. Se você olhar seu código 3 meses depois, será mais fácil de compreender quando precisar corrigir um bug ou adicionar um recurso.

É uma filosofia geral, é claro, e como você não forneceu detalhes, também não posso dar conselhos mais detalhados. :-)

dimitarvp
fonte
a coleta de lixo será um problema?
rupinderjeet
Não se você tornar o escopo local do objeto de parâmetro na função do chamador e se não alterá-lo. É mais provável que seja coletado e sua memória reutilizada rapidamente sob tais circunstâncias.
dimitarvp
Imo, você também deve ter um XXXParameter param = new XXXParameter();disponível e, em seguida, usar XXXParameter.setObjA(objA); etc ...
satibel
10

Primeiro, tentaria refatorar o método. Se estiver usando tantos parâmetros, pode ser muito longo. Dividi-lo melhoraria o código e reduziria potencialmente o número de parâmetros para cada método. Você também pode refatorar toda a operação em sua própria classe. Em segundo lugar, procuraria outras instâncias em que estou usando o mesmo (ou superconjunto) da mesma lista de parâmetros. Se você tiver várias instâncias, isso provavelmente indicará que essas propriedades pertencem umas às outras. Nesse caso, crie uma classe para conter os parâmetros e usá-la. Por último, eu avaliaria se o número de parâmetros vale a pena criar um objeto de mapa para melhorar a legibilidade do código. Eu acho que esta é uma chamada pessoal - há dor em cada sentido com esta solução e onde o ponto de troca pode ser diferente. Por seis parâmetros, provavelmente não o faria. Por 10, provavelmente o faria (se nenhum dos outros métodos funcionasse primeiro).

Tvanfosson
fonte
8

Isso costuma ser um problema ao construir objetos.

Nesse caso, use o padrão de objeto do construtor , ele funciona bem se você tiver uma grande lista de parâmetros e nem sempre precisar de todos eles.

Você também pode adaptá-lo à chamada de método.

Também aumenta muito a legibilidade.

public class BigObject
{
  // public getters
  // private setters

  public static class Buider
  {
     private A f1;
     private B f2;
     private C f3;
     private D f4;
     private E f5;

     public Buider setField1(A f1) { this.f1 = f1; return this; }
     public Buider setField2(B f2) { this.f2 = f2; return this; }
     public Buider setField3(C f3) { this.f3 = f3; return this; }
     public Buider setField4(D f4) { this.f4 = f4; return this; }
     public Buider setField5(E f5) { this.f5 = f5; return this; }

    public BigObject build()
    {
      BigObject result = new BigObject();
      result.setField1(f1);
      result.setField2(f2);
      result.setField3(f3);
      result.setField4(f4);
      result.setField5(f5);
      return result;
    }
  }
}

// Usage:
BigObject boo = new BigObject.Builder()
  .setField1(/* whatever */)
  .setField2(/* whatever */)
  .setField3(/* whatever */)
  .setField4(/* whatever */)
  .setField5(/* whatever */)
  .build();

Você também pode colocar lógica de verificação nos métodos Builder set .. () e build ().

Bohdan
fonte
O que você recomendaria se muitos de seus campos fossem final? Essa é a principal coisa que me impede de escrever funções auxiliares. Suponho que posso tornar os campos privados e ter certeza de não modificá-los incorretamente no código dessa classe, mas espero algo mais elegante.
ragerdl
7

Existe um padrão chamado de objeto Parameter .

A ideia é usar um objeto no lugar de todos os parâmetros. Agora, mesmo que você precise adicionar parâmetros posteriormente, você só precisa adicioná-los ao objeto. A interface do método permanece a mesma.

Padmarag
fonte
5

Você pode criar uma classe para armazenar esses dados. Precisa ser significativo o suficiente, mas muito melhor do que usar um mapa (OMG).

Johannes Rudolph
fonte
Não acho que seja necessário criar uma classe para conter um parâmetro de método.
Sawyer,
Eu só criaria a classe se houvesse várias instâncias de passagem dos mesmos parâmetros. Isso sinalizaria que os parâmetros estão relacionados e provavelmente pertencem um ao outro de qualquer maneira. Se você estiver criando uma classe para um único método, a cura provavelmente é pior do que a doença.
tvanfosson
Sim - você pode mover os parâmetros relacionados para um DTO ou objeto de valor. Alguns dos parâmetros múltiplos são opcionais, ou seja, o método principal está sobrecarregado com esses parâmetros adicionais? Nesses casos, acho que é aceitável.
JoseK
Isso é o que eu quis dizer ao dizer que deve ser significativo o suficiente.
Johannes Rudolph
4

O Código Completo * sugere algumas coisas:

  • "Limite o número de parâmetros de uma rotina a cerca de sete. Sete é um número mágico para a compreensão das pessoas" (p. 108).
  • "Coloque os parâmetros na ordem de entrada-modificação-saída ... Se várias rotinas usam parâmetros semelhantes, coloque os parâmetros semelhantes em uma ordem consistente" (p 105).
  • Coloque as variáveis ​​de status ou erro por último.
  • Como tvanfosson mencionou, passe apenas as partes de uma variável estruturada (objetos) que a rotina precisa. Dito isso, se você estiver usando a maior parte da variável estruturada na função, basta passar a estrutura inteira, mas esteja ciente de que isso promove o acoplamento em algum grau.

* Primeira edição, sei que devo atualizar. Além disso, é provável que alguns desses conselhos tenham mudado desde que a segunda edição foi escrita, quando OOP estava começando a se tornar mais popular.

Justin Johnson
fonte
2

A boa prática seria refatorar. O que significa que esses objetos devem ser passados ​​para este método? Eles devem ser encapsulados em um único objeto?

GaryF
fonte
sim, eles deveriam. Por exemplo, um grande formulário de pesquisa tem muitas restrições não relacionadas e necessidades de paginação. você precisa passar currentPageNumber, searchCriteria, pageSize ...
Sawyer
2

Usar um mapa é uma maneira simples de limpar a assinatura da chamada, mas então você tem outro problema. Você precisa olhar dentro do corpo do método para ver o que o método espera naquele mapa, quais são os nomes das chaves ou quais tipos os valores têm.

Uma maneira mais limpa seria agrupar todos os parâmetros em um bean de objeto, mas isso ainda não corrige o problema inteiramente.

O que você tem aqui é um problema de design. Com mais de 7 parâmetros para um método você começará a ter problemas para lembrar o que eles representam e a ordem que eles têm. A partir daqui, você obterá muitos bugs apenas chamando o método na ordem de parâmetro errada.

Você precisa de um design melhor do aplicativo, não uma prática recomendada para enviar muitos parâmetros.


fonte
1

Crie uma classe de bean e defina todos os parâmetros (método setter) e passe este objeto de bean para o método.

Tony
fonte
1
  • Observe seu código e veja por que todos esses parâmetros são passados. Às vezes, é possível refatorar o próprio método.

  • Usar um mapa deixa seu método vulnerável. E se alguém usando seu método escrever incorretamente um nome de parâmetro ou postar uma string onde seu método espera um UDT?

  • Defina um objeto de transferência . Ele fornecerá, no mínimo, verificação de tipo; pode até ser possível executar alguma validação no ponto de uso, em vez de dentro do seu método.

Todos
fonte
0

Isso geralmente é uma indicação de que sua classe tem mais de uma responsabilidade (ou seja, sua classe tem DEMAIS).

Veja o princípio de responsabilidade única

para mais detalhes.

método auxiliar
fonte
0

Se você estiver passando muitos parâmetros, tente refatorar o método. Talvez esteja fazendo muitas coisas que não deveria fazer. Se esse não for o caso, tente substituir os parâmetros por uma única classe. Desta forma, você pode encapsular tudo em uma única instância de classe e passar a instância e não os parâmetros.

azamsharp
fonte
0

Eu diria que continue do jeito que você fez antes. O número de parâmetros em seu exemplo não é muito, mas as alternativas são muito mais horríveis.

  1. Mapa - Existe a questão da eficiência que você mencionou, mas o maior problema aqui são:

    • Os chamadores não sabem o que enviar sem se referir a
      outra coisa ... Você tem javadocs que afirmam exatamente quais chaves e
      valores são usados? Se você fizer isso (o que é ótimo), ter muitos parâmetros também não será um problema.
    • Torna-se muito difícil aceitar diferentes tipos de argumento. Você pode restringir os parâmetros de entrada a um único tipo ou usar Map <String, Object> e converter todos os valores. Ambas as opções são horríveis na maioria das vezes.
  2. Objetos de invólucro - isso apenas move o problema, visto que você precisa preencher o objeto de invólucro em primeiro lugar - em vez de diretamente para o seu método, será para o construtor do objeto de parâmetro. Para determinar se mover o problema é apropriado ou não depende da reutilização do referido objeto. Por exemplo:

Não usaria: Seria usado apenas uma vez na primeira chamada, então muito código adicional para lidar com 1 linha ...?

{
    AnObject h = obj.callMyMethod(a, b, c, d, e, f, g);
    SomeObject i = obj2.callAnotherMethod(a, b, c, h);
    FinalResult j = obj3.callAFinalMethod(c, e, f, h, i);
}

Pode usar: aqui, pode fazer um pouco mais. Primeiro, ele pode fatorar os parâmetros para 3 chamadas de método. ele também pode executar 2 outras linhas em si mesmo ... então se torna uma variável de estado em certo sentido ...

{
    AnObject h = obj.callMyMethod(a, b, c, d, e, f, g);
    e = h.resultOfSomeTransformation();
    SomeObject i = obj2.callAnotherMethod(a, b, c, d, e, f, g);
    f = i.somethingElse();
    FinalResult j = obj3.callAFinalMethod(a, b, c, d, e, f, g, h, i);
}
  1. Padrão Builder - este é um anti-padrão na minha opinião. O mecanismo de tratamento de erros mais desejável é detectar antes, não depois; mas com o padrão do construtor, as chamadas com parâmetros obrigatórios ausentes (o programador não pensou em incluí-lo) são movidas do tempo de compilação para o tempo de execução. Claro, se o programador intencionalmente colocar null ou algo semelhante no slot, isso será o tempo de execução, mas ainda detectar alguns erros antes é uma vantagem muito maior do que atender aos programadores que se recusam a olhar os nomes dos parâmetros do método que estão chamando. Acho que é apropriado apenas ao lidar com um grande número de parâmetros opcionais e, mesmo assim, o benefício é marginal, na melhor das hipóteses. Sou totalmente contra o "padrão" do construtor.

Outra coisa que as pessoas esquecem de considerar é o papel do IDE em tudo isso. Quando os métodos têm parâmetros, os IDEs geram a maior parte do código para você e você tem as linhas vermelhas lembrando o que você precisa fornecer / definir. Ao usar a opção 3 ... você perde isso completamente. Agora cabe ao programador acertar, e não há pistas durante a codificação e o tempo de compilação ... o programador deve testá-lo para descobrir.

Além disso, as opções 2 e 3, se adotadas de forma ampla desnecessariamente, têm implicações negativas de longo prazo em termos de manutenção devido à grande quantidade de código duplicado que gera. Quanto mais código houver, mais há para manter, mais tempo e dinheiro é gasto para mantê-lo.

John
fonte