Ocasionalmente, encontro métodos com um número desconfortável de parâmetros. Na maioria das vezes, eles parecem ser construtores. Parece que deveria haver uma maneira melhor, mas não consigo ver qual é.
return new Shniz(foo, bar, baz, quux, fred, wilma, barney, dino, donkey)
Pensei em usar structs para representar a lista de parâmetros, mas isso parece apenas deslocar o problema de um lugar para outro e criar outro tipo no processo.
ShnizArgs args = new ShnizArgs(foo, bar, baz, quux, fred, wilma, barney, dino, donkey)
return new Shniz(args);
Portanto, isso não parece uma melhoria. Então, qual é a melhor abordagem?
refactoring
recursivo
fonte
fonte
Respostas:
A melhor maneira seria encontrar maneiras de agrupar os argumentos. Isso pressupõe, e realmente só funciona se, você acabar com vários "agrupamentos" de argumentos.
Por exemplo, se você está passando a especificação de um retângulo, pode passar x, y, largura e altura ou pode apenas passar um objeto retângulo que contém x, y, largura e altura.
Procure coisas assim ao refatorar para limpá-lo um pouco. Se os argumentos realmente não podem ser combinados, comece a ver se você violou o Princípio da Responsabilidade Única.
fonte
Vou supor que você quer dizer C # . Algumas dessas coisas também se aplicam a outros idiomas.
Você tem várias opções:
mudar de construtor para configuradores de propriedade . Isso pode tornar o código mais legível, porque é óbvio para o leitor qual valor corresponde a quais parâmetros. A sintaxe do Object Initializer torna isso bonito. Também é simples de implementar, pois você pode apenas usar as propriedades geradas automaticamente e pular a escrita dos construtores.
No entanto, você perde a imutabilidade e a capacidade de garantir que os valores necessários sejam definidos antes de usar o objeto em tempo de compilação.
Padrão de construtor .
Pense na relação entre
string
eStringBuilder
. Você pode conseguir isso para suas próprias aulas. Gosto de implementá-lo como uma classe aninhada, portanto, classeC
tem classe relacionadaC.Builder
. Também gosto de uma interface fluente no construtor. Feito da maneira certa, você pode obter uma sintaxe como esta:Eu tenho um script do PowerShell que me permite gerar o código do construtor para fazer tudo isso, onde a entrada se parece com:
Assim, posso gerar em tempo de compilação.
partial
classes me permitem estender a classe principal e o construtor sem modificar o código gerado.Refatoração "Introduce Parameter Object" . Veja o Catálogo de Refatoração . A ideia é que você pegue alguns dos parâmetros que está passando e os coloque em um novo tipo e, em seguida, passe uma instância desse tipo. Se você fizer isso sem pensar, você acabará de volta ao ponto de partida:
torna-se
No entanto, essa abordagem tem o maior potencial para causar um impacto positivo em seu código. Portanto, continue seguindo estas etapas:
Procure subconjuntos de parâmetros que façam sentido juntos. Apenas agrupar descuidadamente todos os parâmetros de uma função não é muito útil; o objetivo é ter agrupamentos que façam sentido. Você saberá que acertou quando o nome do novo tipo for óbvio.
Procure outros lugares onde esses valores são usados juntos e use o novo tipo lá também. Provavelmente, quando você encontrar um bom tipo novo para um conjunto de valores que já usa em todos os lugares, esse novo tipo fará sentido em todos esses lugares também.
Procure a funcionalidade que está no código existente, mas pertence ao novo tipo.
Por exemplo, talvez você veja algum código parecido com:
Você poderia tomar as
minSpeed
emaxSpeed
parâmetros e colocá-los em um novo tipo:Isso é melhor, mas para realmente aproveitar as vantagens do novo tipo, mova as comparações para o novo tipo:
E agora estamos chegando a algum lugar: a implementação de
SpeedIsAcceptable()
now diz o que você quer dizer e você tem uma classe útil e reutilizável. (O próximo passo óbvio é fazerSpeedRange
emRange<Speed>
.)Como você pode ver, Introduce Parameter Object foi um bom começo, mas seu valor real foi que nos ajudou a descobrir um tipo útil que estava faltando em nosso modelo.
fonte
Se for um construtor, especialmente se houver várias variantes sobrecarregadas, você deve olhar para o padrão Builder:
Se for um método normal, você deve pensar sobre as relações entre os valores que estão sendo passados e talvez criar um Transfer Object.
fonte
A resposta clássica para isso é usar uma classe para encapsular alguns ou todos os parâmetros. Em teoria, isso parece ótimo, mas sou o tipo de cara que cria classes para conceitos que têm significado no domínio, então nem sempre é fácil aplicar esse conselho.
Por exemplo, em vez de:
Você poderia usar
YMMV
fonte
Este é citado do livro de Fowler e Beck: "Refactoring"
fonte
Não quero soar como um espertinho, mas você também deve verificar para ter certeza de que os dados que você está passando realmente devem ser repassados: Passar coisas para um construtor (ou método para esse assunto) cheira um pouco a pouca ênfase no comportamento de um objeto.
Não me interpretem mal: métodos e construtores vai ter um monte de parâmetros, às vezes. Mas, quando encontrado, tente considerar o encapsulamento de dados com comportamento .
Esse tipo de cheiro (já que estamos falando de refatoração, essa palavra horrível parece apropriada ...) também pode ser detectado para objetos que têm muitas (leia-se: qualquer) propriedades ou getters / setters.
fonte
Quando vejo longas listas de parâmetros, minha primeira pergunta é se esta função ou objeto está fazendo muito. Considerar:
Claro que este exemplo é deliberadamente ridículo, mas eu vi muitos programas reais com exemplos apenas um pouco menos ridículos, onde uma classe é usada para conter muitas coisas mal relacionadas ou não relacionadas, aparentemente apenas porque o mesmo programa de chamada precisa de ambos ou porque o o programador pensou em ambos ao mesmo tempo. Às vezes, a solução fácil é apenas dividir a classe em várias partes, cada uma delas fazendo sua própria função.
Um pouco mais complicado é quando uma classe realmente precisa lidar com várias coisas lógicas, como um pedido do cliente e informações gerais sobre o cliente. Nesses casos, crie uma classe para o cliente e uma classe para o pedido, e deixe que conversem entre si quando necessário. Então, em vez de:
Poderíamos ter:
Embora eu prefira funções que usem apenas 1 ou 2 ou 3 parâmetros, às vezes temos que aceitar que, realisticamente, essa função exige muito e que o número por si só não cria complexidade. Por exemplo:
Sim, é um monte de campos, mas provavelmente tudo o que vamos fazer com eles é salvá-los em um registro de banco de dados ou jogá-los em uma tela ou algo parecido. Não há realmente muito processamento aqui.
Quando minhas listas de parâmetros ficam longas, prefiro dar aos campos diferentes tipos de dados. Como quando vejo uma função como:
E então eu vejo que é chamado com:
Eu fico preocupado. Olhando para a chamada, não está claro o que todos esses números, códigos e sinalizadores crípticos significam. Isso é apenas pedir erros. Um programador pode facilmente ficar confuso sobre a ordem dos parâmetros e acidentalmente trocar dois, e se eles forem do mesmo tipo de dados, o compilador simplesmente os aceitaria. Eu prefiro ter uma assinatura onde todas essas coisas são enums, então uma chamada passa em coisas como Type.ACTIVE em vez de "A" e CreditWatch.NO em vez de "false", etc.
fonte
Se alguns dos parâmetros do construtor forem opcionais, faz sentido usar um construtor, que obteria os parâmetros necessários no construtor e teria métodos para os opcionais, retornando o construtor, a serem usados desta forma:
Os detalhes disso são descritos em Effective Java, 2nd Ed., P. 11. Para argumentos de método, o mesmo livro (p. 189) descreve três abordagens para encurtar listas de parâmetros:
DinoDonkey
vez dedino
edonkey
fonte
Eu usaria o construtor padrão e os settors de propriedade. C # 3.0 tem uma ótima sintaxe para fazer isso de forma automática.
A melhoria do código vem simplificando o construtor e não tendo que oferecer suporte a vários métodos para oferecer suporte a várias combinações. A sintaxe de "chamada" ainda é um pouco "prolixa", mas não é realmente pior do que chamar os definidores de propriedade manualmente.
fonte
Você não forneceu informações suficientes para justificar uma boa resposta. Uma longa lista de parâmetros não é inerentemente ruim.
pode ser interpretado como:
Nesse caso, é muito melhor criar uma classe para encapsular os parâmetros, porque você dá significado aos diferentes parâmetros de uma forma que o compilador pode verificar e também torna o código mais fácil de ler visualmente. Também torna mais fácil ler e refatorar posteriormente.
Alternativamente, se você tivesse:
Este é um caso muito diferente porque todos os objetos são diferentes (e não é provável que sejam confundidos). Concordou que se todos os objetos são necessários, e são todos diferentes, faz pouco sentido criar uma classe de parâmetro.
Além disso, alguns parâmetros são opcionais? Existem substituições de método (mesmo nome de método, mas assinaturas de método diferentes?) Todos esses tipos de detalhes importam quanto à melhor resposta.
* Uma bolsa de propriedades também pode ser útil, mas não especificamente melhor, visto que não há informações básicas fornecidas.
Como você pode ver, há mais de uma resposta correta para essa pergunta. Faça sua escolha.
fonte
Você pode tentar agrupar seu parâmetro em múltiplas estruturas / classes significativas (se possível).
fonte
Eu geralmente me inclino para a abordagem de estruturas - presumivelmente a maioria desses parâmetros está relacionada de alguma forma e representa o estado de algum elemento que é relevante para o seu método.
Se o conjunto de parâmetros não puder ser transformado em um objeto significativo, provavelmente é um sinal de que
Shniz
está fazendo muito, e a refatoração deve envolver a divisão do método em questões separadas.fonte
Você pode trocar complexidade por linhas de código-fonte. Se o método em si fizer muito (canivete), tente reduzir as tarefas pela metade criando outro método. Se o método for simples, apenas precisa de muitos parâmetros, então os chamados objetos de parâmetro são o caminho a percorrer.
fonte
Se sua linguagem suportar, use parâmetros nomeados e torne tantos opcionais (com padrões razoáveis) quanto possível.
fonte
Acho que o método que você descreveu é o caminho a percorrer. Quando encontro um método com muitos parâmetros e / ou que provavelmente precisará de mais no futuro, geralmente crio um objeto ShnizParams para passar, como você descreve.
fonte
Que tal não configurá-lo de uma só vez nos construtores, mas sim por meio de propriedades / configuradores ? Eu vi algumas classes .NET que utilizam essa abordagem, como
Process
classe:fonte
Eu concordo com a abordagem de mover os parâmetros para um objeto de parâmetro (estrutura). Em vez de apenas colocá-los todos em um único objeto, analise se outras funções usam grupos de parâmetros semelhantes. Um objeto de parâmetro é mais valioso se for usado com várias funções em que você espera que esse conjunto de parâmetros mude consistentemente entre essas funções. Pode ser que você coloque apenas alguns dos parâmetros no novo objeto de parâmetro.
fonte
Se você tiver tantos parâmetros, é provável que o método esteja fazendo muito, então resolva isso primeiro dividindo o método em vários métodos menores. Se você ainda tiver muitos parâmetros depois disso, tente agrupar os argumentos ou transformar alguns dos parâmetros em membros de instância.
Prefira classes / métodos pequenos em vez de grandes. Lembre-se do princípio da responsabilidade única.
fonte
Argumentos nomeados são uma boa opção (presumindo uma linguagem que os suporte) para eliminar a ambigüidade de listas de parâmetros longas (ou mesmo curtas!), Ao mesmo tempo que permite (no caso de construtores) que as propriedades da classe sejam imutáveis sem impor um requisito para permitir sua existência em um estado parcialmente construído.
A outra opção que eu procuraria ao fazer esse tipo de refatoração seriam grupos de parâmetros relacionados que poderiam ser mais bem tratados como um objeto independente. Usando a classe Rectangle de uma resposta anterior como exemplo, o construtor que usa parâmetros para x, y, altura e largura poderia fatorar xey em um objeto Point, permitindo que você passasse três parâmetros para o construtor do Rectangle. Ou vá um pouco mais longe e faça dois parâmetros (UpperLeftPoint, LowerRightPoint), mas isso seria uma refatoração mais radical.
fonte
Depende de que tipo de argumentos você tem, mas se eles forem muitos valores / opções booleanas, talvez você possa usar um Sinalizador Enum?
fonte
Acho que esse problema está profundamente ligado ao domínio do problema que você está tentando resolver com a classe.
Em alguns casos, um construtor de 7 parâmetros pode indicar uma hierarquia de classes ruim: nesse caso, a estrutura / classe auxiliar sugerida acima é geralmente uma boa abordagem, mas você também tende a acabar com cargas de estruturas que são apenas pacotes de propriedade e não faça nada útil. O construtor de 8 argumentos também pode indicar que sua classe é muito genérica / muito multifacetada, portanto, precisa de muitas opções para ser realmente útil. Nesse caso, você pode refatorar a classe ou implementar construtores estáticos que ocultam os construtores complexos reais: por exemplo. Shniz.NewBaz (foo, bar) poderia realmente chamar o construtor real passando os parâmetros corretos.
fonte
Uma consideração é qual dos valores seria somente leitura depois que o objeto fosse criado?
Propriedades graváveis publicamente talvez possam ser atribuídas após a construção.
Em última análise, de onde vêm os valores? Talvez alguns valores sejam verdadeiramente externos, enquanto outros são realmente de alguma configuração ou dados globais mantidos pela biblioteca.
Nesse caso, você pode ocultar o construtor do uso externo e fornecer uma função Criar para ele. A função de criação pega os valores verdadeiramente externos e constrói o objeto, então usa acessores disponíveis apenas para a biblioteca para completar a criação do objeto.
Seria realmente estranho ter um objeto que requer 7 ou mais parâmetros para dar ao objeto um estado completo e todos sendo verdadeiramente de natureza externa.
fonte
Quando um clas tem um construtor que aceita muitos argumentos, geralmente é um sinal de que ele tem muitas responsabilidades. Ele provavelmente pode ser dividido em classes separadas que cooperam para fornecer as mesmas funcionalidades.
No caso de você realmente precisar de tantos argumentos para um construtor, o padrão Builder pode ajudá-lo. O objetivo é ainda passar todos os argumentos para o construtor, de forma que seu estado seja inicializado desde o início e você ainda possa tornar a classe imutável, se necessário.
Ver abaixo :
fonte