Percebi que algumas funções com as quais trabalho têm 6 ou mais parâmetros, enquanto na maioria das bibliotecas eu uso é raro encontrar uma função com mais de 3.
Muitas vezes, muitos desses parâmetros extras são opções binárias para alterar o comportamento da função. Eu acho que algumas dessas funções com muitos parâmetros provavelmente deveriam ser refatoradas. Existe uma diretriz para qual número é demais?
functions
parameters
Darth Egregious
fonte
fonte
Respostas:
Eu nunca vi uma diretriz, mas, na minha experiência, uma função que leva mais de três ou quatro parâmetros indica um dos dois problemas:
É difícil dizer o que você está vendo sem mais informações. Provavelmente, a refatoração que você precisa fazer é dividir a função em funções menores, chamadas pelo pai, dependendo dos sinalizadores que estão sendo transmitidos atualmente para a função.
Existem alguns bons ganhos ao fazer isso:
if
estrutura que chama muitos métodos com nomes descritivos do que uma estrutura que faz tudo em um único método.fonte
De acordo com "Código Limpo: Um Manual de Artesanato em Software Ágil", zero é o ideal, um ou dois são aceitáveis, três em casos especiais e quatro ou mais, nunca!
As palavras do autor:
Neste livro, há um capítulo falando apenas sobre funções em que os parâmetros são amplos discutidos, então acho que este livro pode ser uma boa diretriz de quantos parâmetros você precisa.
Na minha opinião pessoal, um parâmetro é melhor que ninguém, porque acho mais claro o que está acontecendo.
Como exemplo, na minha opinião, a segunda opção é melhor, porque é mais claro o que o método está processando:
vs.
Sobre muitos parâmetros, isso pode ser um sinal de que algumas variáveis podem ser agrupadas em um único objeto ou, nesse caso, muitos booleanos podem representar que a função / método está fazendo mais do que uma coisa e, neste caso, é melhor refatorar cada um desses comportamentos em uma função diferente.
fonte
String language = detectLanguage(someText);
. Em qualquer um dos seus casos, você passou exatamente o mesmo número de argumentos; acontece que você dividiu a execução da função em dois por causa de uma linguagem ruim.Matrix.Create(input);
ondeinput
está, digamos, um .NETIEnumerable<SomeAppropriateType>
? Dessa forma, você também não precisa de uma sobrecarga separada quando você quer criar uma matriz segurando 10 elementos em vez de 9.Se as classes de domínio no aplicativo forem projetadas corretamente, o número de parâmetros que passamos para uma função será automaticamente reduzido - porque as classes sabem como fazer seu trabalho e possuem dados suficientes para fazê-lo.
Por exemplo, digamos que você tenha uma turma de gerentes que solicite uma turma da 3ª série para concluir as tarefas.
Se você modelar corretamente,
Isto é simples.
Se você não possui o modelo correto, o método será assim
O modelo correto sempre reduz os parâmetros de função entre as chamadas de método, pois as funções corretas são delegadas para suas próprias classes (responsabilidade única) e elas possuem dados suficientes para realizar seu trabalho.
Sempre que vejo o número de parâmetros aumentando, verifico meu modelo para ver se eu projetei meu modelo de aplicativo corretamente.
Porém, existem algumas exceções: Quando eu precisar criar um objeto de transferência ou objetos de configuração, usarei um padrão de construtor para produzir objetos pequenos antes de construir um grande objeto de configuração.
fonte
Um aspecto que as outras respostas não abordam é o desempenho.
Se você estiver programando em uma linguagem de nível suficientemente baixo (C, C ++, assembly), um grande número de parâmetros poderá prejudicar o desempenho em algumas arquiteturas, especialmente se a função for chamada uma grande quantidade de vezes.
Quando uma chamada de função é feita em ARM, por exemplo, os quatro primeiros argumentos são colocados em registos
r0
parar3
e restantes argumentos têm de ser colocados na pilha. Manter o número de argumentos abaixo de cinco pode fazer muita diferença para funções críticas.Para funções que são chamadas extremamente muitas vezes, até mesmo o fato de que o programa tem de configurar os argumentos antes de cada chamada pode afetar o desempenho (
r0
ar3
pode ser substituído pela função chamada e terá de ser substituída antes da próxima chamada) de modo a esse respeito zero argumentos são os melhores.Atualizar:
O KjMag traz à tona o interessante tópico de embutir. O alinhamento de alguma forma atenuará isso, pois permitirá que o compilador execute as mesmas otimizações que você seria capaz de fazer se escrever em assembly puro. Em outras palavras, o compilador pode ver quais parâmetros e variáveis são usados pela função chamada e pode otimizar o uso do registro para que a leitura / gravação da pilha seja minimizada.
Existem alguns problemas com inlining.
inline
fosse tratado como obrigatório em todos os casos.inline
ou realmente dão erros quando o encontram.fonte
inline
.)Quando a lista de parâmetros aumentar para mais de cinco, considere definir uma estrutura ou objeto de "contexto".
Isso é basicamente uma estrutura que contém todos os parâmetros opcionais com alguns padrões sensíveis definidos.
No mundo processual C, uma estrutura simples serviria. Em Java, C ++, um objeto simples será suficiente. Não mexa com getters ou setters porque o único objetivo do objeto é manter valores "publicamente" configuráveis.
fonte
Não, não há diretrizes padrão
Mas existem algumas técnicas que podem tornar uma função com muitos parâmetros mais suportáveis.
Você pode usar um parâmetro list-if-args (args *) ou um parâmetro dictionary-of-args (kwargs
**
)Por exemplo, em python:
Saídas:
Ou você pode usar a sintaxe de definição literal do objeto
Por exemplo, aqui está uma chamada jQuery JavaScript para iniciar uma solicitação AJAX GET:
Se você der uma olhada na classe ajax do jQuery, há muito (aproximadamente 30) mais propriedades que podem ser definidas; principalmente porque as comunicações ajax são muito complexas. Felizmente, a sintaxe literal do objeto facilita a vida.
O C # intellisense fornece documentação ativa de parâmetros, portanto não é incomum ver arranjos muito complexos de métodos sobrecarregados.
Linguagens tipadas dinamicamente como python / javascript não têm essa capacidade, por isso é muito mais comum ver argumentos de palavras-chave e definições literais de objetos.
Prefiro definições literais de objetos ( mesmo em C # ) para gerenciar métodos complexos, porque é possível ver explicitamente quais propriedades estão sendo definidas quando um objeto é instanciado. Você precisará trabalhar um pouco mais para lidar com argumentos padrão, mas, a longo prazo, seu código será muito mais legível. Com definições literais de objeto, você pode quebrar sua dependência da documentação para entender o que seu código está fazendo à primeira vista.
IMHO, métodos sobrecarregados são altamente superestimados.
Nota: Se eu me lembro direito, o controle de acesso somente leitura deve funcionar para construtores literais de objeto em C #. Eles funcionam essencialmente da mesma maneira que a configuração de propriedades no construtor.
Se você nunca escreveu nenhum código não trivial em uma linguagem baseada dinamicamente (python) e / ou funcional / protótipo em javaScript, sugiro experimentá-lo. Pode ser uma experiência esclarecedora.
Pode ser assustador primeiro interromper sua dependência de parâmetros para a abordagem completa da inicialização de função / método, mas você aprenderá a fazer muito mais com seu código sem precisar adicionar complexidade desnecessária.
Atualizar:
Eu provavelmente deveria ter fornecido exemplos para demonstrar o uso em uma linguagem estaticamente tipada, mas atualmente não estou pensando em um contexto estaticamente tipificado. Basicamente, tenho trabalhado muito em um contexto digitado dinamicamente para voltar subitamente.
O que eu não sei é objeto sintaxe de definição literal é completamente possível em linguagens de tipagem estática (pelo menos em C # e Java) porque eu usei antes. Nas linguagens de tipo estaticamente, elas são chamadas de 'Inicializadores de Objetos'. Aqui estão alguns links para mostrar seu uso em Java e C # .
fonte
Pessoalmente, mais de 2 é onde meu alarme de cheiro de código é acionado. Quando você considera funções como operações (ou seja, uma conversão de entrada para saída), é incomum que mais de 2 parâmetros sejam usados em uma operação. Os procedimentos (que são uma série de etapas para atingir uma meta) receberão mais contribuições e, às vezes, são a melhor abordagem, mas na maioria dos idiomas hoje em dia não deve ser a norma.
Mas, novamente, essa é a diretriz e não uma regra. Costumo ter funções que levam mais de dois parâmetros devido a circunstâncias incomuns ou facilidade de uso.
fonte
Bem como Evan Plaice está dizendo, sou um grande fã de simplesmente passar matrizes associativas (ou a estrutura de dados comparáveis da sua linguagem) para funções sempre que possível.
Assim, em vez de (por exemplo) isso:
Ir para:
O Wordpress faz muitas coisas dessa maneira, e acho que funciona bem. (Embora meu código de exemplo acima seja imaginário e não seja um exemplo do Wordpress.)
Essa técnica permite que você passe muitos dados para suas funções com facilidade, mas evita que você precise se lembrar da ordem em que cada uma deve ser passada.
Você também apreciará essa técnica quando chegar a hora de refatorar - em vez de ter que alterar potencialmente a ordem dos argumentos de uma função (como quando você perceber que precisa passar Yet Another Argument), não precisará alterar o parâmetro de suas funções lista em tudo.
Isso não apenas poupa a necessidade de reescrever sua definição de função, como também poupa a necessidade de alterar a ordem dos argumentos cada vez que a função é invocada. Essa é uma grande vitória.
fonte
Uma resposta anterior mencionou um autor confiável que afirmou que quanto menos parâmetros suas funções tiverem, melhor você estará. A resposta não explicou o porquê, mas os livros o explicam, e aqui estão duas das razões mais convincentes pelas quais você precisa adotar essa filosofia e com a qual eu pessoalmente concordo:
Os parâmetros pertencem a um nível de abstração diferente do da função. Isso significa que o leitor do seu código terá que pensar sobre a natureza e o objetivo dos parâmetros de suas funções: esse pensamento é "nível mais baixo" do que o nome e o objetivo de suas funções correspondentes.
A segunda razão para ter o menor número possível de parâmetros para uma função é testar: por exemplo, se você tem uma função com 10 parâmetros, pense em quantas combinações de parâmetros você precisa para cobrir todos os casos de teste, por exemplo, uma unidade teste. Menos parâmetros = menos testes.
fonte
Para fornecer um pouco mais de contexto em torno do conselho para que o número ideal de argumentos de função seja zero no "Código Limpo: Um Manual de Artesanato em Software Ágil" de Robert Martin, o autor diz o seguinte como um de seus pontos:
Para o
includeSetupPage()
exemplo acima, aqui está um pequeno trecho de seu "código limpo" refatorado no final do capítulo:A "escola de pensamento" do autor defende classes pequenas, número baixo (idealmente de 0) de argumentos de função e funções muito pequenas. Embora eu também não concorde plenamente com ele, achei instigante e acho que vale a pena considerar a ideia de argumentos de função zero como ideal. Além disso, observe que mesmo o pequeno trecho de código acima também possui funções de argumento diferentes de zero, então acho que depende do contexto.
(E como outros já apontaram, ele também argumenta que mais argumentos tornam mais difícil do ponto de vista de teste. Mas aqui eu queria principalmente destacar o exemplo acima e sua justificativa para argumentos de função zero.)
fonte
Idealmente zero. Um ou dois estão ok, três em certos casos.
Quatro ou mais é geralmente uma má prática.
Além dos princípios de responsabilidade única que outros observaram, você também pode pensar nisso a partir de perspectivas de teste e depuração.
Se houver um parâmetro, conhecer seus valores, testá-los e encontrar erros com eles é 'relativamente fácil, pois há apenas um fator. À medida que você aumenta os fatores, a complexidade total aumenta rapidamente. Para um exemplo abstrato:
Considere um programa 'o que vestir neste clima'. Considere o que ele poderia fazer com uma entrada - temperatura. Como você pode imaginar, os resultados do que vestir são bastante simples com base nesse único fator. Agora considere o que o programa poderia / poderia / deveria fazer se, na verdade, passasse temperatura, umidade, ponto de orvalho, precipitação etc. Agora imagine como seria difícil depurar se desse uma resposta "errada" a alguma coisa.
fonte