Qual é a melhor prática para solicitar parâmetros em uma função?

52

Às vezes (raramente), parece que criar uma função que requer uma quantidade decente de parâmetros é a melhor rota. No entanto, quando o faço, sinto que estou frequentemente escolhendo a ordem dos parâmetros aleatoriamente. Eu costumo ir por "ordem de importância", com o parâmetro mais importante primeiro.

Existe uma maneira melhor de fazer isso? Existe uma maneira de "melhores práticas" de ordenar parâmetros que aprimore a clareza?

Casey Patton
fonte
5
Os parâmetros nomeados tornam isso menos que um problema. Python leva ao extremo, olhe para ele. Um bom exemplo está em C # - as várias maneiras de ligar MessageBox.Show. Olhe para isso também.
Job
8
Em Haskell, a possível utilidade da aplicação de funções parciais geralmente sugere uma ordenação natural de argumentos.
tdammers 16/08
O que você consideraria uma quantidade decente de parâmetros?
Chris
Eu estava pensando> 2. Embora eu ache que a ordem se aplica a 2 parâmetros da mesma forma.
Casey Patton
3
@ Tdammers isso também se aplica a qualquer idioma que tenha suporte direto para currying
jk.

Respostas:

55

Em geral: use-o .

Escreva um teste para sua função, um teste do mundo real.
Algo que você realmente gostaria de fazer com essa função .

E veja em que ordem você colocou essas anotações.

A menos que você já tenha (ou conheça) algumas funções que fazem algo semelhante.
Nesse caso: conforme o que eles já fazem, pelo menos para os primeiros argumentos.

Por exemplo , todos eles tomam um documento / objeto / ponteiro de arquivo / série de valores / coordenadas como o primeiro argumento (s)? Pelo amor de Deus, de acordo com esses argumentos .

Evite confundir seus colegas de trabalho e seu futuro eu .

ZJR
fonte
12
"Evite confundir ... seu futuro eu" soa como uma boa filosofia em geral.
31411 Jeanne Pindar
28

Eu costumo seguir essas regras, embora nem sempre com a mesma precedência. Acho que agora é um processo de pensamento automático e não penso demais, exceto pelo design público da API .

Funil de seleção

  1. Semântica
  2. Importância / Relevância
  3. Frequência de uso
  4. Preocupações de E / S

1. Semântica Primeiro

Especialmente no POO, escolha parâmetros com base em seu significado semântico para a ação ou mensagem. A assinatura de um método bem nomeado com um parâmetro bem nomeado deve:

  • sinta-se natural para ligar,
  • seja auto-descritivo em termos de intenção e comportamento.

(Por esses motivos, às vezes o uso de tipos ou aliases personalizados em vez de primitivos pode aumentar a expressividade da sua assinatura.)

2. Então Importância

O parâmetro mais "significativo" vem primeiro (ou próximo ...)

3. Então Frequência

A frequência também é importante, especialmente em um idioma em que você não tem parâmetros nomeados, mas pode ter valores padrão em parâmetros posicionais. Isso implica que a ordem dos parâmetros não varia e que, obviamente, você não pode definir os parâmetros N + 1 se desejar forçar o valor padrão do parâmetro N (exceto se o seu idioma tiver o conceito de parâmetro de marcador de posição) )

A boa notícia para você é que, geralmente, a frequência está relacionada à importância, de modo que isso acompanha o ponto anterior. E, provavelmente, cabe a você criar sua API para que ela tenha a semântica apropriada.

4. Não vamos esquecer a E / S

se seu método / função recebe alguma entrada e produz uma saída, e a última não deve ser "retornada" (por meio de uma declaração de retorno) ou "lançada" (usando um sistema de exceção), você terá a opção de passar valores de volta ao chamador usando seus outros parâmetros (ou o parâmetro de entrada). Isso está relacionado à semântica e, na maioria dos casos, faz sentido que os primeiros parâmetros definam a saída e os últimos parâmetros recebam a saída.

Além disso, uma outra abordagem para ter menos parâmetros e maximizar a semântica seria usar uma abordagem funcional ou definir um padrão do Construtor , para que você possa empilhar claramente suas entradas, definir suas saídas e recuperá-las quando necessário.

(Observe que eu não mencionei variáveis ​​globais, porque por que você usaria uma, certo?)


Algumas coisas a considerar

  • Usabilidade

    A maioria dos itens acima será exibida naturalmente se você seguir o conselho da ZJR : Use It!

  • Considere a refatoração

    Se você se preocupa com a ordenação de parâmetros, talvez essa preocupação encontre sua raiz no exposto acima e no fato de sua API ter sido mal projetada. Se você tiver muitos parâmetros, é provável que algo possa ser componente / modularizado e refatorado .

  • Considere o desempenho

    Lembre-se de que as implementações de alguns idiomas terão impactos muito importantes no gerenciamento de memória de tempo de execução ao usar parâmetros. Daí a razão pela qual os livros de estilos de muitos idiomas recomendam manter a lista de parâmetros simples e curta . Com no máximo 4 parâmetros, por exemplo. Deixo como um exercício para você descobrir o porquê.

  • A resposta de Bevan e a menção às recomendações do Clean Code também são definitivamente relevantes!

haylem
fonte
18

Eu respeitosamente afirmo que se preocupar com a ordenação de parâmetros está se preocupando com a coisa errada.

No livro do tio Bob, " Código Limpo ", ele defende, persuasivamente, que os métodos nunca devem ter mais de dois argumentos - e a maioria deve ter apenas um, se houver. Nesse caso, a encomenda é óbvia ou sem importância.

Embora imperfeitamente, estou tentando seguir o conselho do tio Bob - e está melhorando meu código.

Nos raros casos em que um método parece exigir mais informações, a introdução de um objeto de parâmetro é uma boa idéia. Normalmente, acho que esse é o primeiro passo para a descoberta de um novo conceito (objeto) que é fundamental para o meu algoritmo.

Bevan
fonte
Eu definitivamente concordo com você! Minha primeira afirmação teve como objetivo chegar ao ponto de que isso não é algo típico para mim, haha. Mas, no caso em que realmente preciso passar alguns parâmetros e não vale a pena refatorar todo o programa, pergunto-me sobre o pedido.
Casey Patton
3
tosse PHP tosse . Sinto muito - você estava dizendo algo sobre não se preocupar com a ordem dos parâmetros?
Craige
Você não terá apenas um construtor para o seu objeto de parâmetro que use tantos argumentos?
detly 9/03/12
Não - porque o objeto de parâmetro pode ser "construído" em várias instruções de uma maneira que seja mais legível.
Bevan
2
@Bevan: isso é altamente discutível. Construir objetos dessa maneira significa que eles não podem mais ser imutáveis; manter seus objetos imutáveis ​​sempre que possível, no entanto, é outra ótima maneira de aumentar a capacidade de manutenção.
tdammers
10

Eu tento colocar os parâmetros IN primeiro, os parâmetros OUT em segundo. Existem também algumas ordens naturais, por exemplo, createPoint(double x, double y)é fortemente preferível createPoint(double y, double x).

quant_dev
fonte
5
Às vezes, o oposto é melhor, por exemplo, quando sempre há apenas um argumento OUT e um número variável de argumentos in, como in addAllTo(target, something1, something2).
maaartinus
Embora eu siga a mesma regra, tento evitar os parâmetros OUT sempre que possível, e a maioria dos bons programadores também. Portanto, o número de funções em que essa recomendação realmente ajuda o OP deve ser bastante pequeno.
Doc Brown
7
@DocBrown Você realmente não deve evitar bons programadores, pois eles podem ser úteis.
RyanfaeScotland
@RyanfaeScotland: caramba, eu deveria ler meus comentários duas vezes antes de publicá-las (ou menos dentro dos cinco minutos eu posso mudá-las) ;-)
Doc Brown
6

Nunca vi uma "prática recomendada" documentada em relação a esse tópico específico, mas meu padrão pessoal é listá-las na ordem em que aparecerão no método para o qual estão sendo usadas ou se o método é mais passagem para uma camada de dados, listarei-os na ordem em que apareceriam no esquema db ou nos métodos da camada de dados.

Além disso, quando há várias sobrecargas de um método, percebo que a maneira típica é listá-las começando com parâmetros comuns a todos (ou à maioria) dos métodos, com cada método diferente sendo anexado ao final para cada sobrecarga de método, como :

void func1(string param) { }
void func2(string param, int param2) { }
void func3(string param, string param3) { }
void func3(string param, int param2, string param3) { }
Joel Etherton
fonte
6

Ordem: entrada (s), saída (s), parâmetros opcionais.

Jonathan Khoo
fonte
3

Costumo seguir a convenção C / C ++ de colocar os constparâmetros primeiro (ou seja, os parâmetros que você passa por valor) e depois os que você passa por referência. Esse pode não ser necessariamente o método correto para chamar funções, mas, se você estiver interessado em saber como cada compilador manipula parâmetros, consulte os seguintes links para as regras que regem e / ou a ordem em que os parâmetros são colocados na pilha.

http://msdn.microsoft.com/en-us/library/zthk2dkh%28v=vs.80%29.aspx

http://en.wikipedia.org/wiki/Calling_convention

Conta
fonte
Outro link no qual você pode estar interessado é msdn.microsoft.com/en-us/library/984x0h58%28v=vs.71%29.aspx Para funções recursivas, consulte o seguinte link. publicações.gbdirect.co.uk/ c_book/ chapter4/… Não que eu tenha esquecido, mas sendo um novo usuário, não consigo postar um comentário com mais de dois links ... que frustrante!
Bill
1

Normalmente, eu apenas uso a ordenação de parâmetros "o que parece menos cítrico". Quanto menos vezes eu precisar ir para a definição de método / função, melhor. E é bom ter parâmetros nomeados que são descritivos para o que eles são usados, dessa maneira, quando a pequena dica de ferramenta aparece (VS), isso torna ainda mais fácil.

Se você tiver linhas e linhas de parâmetros, poderá considerar um design diferente. Volte e veja como você pode dividir isso em mais funções / métodos. Apenas uma ideia, mas quando tenho uma dúzia de parâmetros em minha função, quase sempre não é um problema de parâmetro, mas um problema de design.


fonte
2
"cyprtic": uma ótima maneira de expressar o argumento.
Bevan
2
Você nunca teve um erro de digitação antes, @Bevan?
11
Eu tenho erros de digitação o tempo todo - escrever cyprtic era tão bom que pensei que fosse de propósito . Por favor, troque de volta, pois isso ajuda a mostrar seu ponto de vista, mesmo que tenha sido um acidente.
Bevan
11
@Bevan, haha ​​ok, eu vejo o que você está dizendo. Bom ponto! Sua alterado novamente =)
1

Às vezes (raramente), parece que criar uma função que requer uma quantidade decente de parâmetros é a melhor rota.

O uso de vários parâmetros geralmente é um indicador claro de que você viola o SRP neste método. É improvável que um método que precise de muitos parâmetros faça apenas uma coisa. A excitação pode ser uma função matemática ou um método de configuração, onde de fato vários parâmetros são necessários. Eu evitaria vários parâmetros, pois o diabo evita a água benta. Quanto mais parâmetros você usa em um método, maior a chance de o método ser (também) complexo; quanto mais complexidade significa: mais difícil de manter e isso é menos desejável.

No entanto, quando o faço, sinto que estou frequentemente escolhendo a ordem dos parâmetros aleatoriamente. Eu costumo ir por "ordem de importância", com o parâmetro mais importante primeiro.

No princípio você está escolhendo aleatoriamente . Claro que você pode pensar que o parâmetro A é mais relevante que o parâmetro B ; mas pode não ser o caso dos usuários da sua API, que consideram B o parâmetro mais relevante. Portanto, mesmo se você estivesse atento na escolha do pedido - para outros, isso poderia parecer aleatório .

Existe uma maneira melhor de fazer isso? Existe uma maneira de "melhores práticas" de ordenar parâmetros que aprimore a clareza?

Existem várias maneiras de sair:

a) O caso trivial: Não use mais de um parâmetro.

b) Como você não especificou qual idioma você escolheu, existe a chance de você escolher um idioma com parâmetros nomeados . Este é um bom açúcar sintático, que permite diminuir a importância da ordem dos parâmetros:fn(name:"John Doe", age:36)

Nem todo idioma permite essas sutilezas. Então o que então?

c) Você pode usar um Dicionário / Hashmap / Matriz associativa como parâmetro: por exemplo, o Javascript permitiria o seguinte: o fn({"name":"John Doe", age:36})que não está longe de (b).

d) Obviamente, se você trabalha com uma linguagem estaticamente tipada como Java. você poderia usar um Hashmap , mas perderia informações de tipo (por exemplo, ao trabalhar com HashMap<String, Object>) quando os parâmetros tivessem tipos diferentes (e precisassem ser convertidos).

O próximo passo lógico seria passar um Object(se você estiver usando Java) com propriedades apropriadas ou algo mais leve como uma estrutura (se você escrever, por exemplo, C # ou C / C ++).

Regra prática:

1) Melhor caso - seu método não precisa de nenhum parâmetro

2) Bom caso - seu método precisa de um parâmetro

3) Caso tolerável - seu método precisa de dois parâmetros

4) Todos os outros casos devem ser refatorados

Thomas Junk
fonte
0

Muitas vezes, um objeto complexo como parâmetro é melhor - uma versão pobre de parâmetros nomeados que funciona na maioria das plataformas. E abre a porta para parâmetros com comportamento para inicializar.

Para um exemplo da maioria das coisas que você não deve fazer, tente ler a documentação padrão da biblioteca do PHP.

Wyatt Barnett
fonte
0

Normalmente, eu os ordeno com o necessário primeiro, do que por alguma medida combinada de importância e frequência de uso de acordo com um "sentimento" (pode ser visto como ORDER BY required DESC, SOME_MAGIC_FEELING(importancy,frequency)) e não de acordo com qualquer prática específica.

No entanto, como outros observaram, acho que o problema subjacente que torna esse problema está usando muitos parâmetros (IMHO, qualquer coisa> 3 é demais) e esse é o problema real que você deve abordar. Há um post interessante sobre isso no blog de Rebecca Murphey.

Eu acho que quando você tem apenas 1 a 3 argumentos, a ordem correta é bastante óbvia e você apenas "sente" o que é certo.

shesek
fonte
0

Semelhante à resposta do @Wyatt Barnetts, qualquer coisa além de alguns parâmetros ou parâmetros muito explícitos para o método, eu recomendaria a passagem de um objeto. Isso geralmente é mais fácil de atualizar / manter, mais claro para ler e remove a necessidade de se preocupar com pedidos . Além disso, muitos parâmetros para um método são um cheiro de código e existem padrões de refatoração comuns que você pode seguir para ajudar a corrigi-lo.

Exemplo explícito:

public int add(int left, int right)
{
  return left + right;
}

Como esse é um exemplo bem definido e a adição é comutativa (a ordem não importa), basta seguir com ele.

No entanto, se você adicionar algo mais complexo:

public SomeComplexReturnValue Calculate(int i, float f, string s, object o)
{
  // do work here
}

Se tornaria:

public class SomeComplexInputForCalculation
{
  public int i; 
  public float f;
  public string s; 
  public object o;
}

public SomeComplexReturnValue Calculate(SomeComplexInputForCalculation input)
{
  // do work here
}

Espero que isto ajude...

longda
fonte
0

Encomende da maneira que achar que o curry provavelmente se beneficiará. Por exemplo, parâmetros de função primeiro.

alternativa
fonte
0

"Importante primeiro" não significa muito. Existem algumas convenções.

Se você passar o objeto de chamada (geralmente chamado remetente), isso ocorrerá primeiro.

Deve haver alguma fluência na lista, o que significa que você deve saber sobre o que é um argumento no momento em que o lê. Exemplo:

CopyFolder (caminho da string, bool recursivo);

Se você colocasse recursiva primeiro, seria confuso porque ainda não há contexto. Se você já se trata de copiar (1), uma pasta (2), o argumento recursivo começa a fazer sentido.

Isso também faz com que os recursos semelhantes ao IntelliSense funcionem bem: o usuário aprende à medida que preenche os argumentos, cria uma compreensão do método e do que ele faz. Da mesma forma, as sobrecargas devem manter a mesma ordem para as peças iguais.

Então você ainda pode achar que existem argumentos "iguais" a esse respeito e pode ficar tentado a deixar a ordem depender do tipo do argumento. Só porque ter duas cordas seguidas e depois dois booleanos parece melhor. Em algum nível, isso se tornará uma arte e não uma habilidade.

Não me importo muito com a afirmação de que você deve agrupar todos os argumentos em um objeto apenas para terminar com um único argumento. Isso é apenas enganar a si mesmo (ele não torna o método menos complexo, ainda possui todos esses argumentos, você apenas os ocultou). Isso ainda pode ser conveniente se você passar o conjunto de método para método algumas vezes, a refatoração certamente se tornará muito mais fácil, mas em termos de design, é apenas fingir que você está fazendo a diferença. Não é como se você acabasse com um objeto significativo que represente algo; será apenas um monte de argumentos, não diferentes da lista na declaração do método. Não fará o tio Bob feliz.

Martin Maat
fonte