do..end vs accolades pour les blocs en Ruby

155

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

Je suis fermement dans le camp de n'utiliser que des accolades bouclées pour les courtes one-liners et je ne ... finis pour tout le reste. Mais j'ai pensé que je contacterais la communauté dans son ensemble pour obtenir une solution.

Alors, de quoi s'agit-il 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 pour moi, mais je voulais ouvrir cela à la plus grande communauté.

Blake Taylor
la source
3
Vous vous demandez simplement quels sont les arguments de vos collègues pour utiliser des accolades? Cela ressemble plus à une préférence personnelle qu'à une chose logique.
Daniel Lee
6
Si vous voulez une discussion, faites-en un wiki communautaire. À votre question: c'est juste un style personnel. Je préfère les bretelles bouclées car elles ont l'air plus ringard.
halfdan
7
Ce n'est pas une chose de préférence, il y a des raisons syntaxiques pour utiliser l'un sur l'autre en plus des raisons stylistiques. Plusieurs des réponses expliquent pourquoi. Ne pas utiliser le bon peut causer des bogues très subtils qui sont difficiles à trouver si un autre choix "stylistique" est fait pour ne jamais utiliser de parenthèses enveloppantes pour les méthodes.
le Tin Man
1
Les «conditions de bord» ont la mauvaise habitude de se faufiler sur des personnes qui ne les connaissent pas. Le codage défensif signifie beaucoup de choses, y compris la décision d'utiliser des styles de codage qui minimisent le risque de tomber dans les cas. VOUS le savez peut-être, mais le type deux personnes après vous ne sera peut-être pas après que les connaissances tribales aient été oubliées. Cela a malheureusement tendance à se produire dans les environnements d'entreprise.
the Tin Man
1
@tinman Ces types de conditions de bord sont gérés par TDD. C'est pourquoi Rubist peut écrire du code comme celui-ci et avoir une nuit de repos complète en sachant que de telles erreurs n'existent pas dans leur code. Lors du développement avec TDD ou BDD et une telle erreur de priorité est faite, le rouge affiché à l'écran est ce qui nous rappelle la "connaissance tribale". Habituellement, la solution consiste à ajouter quelques parenthèses quelque part, ce qui est totalement acceptable dans les conventions standard. :)
Blake Taylor

Réponses:

247

La convention générale consiste à utiliser do..end pour les blocs multilignes et les accolades pour les blocs à une seule ligne, mais il existe également une différence entre les deux qui peut être illustrée par 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, alors gardez cela à l'esprit lorsque vous décidez de ce que vous voulez utiliser.

PS: Encore un exemple à garder à l'esprit pendant que 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 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..end dans ces cas pour éviter cette confusion.

Pan Thomakos
la source
44
Je vote pour toujours utiliser des parenthèses autour des paramètres de méthode pour éviter tout le problème.
the Tin Man
@the Tin Man: Vous utilisez même des parenthèses dans Rake?
Andrew Grimm
9
C'est une habitude de programmation à l'ancienne pour moi. Si une langue prend en charge les parenthèses, je les utilise.
the Tin Man
8
+1 pour avoir signalé le problème de priorité. J'obtiens parfois des exceptions inattendues lorsque j'essaye de convertir do; fin à {}
idrinkpabst
1
Pourquoi puts [1,2,3].map do |k| k+1; endn'imprime que l'énumérateur, c'est-à-dire une chose. Est-ce que met pas passé deux arguments ici à savoir [1,2,3].mapet do |k| k+1; end?
ben
55

Depuis Programming Ruby :

Les accolades ont une priorité élevée; do a une faible priorité. Si l'invocation de 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'appel global. Le formulaire do se liera à l'invocation.

Donc le code

f param {do_something()}

Lie le bloc à la paramvariable 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 les arguments de fonction entre parenthèses.

David Brown
la source
7
+1 "Cependant, ce n'est pas un problème si vous mettez les arguments de fonction entre parenthèses.". Je fais cela par habitude, plutôt que de me fier au hasard et au caprice de l'interprète. :-)
the Tin Man
4
@theTinMan, si votre interpréteur utilise le hasard dans son analyseur, vous avez besoin d'un nouvel interpréteur.
Paul Draper
@PaulDraper - en effet, mais toutes les langues ne sont pas d'accord sur cette question. Mais (je pense) qu'il est assez rare pour les parents de ne pas "faire leur travail", donc utiliser les parens vous évite universellement de vous souvenir des décisions "aléatoires" prises par les concepteurs de langage - même si elles sont fidèlement exécutées par les implémenteurs de compilateurs et d'interprètes .
dlu
15

Il y a quelques points de vue là-dessus, c'est vraiment une question de préférence personnelle. De nombreux rubisistes 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 do ... endpour les blocs qui sont exécutés pour les effets secondaires.

GSto
la source
2
Ce n'est pas seulement une question de préférence personnelle. La liaison de paramètres peut provoquer des bogues subtils si les paramètres de méthode ne sont pas entre parenthèses.
the Tin Man
1
Je ne fais pas actuellement la dernière option, mais je suis +1 pour avoir mentionné que certaines personnes adoptent cette approche.
Andrew Grimm
Avdi Grimm plaide pour cela dans un article de blog: devblog.avdi.org/2011/07/26/…
alxndr
8

Il y a un avantage majeur aux accolades: de nombreux éditeurs ont beaucoup plus de facilité à 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 à trouver, d'autant plus que "end" correspond également aux "if".

GlypheGryphe
la source
RubyMine par exemple, met en évidence les do ... endmots-clés par défaut
ck3g
1
Bien que je préfère les accolades, je ne vois aucune raison pour laquelle un éditeur aurait du mal à trouver une chaîne de caractères 2/3 par rapport à un seul caractère. Bien qu'il y ait l'argument selon lequel la plupart des éditeurs correspondent déjà aux accolades, alors qu'il do ... ends'agit d'un cas particulier qui nécessite une logique spécifique au langage.
Chinoto Vokro
7

La règle la plus courante que j'ai vue (la 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 d'une seule ligne, utilisez {}
Peter Brown
la source
7

Je vote pour faire / terminer


La convention est do .. endpour multiligne et{ ... } aux monoplaces.

Mais j'aime do .. endmieux, donc quand j'ai un one liner, je l'utilise do .. endquand même mais je le formate comme d'habitude pour faire / finir en trois lignes. Cela rend tout le monde heureux.

  10.times do 
    puts ...
  end

Un problème avec { }est qu'il est hostile au mode poésie (car 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 ils ne sont pas aussi beaux, à mon avis. Ce ne sont pas des groupes d'instructions et ils entrent en conflit avec les constantes de hachage pour la lisibilité.

De plus, j'en ai assez vu { }dans les programmes C. La manière de Ruby, comme d'habitude, est meilleure. Il existe exactement un type de ifbloc, et vous n'avez jamais à revenir en arrière et à convertir une instruction en une instruction composée.

DigitalRoss
la source
2
"J'en ai assez vu {} dans les programmes C" ... et Perl. Et, +1 pour plusieurs déclarations et pour avoir préconisé les styles de codage zen de Ruby. :-)
the Tin Man
1
À proprement parler, il existe une autre syntaxe «si» - le suffixe «if» ( do_stuff if x==1), qui est assez ennuyeux à convertir en syntaxe normale. Mais c'est une autre discussion.
Kelvin
Il y a un préfixe if, un suffixe if, un suffixe unless(également une sorte de if, un sinon) et une ligne ifavec then. La méthode Ruby est d'avoir de nombreuses façons d'exprimer en fait quelque chose qui est toujours la même chose, bien pire que ce que propose C qui suit des règles très simples et strictes.
Mecki le
2
Donc, si nous aimons les {} en C, cela signifie que nous devrions également les utiliser dans Ruby?
Warren Dew le
6

Quelques rubisistes influents suggèrent d'utiliser des accolades lorsque vous utilisez la valeur de retour, et do / end quand 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 cela se liera au paramètre final d'une méthode au lieu de l'appel entier de la méthode. Ajoutez simplement des parenthèses pour éviter cela.

Kelvin
la source
2

En fait, c'est une préférence personnelle, mais cela dit, au cours des 3 dernières années de mes expériences avec le rubis, j'ai appris que le rubis a son style.

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

def isExpired
  #some code
end 

remarquez le cas du chameau et le plus souvent le préfixe «est» pour l'identifier comme une méthode booléenne.

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

def expired?
  #code
end

donc je pense personnellement qu'il vaut mieux aller avec 'ruby way' (mais je sais qu'il faut du temps pour que quelqu'un comprenne (cela m'a pris environ 1 an: D)).

Enfin, j'irais avec

do 
  #code
end

blocs.

sameera207
la source
2

J'ai mis une autre réponse, bien que la grande différence ait déjà été signalée (précedence / binding), et que cela 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 les dimanches:

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 du 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 {}ici, do/endvous obtiendrez l'erreur, cette méthodetranslate n'existe pas ...

Pourquoi cela se produit est souligné ici plus d'un - la préséance. 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 tout simplement faux 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 fait 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
demi-bit
la source
1

Se résume à un préjugé personnel, je préfère les accolades à 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 d'arrière-plan 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 6/10 développeurs que TOUT LE MONDE devrait les utiliser, si 6/10 utilisent des accolades, alors tenez-vous-en à 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.

Jake Kalstad
la source
3
C'est plus qu'une préférence. La liaison entre les deux est différente et peut entraîner des problèmes difficiles à diagnostiquer. Plusieurs réponses détaillent le problème.
the Tin Man
4
Pour ce qui est des personnes d'autres langues - je pense que la différence de «compréhensibilité» est si petite dans ce cas qu'elle ne mérite pas d'être prise en considération. (Ce n'était pas mon vote contre)
Kelvin
1

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

Shankar Raju
la source
0

Mon style personnel est de mettre l'accent sur la lisibilité plutôt que sur des règles rigides de choix {... }vs do... end, lorsqu'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 les blocs imbriqués multilignes, j'essaie d'intercaler les délimiteurs {... }et do... endpour 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 |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 pense que l'optimisation au cas par cas pour la lisibilité, bien que subjective, est un net gain par rapport au respect de règles rigides.

Boris Stitnicky
la source
1
Venant de Python, je devrais peut-être ignorer Ruby et apprendre Wisp à la place.
Cees Timmerman
J'avais l'habitude de croire que Rubyc'était le meilleur langage pragmatique qui soit. Dois-je dire langage de script. Aujourd'hui, je pense que vous seriez tout aussi bien d'apprendre Brainfuck.
Boris Stitnicky
0

J'ai pris un exemple de la réponse la plus votée ici. Dire,

[1,2,3].map do 
  something 
end

Si vous vérifiez quelle implémentation interne dans array.rb, l'en-tête de la méthode map dit :

Invokes the given block once for each element of self.

def map(*several_variants)
    (yield to_enum.next).to_enum.to_a
end

c'est-à-dire qu'il accepte un bloc de code - tout ce qui se trouve entre do et end est exécuté comme yield. Ensuite, le résultat sera à nouveau collecté sous forme de tableau, ce qui retournera un objet complètement nouveau.

Ainsi, chaque fois que vous rencontrez un bloc do-end ou {} , ayez simplement une carte mentale indiquant que le bloc de code est passé en tant que paramètre, qui sera exécuté en interne.

Vasanth Saminathan
la source
-3

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, piratez ruby ​​donc c'est un drapeau.

Bien sûr, la solution la plus simple "choisissez vos combats" est d'adopter la convention de style selon laquelle une séquence d'extrémités apparaîtra toutes sur la même ligne et d'apprendre à votre coloration syntaxique pour les désactiver. Pour faciliter l'édition, on pourrait utiliser des scripts d'éditeur pour développer / réduire ces séquences.

20% à 25% de mes lignes de code ruby ​​sont "fin" sur leur propre ligne, toutes déduites de manière triviale par mes conventions d'indentation. Ruby est le langage de type lisp à avoir remporté le plus grand succès. Quand les gens contestent cela, demandant où sont toutes les horribles parenthèses, je leur montre une fonction rubis se terminant par sept lignes de "fin" superflues.

J'ai codé pendant des années en utilisant un préprocesseur lisp pour déduire la plupart des parenthèses: Une barre '|' ouvert un groupe qui se fermait automatiquement à la fin de la ligne, et un signe dollar «$» servait d'espace réservé vide où il n'y aurait autrement aucun symbole pour aider à déduire les regroupements. C'est bien sûr un territoire de guerre religieuse. Lisp / schéma sans les parenthèses est le plus poétique de tous les langages. Pourtant, il est plus facile de réduire simplement les parenthèses à l'aide de 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 en retrait en tant que code. Je n'aime pas les personnages de commentaires, quelle que soit la langue.

Syzygies
la source