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 do..end et utiliser des accolades pour définir des blocs multilignes dans Ruby.

Je suis fermement dans le camp de n’utiliser que des accolades bouclés pour les one-liners courts et faire .. aller pour tout le rest. Mais je pensais que je ferais appel à la grande communauté pour obtenir une solution.

Alors, c’est quoi et pourquoi? (Exemple de code hasa)

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, mais je voulais ouvrir cela à la grande communauté.

La convention générale est d’utiliser do..end pour les blocs multi-lignes et les accolades pour les blocs de lignes simples, 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 # => nil 

Cela signifie que {} a une priorité plus élevée que do..end, gardez cela à l’esprit lorsque vous décidez de ce que vous voulez utiliser.

PS: Un autre exemple à 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 } 

Utiliser des accolades pour les parameters est peut-être quelque chose que vous voulez faire de toute façon, mais si vous ne le faites pas, il vaut probablement mieux utiliser do..end dans ces cas-là pour éviter cette confusion.

De la programmation Ruby :

Les accolades ont une haute priorité; faire a une faible priorité. Si l’appel de méthode a des parameters qui ne sont pas entre parenthèses, la forme d’accolade d’un bloc sera liée au dernier paramètre et non à l’appel global. La forme do se liera à l’invocation.

Donc le code

 f param {do_something()} 

Lie le bloc à la variable param alors que le code

 f param do do_something() end 

Lie le bloc à la fonction f .

Cependant, il ne s’agit pas d’un problème si vous placez 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 rubyistes adoptent 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 pour do ... end blocs exécutés pour les effets secondaires.

Les accolades ont un avantage majeur: de nombreux éditeurs ont beaucoup plus de facilité à les faire correspondre, ce qui facilite certains types de débogage. En attendant, le mot-clé “do … end” est un peu plus difficile à concilier, d’autant plus que “end” correspond également à “if” s.

Je vote pour do / end


La convention est do .. end pour multiline et { ... } pour one-liners.

Mais j’aime do .. end mieux, alors quand j’ai un seul paquebot, j’utilise do .. end quand même, mais formate-le 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 lié à poésie-mode-hostile (parce qu’il se lie étroitement au dernier paramètre et non à l’appel de la méthode entière, vous devez donc inclure la méthode parens) et, à mon avis, ils ne semblent pas agréable. Ce ne sont pas des groupes d’instructions et ils se heurtent aux constantes de hachage pour plus de lisibilité.

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

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

  • Si c’est un bloc multi-lignes, utilisez do / end
  • S’il s’agit d’un bloc de ligne unique, utilisez {}

Quelques rubyistes influents suggèrent d’utiliser des accolades lorsque vous utilisez la valeur de retour, et faites / fin lorsque vous n’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 être plus prudent en utilisant des accolades, car cela va lier au paramètre final d’une méthode au lieu de l’appel de la méthode entière. Ajoutez simplement des parenthèses pour éviter cela.

En raison de préjugés personnels, je préfère les accolades à un bloc do / end car il est plus compréhensible pour un plus grand nombre de développeurs car la majorité des langages d’arrière-plan les utilisent par rapport à la convention do / end. Cela étant dit, la vraie clé est de parvenir à un accord dans votre boutique, si do / end est utilisé par 6/10 développeurs, EVERYONE devrait les utiliser, si 6/10 utilisent des accolades, respectez ce paradigme.

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

Il existe 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, au cours des trois dernières années de mes expériences de rbuy, j’ai appris que le rbuy a son style.

Un exemple serait, si vous venez d’un arrière-plan JAVA, d’utiliser une méthode booléenne

 def isExpired #some code end 

remarquez le cas camel et le plus souvent le préfixe ‘is’ pour l’identifier comme une méthode booléenne.

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

 def expired? #code end 

donc je pense personnellement, il vaut mieux aller avec «Ruby Way» (Mais je sais que cela prend du temps pour que quelqu’un comprenne (ça m’a pris environ un an: D)).

Enfin, j’irais avec

 do #code end 

des blocs.

Je mets une autre réponse, bien que la grande différence ait déjà été mise en évidence (prédence / liaison), et que cela peut causer 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 se 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 du code d’embellissement …

 #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, la méthode translate n’existe pas …

Pourquoi cela se produit est souligné ici plus d’un – préséance. Mais où mettre des accolades ici? (@ the Tin Man: j’utilise toujours des accolades, comme toi, mais ici … surveillé)

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 oeil sur les accolades / préséance!”

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

Mon style personnel est de mettre l’accent sur la lisibilité par rapport aux règles rigides de {} vs do … le choix end , lorsqu’un tel 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 les blocs nesteds multilignes, j'essaie d’interpénétrer { ... } et de do ... des délimiteurs pour obtenir le résultat le plus naturel, par exemple.

 Foo = Module.new { if true then Bar = Module.new { # I intersperse {} and keyword delimiters def quux "quux".tap do |ssortingng| # I choose not to intersperse here, because puts "(#{ssortingng.size} characters)" # for multiline tap, do ... end in this end # case still loks more readable to me. end } end } 

Bien que le manque de règles rigides puisse produire des choix légèrement différents pour différents programmeurs, je pense que l’optimisation au cas par cas pour 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 “fin” sur sa propre ligne, à partir de l’indentation. Les penseurs profonds qui préfèrent un code concis ont raison.

Mieux encore, hack ruby ​​c’est un drapeau.

Bien entendu, la solution la plus simple consiste à adopter la convention de style selon laquelle une séquence de fins apparaît sur la même ligne et à apprendre à colorer votre syntaxe pour les couper. Pour faciliter l’édition, on pourrait utiliser des scripts d’édition pour développer / réduire ces séquences.

De 20% à 25% de mes lignes de codes rbuy sont “terminées” sur leur propre ligne, toutes étant inférées par mes conventions d’indentation. Ruby est la langue de type lisp pour avoir obtenu le plus grand succès. Lorsque les gens contestent cela, demandant où sont toutes les parenthèses horribles, je leur montre une fonction de rbuy 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 qui a autoclosé à la fin de la ligne, et un signe dollar «$» a servi d’espace réservé vide où il n’y aurait pas de symboles pour aider à déduire les regroupements. C’est bien sûr un territoire de guerre religieuse. Lisp / scheme sans les parenthèses est le plus poétique de toutes les langues. Cependant, il est plus facile de simplement supprimer les parenthèses en utilisant la coloration syntaxique.

Je code toujours avec un préprocesseur pour Haskell, pour append heredocs, et par défaut pour toutes les lignes de flush comme des commentaires, tout en retrait comme code. Je n’aime pas les commentaires, quelle que soit la langue.