¿Hay alguna razón para no modificar los valores de los parámetros pasados ​​por valor?

9

¿Existen argumentos objetivos y compatibles de ingeniería de software a favor o en contra de modificar los valores de los parámetros de sub-valor en el cuerpo de una función?

Una disputa recurrente (principalmente en buena diversión) en mi equipo es si los parámetros pasados ​​por valor deben modificarse o no. Un par de miembros del equipo están convencidos de que nunca se deben asignar parámetros, por lo que el valor que se pasó originalmente a la función siempre se puede consultar. No estoy de acuerdo y sostengo que los parámetros no son más que variables locales inicializadas por la sintaxis de llamar al método; si el valor original de un parámetro por valor es importante, entonces se puede declarar una variable local para almacenar explícitamente este valor. No estoy seguro de que ninguno de nosotros tenga un muy buen apoyo para nuestra posición.

¿Es este un conflicto religioso que no se puede resolver, o hay buenas razones objetivas de ingeniería de software en cualquier dirección?

Nota: La cuestión de principio permanece independientemente de los detalles de implementación del lenguaje particular. En JavaScript, por ejemplo, donde la lista de argumentos es siempre dinámica, los parámetros pueden considerarse como azúcar sintáctica para la inicialización de variables locales desde el argumentsobjeto. Aun así, uno podría tratar los identificadores declarados por parámetros como "especiales" porque aún capturan el paso de información de la persona que llama a la persona que llama.

Joshua Honig
fuente
Todavía es específico del idioma; argumentando que es independiente del lenguaje porque "es así, desde el punto de vista de mi (idioma favorito)" es una forma de argumento inválido. Una segunda razón por la cual es específica del idioma es porque una pregunta que no tiene una respuesta universal para todos los idiomas está sujeta a las culturas y normas de cada idioma; en este caso, las diferentes normas específicas del idioma dan como resultado diferentes respuestas a esta pregunta.
rwong
El principio que los miembros de su equipo están tratando de enseñarle es "una variable, un propósito". Desafortunadamente, no vas a encontrar pruebas definitivas de ninguna manera. Por lo que vale, no recuerdo haber cooptado una variable de parámetro para algún otro propósito, ni recuerdo haberlo considerado alguna vez. Nunca se me ocurrió que podría ser una buena idea, y todavía no creo que lo sea.
Robert Harvey
Pensé que esto debía haberse hecho antes, pero el único engaño que pude encontrar es esta vieja pregunta SO .
Doc Brown
3
Se considera que un lenguaje es menos propenso a errores para prohibir la mutación de argumentos, al igual que los diseñadores de lenguaje funcional consideran que las asignaciones de "dejar" son menos propensas a errores. Dudo que alguien haya probado esta suposición utilizando un estudio.
Frank Hileman

Respuestas:

15

No estoy de acuerdo y sostengo que los parámetros no son más que variables locales inicializadas por la sintaxis de llamar al método

Adopto una tercera posición: los parámetros son como las variables locales: ambos deben ser tratados como inmutables por defecto. Por lo tanto, las variables se asignan una vez y luego solo se leen, no se modifican. En el caso de los bucles, for each (x in ...)la variable es inmutable dentro del contexto de cada iteración. Las razones para esto son:

  1. hace que el código sea más fácil de "ejecutar en mi cabeza",
  2. permite nombres más descriptivos para las variables.

Frente a un método con un montón de variables que se asignan y luego no cambian, puedo concentrarme en leer el código, en lugar de tratar de recordar el valor actual de cada una de esas variables.

Frente al mismo método, esta vez con menos variables, pero ese valor cambia todo el tiempo, ahora tengo que recordar el estado actual de esas variables, así como trabajar en lo que está haciendo el código.

En mi experiencia, lo primero es mucho más fácil para mi pobre cerebro. Es posible que otras personas más inteligentes que yo no tengan este problema, pero me ayudan.

En cuanto al otro punto:

double Foo(double r)
{
    r = r * r;
    r = Math.Pi * r;
    return r;
}

versus

double Foo(int r)
{
    var rSquared = r * r;
    var area = Math.Pi * rSquared;
    return area;
} 

Ejemplos contribuidos, pero en mi opinión, es mucho más claro lo que está sucediendo en el segundo ejemplo debido a los nombres de las variables: transmiten mucha más información al lector que esa masa ren el primer ejemplo.

David Arno
fuente
"las variables locales ... deben tratarse como inmutables por defecto". - ¿Qué?
whatsisname
1
Es bastante difícil ejecutar forbucles con variables inmutables. ¿Encapsular la variable en la función no es suficiente para ti? Si tiene problemas para seguir sus variables en una mera función, quizás sus funciones sean demasiado largas.
Robert Harvey
@RobertHarvey for (const auto thing : things)es un bucle for, y la variable que introduce es (localmente) inmutable. for i, thing in enumerate(things):tampoco muta a ningún lugareño
Caleth
3
Este es en mi humilde opinión en general un muy buen modelo mental. Sin embargo, una cosa que me gustaría agregar: a menudo es aceptable inicializar una variable en más de un paso y tratarla como inmutable después. Esto no está en contra de la estrategia general presentada aquí, solo en contra de seguir esto siempre literalmente.
Doc Brown
3
Wow, leyendo estos comentarios, puedo ver que muchas personas aquí obviamente nunca han estudiado el paradigma funcional.
Joshua Jones
4

¿Es este un conflicto religioso que no se puede resolver, o hay buenas razones objetivas de ingeniería de software en cualquier dirección?

Eso es algo que realmente me gusta de C ++ (bien escrito): puedes ver qué esperar. const string& x1es una referencia constante, string x2es una copia y const string x3es una copia constante. Yo lo que sucederá en la función. x2 será cambiado, porque si no fuera así, no habría razón para hacerlo no constante.

Entonces, si tiene un idioma que lo permite , declare lo que está a punto de hacer con los parámetros. Esa debería ser la mejor opción para todos los involucrados.

Si su idioma no permite esto, me temo que no hay una solución de bala de plata. Ambos es posible. La única pauta es el principio de menor sorpresa. Tome un enfoque y continúe con él en toda su base de código, no lo mezcle.

nvoigt
fuente
Otra cosa buena es que int f(const T x)y int f(T x)solo son diferentes en el cuerpo de la función.
Deduplicador
3
¿Una función C ++ con un parámetro como const int xsimplemente para aclarar x no se cambia por dentro? A mí me parece un estilo muy extraño.
Doc Brown
@DocBrown No estoy juzgando ninguno de los estilos, solo digo que alguien que piensa que los parámetros no deberían cambiarse puede hacer que el compilador lo garantice y alguien que piense que cambiarlos está bien también puede hacerlo. Ambas intenciones se pueden comunicar claramente a través del lenguaje en sí y eso es una gran ventaja sobre un lenguaje que no puede garantizarlo de ninguna manera y en el que debe confiar en pautas arbitrarias y esperar que todos las lean y se ajusten a ellas.
nvoigt
@nvoigt: si alguien prefiere modificar un parámetro de función ("por valor"), simplemente lo consteliminará de la lista de parámetros si esto lo obstaculiza. Entonces no creo que esto responda la pregunta.
Doc Brown
@DocBrown Entonces ya no es constante. No es importante si es constante o no, lo importante es que el lenguaje incluye una forma de comunicarlo al desarrollador sin lugar a dudas. Mi respuesta es que no importa, siempre que lo comunique con claridad , lo que C ++ facilita, otros no y necesita convenciones.
nvoigt
1

Sí, es un "conflicto religioso", excepto que Dios no lee los programas (hasta donde yo sé), por lo que se reduce a un conflicto de legibilidad que se convierte en una cuestión de juicio personal. Y ciertamente lo he hecho, y lo he visto hecho, en ambos sentidos. Por ejemplo,

propagación nula (OBJETO * objeto, doble t0, doble dt) {
  doble t = t0; / * copia local de t0 para evitar cambiar su valor * /
  mientras que (lo que sea) {
    timestep_the_object (...);
    t + = dt; }
  regreso; }

Entonces aquí, solo por el nombre del argumento t0 , probablemente elegiría no cambiar su valor. Pero supongamos, en cambio, que simplemente cambiamos el nombre de ese argumento a tNow , o algo así. Entonces me había olvidado de una copia local y escribí tNow + = dt; por esa última declaración.

Pero, por el contrario, supongamos que sabía que el cliente para el que se escribió el programa está a punto de contratar a algunos programadores nuevos con muy poca experiencia, que leerían y trabajarían en ese código. Entonces probablemente me preocuparía confundirlos con el paso por valor versus la referencia, mientras que sus mentes están 100% ocupadas tratando de digerir toda la lógica comercial. Entonces, en este tipo de casos, siempre declararía una copia local de cualquier argumento que se cambie, independientemente de si es más o menos legible para mí.

Las respuestas a este tipo de preguntas de estilo surgen con experiencia y rara vez tienen una respuesta religiosa canónica. Cualquiera que piense que hay una respuesta única (y que él lo sabe :) probablemente necesite algo más de experiencia.

John Forkosh
fuente