¿Dividir el cálculo del valor de retorno y la declaración de retorno en métodos de una línea?

26

He tenido una discusión con un compañero de trabajo sobre romper una returndeclaración y la declaración que calcula el valor de retorno en dos líneas.

Por ejemplo

private string GetFormattedValue()
{
    var formattedString = format != null ? string.Format(format, value) : value.ToString();
    return formattedString;
}

en lugar de

private string GetFormattedValue()
{
    return format != null ? string.Format(format, value) : value.ToString();
}

En cuanto al código, realmente no veo un valor en la primera variante. Para mí, este último es más claro, particularmente para los métodos que son cortos. Su argumento fue que la variante anterior es más fácil de depurar, lo cual es un mérito bastante pequeño, ya que VisualStudio nos permite una inspección muy detallada de las declaraciones, cuando la ejecución se detiene debido a un punto de interrupción.

Mi pregunta es, si todavía es un punto válido para escribir código menos claro, ¿solo para hacer más fácil la depuración? ¿Hay más argumentos para la variante con el cálculo dividido y la returndeclaración?

Paul Kertscher
fuente
18
No funciona en VS, pero supondría que no puede establecer un punto de interrupción condicional en una expresión complicada (o sería complicado ingresar), por lo que probablemente pondría asignar y regresar en declaraciones separadas, solo por conveniencia. De todos modos, el compilador probablemente obtendrá el mismo código para ambos.
tofro
1
Esto es posiblemente dependiente del lenguaje, especialmente en lenguajes donde las variables tienen un comportamiento de objeto (posiblemente complejo) en lugar de un comportamiento de puntero. La declaración de @Paul K probablemente sea cierta para lenguajes con comportamiento de puntero, lenguajes donde los objetos tienen un comportamiento de valor simple e idiomas con compiladores maduros y de alta calidad.
MSalters
44
"dado que VisualStudio nos permite una inspección muy detallada de las declaraciones, cuando la ejecución se detiene debido a un punto de interrupción", es así. Entonces, ¿cómo se obtiene el valor de retorno si la función devuelve una estructura con más de un miembro? (y el soporte para esa característica es irregular en el mejor de los casos, hay muchas combinaciones en las que no obtienes el valor de retorno).
Voo
2
¿De qué manera tener una "inspección muy detallada de las declaraciones" en el depurador que alguien usa HOY, hace que sea una mala opción escribir el código para que sea fácil de depurar en CUALQUIER depurador?
Ian
16
Molestarlo aún más mediante la reducción de todo el cuerpo de la función aprivate string GetFormattedValue() => string.Format(format ?? "{0}", value);
Graham

Respuestas:

46

La introducción de variables explicativas es una refactorización bien conocida que a veces puede ayudar a hacer que las expresiones complicadas sean más legibles. Sin embargo, en el caso mostrado,

  • la variable adicional no "explica" nada que no esté claro en el nombre del método circundante
  • la declaración se hace aún más larga, por lo que (ligeramente) menos legible

Además, las versiones más nuevas del depurador de Visual Studio pueden mostrar el valor de retorno de una función en la mayoría de los casos sin introducir una variable superflua (pero tenga cuidado, hay algunas advertencias, eche un vistazo a esta publicación SO más antigua y las diferentes respuestas ).

Entonces, en este caso específico, estoy de acuerdo con usted, sin embargo, hay otros casos en los que una variable explicativa puede mejorar la calidad del código.

Doc Brown
fuente
También estoy de acuerdo en que definitivamente hay casos en los que es útil, sin duda.
Paul Kertscher
2
Usualmente uso resultcomo el nombre de la variable. No es mucho más largo y más fácil de depurar
edc65
26
@ edc65: un nombre genérico como a result menudo solo agrega ruido al código y rara vez aumenta la legibilidad, que es exactamente el punto de mi respuesta. Esto puede justificarse en un contexto en el que ayuda a la depuración, pero evitaría utilizar un depurador que no necesita una variable separada.
Doc Brown
66
@JonHanna herramienta de manera larga según yo. El nombre resulttransmite la información de que este es el valor resultante de la función, para que pueda verlo antes de que la función regrese.
edc65
1
@ edc65 pero eso hace que parezca útil. Entonces, cuando estoy leyendo su código, no me doy cuenta de inmediato de que no lo es. Entonces su código se ha vuelto menos legible.
Jon Hanna
38

Dados los hechos que:

a) No hay impacto en el código final ya que el compilador optimiza la variable.

b) Tenerlo separado mejora la capacidad de depuración.

Personalmente llegué a la conclusión de que es una buena práctica separarlos el 99% del tiempo.

No hay desventajas materiales para hacerlo de esta manera. El argumento de que hincha el código es incorrecto, porque el código hinchado es un problema trivial en comparación con el código ilegible o difícil de depurar. Además, este método por sí solo no puede crear código confuso, eso depende completamente del desarrollador.

Dom
fuente
99
Esta es la respuesta correcta para mí. Esto hace que sea más fácil establecer un punto de interrupción y ver el valor al depurar, y no tengo inconvenientes que yo sepa.
Matthew James Briggs
Para el punto b, en Visual Studio Code, simplemente coloque el punto de interrupción en el retorno y luego agregue la expresión: GetFormattedValue () y eso mostrará el resultado cuando se alcanza el punto de interrupción, por lo que no se necesita la línea adicional. Pero es más fácil ver a los locales con una línea adicional, ya que no requerirá agregar ninguna expresión adicional en el depurador. Entonces, realmente es una cuestión de preferencia personal.
Jon Raynor
3
@JonRaynor para el valor de retorno, coloque el punto de interrupción en el corchete de cierre de la función. Captura el valor devuelto, incluso en funciones con múltiples retornos.
Baldrickk
16

A menudo, la introducción de una variable solo para nombrar algún resultado es muy útil cuando hace que el código sea más autodocumentado. En este caso, eso no es un factor porque el nombre de la variable es muy similar al nombre del método.

Tenga en cuenta que los métodos de una línea no tienen ningún valor inherente. Si un cambio introduce más líneas pero aclara el código, es un buen cambio.

Pero, en general, estas decisiones dependen en gran medida de sus preferencias personales. Por ejemplo, encuentro ambas soluciones confusas porque el operador condicional se está utilizando innecesariamente. Hubiera preferido una declaración if. Pero en su equipo puede haber acordado diferentes convenciones. Luego haga lo que sus convenciones sugieran. Si las convenciones guardan silencio en un caso como este, tenga en cuenta que este es un cambio extremadamente menor que no importa a largo plazo. Si este patrón ocurre repetidamente, tal vez inicie una discusión sobre cómo usted, como equipo, desea manejar estos casos. Pero eso es dividir los pelos entre "buen código" y "quizás un código un poco mejor".

amon
fuente
1
"Encuentro ambas soluciones confusas porque el operador condicional se está utilizando innecesariamente". - No es un ejemplo del mundo real, solo tenía que inventar algo, rápidamente. Es cierto que este podría no ser el mejor ejemplo.
Paul Kertscher
44
+1 por decir esencialmente que esta es una diferencia "bajo el radar" que (si todo lo demás es igual) no vale la pena preocuparse por eso.
TripeHound
3
@Mindwin, cuando uso el operador ternario, generalmente lo divido en varias líneas para que quede claro cuál es el caso verdadero y cuál es el caso falso.
Arturo Torres Sánchez
2
@ ArturoTorresSánchez Yo también hago eso, pero en lugar de a ?y a :uso if() {y } else {- - - - \\ :)
Mindwin
3
@Mindwin, pero no puedo hacer eso cuando estoy en el medio de una expresión (como un inicializador de objeto)
Arturo Torres Sánchez
2

En respuesta a sus preguntas:

Mi pregunta es, si todavía es un punto válido para escribir código menos claro, ¿solo para hacer más fácil la depuración?

Sí. De hecho, parte de su declaración anterior me parece (sin ofender) un poco miope (ver negrita a continuación) " Su argumento fue que la variante anterior es más fácil de depurar, lo cual es un mérito bastante pequeño , ya que VisualStudio nos permite una inspección muy detallada de las declaraciones, cuando la ejecución se detiene debido a un punto de interrupción " .

Hacer la depuración más fácil (casi) nunca es de " mérito pequeño " porque, según algunas estimaciones, el 50% del tiempo del programador se dedica a la depuración ( software de depuración reversible ).

¿Hay más argumentos para la variante con el cálculo dividido y la declaración de devolución?

Sí. Algunos desarrolladores argumentan que el cálculo dividido es más fácil de leer. Esto, por supuesto, ayuda con la depuración, pero también ayuda cuando alguien intenta descifrar las reglas de negocios que su código puede cumplir o aplicar.

NOTA: Las reglas de negocio se pueden cumplir mejor en una base de datos, ya que pueden cambiar con frecuencia. Sin embargo, la codificación clara en esta área sigue siendo primordial. ( Cómo construir un motor de reglas de negocio )

tale852150
fuente
1

Yo iría aún más lejos:

private string GetFormattedValue()
{
    if (format != null) {
        formattedString = string.Format(format, value);
    } else {
        formattedString = value.ToString()
    }
    return formattedString;
}

¿Por qué?

Usar operadores ternarios para una lógica más compleja sería ilegible, por lo que usaría un estilo como el anterior para declaraciones más complejas. Al usar siempre este estilo, su código es coherente y más fácil de analizar para un humano. Además, al introducir este tipo de coherencia (y usar código hilachas y otras pruebas) puede evitar goto failerrores de tipo.

Otra ventaja es que su informe de cobertura de código le avisará si olvidó incluir una prueba para formatque no sea nulo. Este no sería el caso del operador ternario.


Mi alternativa preferida: si está en la "obtención de un retorno lo más rápido posible" y no en contra de múltiples retornos de un método:

private string GetFormattedValue()
{
    if (format != null) {
        return string.Format(format, value);
    }

    return value.ToString();
}

Por lo tanto, puede mirar el último retorno para ver cuál es el valor predeterminado.

Sin embargo, es importante ser coherente y que todos sus métodos sigan una u otra convención.

HorusKol
fuente
1
El primer ejemplo parece una mala práctica porque value.ToString()se llama innecesariamente cuando el formato no es nulo. En el caso general, esto podría incluir cálculos no triviales y puede llevar más tiempo que la versión que incluye una cadena de formato. Considere, por ejemplo, un valueque almacena PI a un millón de decimales, y una cadena de formato que solicita solo los primeros dígitos.
Steve
1
por qué no El private string GetFormattedValue() => string.Format(format ?? "{0}", value); mismo efecto, y use pruebas unitarias para garantizar la corrección en lugar de confiar en el depurador.
Berin Loritsch
1
Aunque estoy de acuerdo ternario puede ser menos clara, el terminador nulo puede hacer las cosas más claras. Al menos en este caso.
Berin Loritsch
1
Querido diario, hoy he leído que escribir código claro y conciso utilizando paradigmas, modismos y operadores bien conocidos (existentes desde hace aproximadamente 40 años) es, entre comillas, comillas dobles ser comillas dobles inteligentes, entre comillas, pero en lugar de escribir código excesivamente detallado que viola SECO y no usar los operadores, modismos y paradigmas mencionados anteriormente, mientras que, en cambio, trata de evitar cualquier cosa que pueda parecer críptica solo para un niño de cinco años sin antecedentes previos de programación, es claridad . Demonios, debo haber envejecido mucho, mi querido diario ... Debería haber aprendido Go cuando tuve la oportunidad.
vaxquis
1
"El uso de operadores ternarios para una lógica más compleja sería ilegible" Si bien es cierto (y he visto personas que complican demasiado la lógica), esto no es cierto para el código del OP, y tampoco es algo específico para los operadores ternarios. Lo único que puedo decir con plena confianza es que la línea es demasiado larga. gist.github.com/milleniumbug/cf9b62cac32a07899378feef6c06c776 es cómo lo reformatearía.
milleniumbug
1

No creo que tal técnica pueda justificarse por la necesidad de depurar. Me he encontrado con este enfoque mil veces, y de vez en cuando sigo haciendo esto, pero siempre tengo en cuenta lo que dijo Martin Fowler sobre la depuración :

Las personas también subestiman el tiempo que pasan depurando. Subestiman cuánto tiempo pueden pasar persiguiendo un error largo. Con las pruebas, sé de inmediato cuando agregué un error. Eso me permite corregir el error inmediatamente, antes de que pueda arrastrarse y esconderse. Hay pocas cosas más frustrantes o pérdida de tiempo que la depuración. ¿No sería mucho más rápido si no creáramos los errores en primer lugar?

Zapadlo
fuente
Martin Fowler es un hombre inteligente y disfruté leyendo sus puntos de vista (y los tuyos). Si bien creo firmemente que las pruebas son necesarias y que se debe dedicar más tiempo a ese esfuerzo, el hecho de que todos seamos seres humanos falibles sugiere que ninguna cantidad de pruebas erradicará todos los errores. Por lo tanto, la depuración siempre será parte del proceso de desarrollo y soporte del programa.
tale852150
1

Creo que algunas personas se están obsesionando con cuestiones tangenciales a la pregunta, como el operador ternario. Sí, mucha gente lo odia, así que tal vez sea bueno mencionarlo de todos modos.

Con respecto al enfoque de su pregunta, mover la declaración devuelta para que sea referenciada por una variable ...

Esta pregunta hace 2 suposiciones con las que no estoy de acuerdo:

  1. Que la segunda variante es más clara o fácil de leer (digo lo contrario es cierto), y

  2. que todos usan Visual Studio. He usado Visual Studio muchas veces y puedo usarlo bien, pero generalmente estoy usando otra cosa. Un entorno de desarrollo que obliga a un IDE específico es uno de los que sería escéptico.

Romper algo con una variable nombrada rara vez hace que algo sea más difícil de leer, casi siempre hace lo contrario. La forma específica en que alguien lo hace podría causar problemas, como si un jefe de autodocumentación lo hace var thisVariableIsTheFormattedResultAndWillBeTheReturnValue = ..., obviamente, eso es malo, pero ese es un tema separado. var formattedText = ...está bien.

En este caso específico , y posiblemente en muchos casos ya que estamos hablando de 1-liners, la variable no le dirá mucho que el nombre de la función ya no le dice. Por lo tanto, la variable no agrega tanto. El argumento de depuración aún podría mantenerse, pero nuevamente, en este caso específico, no veo nada que pueda ser su foco al depurar, y siempre se puede cambiar fácilmente más adelante si de alguna manera alguien necesita ese formato para la depuración o cualquier otra cosa.

En general, y solicitó la regla general (su ejemplo fue solo eso, un ejemplo de una forma generalizada), todos los puntos hechos a favor de la variante 1 (2 líneas) son correctos. Esas son buenas pautas para tener. Pero las pautas deben ser flexibles. Por ejemplo, el proyecto en el que estoy trabajando ahora tiene un máximo de 80 caracteres por línea, así que divido muchas líneas, pero comúnmente encuentro líneas de 81-85 caracteres que serían difíciles de dividir o reducir la legibilidad y los dejo. el límite.

Como es poco probable que agregue valor, no haría 2 líneas para el ejemplo específico dado. Haría la variante 2 (la línea 1) porque los puntos no son lo suficientemente fuertes como para hacer lo contrario en este caso.

Aaron
fuente