Je constate qu'en Ruby (et dans les langages à typage dynamique en général) une pratique très courante consiste à transmettre un hachage au lieu de déclarer des paramètres de méthode concrets. Par exemple, au lieu de déclarer une méthode avec des paramètres et de l'appeler comme suit:
def my_method(width, height, show_border)
my_method(400, 50, false)
vous pouvez le faire de cette façon:
def my_method(options)
my_method({"width" => 400, "height" => 50, "show_border" => false})
J'aimerais connaître votre opinion à ce sujet. Est-ce une bonne ou une mauvaise pratique, devrions-nous le faire ou non? Dans quelle situation utiliser cette pratique est-il valable et quelle situation peut-il être dangereux?
Les deux approches ont leurs avantages et leurs inconvénients. Lorsque vous utilisez un hachage d'options en remplacement d'arguments standard, vous perdez la clarté du code définissant la méthode, mais vous gagnez en clarté chaque fois que vous utilisez la méthode en raison des pseudonymes paramétrés créés à l'aide d'un hachage d'options.
Ma règle générale est que si vous avez beaucoup d'arguments pour une méthode (plus de 3 ou 4) ou beaucoup d'arguments optionnels, utilisez un hachage d'options, sinon utilisez des arguments standard. Toutefois, lors de l'utilisation d'un hachage d'options, il est important de toujours inclure un commentaire avec la définition de la méthode décrivant les arguments possibles.
Ruby a des paramètres de hachage implicites, vous pouvez donc aussi écrire
def my_method(options = {})
my_method(:width => 400, :height => 50, :show_border => false)
et avec Ruby 1.9 et la nouvelle syntaxe de hachage, il peut être
my_method( width: 400, height: 50, show_border: false )
Quand une fonction prend plus de 3-4 paramètres, il est beaucoup plus facile de voir ce qui est quoi sans compter les positions respectives.
Je dirais que si vous êtes soit:
Vous voudrez probablement utiliser un hachage. Il est beaucoup plus facile de voir ce que les arguments signifient sans consulter la documentation.
Pour ceux d'entre vous qui disent qu'il est difficile d'imaginer les options d'une méthode, cela signifie simplement que le code est mal documenté. Avec YARD , vous pouvez utiliser la balise @option
pour spécifier les options:
##
# Create a box.
#
# @param [Hash] options The options hash.
# @option options [Numeric] :width The width of the box.
# @option options [Numeric] :height The height of the box.
# @option options [Boolean] :show_border (false) Whether to show a
# border or not.
def create_box(options={})
options[:show_border] ||= false
end
Mais dans cet exemple spécifique, il y a tellement peu de paramètres simples, donc je pense que je vais aller avec ceci:
##
# Create a box.
#
# @param [Numeric] width The width of the box.
# @param [Numeric] height The height of the box.
# @param [Boolean] show_border Whether to show a border or not.
def create_box(width, height, show_border=false)
end
L'avantage d'utiliser un paramètre Hash
en tant que paramètre est que vous supprimez la dépendance sur le nombre et l'ordre des arguments.
En pratique, cela signifie que vous aurez plus tard la possibilité de refactoriser/modifier votre méthode sans rompre la compatibilité avec le code client (ce qui est très utile lors de la création de bibliothèques car vous ne pouvez pas réellement modifier le code client).
(Sandy Metz's "Conception pratique orientée objet en Ruby" est un excellent livre si la conception de logiciels en Ruby vous intéresse.)
Je pense que cette méthode de passage de paramètre est beaucoup plus claire quand il y a plus que quelques paramètres ou quand il y a un certain nombre de paramètres optionnels. Il fait essentiellement des appels de méthode manifestement auto-documentés.
Il n'est pas courant en Ruby d'utiliser un hachage plutôt que des paramètres formels.
Je pense que ceci est confondu avec le modèle courant consistant à passer un hachage en tant que paramètre lorsque le paramètre peut prendre plusieurs valeurs, par exemple définition des attributs d’une fenêtre dans une boîte à outils graphique.
Si votre méthode ou votre fonction contient un certain nombre d'arguments, déclarez-les et transmettez-les explicitement. Vous obtenez le bénéfice que l'interprète vérifiera que vous avez passé tous les arguments.
N'abusez pas de la fonctionnalité linguistique, sachez quand l'utiliser et quand ne pas l'utiliser.
C'est une bonne pratique. Vous n'avez pas besoin de penser à la signature de la méthode ni à l'ordre des arguments. Un autre avantage est que vous pouvez facilement omettre les arguments que vous ne voulez pas entrer . Vous pouvez jeter un coup d'œil au framework ExtJS car il utilise ce type d'argument de manière extensive.
Je suis sûr que personne n'utilise les langages dynamiques, mais pensez à la pénalité de performance que votre programme subira lorsque vous commencerez à passer des hachages à des fonctions.
Il se peut que l'interpréteur éventuellement soit suffisamment intelligent pour créer un objet de hachage const statique et le référencer uniquement par un pointeur, si le code utilise un hachage avec tous les membres qui sont des littéraux de code source.
Mais si l'un de ces membres est une variable, le hachage doit être reconstruit à chaque appel.
J'ai fait quelques optimisations Perl et ce genre de choses peut devenir perceptible dans les boucles de code internes.
Les paramètres de fonction fonctionnent beaucoup mieux.
C'est un compromis. Vous perdez un peu de clarté (comment savoir quels paramètres passer) et de vérifier (ai-je passé le bon nombre d'arguments?) Et gagnez en souplesse (la méthode peut par défaut ne pas recevoir les paramètres, nous pouvons déployer une nouvelle version plus de paramètres et ne casser aucun code existant)
Vous pouvez voir cette question dans le cadre de la discussion de type plus fort/faible. Voir Steve yegge's blog ici. J'ai utilisé ce style en C et C++ dans les cas où je souhaitais prendre en charge le passage d'arguments assez souple. On peut dire qu'un HTTP GET standard, avec quelques paramètres de requête, correspond exactement à ce style.
Si vous optez pour l'approche de hachage, je dirais que vous devez vous assurer que vos tests sont vraiment bons. Les problèmes liés à des noms de paramètres mal orthographiés n'apparaîtront qu'au moment de l'exécution.
Les hachages sont particulièrement utiles pour transmettre plusieurs arguments facultatifs. J'utilise hash, par exemple pour initialiser une classe dont les arguments sont facultatifs.
class Example
def initialize(args = {})
@code
code = args[:code] # No error but you have no control of the variable initialization. the default value is supplied by Hash
@code = args.fetch(:code) # returns IndexError exception if the argument wasn't passed. And the program stops
# Handling the execption
begin
@code = args.fetch(:code)
rescue
@code = 0
end
end
En général, nous devrions toujours utiliser des arguments standard, à moins que ce ne soit pas possible. L'utilisation d'options lorsque vous n'êtes pas obligé de les utiliser est une mauvaise pratique. Les arguments standard sont clairs et auto-documentés (s'ils sont nommés correctement).
Une (et peut-être la seule) raison d'utiliser des options est si la fonction reçoit des arguments qui ne sont pas traités mais simplement passés à une autre fonction.
Voici un exemple illustrant ceci:
def myfactory(otype, *args)
if otype == "obj1"
myobj1(*args)
elsif otype == "obj2"
myobj2(*args)
else
puts("unknown object")
end
end
def myobj1(arg11)
puts("this is myobj1 #{arg11}")
end
def myobj2(arg21, arg22)
puts("this is myobj2 #{arg21} #{arg22}")
end
Dans ce cas, "myfactory" n'est même pas au courant des arguments requis par "myobj1" ou "myobj2". 'myfactory' passe simplement les arguments à 'myobj1' et 'myobj2' et il est de leur responsabilité de les vérifier et de les traiter.