Existem diretrizes sobre quantos parâmetros uma função deve aceitar?


114

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?


4
@ Minus: A idéia é que você deseja manter suas aulas focadas. Classes focadas geralmente não têm tantas dependências / atributos, portanto, você teria menos parâmetros para o construtor. Algumas palavras chatas que as pessoas colocam neste momento são alta coesão e princípio de responsabilidade única . Se você acha que isso não foi violado e ainda precisa de muitos parâmetros, considere usar o padrão Builder.
c_maker

2
Definitivamente, não siga o exemplo de MPI_Sendrecv () , que usa 12 parâmetros!
Chrisaycock

6
O projeto no qual estou trabalhando atualmente usa uma certa estrutura, na qual métodos com mais de 10 parâmetros são comuns. Estou chamando um método específico com 27 parâmetros em alguns lugares. Toda vez que vejo, morro um pouco por dentro.
Perp

3
Nunca adicione opções booleanas para alterar o comportamento das funções. Divida a função. Divida o comportamento comum em novas funções.
Kevin cline # 1 de

2
@Ominus What? Apenas 10 parâmetros? Isso não é nada, é necessário muito mais . : D
maaartinus 4/11

Respostas:


106

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. Há outro objeto escondido lá. Pode ser necessário criar outro objeto ou estrutura de dados que inclua esses parâmetros. Consulte este artigo no padrão Parameter Object para obter mais informações.

É 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:

  • Isso torna seu código mais fácil de ler. Pessoalmente, acho muito mais fácil ler uma "lista de regras" composta de uma ifestrutura que chama muitos métodos com nomes descritivos do que uma estrutura que faz tudo em um único método.
  • É mais testável por unidade. Você dividiu seu problema em várias tarefas menores, individualmente muito simples. A coleção de testes unitários 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.

5
Abstração de parâmetro foi transformada em um padrão de design? O que acontece se você tiver 3 classes de parâmetros. Você adiciona mais 9 sobrecargas de método para lidar com as diferentes combinações possíveis de parâmetros? Isso soa como um problema desagradável de declaração de parâmetro O (n ^ 2). Ah, espere, você pode herdar apenas 1 classe em Java / C #, de modo que será necessário um pouco mais de placa biológica (talvez um pouco mais de subclasse) para fazê-lo funcionar na prática. Desculpe, não estou convencido. Ignorar as abordagens mais expressivas que uma linguagem pode oferecer em favor da complexidade parece errado.
Evan solha

A menos que você use o padrão Objeto Padrão para empacotar variáveis ​​em uma instância de objeto e passá-la como parâmetro. Isso funciona para empacotamento, mas pode ser tentador criar classes de variáveis ​​diferentes apenas pela conveniência de simplificar a definição do método.
Evan solha

@EvanPlaice Não estou dizendo que você precisa usar esse padrão sempre que tiver mais de um parâmetro - você está absolutamente certo de que isso se torna ainda mais desagradável que a primeira lista. Pode haver casos em que você realmente precisa de um grande número de parâmetros e simplesmente não funciona para agrupá-los em um objeto. Ainda não encontrei um caso em desenvolvimento corporativo que não se enquadrasse em um dos dois baldes que mencionei na minha resposta - isso não quer dizer que ainda não exista.
Michael K

@ MichaelK Se você nunca os usou, tente pesquisar 'inicializadores de objetos' no Google. É uma abordagem bastante inovadora que reduz significativamente o clichê de definição. Em teoria, você poderia eliminar os construtores de uma classe, parâmetros e sobrecargas, tudo de uma só vez. Na prática, porém, geralmente é bom manter um construtor comum e confiar na sintaxe do 'inicializador de objetos' para o restante das propriedades obscuras / de nicho. IMHO, é o mais próximo que você chega da expressividade de idiomas tipados dinamicamente em um idioma estaticamente tipado.
Evan solha

@Evain Plaice: Desde quando as línguas de tipo dinâmico são expressivas?
ThomasX

41

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:

O número ideal de argumentos para uma função é zero (niládico). A seguir vem um (monádico), seguido de perto por dois (diádico). Três argumentos (triádicos) devem ser evitados sempre que possível. Mais de três (poládicos) exigem justificativas muito especiais - e, portanto, não devem ser usadas de qualquer maneira.

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:

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


8
"três em casos especiais e quatro ou mais, nunca!" BS. Que tal Matrix.Create (x1, x2, x3, x4, x5, x6, x7, x8, x9); ?
Lukasz Madon

71
Zero é ideal? Como diabos as funções obtêm informações? Global / instância / estática / qualquer variável? QUE NOJO.
Peter C

9
Esse é um mau exemplo. A resposta é obviamente: 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.
Matthieu M.

8
@ukas, em idiomas que suportam construções sofisticadas como matrizes ou listas (gasp!), que tal Matrix.Create(input);onde inputestá, digamos, um .NET IEnumerable<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.
um CVn

9
Zero argumento como "ideal" é um problema e uma razão pela qual eu acho que o Código Limpo é superestimado.
user949300

24

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,

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

Isto é simples.

Se você não possui 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 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.


16

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 r0para r3e 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 ( r0a r3pode 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.

  1. Inlining faz com que o binário compilado aumente, pois o mesmo código é duplicado em formato binário se for chamado de vários locais. Isso é prejudicial quando se trata do uso de cache em I.
  2. Os compiladores geralmente permitem alinhar até um certo nível (3 etapas IIRC?). Imagine chamar uma função embutida a partir de uma função embutida a partir de uma função embutida. O crescimento binário explodiria se inlinefosse tratado como obrigatório em todos os casos.
  3. Existem muitos compiladores que ignoram completamente inlineou realmente dão erros quando o encontram.

Se passar um grande número de parâmetros é bom ou ruim da perspectiva do desempenho depende das alternativas. Se um método precisa de uma dúzia de informações, e uma a chama centenas de vezes com os mesmos valores para onze delas, fazer com que o método faça uma matriz pode ser mais rápido do que usar uma dúzia de parâmetros. Por outro lado, se toda chamada precisar de um conjunto exclusivo de doze valores, a criação e o preenchimento da matriz para cada chamada poderão ser mais lentos do que simplesmente passar os valores diretamente.
Supercat 02/09

O inlining não resolve esse problema?
KjMag #

@KjMag: Sim, até certo ponto. Mas existem muitas dicas, dependendo do compilador. As funções normalmente serão alinhadas apenas até um determinado nível (se você chamar uma função embutida que chama uma função embutida que chama uma função embutida ...). Se a função for grande e for chamada de vários lugares, inlining em qualquer lugar torna o binário maior, o que pode significar mais erros no cache I. Tão inlining pode ajudar, mas não é uma bala de prata. (Para não mencionar existem muito poucos velhos compiladores embutidos que não suportam inline.)
Leo

7

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.


Concordo que um objeto de contexto pode se tornar muito útil quando as estruturas de parâmetros de função começam a se tornar bastante complexas. Eu tinha recentemente colocado no blog sobre o uso de contexto objetos com um padrão do visitante-like
Lukas Eder

5

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:

// 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 jQuery JavaScript 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á 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 # .


3
Não tenho certeza se gosto desse método, principalmente porque você perde o valor de auto-documentação de parâmetros individuais. Para listas de itens semelhantes, isso faz todo o sentido (por exemplo, um método que pega uma lista de seqüências de caracteres e as concatena), mas para um conjunto de parâmetros arbitrário isso é pior do que a chamada de método longa.
Michael K

@MichaelK Dê uma outra olhada nos inicializadores de objetos. Eles permitem que você defina explicitamente propriedades, ao contrário de como elas são definidas implicitamente nos parâmetros tradicionais de método / função. Leia isto, msdn.microsoft.com/en-us/library/bb397680.aspx , para entender o que quero dizer.
Evan solha

3
Criar um novo tipo apenas para lidar com a lista de parâmetros soa exatamente como a definição de complexidade desnecessária ... Certamente, linguagens dinâmicas permitem evitar isso, mas então você obtém um único parâmetro de goo. Independentemente disso, isso não responde à pergunta.
Telastyn

@Telastyn Do que você está falando? Nenhum novo tipo foi criado, você declara propriedades diretamente usando a sintaxe literal do objeto. É como definir um objeto anônimo, mas o método o interpreta como um agrupamento de parâmetros key = value. O que você está vendo é uma instanciação de método (não um objeto de encapsulamento de parâmetro). Se sua carne estiver com embalagem de parâmetros, dê uma olhada no padrão Objeto de parâmetro mencionado em uma das outras perguntas, porque é exatamente isso que é.
Evan solha

@EvanPlaice - Exceto que as linguagens de programação estática em geral exigem um tipo declarado (geralmente novo) para permitir o padrão Parameter Object.
Telastyn 18/04/12

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 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.


2

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:

<?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 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.


Ver este post publicado destaca outro benefício da abordagem de passar a hash: observe que meu primeiro exemplo de código é tão longo que gera uma barra de rolagem, enquanto o segundo se encaixa perfeitamente na página. O mesmo provavelmente será verdade no seu editor de código.
Chris Allen Lane,

0

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.


0

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:

Argumentos são difíceis. Eles tomam muito poder conceitual. Por isso me livrei de quase todos eles do exemplo. Considere, por exemplo, StringBuffero exemplo. Poderíamos tê-lo difundido como argumento em vez de torná-lo uma variável de instância, mas nossos leitores teriam que interpretá-lo cada vez que o vissem. Quando você está lendo a história contada pelo módulo, includeSetupPage() é mais fácil entender do que includeSetupPageInto(newPageContent). O argumento está em um nível diferente de abstração de que o nome da função e obriga a conhecer um detalhe (em outras palavras StringBuffer) que não é particularmente importante nesse ponto.

Para o includeSetupPage()exemplo acima, aqui está um pequeno trecho de seu "código limpo" refatorado no final do capítulo:

// *** NOTE: Commments are mine, not the author's ***
//
// Java example
public class SetupTeardownIncluder {
    private StringBuffer newPageContent;

    // [...] (skipped over 4 other instance variables and many very small functions)

    // this is the zero-argument function in the example,
    // which calls a method that eventually uses the StringBuffer instance variable
    private void includeSetupPage() throws Exception {
        include("SetUp", "-setup");
    }

    private void include(String pageName, String arg) throws Exception {
        WikiPage inheritedPage = findInheritedPage(pageName);
        if (inheritedPage != null) {
            String pagePathName = getPathNameForPage(inheritedPage);
            buildIncludeDirective(pagePathName, arg);
        }
    }

    private void buildIncludeDirective(String pagePathName, String arg) {
        newPageContent
            .append("\n!include ")
            .append(arg)
            .append(" .")
            .append(pagePathName)
            .append("\n");
    }
}

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.)


-2

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.


12
Se uma função tem zero parâmetros, ela está retornando um valor constante (útil em algumas circunstâncias, mas bastante limitante) ou está usando algum estado oculto que seria melhor explicitado. (No método OO chama, o objeto de contexto é suficientemente explícito que ele não causar problemas.)
Donal Fellows

4
-1 para não cita a fonte
Joshua Drake

Você está dizendo seriamente que, idealmente, todas as funções não precisariam de parâmetros? Ou isso é hipérbole?
GreenAsJade

1
Veja o argumento do tio Bob em: informit.com/articles/article.aspx?p=1375308 e observe que, na parte inferior, ele diz "As funções devem ter um pequeno número de argumentos. Nenhum argumento é o melhor, seguido por um, dois e três Mais de três é muito questionável e deve ser evitado com preconceito. "
Michael Durrant

Eu dei a fonte. Engraçado sem comentários desde então. Também tentei responder à parte das "diretrizes", já que muitos consideram o tio Bob e o Clean Code como diretrizes. Interessante que a resposta altamente votada (atualmente) diz não ter conhecimento de nenhuma diretriz. O tio Bob não pretendia ser autoritário, mas é um pouco e essa resposta pelo menos tenta ser uma resposta às especificidades da pergunta.
Michael Durrant 29/11
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.