Objetos múltiplos x objeto de opções

157

Ao criar uma função JavaScript com vários argumentos, sempre sou confrontado com esta opção: passe uma lista de argumentos versus passe um objeto de opções.

Por exemplo, estou escrevendo uma função para mapear um nodeList para uma matriz:

function map(nodeList, callback, thisObject, fromIndex, toIndex){
    ...
}

Eu poderia usar isso:

function map(options){
    ...
}

onde options é um objeto:

options={
    nodeList:...,
    callback:...,
    thisObject:...,
    fromIndex:...,
    toIndex:...
}

Qual é o caminho recomendado? Existem diretrizes para quando usar um vs. o outro?

[Update] Parece haver um consenso a favor do objeto de opções, então eu gostaria de adicionar um comentário: uma razão pela qual fiquei tentada a usar a lista de argumentos no meu caso foi ter um comportamento consistente com o JavaScript incorporado no método array.map.

Christophe
fonte
2
A segunda opção fornece argumentos nomeados, o que é bom na minha opinião.
Werner Kvalem Vesterås 10/10/12
Eles são argumentos opcionais ou obrigatórios?
Eu Odeio preguiçoso
@ user1689607 no meu exemplo, os três últimos são opcionais.
Christophe
Como seus dois últimos argumentos são muito semelhantes, se o usuário passasse apenas um ou outro, você nunca seria capaz de saber qual deles se destinava. Por causa disso, você quase precisaria de argumentos nomeados. Mas entendo que você deseja manter uma API semelhante à API nativa.
Eu Odeio preguiçoso
1
Modelar após a API nativa não é uma coisa ruim, se sua função fizer algo semelhante. Tudo se resume a "o que torna o código mais legível". Array.prototype.maptem uma API simples que não deve deixar nenhum codificador semi-experiente intrigado.
precisa

Respostas:

160

Como muitos outros, geralmente prefiro passar um options objectpara uma função em vez de passar uma longa lista de parâmetros, mas isso realmente depende do contexto exato.

Eu uso a legibilidade do código como teste decisivo.

Por exemplo, se eu tiver essa chamada de função:

checkStringLength(inputStr, 10);

Eu acho que o código é bem legível do jeito que é e passar parâmetros individuais é bom.

Por outro lado, existem funções com chamadas como esta:

initiateTransferProtocol("http", false, 150, 90, null, true, 18);

Completamente ilegível, a menos que você faça alguma pesquisa. Por outro lado, esse código lê bem:

initiateTransferProtocol({
  "protocol": "http",
  "sync":      false,
  "delayBetweenRetries": 150,
  "randomVarianceBetweenRetries": 90,
  "retryCallback": null,
  "log": true,
  "maxRetries": 18
 });

É mais uma arte do que uma ciência, mas se eu tivesse que citar regras práticas:

Use um parâmetro de opções se:

  • Você tem mais de quatro parâmetros
  • Qualquer um dos parâmetros é opcional
  • Você já teve que procurar a função para descobrir quais parâmetros são necessários
  • Se alguém tentar estrangular você enquanto grita "ARRRRRG!"
Jeremy J Starcher
fonte
10
Ótima resposta. Depende. Cuidado com as armadilhas booleanas ariya.ofilabs.com/2011/08/hall-of-api-shame-boolean-trap.html
Trevor Dixon
2
Ah, sim ... eu tinha esquecido esse link. Realmente me fez repensar como as APIs funcionam e até reescrevi várias partes de código depois de saber que fiz coisas idiotas. Obrigado!
11558 Jeremy J Starcher
1
'Você já precisou procurar a função para descobrir quais parâmetros são necessários' - Ao usar um objeto de opções, nem sempre é necessário procurar o método para descobrir que as chaves são necessárias e como são chamadas. O Intellisense no IDE não mostra essas informações, enquanto os parâmetros aparecem. Na maioria dos IDEs, você pode simplesmente passar o mouse sobre o método e ele mostra quais são os parâmetros.
Simbolo 27/03
1
Se vocês estão interessados ​​nas conseqüências de desempenho desse pouco de 'práticas recomendadas de codificação', aqui está um jsPerf testando as duas maneiras: jsperf.com/function-boolean-arguments-vs-options-object/10 Observe a pequena reviravolta no terceira alternativa, onde eu uso um objeto de opções 'preset' (constante), que pode ser feito quando você faz muitas chamadas (durante a vida útil do seu tempo de execução, por exemplo, sua página da web) com as mesmas configurações, conhecidas no momento do desenvolvimento (em resumo : quando seus valores de opção são codificados no código de origem).
precisa saber é o seguinte
2
@ Sean Honestamente, eu não uso mais esse estilo de codificação. Eu mudei para o TypeScript e uso de parâmetros nomeados.
9138 Jeremy J Starcher #
28

Vários argumentos são principalmente para parâmetros obrigatórios. Não há nada de errado com eles.

Se você possui parâmetros opcionais, fica complicado. Se um deles depende dos outros, para que eles tenham uma certa ordem (por exemplo, a quarta precisa da terceira), você ainda deve usar vários argumentos. Quase todos os métodos nativos de EcmaScript e DOM funcionam assim. Um bom exemplo é o openmétodo XMLHTTPrequests , em que os três últimos argumentos são opcionais - a regra é como "sem senha sem usuário" (consulte também os documentos MDN ).

Os objetos Option são úteis em dois casos:

  • Você tem tantos parâmetros que ficam confusos: a "nomeação" o ajudará, você não precisa se preocupar com a ordem deles (especialmente se eles mudarem)
  • Você tem parâmetros opcionais. Os objetos são muito flexíveis e, sem qualquer pedido, você apenas passa as coisas de que precisa e mais nada undefined.

No seu caso, eu recomendo map(nodeList, callback, options). nodeliste callbacksão obrigatórios, os outros três argumentos são apresentados apenas ocasionalmente e têm padrões razoáveis.

Outro exemplo é JSON.stringify. Você pode querer usar o spaceparâmetro sem passar uma replacerfunção - então você precisa chamar …, null, 4). Um objeto de argumentos pode ter sido melhor, embora não seja realmente razoável para apenas 2 parâmetros.

Bergi
fonte
+1 na mesma pergunta que @ trevor-dixon: você viu esse mix usado na prática, por exemplo, nas bibliotecas js?
Christophe
Um exemplo pode ser os métodos ajax do jQuery . Eles aceitam a URL [obrigatória] como o primeiro argumento e um enorme argumento de opções como o segundo.
21312 Bergi
tão estranho! Eu nunca notei isso antes. Eu vi sempre ele é usado com a url como uma propriedade opção ...
Christophe
Sim, jQuery faz coisa estranha com seus parâmetros opcionais, enquanto permanecer :-) compatíveis com versões anteriores
Bergi
1
Na minha opinião, esta é a única resposta sã aqui.
Benjamin Gruenbaum
11

Usar a abordagem 'opções como objeto' será o melhor. Você não precisa se preocupar com a ordem das propriedades e há mais flexibilidade em quais dados são passados ​​(parâmetros opcionais, por exemplo)

Criar um objeto também significa que as opções podem ser usadas facilmente em várias funções:

options={
    nodeList:...,
    callback:...,
    thisObject:...,
    fromIndex:...,
    toIndex:...
}

function1(options){
    alert(options.nodeList);
}

function2(options){
    alert(options.fromIndex);
}
Fluidbyte
fonte
A suposição (razoável) aqui é que o objeto sempre terá os mesmos pares de chaves. Se você estiver trabalhando com uma API de porcaria / inconsistente, terá um problema diferente em suas mãos.
backdesk
9

Eu acho que se você está instanciando algo ou chamando o método de um objeto, deseja usar um objeto de opções. Se é uma função que opera com apenas um ou dois parâmetros e retorna um valor, uma lista de argumentos é preferível.

Em alguns casos, é bom usar os dois. Se sua função tiver um ou dois parâmetros necessários e vários opcionais, torne os dois primeiros parâmetros necessários e o terceiro um hash de opções opcionais.

No seu exemplo, eu faria map(nodeList, callback, options). Nodelist e retorno de chamada são necessários, é bastante fácil dizer o que está acontecendo apenas lendo uma chamada e é como as funções existentes do mapa. Quaisquer outras opções podem ser passadas como um terceiro parâmetro opcional.

Trevor Dixon
fonte
+1 interessante. Você já o viu usado na prática, por exemplo, nas bibliotecas js?
Christophe
7

O seu comentário sobre a pergunta:

no meu exemplo, os três últimos são opcionais.

Então, por que não fazer isso? (Nota: Este é um Javascript bastante bruto. Normalmente, eu usaria um defaulthash e o atualizaria com as opções passadas usando Object.extend ou JQuery.extend ou similar ..)

function map(nodeList, callback, options) {
   options = options || {};
   var thisObject = options.thisObject || {};
   var fromIndex = options.fromIndex || 0;
   var toIndex = options.toIndex || 0;
}

Então, agora que agora é muito mais óbvio o que é opcional e o que não é, todos esses são usos válidos da função:

map(nodeList, callback);
map(nodeList, callback, {});
map(nodeList, callback, null);
map(nodeList, callback, {
   thisObject: {some: 'object'},
});
map(nodeList, callback, {
   toIndex: 100,
});
map(nodeList, callback, {
   thisObject: {some: 'object'},
   fromIndex: 0,
   toIndex: 100,
});
Izkata
fonte
Isso é semelhante à resposta do @ trevor-dixon.
Christophe
5

Posso estar um pouco atrasado para a festa com essa resposta, mas estava procurando as opiniões de outros desenvolvedores sobre esse mesmo tópico e me deparei com esse tópico.

Eu discordo muito da maioria dos respondentes e sou do lado da abordagem de 'múltiplos argumentos'. Meu argumento principal é que desencoraja outros antipadrões como "mutação e retorno do objeto param" ou "transmissão do mesmo objeto param para outras funções". Eu trabalhei em bases de código que abusaram extensivamente desse anti-padrão e o código de depuração que faz isso rapidamente se torna impossível. Eu acho que essa é uma regra prática muito específica para Javascript, pois o Javascript não é fortemente digitado e permite esses objetos estruturados arbitrariamente.

Minha opinião pessoal é que os desenvolvedores devem ser explícitos ao chamar funções, evitar a transmissão de dados redundantes e evitar a modificação por referência. Não é que esse padrão impeça a criação de um código conciso e correto. Eu apenas sinto que torna muito mais fácil o seu projeto cair em más práticas de desenvolvimento.

Considere o seguinte código terrível:

function main() {
    const x = foo({
        param1: "something",
        param2: "something else",
        param3: "more variables"
    });

    return x;
}

function foo(params) {
    params.param1 = "Something new";
    bar(params);
    return params;
}


function bar(params) {
    params.param2 = "Something else entirely";
    const y = baz(params);
    return params.param2;
}

function baz(params) {
    params.params3 = "Changed my mind";
    return params;
}

Esse tipo de documentação não apenas exige uma documentação mais explícita para especificar a intenção, como também deixa espaço para erros vagos. O que se um desenvolvedor modifica param1em bar()? Quanto tempo você acha que seria necessário analisar uma base de código de tamanho suficiente para capturar isso? É certo que este exemplo é um pouco falso, pois supõe que os desenvolvedores já tenham cometido vários antipadrões nesse ponto. Mas mostra como a passagem de objetos contendo parâmetros permite maior espaço para erro e ambiguidade, exigindo um maior grau de consciência e observância da correção constante.

Apenas meus dois centavos na questão!

ajxs
fonte
3

Depende.

Com base na minha observação sobre o design de bibliotecas populares, aqui estão os cenários que devemos usar o objeto de opção:

  • A lista de parâmetros é longa (> 4).
  • Alguns ou todos os parâmetros são opcionais e não dependem de uma determinada ordem.
  • A lista de parâmetros pode aumentar em futuras atualizações da API.
  • A API será chamada a partir de outro código e o nome da API não é claro o suficiente para informar o significado dos parâmetros. Portanto, pode ser necessário um nome de parâmetro forte para facilitar a leitura.

E cenários para usar a lista de parâmetros:

  • A lista de parâmetros é curta (<= 4).
  • A maioria ou todos os parâmetros são necessários.
  • Parâmetros opcionais estão em uma determinada ordem. (ou seja: $ .get)
  • Fácil de dizer o significado dos parâmetros pelo nome da API.
Lynn Ning
fonte
2

O objeto é mais preferível, porque se você passa um objeto, é fácil estender o número de propriedades nesses objetos e não precisa observar a ordem na qual seus argumentos foram passados.

happyCoda
fonte
1

Para uma função que geralmente usa alguns argumentos predefinidos, é melhor usar o objeto de opção. O exemplo oposto será algo como uma função que está obtendo um número infinito de argumentos como: setCSS ({height: 100}, {width: 200}, {background: "# 000"}).

Ilya Sidorovich
fonte
0

Gostaria de olhar para grandes projetos javascript.

Coisas como o google map, você verá com frequência que objetos instanciados requerem um objeto, mas funções requerem parâmetros. Eu acho que isso tem a ver com argumentos de OPÇÃO.

Se você precisar de argumentos padrão ou opcionais, um objeto provavelmente seria melhor porque é mais flexível. Mas se você não usar argumentos funcionais normais, será mais explícito.

Javascript também tem um argumentsobjeto. https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Functions_and_function_scope/arguments

dm03514
fonte