¿Es un buen estilo regresar explícitamente en Ruby?

155

Viniendo de un fondo de Python, donde siempre hay una "forma correcta de hacerlo" (una forma "pitónica") cuando se trata de estilo, me pregunto si existe lo mismo para Ruby. He estado usando mis propias pautas de estilo, pero estoy pensando en publicar mi código fuente, y me gustaría que se adhiera a las reglas no escritas que puedan existir.

¿Es "The Ruby Way" escribir explícitamente los returnmétodos? Lo he visto hecho con y sin, pero ¿hay una manera correcta de hacerlo? ¿Hay quizás un momento adecuado para hacerlo? Por ejemplo:

def some_func(arg1, arg2, etc)
  # Do some stuff...
  return value # <-- Is the 'return' needed here?
end
Sasha Chedygov
fuente
3
Esta es una pregunta muy antigua, pero para aquellos que tienen preguntas similares, el libro Eloquent Ruby es muy recomendable. No es tanto una guía de estilo como una introducción a la escritura idiomática de Ruby. ¡Desearía que más personas lo leyeran!
Brian Estimado
Como alguien que heredó una gran aplicación heredada desarrollada en Ruby, agradezco mucho que hayas hecho esta pregunta. Esto merece un voto a favor. Esto está plagado en toda nuestra aplicación heredada. Uno de mis trabajos en este equipo es mejorar la base de código (difícil cuando soy nuevo en Ruby).
Stevers

Respuestas:

226

Antigua (y "contestada") pregunta, pero arrojaré mis dos centavos como respuesta.

TL; DR: no es necesario, pero puede hacer que su código sea mucho más claro en algunos casos.

Aunque no usar un retorno explícito puede ser "la forma de Ruby", es confuso para los programadores que trabajan con código desconocido o no están familiarizados con esta característica de Ruby.

Es un ejemplo un tanto artificial, pero imagine tener una pequeña función como esta, que agrega uno al número pasado y lo asigna a una variable de instancia.

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

¿Se suponía que era una función que devolvía un valor o no? Es realmente difícil decir qué quiso decir el desarrollador, ya que asigna la variable de instancia Y devuelve el valor asignado también.

Supongamos que mucho más tarde, aparece otro programador (tal vez no tan familiarizado con cómo Ruby regresa en función de la última línea de código ejecutada) y quiere poner algunas declaraciones de impresión para el registro, y la función se convierte en esto ...

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

Ahora la función está rota si algo espera un valor devuelto . Si nada espera un valor devuelto, está bien. Claramente, si en algún lugar más abajo de la cadena de código, algo que llama a esto espera un valor devuelto, fallará ya que no está recuperando lo que espera.

La verdadera pregunta ahora es esta: ¿esperaba algo realmente un valor devuelto? ¿Esto rompió algo o no? ¿Romperá algo en el futuro? ¡Quién sabe! Solo una revisión completa del código de todas las llamadas te lo hará saber.

Entonces, al menos para mí, el enfoque de mejores prácticas es ser muy explícito de que está devolviendo algo si es importante, o no devolver nada cuando no lo es.

Entonces, en el caso de nuestra pequeña función de demostración, suponiendo que deseamos que devuelva un valor, se escribiría así ...

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

Y sería muy claro para cualquier programador que devuelve un valor, y mucho más difícil para ellos romperlo sin darse cuenta.

Alternativamente, uno podría escribirlo así y omitir la declaración de devolución ...

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

Pero, ¿por qué molestarse en dejar de lado la palabra retorno? ¿Por qué no simplemente ponerlo allí y dejar 100% claro lo que está sucediendo? Literalmente no tendrá ningún impacto en la capacidad de rendimiento de su código.

Tim Holt
fuente
66
Punto interesante Creo que en realidad me encontré con una situación similar cuando hice esta pregunta, donde el desarrollador original usó el valor de retorno implícito en su código, y fue muy difícil entender lo que estaba sucediendo. ¡Gracias por tu respuesta!
Sasha Chedygov
66
Encontré la pregunta mientras buscaba en Google en retornos implícitos, ya que me acababa de quemar. Había agregado algo de lógica al final de una función que estaba devolviendo algo implícitamente. A la mayoría de las llamadas no le importó (o verificó) el valor devuelto, pero a uno sí, y falló. Afortunadamente, al menos apareció en algunas pruebas funcionales.
Tim Holt el
16
Si bien es cierto, es importante que el código sea legible, ya que evitar una característica conocida de un lenguaje de programación a favor de la verbosidad es, en mi opinión, una mala práctica. Si un desarrollador no está familiarizado con Ruby, entonces quizás debería familiarizarse antes de tocar el código. Me parece un poco tonto tener que aumentar el desorden de mi código solo para el evento futuro de que algún desarrollador que no sea de Ruby tenga que hacer algo más tarde. No deberíamos reducir un lenguaje al mínimo común denominador. Parte de la belleza de Ruby es que no tenemos que usar 5 líneas de código para decir algo que solo debería tomar 3.
Brian Estimado
25
Si la palabra returnagrega significado semántico al código, ¿por qué no? No es muy detallado: no estamos hablando de 5 líneas frente a 3, solo una palabra más. Prefiero ver returnal menos en funciones (tal vez no en bloques) para expresar lo que el código realmente hace. A veces tiene que regresar a la mitad de la función: no omita la última returnpalabra clave en la última línea solo porque puede hacerlo . No favorezco la verbosidad en absoluto, pero sí la consistencia.
mindplay.dk
66
Esta es una respuesta genial. Pero aún deja el riesgo de que escriba un método Ruby con la intención de ser un procedimiento (también conocido como unidad de retorno de función), por ejemplo, side_effect()y otro desarrollador decide (ab) usar el valor de retorno implícito de su procedimiento. Para solucionar esto, puede hacer sus procedimientos explícitamente return nil.
Alex Dean
66

No. El buen estilo Ruby generalmente solo usaría un retorno explícito para un retorno temprano . Ruby es grande en el minimalismo de código / magia implícita.

Dicho esto, si un retorno explícito aclararía las cosas o facilitaría la lectura, no dañará nada.

Ben Hughes
fuente
116
¿Cuál es el punto del minimalismo de código si resulta en un maximalismo de confusión?
jononomo
@JonCrowell En su lugar, debería dar lugar a la maximización del documentación.
jazzpi
29
@jazzpi Si tienes que documentar mucho tu código para que se entienda bien, no estás practicando minimalismo, estás practicando golf de código .
Schwern
2
No es confuso excluir el retorno final, es confuso INCLUIRLO: como desarrollador de Ruby, returnya tiene un significado semántico significativo como una SALIDA TEMPRANA a la función.
Devon Parsons
14
@DevonParsons ¿Cómo podría alguien confundir un returnen la última línea como una SALIDA TEMPRANA de todos modos?
DarthPaghius
31

Personalmente uso la returnpalabra clave para distinguir entre lo que llamo métodos funcionales , es decir, métodos que se ejecutan principalmente por su valor de retorno, y métodos de procedimiento que se ejecutan principalmente por sus efectos secundarios. Entonces, los métodos en los que el valor de retorno es importante, obtenga una returnpalabra clave adicional para llamar la atención sobre el valor de retorno.

Utilizo la misma distinción cuando llamo a métodos: los métodos funcionales tienen paréntesis, los métodos de procedimiento no.

Y por último, pero no menos importante, también utilizo esa distinción con los bloques: los bloques funcionales obtienen llaves, los bloques de procedimiento (es decir, los bloques que "hacen" algo) obtienen do/ end.

Sin embargo, trato de no ser religioso al respecto: con bloques, llaves y do/ endtienen precedencia diferente, y en lugar de agregar paréntesis explícitos para desambiguar una expresión, simplemente cambio al otro estilo. Lo mismo ocurre con la llamada al método: si agregar paréntesis alrededor de la lista de parámetros hace que el código sea más legible, lo hago, incluso si el método en cuestión es de naturaleza procesal.

Jörg W Mittag
fuente
1
En este momento omito un retorno en cualquier "one-liners", que es similar a lo que estás haciendo, y hago todo lo demás de la misma manera. Es bueno saber que no estoy completamente fuera de mi estilo. :)
Sasha Chedygov
@Jorg: ¿Utiliza devoluciones anticipadas en su código? ¿Eso causa alguna confusión con su esquema procesal / funcional?
Andrew Grimm
1
@ Andrew Grimm: Sí, y sí. Estoy pensando en invertirlo. La gran mayoría de mis métodos es referencialmente transparente / puro / funcional / como quiera llamarlo de todos modos, por lo que tiene sentido usar la forma más corta allí. Y los métodos escritos en estilo funcional tienden a no tener retornos tempranos, ya que eso implica la noción de "paso", que no es muy funcional. Aunque, no me gustan las devoluciones en el medio. Los uso al principio como guardias o al final para simplificar el flujo de control.
Jörg W Mittag
Gran respuesta, pero en realidad es una mejor práctica llamar a métodos de procedimiento con () y métodos funcionales sin - vea mi respuesta a continuación para saber por qué
Alex Dean
13

En realidad, lo importante es distinguir entre:

  1. Funciones: métodos ejecutados por su valor de retorno
  2. Procedimientos: métodos ejecutados por sus efectos secundarios.

Ruby no tiene una forma nativa de distinguirlos, lo que lo deja vulnerable a escribir un procedimiento side_effect()y a otro desarrollador que decide abusar del valor de retorno implícito de su procedimiento (básicamente tratándolo como una función impura).

Para resolver esto, saque una hoja del libro de Scala y Haskell y haga que sus procedimientos regresen explícitamente nil(aka Unito ()en otros idiomas).

Si sigues esto, entonces usar una returnsintaxis explícita o no solo se convierte en una cuestión de estilo personal.

Para distinguir aún más entre funciones y procedimientos:

  1. Copie la buena idea de Jörg W Mittag de escribir bloques funcionales con llaves y bloques de procedimiento con do/end
  2. Cuando invoque procedimientos, use (), mientras que cuando invoque funciones, no

Tenga en cuenta que Jörg W Mittag en realidad abogó al revés, evitando ()los procedimientos, pero eso no es aconsejable porque desea que las invocaciones de métodos de efectos secundarios se distingan claramente de las variables, particularmente cuando el arity es 0. Consulte la guía de estilo Scala sobre invocación de métodos para detalles.

Alex Dean
fuente
Si necesito el valor devuelto desde function_a, y function_afue escrito para llamar (y solo puedo operar llamando) procedure_b, ¿es function_arealmente una función? Devuelve un valor, cumpliendo su requisito de funciones, pero tiene un efecto secundario, cumpliendo los requisitos de los procedimientos.
Devon Parsons
2
Tienes razón Devon: function_apodría contener un árbol de procedure_...llamadas, como procedure_apodría contener un árbol de function_...llamadas. Al igual que Scala pero a diferencia de Haskell, Ruby no puede garantizar que una función no tenga efectos secundarios.
Alex Dean
8

La guía de estilo dice que no deberías usar returnen tu última declaración. Todavía puede usarlo si no es el último . Esta es una de las convenciones que la comunidad sigue estrictamente, y usted también debería hacerlo si planea colaborar con alguien que use Ruby.


Dicho esto, el argumento principal para usar returns explícito es que es confuso para las personas que vienen de otros idiomas.

  • En primer lugar, esto no es completamente exclusivo de Ruby. Por ejemplo, Perl también tiene retornos implícitos.
  • En segundo lugar, la mayoría de las personas a las que se aplica esto provienen de los idiomas Algol. La mayoría de ellos son de "nivel inferior" que Ruby, por lo tanto, debe escribir más código para hacer algo.

Una heurística común para la longitud del método (excluyendo getters / setters) en java es una pantalla . En ese caso, es posible que no esté viendo la definición del método y / o que ya se haya olvidado de dónde regresaba.

Por otro lado, en Ruby es mejor seguir métodos de menos de 10 líneas . Dado eso, uno se preguntaría por qué tiene que escribir ~ 10% más de declaraciones, cuando están claramente implícitas.


Como Ruby no tiene vacío métodos y todo es mucho más conciso, solo está agregando gastos generales para ninguno de los beneficios si utiliza returns explícito .

ndnenkov
fuente
1
"Esta es una de las convenciones que la comunidad sigue estrictamente". Mi experiencia es que no, las personas no siguen esto estrictamente a menos que sean desarrolladores de Ruby con mucha experiencia.
Tim Holt el
1
@TimHolt Debo haber tenido mucha suerte entonces. (:
ndnenkov
1
@ndn completamente de acuerdo. Lo importante es que cuando un método (o clase) comienza a exceder 5/100 líneas, entonces es una buena idea reconsiderar lo que está tratando de lograr. Realmente las Reglas de Sandi son un marco cognitivo para pensar sobre el código en lugar de un dogma arbitrario. La mayoría del código que encuentro no es problemático porque está artificialmente restringido, generalmente es lo contrario, clases con múltiples responsabilidades, métodos que son más largos que muchas clases. Esencialmente, demasiadas personas "saltan al tiburón", mezclando responsabilidades.
Brian Estimado
1
A veces, la convención sigue siendo una mala idea. Es una convención estilística que se basa en la magia, hace que los tipos de retorno sean difíciles de adivinar. no ofrece otra ventaja que guardar 7 pulsaciones de teclas y es incongruente con casi todos los demás idiomas. Si Ruby alguna vez hace un pase de consolidación y simplificación de estilo Python 3, espero que implícitamente algo sea ​​el primero en el bloque de corte.
Shayne
2
¡Excepto que claramente no hace que las cosas sean más fáciles de leer! La magia y la comprensión no son lo mejor.
Shayne
4

Estoy de acuerdo con Ben Hughes y no estoy de acuerdo con Tim Holt, porque la pregunta menciona la forma definitiva en que Python lo hace y pregunta si Ruby tiene un estándar similar.

Lo hace.

Esta es una característica tan conocida del lenguaje que cualquier persona que esperara depurar un problema en ruby ​​debería saberlo razonablemente.

Devon Parsons
fuente
3
Estoy de acuerdo con usted en teoría, pero en la práctica, los programadores cometen errores y cometen errores. Caso en cuestión, todos sabemos que usa "==" en condicional y no "=", pero TODOS hemos cometido ese error antes y no nos dimos cuenta hasta más tarde.
Tim Holt
1
@TimHolt No estoy abordando ningún caso de uso en particular, solo estoy respondiendo la pregunta. Es explícitamente un mal estilo, según lo elegido por la comunidad, usar la returnpalabra clave cuando no es necesario.
Devon Parsons
3
Lo suficientemente justo. Estoy diciendo que el estilo que mencionas puede conducir a errores si no tienes cuidado. Según mi experiencia, yo personalmente abogo por un estilo diferente.
Tim Holt
1
@TimHolt Por cierto, esta pregunta se hizo mucho antes de que se escribiera la guía de estilo, por lo que las respuestas que no están de acuerdo no son necesariamente incorrectas, simplemente están desactualizadas. Sin embargo, tropecé aquí de alguna manera y no vi el enlace que proporcioné, así que pensé que alguien más podría terminar aquí también.
Devon Parsons
1
No es un "estándar" oficial. La guía de estilo ruby ​​utilizada por rubocop y vinculada anteriormente con la afirmación de Devon Parsons, fue creada por un pirata informático ruby ​​no núcleo. Entonces el comentario de Devon es, de hecho, simplemente incorrecto. Por supuesto, TODAVÍA puede usarlo, pero no es un estándar.
shevy
1

Es cuestión de qué estilo prefieres en su mayor parte. Debe usar la palabra clave return si desea regresar desde algún lugar en medio de un método.

Praveen Angyan
fuente
0

Como dijo Ben. El hecho de que "el valor de retorno de un método ruby ​​es el valor de retorno de la última instrucción en el cuerpo de la función" hace que la palabra clave return rara vez se use en la mayoría de los métodos 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
Gishu
fuente
44
También podría escribir "nula vuelta si failed_some_early_check" si su intención es todavía clara
Mike Woodhouse
@Gishu En realidad me estaba quejando de la declaración if, no del nombre del método.
Andrew Grimm
@ Andrew ... oh, el final perdido :). Entendido. arreglado para pacificar el otro comentario de Mike.
Gishu
¿Por qué no if failed_check then nil else @list end? Eso es realmente funcional .
cdunn2001
@ cdunn2001 No está claro por qué si más es funcional ... la razón de este estilo es que reduce el anidamiento a través de salidas tempranas. En mi humilde opinión también más legible.
Gishu