È un buon stile tornare esplicitamente in Ruby?


155

Proveniente da uno sfondo Python, dove c'è sempre un "modo giusto di farlo" (un modo "Pythonic") quando si tratta di stile, mi chiedo se lo stesso esiste per Ruby. Ho usato le mie linee guida di stile, ma sto pensando di rilasciare il mio codice sorgente e mi piacerebbe che aderisse a qualsiasi regola non scritta che potrebbe esistere.

È "The Ruby Way" a scrivere esplicitamente i returnmetodi? L'ho visto fatto con e senza, ma c'è un modo giusto per farlo? C'è forse il momento giusto per farlo? Per esempio:

def some_func(arg1, arg2, etc)
  # Do some stuff...
  return value # <-- Is the 'return' needed here?
end

3
Questa è una domanda molto antica, ma per coloro che hanno domande simili, il libro Eloquent Ruby è altamente raccomandato. Non è una guida di stile quanto un'introduzione alla scrittura di Ruby idiomatico. Vorrei che più persone lo leggessero!
Brian Caro,

Come persona che ha ereditato un'app legacy di grandi dimensioni sviluppata in Ruby, apprezzo molto che tu abbia posto questa domanda. Questo merita un voto. Questo è disseminato in tutta la nostra app legacy. Uno dei miei lavori in questo team è migliorare la base di codice (difficile quando si è nuovi su Ruby).
Stevers

Risposte:


226

Vecchia (e "risposta") domanda, ma aggiungerò i miei due centesimi come risposta.

TL; DR - Non è necessario, ma in alcuni casi può rendere il codice molto più chiaro.

Anche se non usare un ritorno esplicito può essere "la via di Ruby", è fonte di confusione per i programmatori che lavorano con codice non familiare o non hanno familiarità con questa funzionalità di Ruby.

È un esempio un po 'inventato, ma immagina di avere una piccola funzione come questa, che aggiunge uno al numero passato e lo assegna a una variabile di istanza.

def plus_one_to_y(x)
    @y = x + 1
end

Doveva essere una funzione che restituiva un valore o no? È davvero difficile dire cosa intendesse lo sviluppatore, poiché entrambi assegna la variabile di istanza e restituisce anche il valore assegnato.

Supponiamo molto più tardi, un altro programmatore (forse non così familiare con il modo in cui Ruby ritorna sulla base dell'ultima riga di codice eseguita) arriva e vuole inserire alcune istruzioni di stampa per la registrazione, e la funzione diventa questa ...

def plus_one_to_y(x)
    @y = x + 1
    puts "In plus_one_to_y"
end

Ora la funzione è interrotta se qualcosa prevede un valore restituito . Se nulla prevede un valore restituito, va bene. Chiaramente se da qualche parte più in basso nella catena del codice, qualcosa che chiama questo si aspetta un valore restituito, fallirà perché non sta recuperando ciò che si aspetta.

La vera domanda ora è questa: qualcosa si aspettava davvero un valore restituito? Questo ha rotto qualcosa o no? Romperà qualcosa in futuro? Chissà! Solo una revisione completa del codice di tutte le chiamate ti farà sapere.

Quindi, almeno per me, l'approccio delle migliori pratiche è quello di essere molto esplicito che stai restituendo qualcosa se è importante, o di non restituire nulla quando non lo fa.

Quindi, nel caso della nostra piccola funzione demo, supponendo che volessimo restituire un valore, sarebbe scritto in questo modo ...

def plus_one_to_y(x)
    @y = x + 1
    puts "In plus_one_to_y"
    return @y
end

E sarebbe molto chiaro a qualsiasi programmatore che restituisce un valore, e molto più difficile per loro romperlo senza rendersene conto.

In alternativa, si potrebbe scrivere in questo modo ed escludere la dichiarazione di ritorno ...

def plus_one_to_y(x)
    @y = x + 1
    puts "In plus_one_to_y"
    @y
end

Ma perché preoccuparsi di lasciare fuori la parola return? Perché non metterlo solo lì e chiarire al 100% cosa sta succedendo? Non avrà letteralmente alcun impatto sulla capacità di esecuzione del codice.


6
Punto interessante Penso di essermi effettivamente imbattuto in una situazione simile quando ho posto questa domanda, in cui lo sviluppatore originale utilizzava il valore di ritorno implicito nel proprio codice ed era molto difficile capire cosa stesse succedendo. Grazie per la tua risposta!
Sasha Chedygov,

6
Ho trovato la domanda mentre cercavo su Google i ritorni impliciti, dato che ero appena stato bruciato da questo. Avevo aggiunto un po 'di logica alla fine di una funzione che stava implicitamente restituendo qualcosa. Alla maggior parte delle chiamate non importava (o controllava) il valore restituito, ma uno lo faceva - e falliva. Fortunatamente almeno è apparso in alcuni test funzionali.
Tim Holt,

16
Sebbene sia vero, è importante che il codice sia leggibile, evitare una caratteristica ben nota di un linguaggio di programmazione a favore della verbosità è, a mio avviso, una cattiva pratica. Se uno sviluppatore non ha familiarità con Ruby, forse dovrebbero familiarizzare prima di toccare il codice. Trovo un po 'sciocco dover aumentare il disordine del mio codice solo per il futuro evento in cui qualche sviluppatore non-Ruby dovrà fare qualcosa in seguito. Non dovremmo ridurre una lingua al minimo comune denominatore. Parte della bellezza di Ruby è che non dobbiamo usare 5 righe di codice per dire qualcosa che dovrebbe richiedere solo 3.
Brian Dear

25
Se la parola returnaggiunge significato semantico al codice, perché no? Non è molto prolisso - non stiamo parlando di 5 righe contro 3, solo un'altra parola. Preferirei vedere returnalmeno nelle funzioni (forse non in blocchi) in modo da esprimere ciò che il codice effettivamente fa. A volte devi tornare a metà funzione - non tralasciare l'ultima returnparola chiave sull'ultima riga solo perché puoi. Non favorisco affatto la verbosità, ma preferisco la coerenza.
mindplay.dk,

6
Questa è un'ottima risposta Ma lascia comunque il rischio di scrivere un metodo Ruby con l'intenzione di essere una procedura (nota anche come unità di restituzione della funzione), ad esempio, side_effect()e un altro sviluppatore decide di (ab) utilizzare il valore di ritorno implicito della procedura. Per risolvere questo problema, puoi rendere esplicite le tue procedure return nil.
Alex Dean,

66

No. Un buon stile Ruby userebbe generalmente solo un ritorno esplicito per un ritorno anticipato . Ruby è grande sul minimalismo del codice / magia implicita.

Detto questo, se un ritorno esplicito renderebbe le cose più chiare o più facili da leggere, non danneggerebbe nulla.


116
Qual è il punto del minimalismo del codice se si traduce in un massimo di confusione?
jononomo,

@JonCrowell Dovrebbe invece tradursi in un massimo di documentazione.
jazzpi,

29
@jazzpi Se devi documentare pesantemente il tuo codice per capire che non stai praticando il minimalismo, stai praticando il golf del codice .
Schwern,

2
Non è confuso escludere il ritorno finale, è confuso includerlo - in quanto sviluppatore di Ruby, returnha già un significato semantico significativo come EARLY EXIT alla funzione.
Devon Parsons,

14
@DevonParsons Come diavolo qualcuno potrebbe confondere un returnnell'ultima riga come EARLY EXIT comunque?
DarthPaghius,

31

Personalmente uso la returnparola chiave per distinguere tra quelli che chiamo metodi funzionali , ovvero metodi che vengono eseguiti principalmente per il loro valore di ritorno, e metodi procedurali che vengono eseguiti principalmente per i loro effetti collaterali. Pertanto, i metodi in cui il valore restituito è importante, ottengono una returnparola chiave aggiuntiva per attirare l'attenzione sul valore restituito.

Uso la stessa distinzione quando chiamo metodi: i metodi funzionali ottengono le parentesi, i metodi procedurali no.

E, ultimo ma non meno importante, uso anche questa distinzione con i blocchi: i blocchi funzionali ottengono parentesi graffe, i blocchi procedurali (cioè i blocchi che "fanno" qualcosa) ottengono do/ end.

Tuttavia, cerco di non essere religioso al riguardo: con blocchi, parentesi graffe e do/ endhanno una precedenza diversa, e piuttosto che aggiungere parentesi esplicite per chiarire un'espressione, passo semplicemente all'altro stile. Lo stesso vale per il metodo di chiamata: se l'aggiunta di parentesi all'elenco dei parametri rende il codice più leggibile, lo faccio, anche se il metodo in questione è di natura procedurale.


1
In questo momento ometto un ritorno in qualsiasi "one-liner", che è simile a quello che stai facendo, e faccio tutto il resto allo stesso modo. Buono a sapersi, non sono completamente nel mio stile. :)
Sasha Chedygov il

@Jorg: usi i ritorni anticipati nel tuo codice? Ciò provoca confusione con il tuo schema procedurale / funzionale?
Andrew Grimm,

1
@Andrew Grimm: Sì e Sì. Sto pensando di invertirlo. La stragrande maggioranza dei miei metodi è referenzialmente trasparente / puro / funzionale / qualunque cosa tu voglia chiamarlo comunque, quindi ha senso usare la forma più breve lì. E i metodi scritti in stile funzionale tendono a non avere ritorni precoci, poiché ciò implica la nozione di "passo", che non è molto funzionale. Anche se non mi piacciono i ritorni nel mezzo. Le uso all'inizio come guardie o alla fine per semplificare il flusso di controllo.
Jörg W Mittag,

Ottima risposta, ma in realtà è meglio chiamare metodi procedurali con () e metodi funzionali senza - vedi la mia risposta qui sotto per il perché
Alex Dean,

13

In realtà l'importante è distinguere tra:

  1. Funzioni: metodi eseguiti per il loro valore di ritorno
  2. Procedure: metodi eseguiti per i loro effetti collaterali

Ruby non ha un modo nativo di distinguerli, il che ti rende vulnerabile alla stesura di una procedura side_effect()e un altro sviluppatore che decide di abusare del valore di ritorno implicito della tua procedura (trattandola sostanzialmente come una funzione impura).

Per risolvere questo problema, prendi un foglio dal libro di Scala e Haskell e fai in modo che le tue procedure ritornino esplicitamente nil(aka Unito ()in altre lingue).

Se lo segui, l'uso della returnsintassi esplicita o non diventa solo una questione di stile personale.

Per distinguere ulteriormente tra funzioni e procedure:

  1. Copia la simpatica idea di Jörg W Mittag di scrivere blocchi funzionali con parentesi graffe e blocchi procedurali con do/end
  2. Quando invochi le procedure, usa (), mentre quando invochi funzioni, non farlo

Nota che Jörg W Mittag ha effettivamente sostenuto il contrario - evitando ()s per le procedure - ma questo non è consigliabile perché vuoi che le invocazioni dei metodi con effetti collaterali siano chiaramente distinguibili dalle variabili, in particolare quando l'arity è 0. Vedi la guida dello stile Scala sull'invocazione del metodo per dettagli.


Se ho bisogno del valore restituito da function_a, ed è function_astato scritto per chiamare (e posso operare solo chiamando) procedure_b, allora è function_adavvero una funzione? Restituisce un valore, soddisfacendo i requisiti per le funzioni, ma ha un effetto collaterale, soddisfacendo i requisiti delle procedure.
Devon Parsons,

2
Hai ragione Devon - function_apotrebbe contenere un albero di procedure_...chiamate, come in effetti procedure_apotrebbe contenere un albero di function_...chiamate. Come Scala ma a differenza di Haskell, in Ruby non può esserci alcuna garanzia che una funzione non abbia effetti collaterali.
Alex Dean,

8

La guida di stile afferma che non dovresti usare returnl'ultima dichiarazione. Puoi ancora usarlo se non è l'ultimo . Questa è una delle convenzioni che la comunità segue rigorosamente, e così dovresti se hai intenzione di collaborare con chiunque usi Ruby.


Detto questo, l'argomento principale per l'uso di espliciti returnè che è fonte di confusione per le persone che provengono da altre lingue.

  • Innanzitutto, questo non è del tutto esclusivo per Ruby. Ad esempio, anche Perl ha rendimenti impliciti.
  • In secondo luogo, la maggior parte delle persone a cui questo si applica proviene dalle lingue algolesi. Molti di questi sono molto più "bassi" di Ruby, quindi devi scrivere più codice per fare qualcosa.

Un euristico comune per la lunghezza del metodo (esclusi getter / setter) in Java è una schermata . In tal caso, potresti non vedere la definizione del metodo e / o aver già dimenticato da dove stavi tornando.

D'altra parte, in Ruby è meglio attenersi a metodi lunghi meno di 10 righe . Detto questo, ci si potrebbe chiedere perché debba scrivere il 10% in più di dichiarazioni, quando sono chiaramente implicite.


Dal momento che Ruby non ha metodi nulli e tutto è molto più conciso, stai solo aggiungendo un sovraccarico per nessuno dei vantaggi se vai con esplicite returns.


1
"Questa è una delle convenzioni che la comunità segue rigorosamente" La mia esperienza è che no, le persone non lo seguono rigorosamente a meno che non siano sviluppatori Ruby molto esperti.
Tim Holt

1
@TimHolt Devo essere stato molto fortunato allora. (:
ndnenkov

1
@ndn è completamente d'accordo. L'importante è che quando un metodo (o classe) inizia a superare le 5/100 righe, è una buona idea riconsiderare ciò che si sta tentando di realizzare. Davvero le Regole di Sandi sono un quadro cognitivo per pensare al codice al contrario di un dogma arbitrario. La maggior parte del codice che incontro non è problematica perché è artificialmente vincolata - generalmente è l'opposto - classi con responsabilità multiple, metodi che sono più lunghi di molte classi. In sostanza troppe persone "saltano lo squalo" - mescolando le responsabilità.
Brian Caro,

1
A volte la convenzione è ancora una cattiva idea. È una convenzione stilistica che si basa sulla magia, rende i tipi di ritorno difficili da indovinare. non offre alcun vantaggio se non quello di salvare 7 sequenze di tasti ed è incongruente con quasi tutte le altre lingue. Se mai Ruby eseguisse un passaggio di consolidamento e semplificazione dello stile Python 3, spero implicitamente che qualsiasi cosa sia la prima sul blocco di taglio.
Shayne,

2
Tranne chiaramente, in realtà non rende le cose più facili da leggere! Magia e comprensibilità non sono la cosa giusta.
Shayne,

4

Sono d'accordo con Ben Hughes e non sono d'accordo con Tim Holt, perché la domanda menziona il modo in cui Python lo fa e chiede se Ruby ha uno standard simile.

Lo fa.

Questa è una caratteristica così ben nota del linguaggio che chiunque dovrebbe aspettarsi di eseguire il debug di un problema in Ruby dovrebbe ragionevolmente aspettarselo.


3
Sono d'accordo con te in teoria, ma in pratica i programmatori commettono errori e sbagliano. Caso in questione, sappiamo tutti che usi "==" in modo condizionale e non "=", ma TUTTI abbiamo commesso quell'errore prima e non ce ne siamo accorti fino a dopo.
Tim Holt

1
@TimHolt Non sto affrontando alcun caso d'uso particolare, sto solo rispondendo alla domanda. È esplicitamente cattivo stile, come scelto dalla community, utilizzare la returnparola chiave quando non è necessario.
Devon Parsons,

3
Giusto. Sto dicendo che lo stile che menzioni può portare a errori se non stai attento. Sulla base della mia esperienza, sostengo personalmente uno stile diverso.
Tim Holt

1
@TimHolt A proposito, questa domanda è stata posta molto prima che la guida di stile fosse scritta, quindi le risposte che non sono d'accordo non sono necessariamente sbagliate, ma non aggiornate. In qualche modo sono inciampato qui e non ho visto il link che ho fornito, quindi ho pensato che anche qualcun altro potesse finire qui.
Devon Parsons,

1
Non è uno "standard" ufficiale. La guida allo stile ruby ​​utilizzata da rubocop e collegata in precedenza con l'affermazione che c'è di Devon Parsons, è stata creata da un hacker ruby ​​non core. Quindi il commento di Devon è di fatto semplicemente sbagliato. Naturalmente puoi ANCORA usarlo ma non è uno standard.
shevy

1

È una questione di quale stile preferisci per la maggior parte. È necessario utilizzare la parola chiave return se si desidera tornare da qualche parte nel mezzo di un metodo.


0

Come ha detto Ben. Il fatto che "il valore restituito di un metodo ruby ​​è il valore restituito dell'ultima istruzione nel corpo della funzione" fa sì che la parola chiave return sia utilizzata raramente nella maggior parte dei metodi ruby.

def some_func_which_returns_a_list( x, y, z)
  return nil if failed_some_early_check


  # function code 

  @list     # returns the list
end

4
potresti anche scrivere "return nil if failed_some_early_check" se la tua intenzione è ancora chiara
Mike Woodhouse,

@Gishu In realtà mi stavo lamentando dell'istruzione if, non del nome del metodo.
Andrew Grimm,

@Andrew .. oh l'estremità mancante :). Fatto. risolto per pacificare anche l'altro commento di Mike.
Gishu,

Perché no if failed_check then nil else @list end? Questo è veramente funzionale .
cdunn2001,

@ cdunn2001 Non è chiaro il motivo per cui se altrimenti è funzionale .. la ragione di questo stile è che riduce l'annidamento attraverso le uscite anticipate. IMHO anche più leggibile.
Gishu,
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.