Vários argumentos na chamada de função vs matriz única


24

Eu tenho uma função que recebe um conjunto de parâmetros e depois se aplica a eles como condições para uma consulta SQL. No entanto, embora eu fosse a favor de uma única matriz de argumentos contendo as próprias condições:

function searchQuery($params = array()) {
    foreach($params as $param => $value) {
        switch ($param) {
            case 'name':
                $query->where('name', $value);
                break;
            case 'phone':
                $query->join('phone');
                $query->where('phone', $value);
                break;
        }
    }
}

Meu colega preferiu listar todos os argumentos explicitamente:

function searchQuery($name = '', $phone = '') {
    if ($name) {
        $query->where('name', $value);
    }

    if ($phone) {
        $query->join('phone');
        $query->where('phone', $value);
    }
}

Seu argumento era que, ao listar os argumentos explicitamente, o comportamento da função se torna mais aparente - em vez de ter que se aprofundar no código para descobrir qual $paramera o argumento misterioso .

Meu problema era que isso fica muito detalhado ao lidar com muitos argumentos, como 10 ou mais. Existe alguma prática preferida? Meu pior cenário seria ver algo como o seguinte:

searchQuery('', '', '', '', '', '', '', '', '', '', '', '', 'search_query')


11
Se a função espera chaves específicas como parâmetros, pelo menos essas chaves devem ser documentadas em um DocBlock - para que os IDEs possam mostrar as informações relevantes sem ter que se aprofundar no código. en.wikipedia.org/wiki/PHPDoc
Ilari Kajaste

2
Dica de desempenho: foreachnesse caso, é desnecessário; você pode usar em if(!empty($params['name']))vez de foreache switch.
chiborg

11
Agora você tem um método que você usa. Eu sugeriria dar uma olhada aqui: book.cakephp.org/2.0/en/models/… para criar mais métodos. Eles podem até ser gerados magicamente para descobertas padrão e desenvolvidos de forma personalizada para pesquisas específicas. Em geral, isso faz uma API clara para os usuários do modelo.
Luc Franken


2
Uma observação na 'dica de desempenho' acima: não use cegamente !empty($params['name'])para testar parâmetros - por exemplo, a string "0" estaria vazia. É melhor usar array_key_existspara verificar a chave ou issetse você não se importa null.
AmadeusDrZaius

Respostas:


27

IMHO seu colega está correto para o exemplo acima. Sua preferência pode ser concisa, mas também é menos legível e, portanto, menos sustentável. Faça a pergunta por que você se incomodou em escrever a função em primeiro lugar, o que sua função 'traz para a mesa' - eu tenho que entender o que faz e como faz, em grande detalhe, apenas para usá-la. Com o exemplo dele, mesmo que eu não seja um programador PHP, posso ver detalhes suficientes na declaração da função que não preciso me preocupar com sua implementação.

Quanto a um número maior de argumentos, isso normalmente é considerado um cheiro de código. Normalmente, a função está tentando fazer muito? Se você encontrar uma necessidade real de um grande número de argumentos, é provável que eles estejam relacionados de alguma forma e pertençam juntos a uma ou poucas estruturas ou classes (talvez até uma variedade de itens relacionados, como linhas em um endereço). No entanto, passar uma matriz não estruturada não faz nada para resolver os odores do código.


Quanto à necessidade de um grande número de argumentos, a função está essencialmente pegando zero ou mais argumentos e depois limitando o resultado definido por esses argumentos. Os argumentos em si não têm muito a ver um com o outro (como cláusulas SQL distintas) e podem nem ter a mesma estrutura (um pode ser um WHERE simples, mas outro exigiria vários JOINs além do WHERE). Ainda seria considerado um cheiro de código nesse caso específico?
Xiankai #

2
@xiankai Nesse exemplo, talvez eu faça um array de parâmetros para whereargumentos, um para joinespecificadores etc. Nomeando-os adequadamente, isso ainda seria auto-documentado.
Jan

E se eu usar o setter / getter e não passar nenhum argumento? É uma prática ruim? Não é um propósito usar setter / getter?
lyhong

Eu questionaria que a preferência do OP é "menos legível" (como?) E menos sustentável. searchQuery ('', '', '', '', 'foo', '', '', '', 'bar') é muito menos legível ou sustentável que searchQuery (['q' => 'foo', 'x' => 'bar']) Um grande número de argumentos também não é necessariamente um cheiro de código; uma query (), por exemplo. E mesmo para um número menor de argumentos, a falta de consistência na ordem dos argumentos que ocorre quando os argumentos são passados ​​ilustra diretamente a má ideia de codificar os parâmetros. Basta olhar para as funções de string e array no PHP para obter a inconsistência.
18718 MikeSchinkel

4

Minha resposta é mais ou menos independente da linguagem.

Se o único objetivo de agrupar argumentos em uma estrutura de dados complexa (tabela, registro, dicionário, objeto ...) é passá-los como um todo para uma função, é melhor evitá-lo. Isso adiciona uma camada inútil de complexidade e torna sua intenção obscura.

Se os argumentos agrupados tiverem um significado por si mesmos, essa camada de complexidade ajudará a entender todo o design: nomeie-a como camada de abstração.

Você pode descobrir que, em vez de uma dúzia de argumentos individuais ou uma grande matriz, o melhor design é com dois ou três argumentos, cada um agrupando dados correlacionados.


1

No seu caso, eu preferiria o método do seu colega. Se você estivesse escrevendo modelos e eu estivesse usando seus modelos para desenvolvê-los. Vejo a assinatura do método do seu colega e posso usá-lo imediatamente.

Enquanto, eu teria que passar pela implementação de sua searchQueryfunção para ver quais parâmetros são esperados por sua função.

Eu preferiria sua abordagem apenas no caso em que a searchQuerylimitação é procurar apenas em uma única tabela, para que não haja junções. Nesse caso, minha função ficaria assim:

function searchQuery($params = array()) {
    foreach($params as $param => $value) {
        $query->where($param, $value);
    }
} 

Então, eu sei imediatamente que os elementos da matriz são realmente os nomes das colunas de uma tabela específica que a classe que tem esse método representa no seu código.


1

Faça os dois, mais ou menos. array_mergepermite uma lista explícita na parte superior da função, como seu colega gosta, enquanto evita que os parâmetros se tornem difíceis de manejar, como você preferir.

Também sugiro usar a sugestão de @ chiborg nos comentários da pergunta - é muito mais claro o que você pretende.

function searchQuery($params = array()) {
    $defaults = array(
        'name' => '',
        'phone' => '',
        ....
    );
    $params = array_merge($defaults, $params);

    if(!empty($params['name'])) {
        $query->where('name', $params['name']);
    }
    if (!empty($params['phone'])) {
        $query->join('phone');
        $query->where('phone', $params['phone']);
    }
    ....
}

0

Além disso, você pode passar uma string semelhante a uma string de consulta e usar parse_str(porque parece que você está usando PHP, mas outras soluções provavelmente estão disponíveis em outras linguagens) para processá-la em uma matriz dentro do método:

/**
 * Executes a search in the DB with the constraints specified in the $queryString
 * @var $queryString string The search parameters in a query string format (ie
 *      "foo=abc&bar=hello"
 * @return ResultSet the result set of performing the query
 */
function searchQuery($queryString) {
  $params = parse_str($queryString);
  if (isset($params['name'])) {
    $query->where('name', $params['name']);
  }
  if (isset($params['phone'])) {
    $query->join('phone');
    $query->where('phone', $params['phone']);
  }
  ...

  return ...;
}

e chame assim

$result = searchQuery('name=foo&phone=555-123-456');

Você pode usar http_build_querypara converter de uma matriz associativa em uma sequência de caracteres (o inverso que parse_strocorre).

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.