faire..end vs accolades bouclés pour les blocs en Ruby
J'ai un collègue qui essaie activement de me convaincre que je ne devrais pas utiliser do..terminez et utilisez plutôt des accolades pour définir des blocs multilignes dans Ruby.
Je suis fermement dans le camp de n'utiliser que des accolades pour les courts one-liners et do..fin pour tout le reste. Mais je pensais que je voudrais tendre la main à la grande communauté pour obtenir une résolution.
Alors, qui est-ce, et pourquoi? (Exemple de code shoulda)
context do
setup { do_some_setup() }
should "do somthing" do
# some more code...
end
end
Ou
context {
setup { do_some_setup() }
should("do somthing") {
# some more code...
}
}
Personnellement, il suffit de regarder le ci-dessus répond à la question pour moi, mais je voulais ouvrir cela à la plus grande communauté.
13 réponses
La convention générale est d'utiliser faire..fin pour les blocs multi-lignes et accolades pour les blocs à une seule ligne, mais il y a aussi une différence entre les deux qui peut être illustrée avec cet exemple:
puts [1,2,3].map{ |k| k+1 }
2
3
4
=> nil
puts [1,2,3].map do |k| k+1; end
#<Enumerator:0x0000010a06d140>
=> nil
Cela signifie que {} a une priorité plus élevée que do..fin, donc gardez cela à l'esprit lorsque vous décidez ce que vous voulez utiliser.
P. S: un autre exemple à garder à l'esprit pendant que vous développez vos préférences.
Le code suivant:
task :rake => pre_rake_task do
something
end
Vraiment moyens:
task(:rake => pre_rake_task){ something }
Et ce code:
task :rake => pre_rake_task {
something
}
Signifie Vraiment:
task :rake => (pre_rake_task { something })
Donc, pour obtenir la définition réelle que vous voulez, avec des accolades, vous devez faire:
task(:rake => pre_rake_task) {
something
}
Peut-être que l'utilisation d'accolades pour les paramètres est quelque chose que vous voulez faire de toute façon, mais si vous ne le faites pas, il est probablement préférable d'utiliser do..fin dans ces cas pour éviter cette confusion.
À Partir De Programmation Ruby:
Les accolades ont une priorité élevée; do a une priorité faible. Si l'invocation de la méthode a des paramètres qui ne sont pas entre parenthèses, la forme d'Accolade d'un bloc se liera au dernier paramètre, pas à l'invocation globale. La forme do se liera à l'invocation.
Donc le code
f param {do_something()}
Lie le bloc à la variable param
tandis que le code
f param do do_something() end
Lie le bloc à la fonction f
.
Cependant, ce n'est pas un problème si vous mettez des arguments de fonction entre parenthèses.
Il y a quelques points de vue à ce sujet, c'est vraiment une question de préférence personnelle. Beaucoup de rubyists prennent l'approche que vous faites. Cependant, deux autres styles qui sont communes à l'est de toujours utiliser l'un ou l'autre, ou d'utiliser {}
pour les blocs de valeurs de retour, et do ... end
pour les blocs qui sont exécutées pour des effets secondaires.
Il y a un avantage majeur à accolades - de nombreux éditeurs ont un temps beaucoup plus facile de les faire correspondre, ce qui rend certains types de débogage beaucoup plus facile. Pendant ce temps, le mot-clé " faire...end " est un peu plus difficile à faire correspondre, d'autant plus que "end"correspond également à "if".
Je vote pour do / end
La convention est do .. end
pour multiline et { ... }
Pour one-liners.
, Mais j'aime do .. end
mieux, donc quand j'ai une seule ligne, j'utilise do .. end
de toute façon, mais en forme comme d'habitude pour faire/fin en trois lignes. Cela rend tout le monde heureux.
10.times do
puts ...
end
Un problème avec { }
est que c'est la poésie-mode-hostile (parce qu'ils se lient étroitement au dernier paramètre et non à l'appel de méthode entier, donc vous devez inclure la méthode parens) et ils juste, à mon avis, ne regardez pas aussi beau. Ils ne sont pas des groupes d'instructions et ils entrent en conflit avec les constantes de hachage pour la lisibilité.
De plus, j'ai vu assez de { }
dans les programmes C. Le chemin de Ruby, comme d'habitude, est meilleur. Il y a exactement un type de bloc if
, et vous n'avez jamais à revenir en arrière et à convertir une instruction en une instruction composée.
La règle la plus courante que j'ai vue (plus récemment dans Eloquent Ruby ) est:
- S'il s'agit d'un bloc multiligne, utilisez do / end
- S'il s'agit d'un bloc à une seule ligne, utilisez {}
Quelques rubyistes influents suggèrent d'utiliser des accolades lorsque vous utilisez la valeur de retour,et do / end lorsque vous ne le faites pas.
Http://talklikeaduck.denhaven2.com/2007/10/02/ruby-blocks-do-or-brace (sur archive.org)
Http://onestepback.org/index.cgi/Tech/Ruby/BraceVsDoEnd.rdoc (sur archive.org)
Cela semble être une bonne pratique en général.
Je modifierais un peu ce principe pour dire que vous devriez éviter d'utiliser do/end sur une seule ligne car c'est plus difficile à lire.
Vous devez être plus prudent en utilisant des accolades car il se liera au paramètre final d'une méthode au lieu de l'appel de la méthode entière. Juste ajouter des parenthèses pour éviter cela.
Se résume à un biais personnel, je préfère les accolades sur un bloc do/end car il est plus compréhensible pour un plus grand nombre de développeurs en raison de la majorité des langages de fond les utilisent sur la Convention do/end. Cela étant dit, la vraie clé est de parvenir à un accord au sein de votre boutique, si do/end est utilisé par les développeurs 6/10 que tout le monde devrait les utiliser, si 6/10 utilise des accolades, puis s'en tenir à ce paradigme.
Il s'agit de créer un modèle pour que l'équipe dans son ensemble puisse identifiez les structures de code plus rapidement.
Il y a une différence subtile entre eux, mais { } se lie plus étroitement que do / end.
En fait, c'est une préférence personnelle, mais cela dit, pour les 3 dernières années de mes expériences ruby, ce que j'ai appris, c'est que ruby a son style.
Un exemple serait, si vous venez d'un arrière-plan JAVA, pour une méthode booléenne que vous pouvez utiliser
def isExpired
#some code
end
Notez le cas de chameau et le plus souvent le préfixe 'est' pour l'identifier comme une méthode booléenne.
Mais dans ruby world, la même méthode serait
def expired?
#code
end
Donc je pense personnellement, il vaut mieux aller avec 'Ruby way' (Mais je sais qu'il faut du temps pour comprendre (il m'a fallu environ 1 an :D)).
Enfin, j'irais avec
do
#code
end
Blocs.
J'ai mis une autre réponse, bien que la différenceGrande ait déjà été soulignée (prcedence/binding), et cela peut causer des problèmes difficiles à trouver(L'homme de fer, et d'autres l'ont souligné). Je pense que mon exemple montre le problème avec un extrait de code pas si habituel, même les programmeurs expérimentés ne lisent pas comme le sunday times:
module I18n
extend Module.new {
old_translate=I18n.method(:translate)
define_method(:translate) do |*args|
InplaceTrans.translate(old_translate, *args)
end
alias :t :translate
}
end
module InplaceTrans
extend Module.new {
def translate(old_translate, *args)
Translator.new.translate(old_translate, *args)
end
}
end
Ensuite, j'ai fait un peu d'embellissement de code ...
#this code is wrong!
#just made it 'better looking'
module I18n
extend Module.new do
old_translate=I18n.method(:translate)
define_method(:translate) do |*args|
InplaceTrans.translate(old_translate, *args)
end
alias :t :translate
end
end
Si vous modifiez le {}
ici do/end
, vous obtiendrez l'erreur, que la méthode translate
ne ne pas exister ...
Pourquoi cela se produit est souligné ici plus d'un-priorité. Mais où mettre des accolades ici? (@ The Tin Man: j'utilise toujours des accolades, comme vous, mais ici ... supervisé)
Donc chaque réponse comme
If it's a multi-line block, use do/end If it's a single line block, use {}
Est juste faux {[11] } s'il est utilisé sans le "mais gardez un œil sur les accolades / priorité!"
Encore:
extend Module.new {} evolves to extend(Module.new {})
Et
extend Module.new do/end evolves to extend(Module.new) do/end
(ce que le résultat de extend fait avec le bloc ...)
Donc, si vous voulez utiliser faites / utilisez ceci:
#this code is ok!
#just made it 'better looking'?
module I18n
extend(Module.new do
old_translate=I18n.method(:translate)
define_method(:translate) do |*args|
InplaceTrans.translate(old_translate, *args)
end
alias :t :translate
end)
end
Mon style personnel est de mettre l'accent sur la lisibilité sur les règles rigides de {
...}
vs do
...end
choix, quand un tel choix est possible. Mon idée de la lisibilité est la suivante:
[ 1, 2, 3 ].map { |e| e + 1 } # preferred
[ 1, 2, 3 ].map do |e| e + 1 end # acceptable
[ 1, 2, 3 ].each_with_object [] do |e, o| o << e + 1 end # preferred, reads like a sentence
[ 1, 2, 3 ].each_with_object( [] ) { |e, o| o << e + 1 } # parens make it less readable
Foo = Module.new do # preferred for a multiline block, other things being equal
include Comparable
end
Foo = Module.new { # less preferred
include Comparable
}
Foo = Module.new { include Comparable } # preferred for a oneliner
Foo = module.new do include Comparable end # imo less readable for a oneliner
[ [ 1 ], [ 1, 2 ] ].map { |e| e.map do |e| e + 1 end } # slightly better
[ [ 1 ], [ 1, 2 ] ].map { |e| e.map { |e| e + 1 } } # slightly worse
Dans une syntaxe plus complexe, telle que des blocs imbriqués multilignes, j'essaie d'intercaler {
...}
et do
...end
délimiteurs pour la plupart des résultats naturels, par exemple.
Foo = Module.new {
if true then
Bar = Module.new { # I intersperse {} and keyword delimiters
def quux
"quux".tap do |string| # I choose not to intersperse here, because
puts "(#{string.size} characters)" # for multiline tap, do ... end in this
end # case still loks more readable to me.
end
}
end
}
Bien que l'absence de règles rigides puisse produire des choix légèrement différents pour différents programmeurs, je crois que cas par cas l'optimisation de la lisibilité, bien que subjective, est un gain net par rapport au respect de règles rigides.
Il y a une troisième option: Écrire un préprocesseur pour déduire " end " sur sa propre ligne, à partir de l'indentation. Les penseurs profonds qui préfèrent le code concis ont raison.
Mieux encore, pirater ruby donc c'est un drapeau.
Bien sûr, la solution la plus simple est d'adopter la convention de style selon laquelle une séquence de extrémités apparaît sur la même ligne, et d'apprendre à votre coloration syntaxique à les couper. Pour faciliter l'édition, on pourrait utiliser des scripts d'éditeur pour développer / réduire ces séquence.
20% à 25% de mes lignes de code ruby sont "end" sur leur propre ligne, toutes inférées trivialement par mes conventions d'indentation. Ruby est le langage lisp à avoir obtenu le plus grand succès. Quand les gens contestent cela, demandant où sont toutes les parenthèses horribles, je leur montre une fonction ruby se terminant par sept lignes de "fin" superflue.
J'ai codé pendant des années en utilisant un préprocesseur lisp pour déduire la plupart des parenthèses: une barre ' | ' a ouvert un groupe autoclosé à la fin de la ligne, et un signe dollar ' $ ' a servi d'espace réservé vide où il n'y aurait autrement aucun symbole pour aider à déduire les groupements. C'est bien sûr un territoire de guerre de religion. Lisp / scheme sans les parenthèses est le plus poétique de toutes les langues. Pourtant, il est plus facile de simplement calmer les parenthèses en utilisant la coloration syntaxique.
Je code toujours avec un préprocesseur pour Haskell, pour ajouter des heredocs, et pour par défaut toutes les lignes de vidage en tant que Commentaires, tout est indenté en tant que code. Je n'aime pas les caractères de commentaire, quelle que soit la langue.