Arguments multiples dans l'appel de fonction vs tableau unique

24

J'ai une fonction qui prend un ensemble de paramètres, puis les applique en tant que conditions à une requête SQL. Cependant, alors que je favorisais un tableau d'arguments unique contenant les conditions elles-mêmes:

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;
        }
    }
}

Mon collègue a préféré plutôt énumérer tous les arguments explicitement:

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

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

Son argument était qu'en listant explicitement les arguments, le comportement de la fonction devient plus apparent - au lieu d'avoir à fouiller dans le code pour découvrir quel était l'argument mystérieux $param.

Mon problème était que cela devient très verbeux lorsqu'il s'agit de nombreux arguments, comme 10+. Y a-t-il une pratique préférée? Mon pire scénario serait de voir quelque chose comme ceci:

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

xiankai
la source
1
Si la fonction attend des clés spécifiques comme paramètres, au moins ces clés doivent être documentées dans un DocBlock - de cette façon, les IDE peuvent afficher les informations pertinentes sans avoir à fouiller dans le code. en.wikipedia.org/wiki/PHPDoc
Ilari Kajaste
2
Conseil de performance: le foreachn'est pas nécessaire dans ce cas, vous pouvez utiliser à la if(!empty($params['name']))place de foreachet switch.
chiborg
1
Vous avez maintenant une méthode que vous utilisez. Je suggère de jeter un œil ici: book.cakephp.org/2.0/en/models/… pour créer plus de méthodes. Ils peuvent même être générés par magie pour les recherches standard et développés sur mesure pour des recherches spécifiques. En général, cela fait une API claire pour les utilisateurs du modèle.
Luc Franken
2
Une note sur le «conseil de performance» ci-dessus: ne pas utiliser aveuglément !empty($params['name'])pour tester les paramètres - par exemple, la chaîne «0» serait vide. Il vaut mieux utiliser array_key_existspour vérifier la clé, ou issetsi vous ne vous souciez pas null.
AmadeusDrZaius

Réponses:

27

À mon humble avis, votre collègue a raison pour l'exemple ci-dessus. Votre préférence peut être concise, mais elle est également moins lisible et donc moins maintenable. Posez-vous la question de savoir pourquoi s'embêter à écrire la fonction en premier lieu, qu'est-ce que votre fonction «apporte à la table» - je dois comprendre ce qu'elle fait et comment elle le fait, en détail, juste pour l'utiliser. Avec son exemple, même si je ne suis pas un programmeur PHP, je peux voir suffisamment de détails dans la déclaration de fonction que je n'ai pas à me soucier de son implémentation.

En ce qui concerne un plus grand nombre d'arguments, cela est normalement considéré comme une odeur de code. En général, la fonction essaie d'en faire trop? Si vous trouvez un réel besoin pour un grand nombre d'arguments, il est probable qu'ils sont liés d'une manière ou d'une autre et appartiennent ensemble à une ou quelques structures ou classes (peut-être même un tableau d'éléments connexes tels que des lignes dans une adresse). Cependant, le passage d'un tableau non structuré ne résout rien aux odeurs de code.

mattnz
la source
Quant à la nécessité d'un grand nombre d'arguments, la fonction prend essentiellement zéro ou plusieurs arguments, puis limite le résultat défini par ces arguments. Les arguments eux-mêmes n'ont pas grand-chose à voir les uns avec les autres (en tant que clauses SQL distinctes) et peuvent même ne pas avoir la même structure (l'un peut être un simple WHERE, mais un autre nécessiterait plusieurs JOIN en plus du WHERE). Serait-il toujours considéré comme une odeur de code dans ce cas spécifique?
xiankai
2
@xiankai Dans cet exemple, je ferais peut-être un paramètre de tableau pour les wherearguments, un pour les joinspécificateurs, etc. En les nommant de manière appropriée, ce serait toujours auto-documenté.
Jan Doggen
Et si j'utilise setter / getter à la place et que je ne passe aucun argument? Est-ce une mauvaise pratique? N'est-ce pas un but d'utiliser setter / getter?
lyhong
Je conteste que la préférence du PO soit "moins lisible" (comment?) Et moins maintenable. searchQuery ('', '', '', '', 'foo', '', '', '', 'bar') est beaucoup moins lisible ou maintenable que searchQuery (['q' => 'foo', 'x' => 'bar']) Un grand nombre d'arguments n'est pas nécessairement une odeur de code non plus; une requête (), par exemple. Et même pour un plus petit nombre d'arguments, le manque de cohérence dans l'ordre des arguments qui se produit lorsque les arguments sont passés illustre directement quelle mauvaise idée de coder en dur les paramètres. Il suffit de regarder les fonctions de chaîne et de tableau en PHP pour cette incohérence.
MikeSchinkel
4

Ma réponse est plus ou moins indépendante de la langue.

Si le seul but de regrouper des arguments dans une structure de données complexe (table, enregistrement, dictionnaire, objet ...) est de les transmettre dans leur ensemble à une fonction, mieux vaut l'éviter. Cela ajoute une couche inutile de complexité et rend votre intention obscure.

Si les arguments groupés ont une signification par eux-mêmes, alors cette couche de complexité aide à comprendre l'ensemble du design: nommez-la plutôt couche d'abstraction.

Vous pouvez constater qu'au lieu d'une douzaine d'arguments individuels ou d'un grand tableau, la meilleure conception est de deux ou trois arguments regroupant chacun des données corrélées.

mouviciel
la source
1

Dans votre cas, je préférerais la méthode de votre collègue. Si vous écriviez des modèles et que j'utilisais vos modèles pour les développer. Je vois la signature de la méthode de votre collègue et je peux l'utiliser immédiatement.

Alors, je devrais passer par l'implémentation de votre searchQueryfonction pour voir quels paramètres sont attendus par votre fonction.

Je préférerais votre approche uniquement dans le cas où le searchQueryest limité à la recherche uniquement dans une seule table, donc il n'y aura pas de jointures. Dans ce cas, ma fonction ressemblerait à ceci:

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

Donc, je sais immédiatement que les éléments du tableau sont en fait les noms de colonnes d'une table particulière que la classe ayant cette méthode représente dans votre code.

Ozair Kafray
la source
1

Faites les deux, en quelque sorte. array_mergepermet une liste explicite en haut de la fonction, comme le souhaite votre collègue, tout en empêchant les paramètres de devenir trop lourds, comme vous préférez.

Je suggère également fortement d'utiliser la suggestion de @ chiborg dans les commentaires de la question - c'est beaucoup plus clair ce que vous avez l'intention.

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']);
    }
    ....
}
Izkata
la source
0

Vous pouvez également passer une chaîne ressemblant à une chaîne de requête et utiliser parse_str(car il semble que vous utilisez PHP, mais d'autres solutions sont probablement disponibles dans d'autres langues) pour la traiter dans un tableau à l'intérieur de la méthode:

/**
 * 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 ...;
}

et l'appelle comme

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

Vous pouvez utiliser http_build_querypour convertir d'un tableau associatif en une chaîne (l'inverse qui le parse_strfait).

Carlos Campderrós
la source