Existem orientações sobre quantos parâmetros uma função deve aceitar?

95

Eu notei que algumas funções com as quais trabalho têm 6 ou mais parâmetros, enquanto que na maioria das bibliotecas eu uso é raro encontrar uma função que leve 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 devem ser refatoradas. Existe uma diretriz para qual número é demais?

    
por Darth Egregious 18.04.2012 / 19:31
fonte

10 respostas

93

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:

  1. A função está fazendo muito. Ele deve ser dividido em várias funções menores, cada uma com um conjunto de parâmetros menor.
  2. Existe outro objeto escondido lá. Talvez seja necessário criar outro objeto ou estrutura de dados que inclua esses parâmetros. Veja este artigo sobre o padrão Objeto de Parâmetro para mais informações.

É difícil dizer o que você está vendo sem mais informações. É provável que a refatoração que você precisa fazer seja dividir a função em funções menores que são chamadas do pai, dependendo dos sinalizadores que estão sendo passados para a função.

Há alguns bons ganhos a serem obtidos ao fazer isso:

  • Torna seu código mais fácil de ler. Pessoalmente, acho muito mais fácil ler uma "lista de regras" composta de uma estrutura if que chama muitos métodos com nomes descritivos do que uma estrutura que faz tudo em um método.
  • É mais testável na unidade. Você dividiu seu problema em várias tarefas menores que são individualmente muito simples. A coleção de testes de unidade seria então composta por um conjunto de testes comportamentais que verifica os caminhos através do método mestre e uma coleção de testes menores para cada procedimento individual.
por 18.04.2012 / 20:09
fonte
37

De acordo com o "Código Limpo: Um Manual de Artesanato Ágil de Software", zero é o ideal, um ou dois são aceitáveis, três em casos especiais e quatro ou mais, nunca!

As palavras do autor:

The ideal number of arguments for a function is zero (niladic). Next comes one (monadic), followed closely by two (dyadic). Three arguments (triadic) should be avoided where possible. More than three (polyadic) requires very special justification—and then shouldn’t be used anyway.

Neste livro, há um capítulo falando apenas sobre funções em que os parâmetros são muito discutidos, por isso acho que este livro pode ser uma boa diretriz sobre a quantidade de parâmetros necessários.

Na minha opinião pessoal, um parâmetro é melhor que ninguém porque eu acho mais claro o que está acontecendo.

Como exemplo, na minha opinião, a segunda opção é melhor, porque fica mais claro o que o método está processando:

LangDetector detector = new LangDetector(someText);
//lots of lines
String language = detector.detectLanguage();

vs.

LangDetector detector = new LangDetector();
//lots of lines
String language = detector.detectLanguage(someText);

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 em Nesse caso, é melhor refatorar cada um desses comportamentos em uma função diferente.

    
por 18.04.2012 / 22:01
fonte
22

Se as classes de domínio no aplicativo forem projetadas corretamente, o número de parâmetros que passamos para uma função será reduzido automaticamente - porque as classes sabem como realizar seu trabalho e possuem dados suficientes para realizar seu trabalho.

Por exemplo, digamos que você tenha uma turma de gerentes que peça a uma turma do terceiro ano para concluir as tarefas.

Se você modelar corretamente,

3rdGradeClass.finishHomework(int lessonId) {
    result = students.assignHomework(lessonId, dueDate);
    teacher.verifyHomeWork(result);
}

Isso é simples.

Se você não tiver o modelo correto, o método será assim

Manager.finishHomework(grade, students, lessonId, teacher, ...) {
    // This is not good.
}

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 às suas próprias classes (responsabilidade única) e elas têm dados suficientes para fazer seu trabalho.

Sempre que vejo o número de parâmetros aumentando, verifico meu modelo para ver se projetei meu modelo de aplicativo corretamente.

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 pequenos objetos construídos antes de construir um objeto de configuração grande.

    
por 18.04.2012 / 20:59
fonte
15

Um aspecto que as outras respostas não aceitam é o desempenho.

Se você estiver programando em uma linguagem de nível suficientemente baixo (C, C ++, assembly), um grande número de parâmetros pode ser bastante prejudicial para o desempenho em algumas arquiteturas, especialmente se a função for chamada muitas vezes.

Quando uma chamada de função é feita no ARM, por exemplo, os primeiros quatro argumentos são colocados nos registros r0 to r3 e os argumentos restantes devem ser colocados na pilha. Manter o número de argumentos abaixo de cinco pode fazer uma grande diferença para funções críticas.

Para funções que são chamadas com muita frequência, até mesmo o fato de o programa ter que configurar os argumentos antes de cada chamada pode afetar o desempenho ( r0 to r3 pode ser substituído pela função chamada e terá que ser substituído antes da próxima chamada) então, nesse aspecto, os argumentos zero são os melhores.

Atualização:

O KjMag traz o interessante tópico do inlining. Inlining irá, de certa forma, mitigar isso, pois permitirá que o compilador realize as mesmas otimizações que você seria capaz de fazer se estivesse escrevendo 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 o inlining.

  1. Inline faz com que o binário compilado cresça, já que o mesmo código é duplicado em formato binário se for chamado de vários locais. Isso é prejudicial quando se trata do uso do cache de I.
  2. Os compiladores geralmente só permitem o preenchimento de um certo nível (3 etapas do IIRC?). Imagine chamar uma função inline de uma função inline de uma função inline. O crescimento binário explodiria se inline fosse tratado como obrigatório em todos os casos.
  3. Existem muitos compiladores que ignoram completamente o inline ou, na verdade, cometem erros quando o encontram.
por 19.04.2012 / 16:38
fonte
6

Quando a lista de parâmetros aumentar para mais de cinco, considere a definição de uma estrutura ou objeto "contextual".

Esta é basicamente apenas uma estrutura que contém todos os parâmetros opcionais com alguns padrões sensíveis definidos.

No mundo procedural de C, uma estrutura simples faria. Em Java, C ++, um objeto simples será suficiente. Não mexa com getters ou setters porque o único objetivo do objeto é manter valores "públicos" configuráveis.

    
por 19.04.2012 / 03:39
fonte
5

Não, não há nenhuma diretriz 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:

// Example definition
def example_function(normalParam, args*, kwargs**):
  for i in args:
    print 'args' + i + ': ' + args[i] 
  for key in kwargs:
    print 'keyword: %s: %s' % (key, kwargs[key])
  somevar = kwargs.get('somevar','found')
  missingvar = kwargs.get('somevar','missing')
  print somevar
  print missingvar

// Example usage

    example_function('normal parameter', 'args1', args2, 
                      somevar='value', missingvar='novalue')

Saídas:

args1
args2
somevar:value
someothervar:novalue
value
missing

Ou você pode usar a sintaxe de definição literal do objeto

Por exemplo, aqui está uma chamada JavaScript jQuery para iniciar uma solicitação AJAX GET:

$.ajax({
  type: 'GET',
  url: 'http://someurl.com/feed',
  data: data,
  success: success(),
  error: error(),
  complete: complete(),
  dataType: 'jsonp'
});

Se você der uma olhada na classe ajax do jQuery, há um lote (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 intellisense C # fornece documentação ativa de parâmetros, portanto não é incomum ver arranjos muito complexos de métodos sobrecarregados.

Linguagens dinamicamente tipificadas como python / javascript não têm essa capacidade, por isso é muito mais comum ver argumentos de palavra-chave e definições de literal de objeto.

Eu prefiro definições literais de objeto ( mesmo em C # ) para gerenciar métodos complexos porque você pode ver explicitamente quais propriedades estão sendo definidas quando um objeto é instanciado. Você terá que fazer um pouco mais de trabalho para lidar com argumentos padrão, mas no longo prazo seu código será muito mais legível. Com as definições de literal de objeto, você pode quebrar sua dependência de documentação para entender o que seu código está fazendo à primeira vista.

IMHO, os métodos sobrecarregados são altamente superestimados.

Nota: Se bem me lembro direito readonly controle de acesso deve funcionar para construtores de literal de objeto em c #. Eles essencialmente funcionam da mesma forma que as propriedades de configuração no construtor.

Se você nunca escreveu nenhum código não-trivial em uma linguagem baseada em javaScript dinamicamente tipificada (python) e / ou funcional / prototype, sugiro experimentá-lo. Pode ser uma experiência esclarecedora.

Pode ser assustador primeiro quebrar sua confiança em parâmetros para a abordagem final, tudo, para a inicialização de função / método, mas você aprenderá a fazer muito mais com seu código sem precisar adicionar complexidade desnecessária.

Atualização:

Provavelmente deveria ter fornecido exemplos para demonstrar o uso em uma linguagem de tipagem estática, mas não estou pensando em um contexto estaticamente tipado. Basicamente, tenho trabalhado muito em um contexto digitado dinamicamente para voltar de repente.

O que eu faço sabe é que a sintaxe de definição literal do objeto é completamente possível em linguagens estaticamente tipadas (pelo menos em C # e Java) porque as usei antes. Em linguagens estaticamente tipadas, elas são chamadas de 'Inicializadores de objeto'. Aqui estão alguns links para mostrar seu uso em Java e C # .

    
por 18.04.2012 / 20:25
fonte
3

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 dois parâmetros sejam usados em uma operação. Procedimentos (isto é, uma série de etapas para alcançar um objetivo) requererá mais insumos e algumas vezes é a melhor abordagem, mas na maioria dos idiomas, esses dias não devem ser a norma.

Mas, novamente, essa é a diretriz e não uma regra. Muitas vezes tenho funções que levam mais de dois parâmetros devido a circunstâncias incomuns ou facilidade de uso.

    
por 18.04.2012 / 20:09
fonte
2

Muito parecido com Evan Plaice, eu sou um grande fã de simplesmente passar matrizes associativas (ou a estrutura de dados comparável da sua linguagem) em funções sempre que possível.

Assim, em vez de (por exemplo) isso:

<?php

createBlogPost('the title', 'the summary', 'the author', 'the date of publication, 'the keywords', 'the category', 'etc');

?>

Ir para:

<?php

// create a hash of post data
$post_data = array(
  'title'    => 'the title',
  'summary'  => 'the summary',
  'author'   => 'the author',
  'pubdate'  => 'the publication date',
  'keywords' => 'the keywords',
  'category' => 'the category',
  'etc'      => 'etc',
);

// and pass it to the appropriate function
createBlogPost($post_data);

?>

O Wordpress faz muita coisa dessa maneira, e eu 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ê tenha que lembrar a ordem na qual cada um deve ser passado.

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ê percebe que precisa passar Yet Another Argument), não é necessário alterar lista de parâmetros das suas funções.

Isso não apenas evita que você tenha que reescrever sua definição de função - ela evita que você tenha que alterar a ordem dos argumentos toda vez que a função é invocada. Essa é uma grande vitória.

    
por 24.04.2012 / 21:26
fonte
0

A answer mencionou um autor confiável que afirmou que quanto menos parâmetros suas funções tiverem, melhor você estará. A resposta não explica por que, mas os livros explicam isso, e aqui estão duas das razões mais convincentes de porque 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 a finalidade dos parâmetros de suas funções: esse pensamento é "nível inferior" do que o nome e o propósito de suas funções correspondentes.

  • O segundo motivo para ter menos parâmetros possíveis para uma função é testar: por exemplo, se você tiver 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, um teste unitário. Menos parâmetros = menos testes.

por 27.09.2017 / 19:23
fonte
-2

Idealmente zero. Um ou dois estão ok, três em certos casos. Quatro ou mais geralmente é uma prática ruim.

Além dos princípios de responsabilidade única que os outros observaram, você também pode pensar nisso testando e depurando perspectivas.

Se houver um parâmetro, saber seus valores, testá-los e encontrar erros com eles é relativamente fácil, pois há apenas um fator. Conforme você aumenta os fatores, a complexidade total aumenta rapidamente. Para um exemplo abstrato:

Considere o programa "o que usar neste clima". Considere o que poderia fazer com uma entrada - temperatura. Como você pode imaginar, os resultados do que vestir são bem simples, baseados nesse único fator. Agora, considere o que o programa poderia / poderia / deveria fazer se realmente fosse passado a temperatura, umidade, ponto de orvalho, precipitação, etc. Agora imagine como seria difícil depurá-lo se desse a resposta "errada" a algo.

    
por 18.04.2012 / 22:42
fonte