web-dev-qa-db-fra.com

do..end vs accolades pour les blocs dans Ruby

J'ai un collègue qui essaie activement de me convaincre que je ne devrais pas utiliser end..end et utiliser des accolades pour définir des blocs multilignes dans Ruby.

Je suis fermement convaincu que je n'utilise que des accolades pour les doublés et que je… finis avec tout le reste. Mais je pensais que je me rapprocherais de 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, le simple fait de regarder ce qui précède répond à la question qui m'est posée, mais je voulais ouvrir cette question à l'ensemble de la communauté.

136
Blake Taylor

La convention générale est d'utiliser do..end pour les blocs multilignes et les accolades pour les blocs monolignes, mais il existe également 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..end, donc gardez cela à l'esprit lorsque vous décidez de ce que vous voulez utiliser.

P.S: Un exemple supplémentaire à garder à l’esprit lorsque vous développez vos préférences.

Le code suivant:

task :rake => pre_rake_task do
  something
end

signifie vraiment:

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 que vous voulez, avec des accolades, vous devez faire:

task(:rake => pre_rake_task) {
  something
}

De toute façon, vous voudrez peut-être utiliser des accolades pour les paramètres, mais si vous ne le faites pas, il vaut probablement mieux utiliser faire .. finissez dans ces cas pour éviter cette confusion.

229
Pan Thomakos

De programmation Ruby :

Les accolades ont une haute préséance; do a une faible préséance. Si l'invocation de méthode a des paramètres qui ne sont pas entre parenthèses, la forme d'accolade d'un bloc sera liée au dernier paramètre, pas à l'appel global. Le formulaire do sera lié à 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, ceci n’est pas un problème si vous placez les arguments de la fonction entre parenthèses.

50
David Brown

Il y a quelques points de vue là-dessus, c'est vraiment une question de préférence personnelle. Beaucoup de rubyists prennent l'approche que vous faites. Cependant, deux autres styles communs consistent à toujours utiliser l'un ou l'autre, ou à utiliser {} pour les blocs qui renvoient des valeurs et do ... end pour les blocs exécutés pour les effets secondaires.

14
GSto

Les accolades ont un avantage majeur: de nombreux éditeurs ont BEAUCOUP de temps pour les faire correspondre, ce qui facilite beaucoup certains types de débogage. Pendant ce temps, le mot clé "do ... end" est un peu plus difficile à associer, surtout que "end" s correspond également à "if" s.

8
GlyphGryph

La règle la plus courante que j'ai vue (plus récemment dans Eloquent Ruby ) est la suivante:

  • S'il s'agit d'un bloc multiligne, utilisez do/end
  • S'il s'agit d'un bloc d'une seule ligne, utilisez {}.
7
Peter Brown

Je vote pour do/end


La convention est do .. end Pour multiligne et { ... } Pour une ligne.

Mais j'aime mieux do .. end, Alors quand j'ai une ligne, j'utilise quand même do .. end, Mais le formate comme d'habitude pour faire/terminer en trois lignes. Cela rend tout le monde heureux.

  10.times do 
    puts ...
  end

Un problème avec { } Est qu’il est hostile en mode poésie (parce qu’ils se lient étroitement au dernier paramètre et non à l’appel de méthode entier, vous devez donc inclure des parens de méthode) et qu’ils ne font que pas l'air aussi gentil. Ce ne sont pas des groupes d'instructions et ils se heurtent aux constantes de hachage pour des raisons de lisibilité.

De plus, j'ai vu assez de programmes en $ C { }. La manière de Ruby, comme d'habitude, est meilleure. Il existe exactement un type de bloc if et vous ne devez jamais revenir en arrière et convertir une instruction en instruction composée.

7
DigitalRoss

Un couple de rubyists influents suggère d'utiliser des accolades lorsque vous utilisez la valeur de retour et de faire/terminer lorsque vous ne l'utilisez 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 il est plus difficile à lire.

Vous devez faire plus attention en utilisant des accolades car cela liera au paramètre final d'une méthode au lieu de l'appel de la méthode entière. Ajoutez juste des parenthèses pour éviter cela.

5
Kelvin

Je mets une autre réponse, bien que la différence grande ait déjà été soulignée (prédcence/liaison), ce qui peut poser des problèmes difficiles à trouver (le Tin Man, 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 dimanche:

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

Puis j'ai fait un peu de code embellissant ...

#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 changez le {} ici pour do/end vous obtiendrez l'erreur, cette méthode translate n'existe pas ...

Pourquoi cela se produit est souligné ici plus d’une priorité. Mais où mettre des bretelles 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 si utilisé sans le "MAIS gardez un œil sur les accolades/la priorité!"

encore:

extend Module.new {} evolves to extend(Module.new {})

et

extend Module.new do/end evolves to extend(Module.new) do/end

(quel que soit le résultat de extend fait avec le bloc ...)

Donc si vous voulez utiliser do/end 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
1
halfbit

En ce qui concerne les préjugés personnels, je préfère les accolades aux blocs do/end, car ils sont plus compréhensibles pour un plus grand nombre de développeurs car une majorité de langages d’arrière-plan les utilisent par rapport à la convention do/end. Cela dit, la clé est de parvenir à un accord au sein de votre boutique. Si 6 développeurs sur 10 utilisent do/end, alors que TOUT LE MONDE devrait les utiliser, si 6/10 utilise des accolades, respectez ce paradigme.

Il s’agit de créer un modèle afin que l’équipe dans son ensemble puisse identifier les structures de code plus rapidement.

1
Jake Kalstad

Il y a une différence subtile entre eux, mais {} est plus étroit que do/end.

1
Shankar Raju

Mon style personnel est de mettre l’accent sur la lisibilité plutôt que sur les règles rigides de choix { ... } Vs do...end, lorsque ce choix est possible. Mon idée de 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 sur plusieurs lignes, j'essaie de séparer les délimiteurs { ... } Et do...end pour les plus naturels résultat, 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 donner lieu à des choix très différents pour différents programmeurs, j’estime que l’optimisation au cas par cas de la lisibilité, bien que subjective, est un gain net par rapport au respect de règles rigides.

1
Boris Stitnicky

En fait, c’est une préférence personnelle, mais cela dit, au cours des trois dernières années de mon Ruby expérience que j’ai appris, c’est que Ruby a son style.

Par exemple, si vous venez d’un Java arrière-plan, pour une méthode booléenne, vous pourriez utiliser

def isExpired
  #some code
end 

remarquez le cas du chameau et préférez le plus souvent 'is' pour l'identifier comme une méthode booléenne.

Mais dans le monde Ruby, la même méthode serait

def expired?
  #code
end

donc personnellement, je pense qu'il vaut mieux aller avec 'Ruby way' (mais je sais que cela prend un certain temps pour que l'on comprenne (cela m'a pris environ un an: D)).

Enfin, j'irais avec

do 
  #code
end

des blocs.

1
sameera207