do..end vs parentesi graffe per blocchi in Ruby


155

Ho un collega che sta attivamente cercando di convincermi che non dovrei usare do..end e invece usare parentesi graffe per definire blocchi multilinea in Ruby.

Sono fermamente nell'accampamento di usare solo parentesi graffe per una linea corta e fare ... spendere per tutto il resto. Ma ho pensato di raggiungere la comunità più grande per ottenere una soluzione.

Quindi quale è e perché? (Esempio di alcuni codici shoulda)

context do
  setup { do_some_setup() }
  should "do somthing" do
    # some more code...
  end
end

o

context {
  setup { do_some_setup() }
  should("do somthing") {
    # some more code...
  }
}

Personalmente, solo guardando quanto sopra risponde alla domanda per me, ma volevo aprirlo alla grande comunità.


3
Mi chiedo solo, ma quali sono gli argomenti dei tuoi colleghi per usare le parentesi graffe? Sembra più una preferenza personale che una cosa logica.
Daniel Lee,

6
Se vuoi una discussione trasformala in un wiki della community. Alla tua domanda: è solo uno stile personale. Preferisco le parentesi graffe perché sembrano più nerd.
halfdan

7
Non è una preferenza, ci sono ragioni sintattiche per usare l'una sull'altra oltre a ragioni stilistiche. Molte delle risposte spiegano perché. Non utilizzare quello giusto può causare bug molto sottili che sono difficili da trovare se viene fatta un'altra scelta "stilistica" per non usare mai la parentesi avvolgente per i metodi.
Tin Man,

1
Le "condizioni limite" hanno la cattiva abitudine di intrufolarsi in persone che non ne sono a conoscenza. La codifica difensiva significa molte cose, inclusa la decisione di utilizzare stili di codifica che minimizzano la possibilità di imbattersi in casi. Potresti essere consapevole, ma il ragazzo due persone dopo di te potrebbe non essere dopo che la conoscenza tribale è stata dimenticata. Sfortunatamente tende ad accadere negli ambienti aziendali.
Tin Man,

1
@tinman Questi tipi di condizioni dei bordi sono gestiti da TDD. Questo è il motivo per cui Rubist può scrivere codice in questo modo e riposarsi per una notte intera sapendo che tali errori non esistono nel loro codice. Quando si sviluppa con TDD o BDD e si fa un simile errore in precedenza, il rosso mostrato sullo schermo è ciò che ci ricorda la "conoscenza tribale". Di solito la soluzione è aggiungere un paio di parentesi da qualche parte, il che è totalmente accettabile all'interno delle convenzioni standard. :)
Blake Taylor

Risposte:


247

La convenzione generale è usare do..end per i blocchi multi-linea e le parentesi graffe per i blocchi a linea singola, ma c'è anche una differenza tra i due che possono essere illustrati con questo esempio:

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

Ciò significa che {} ha una precedenza più alta di do..end, quindi tienilo a mente quando decidi cosa vuoi usare.

PS: un altro esempio da tenere a mente mentre sviluppi le tue preferenze.

Il seguente codice:

task :rake => pre_rake_task do
  something
end

significa davvero:

task(:rake => pre_rake_task){ something }

E questo codice:

task :rake => pre_rake_task {
  something
}

significa davvero:

task :rake => (pre_rake_task { something })

Quindi per ottenere la definizione che desideri, con le parentesi graffe, devi fare:

task(:rake => pre_rake_task) {
  something
}

Forse usare parentesi graffe per i parametri è qualcosa che vuoi fare comunque, ma se non lo fai è probabilmente meglio usare do..end in questi casi per evitare questa confusione.


44
Voto per usare sempre la parentesi attorno ai parametri del metodo per eludere l'intero problema.
Tin Man,

@the Tin Man: usi anche le parentesi in Rake?
Andrew Grimm,

9
È un'abitudine di programmazione vecchia scuola per me. Se una lingua supporta le parentesi, le uso.
Tin Man,

8
+1 per indicare il problema di precedenza. A volte ricevo eccezioni impreviste quando provo a convertire do; fine a {}
idrinkpabst il

1
Perché puts [1,2,3].map do |k| k+1; endstampa solo l'enumeratore, ovvero una cosa. Non viene messo qui due argomenti qui [1,2,3].mape precisamente do |k| k+1; end?
ben

55

Dalla programmazione di Ruby :

Le parentesi graffe hanno una precedenza alta; do ha una precedenza bassa. Se l'invocazione del metodo ha parametri che non sono racchiusi tra parentesi, la forma di parentesi graffa di un blocco si legherà all'ultimo parametro, non all'invocazione generale. Il modulo do si legherà all'invocazione.

Quindi il codice

f param {do_something()}

Associa il blocco alla paramvariabile mentre il codice

f param do do_something() end

Associa il blocco alla funzione f.

Tuttavia, ciò non costituisce un problema se si racchiudono argomenti di funzione tra parentesi.


7
+1 "Tuttavia, ciò non costituisce un problema se si racchiudono argomenti di funzione tra parentesi.". Lo faccio per abitudine, piuttosto che affidarmi al caso e al capriccio dell'interprete. :-)
Tin Man,

4
@theTinMan, se il tuo interprete usa la possibilità nel suo parser, hai bisogno di un nuovo interprete.
Paul Draper,

@PaulDraper - davvero, ma non tutte le lingue sono d'accordo su questo problema. Ma (penso) è abbastanza raro che i genitori non "facciano il loro lavoro", quindi l'uso dei genitori ti salva universalmente dal dover ricordare le decisioni "casuali" prese dai progettisti del linguaggio - anche se vengono fedelmente eseguite dagli implementatori di compilatori e interpreti .
dlu

15

Ci sono alcuni punti di vista su questo, è davvero una questione di preferenze personali. Molti rubyists adottano l'approccio che segui. Tuttavia, altri due stili comuni sono usare sempre l'uno o l'altro, oppure usare {}per blocchi che restituiscono valori e do ... endper blocchi eseguiti per effetti collaterali.


2
Non è solo una questione di preferenze personali. Il legame dei parametri può causare bug sottili se i parametri del metodo non sono racchiusi tra parentesi.
Tin Man,

1
Al momento non sto facendo l'ultima opzione, ma sto facendo +1 per menzionare che alcune persone adottano questo approccio.
Andrew Grimm,

Avdi Grimm lo sostiene in un post sul blog: devblog.avdi.org/2011/07/26/…
alxndr

8

C'è un grande vantaggio per le parentesi graffe: molti editor hanno un tempo MOLTO più facile per abbinarli, rendendo alcuni tipi di debug molto più facili. Nel frattempo, la parola chiave "do ... end" è un po 'più difficile da abbinare, soprattutto perché "end" corrisponde anche a "if".


RubyMine, ad esempio, evidenzia le do ... endparole chiave per impostazione predefinita
ck3g

1
Mentre preferisco le parentesi graffe, non vedo alcun motivo per cui un editor avrebbe problemi a trovare una stringa di 2/3 caratteri rispetto a un singolo carattere. Sebbene vi sia l'argomento secondo cui la maggior parte degli editor abbina già parentesi graffe, mentre do ... endè un caso speciale che richiede una logica specifica della lingua.
Chinoto Vokro,

7

La regola più comune che ho visto (più recentemente in Eloquent Ruby ) è:

  • Se si tratta di un blocco multilinea, usa do / end
  • Se si tratta di un blocco a riga singola, utilizzare {}

7

Sto votando per do / end


La convenzione è do .. endper multilinea e { ... }per una linea.

Ma mi piace di do .. endpiù, quindi quando ho un solo liner, lo uso do .. endcomunque ma lo formatto come al solito per fare / finire in tre righe. Questo rende tutti felici.

  10.times do 
    puts ...
  end

Un problema { }è che è poetry-mode-hostile (perché si legano strettamente all'ultimo parametro e non all'intera chiamata del metodo, quindi è necessario includere le parentesi di metodo) e, a mio avviso, non sembrano così belle. Non sono gruppi di istruzioni e si scontrano con le costanti hash per la leggibilità.

Inoltre, ne ho abbastanza di { }nei programmi C. Il modo di Ruby, come al solito, è migliore. Esiste esattamente un tipo di ifblocco e non è necessario tornare indietro e convertire un'istruzione in un'istruzione composta.


2
"Ho visto abbastanza {} nei programmi in C" ... e Perl. E, +1 per diverse affermazioni e per aver sostenuto gli stili di codifica zen di Ruby. :-)
Tin Man,

1
A rigor di termini, esiste un'altra sintassi "if" - il postfisso "if" ( do_stuff if x==1), che è un po 'noioso da convertire nella sintassi normale. Ma questa è un'altra discussione.
Kelvin,

Ci sono prefisso if, postfisso if, postfisso unless(anche una specie di if, un if-not) e una riga ifcon then. Il modo in cui Ruby ha molti modi per esprimere in realtà qualcosa che è sempre la stessa cosa, molto peggio di ciò che C offre, che segue regole molto semplici e rigide.
Mecki,

2
Quindi, se ci piace {} in C, ciò significa che dovremmo usarli anche in Ruby?
Warren Dew

6

Un paio di rubini influenti suggeriscono di usare le parentesi graffe quando si utilizza il valore di ritorno e di fare / finire quando non lo si fa.

http://talklikeaduck.denhaven2.com/2007/10/02/ruby-blocks-do-or-brace (su archive.org)

http://onestepback.org/index.cgi/Tech/Ruby/BraceVsDoEnd.rdoc (su archive.org)

Sembra una buona pratica in generale.

Modificherei un po 'questo principio per dire che dovresti evitare di usare do / end su una sola riga perché è più difficile da leggere.

Devi stare più attento usando le parentesi graffe perché si legherà al parametro finale di un metodo invece dell'intera chiamata del metodo. Basta aggiungere parentesi per evitarlo.


2

In realtà è una preferenza personale, ma detto questo, negli ultimi 3 anni delle mie esperienze con il rubino quello che ho imparato è che il rubino ha il suo stile.

Un esempio potrebbe essere, se provieni da uno sfondo JAVA, per un metodo booleano che potresti usare

def isExpired
  #some code
end 

notare il caso del cammello e molto spesso il prefisso "is" per identificarlo come metodo booleano.

Ma nel mondo rubino, lo stesso metodo sarebbe

def expired?
  #code
end

quindi penso personalmente, è meglio andare con 'ruby way' (Ma so che ci vuole un po 'di tempo per capire (mi ci è voluto circa 1 anno: D)).

Infine, vorrei andare con

do 
  #code
end

blocchi.


2

Ho messo un'altra risposta, sebbene la grande differenza fosse già stata evidenziata (forzata / vincolante), e ciò può causare problemi difficili da trovare (l'Uomo di latta, e altri hanno sottolineato che). Penso che il mio esempio mostri il problema con uno snippet di codice non così normale, anche i programmatori esperti non leggono come i tempi della domenica:

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

Poi ho fatto un po 'di abbellimento del codice ...

#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

se cambi {}qui per do/endottenere l'errore, quel metodo translatenon esiste ...

Perché questo accada è indicato qui più di una precedenza. Ma dove mettere le parentesi graffe qui? (@the Tin Man: uso sempre le parentesi graffe, come te, ma qui ... supervisionato)

così ogni risposta piace

If it's a multi-line block, use do/end
If it's a single line block, use {}

è sbagliato se usato senza "MA tenere d'occhio parentesi graffe / precedenza!"

ancora:

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

e

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

(qualunque cosa faccia il risultato di extension con il blocco ...)

Quindi se vuoi usare do / end usa questo:

#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

Dipende dal pregiudizio personale, preferisco le parentesi graffe rispetto a un blocco do / end in quanto è più comprensibile a un numero maggiore di sviluppatori a causa della maggior parte dei linguaggi di sfondo che li usano rispetto alla convenzione do / end. Detto questo, la vera chiave è quella di raggiungere un accordo all'interno del tuo negozio, se do / end viene utilizzato da sviluppatori 6/10 di quanto TUTTI dovrebbero usarli, se 6/10 usano parentesi graffe, quindi attenersi a quel paradigma.

Si tratta di creare un modello in modo che il team nel suo insieme possa identificare più rapidamente le strutture del codice.


3
È più che una preferenza. Il legame tra i due è diverso e può causare problemi difficili da diagnosticare. Molte delle risposte descrivono in dettaglio il problema.
Tin Man,

4
In termini di persone di altri background linguistici, penso che la differenza nella "comprensibilità" sia così piccola in questo caso da non giustificare la considerazione. (Non è stato il mio downvote tra il.)
Kelvin

1

C'è una sottile differenza tra loro, ma {} si lega più strettamente di do / end.


0

Il mio stile personale è quello di enfatizzare la leggibilità rispetto alle rigide regole della scelta {... }vs do... end, quando tale scelta è possibile. La mia idea di leggibilità è la seguente:

[ 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

Nella sintassi più complessa, come i blocchi nidificati multilinea, provo a intercalare {... }e do... i enddelimitatori per il risultato più naturale, ad es.

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
}

Sebbene la mancanza di regole rigide possa produrre scelte leggermente diverse per programmatori diversi, credo che l'ottimizzazione caso per caso per la leggibilità, sebbene soggettiva, sia un guadagno netto rispetto all'adesione a regole rigide.


1
Venendo da Python, forse dovrei saltare Ruby e imparare invece Wisp.
Cees Timmerman,

Credevo Rubyfosse il miglior linguaggio pragmatico là fuori. Dovrei dire linguaggio di scripting. Oggi penso che saresti ugualmente benestante nell'apprendimento Brainfuck.
Boris Stitnicky,

0

Ho preso un esempio della risposta più votata qui. Dire,

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

Se controlli quale implementazione interna in array.rb, l'intestazione del metodo map dice :

Invokes the given block once for each element of self.

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

cioè accetta un blocco di codice - qualunque cosa sia tra do e end viene eseguita come resa. Quindi il risultato verrà nuovamente raccolto come array, quindi restituirà un oggetto completamente nuovo.

Quindi, ogni volta che si incontra un blocco do-end o {} , basta avere una mappa mentale che il blocco di codice viene passato come parametro, che verrà eseguito internamente.


-3

C'è una terza opzione: scrivere un preprocessore per inferire "end" sulla propria riga, dal rientro. I pensatori profondi che preferiscono il codice conciso sembrano avere ragione.

Meglio ancora, hackerare il rubino quindi questa è una bandiera.

Naturalmente, la soluzione più semplice "scegli i tuoi combattimenti" è quella di adottare la convenzione di stile secondo cui una sequenza di estremità appare tutta sulla stessa linea e insegnare alla colorazione della sintassi per silenziarle. Per facilità di modifica, è possibile utilizzare gli script dell'editor per espandere / comprimere queste sequenze.

Dal 20% al 25% delle mie righe di codice rubino sono "fine" sulla propria riga, tutte banalmente inferite dalle mie convenzioni di rientro. Ruby è il linguaggio lisp-like ad aver ottenuto il massimo successo. Quando le persone contestano questo, chiedendo dove sono tutte le orribili parentesi, mostro loro una funzione rubino che termina in sette righe di "fine" superflua.

Ho scritto il codice per anni usando un preprocessore lisp per dedurre la maggior parte delle parentesi: una barra '|' aprì un gruppo che si chiudeva automaticamente alla fine della riga e un simbolo di dollaro "$" fungeva da segnaposto vuoto dove altrimenti non ci sarebbero stati simboli per aiutare a dedurre i raggruppamenti. Questo è ovviamente territorio di guerra religiosa. Lisp / schema senza parentesi è la più poetica di tutte le lingue. Tuttavia, è più semplice calmare le parentesi usando la colorazione della sintassi.

Codifico ancora con un preprocessore per Haskell, per aggiungere heredocs e per default tutte le linee di flush come commenti, tutto indentato come codice. Non mi piacciono i personaggi dei commenti, qualunque sia la lingua.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.