Paso de la variable miembro como parámetro de método

33

En un proyecto, encontré un código como este:

class SomeClass
{
    private SomeType _someField;

    public SomeType SomeField
    {
        get { return _someField; }
        set { _someField = value; }
    }

    protected virtual void SomeMethod(/*...., */SomeType someVar)
    {
    }

    private void SomeAnotherMethod()
    {
        //.............
        SomeMethod(_someField);
        //.............
    }

};

¿Cómo puedo convencer a mis compañeros de equipo de que este es un código incorrecto?

Creo que esto es una complicación innecesaria. ¿Por qué pasar una variable miembro como parámetro de método si ya tiene acceso a ella? Esto también es una violación de la encapsulación.

¿Ves otros problemas con este código?

tika
fuente
21
¿Qué te hace pensar que es malo?
Yannis
@ Yannis Rizos, ¿crees que es bueno? Al menos esto es una complicación innecesaria. ¿Por qué pasar variable como parámetro de método, si ya tiene acceso a él? Esto también es una violación de la encapsulación.
tika
2
Puntos válidos, edite la pregunta para incluirlos. No podemos ayudarlo a convencer a sus compañeros de equipo, pero podemos ayudarlo a evaluar el código, de eso debería tratarse su pregunta.
yannis
8
Prefiero tener un método que pueda resumir diferentes variables que un método que haga la constante 2 + 2. El parámetro en el método es para la reutilización.
Dante
Un punto que creo que es importante aquí es el tipo de ese parámetro. Si es un tipo de referencia, no veo ninguna ventaja, pero si es un tipo de valor, creo que tiene sentido ya que si modifica ese tipo de variable, el compilador le advertirá sobre el lugar donde rompió el código.
Rémi

Respuestas:

3

Creo que este es un tema válido, pero la razón por la que está obteniendo respuestas mixtas se debe a la forma en que se formuló la pregunta. Personalmente, tuve las mismas experiencias con mi equipo donde pasar miembros como argumentos era innecesario y complicaba el código. Tendríamos una clase que funciona con un conjunto de miembros, pero algunas funciones acceden a los miembros directamente y otras funciones modifican los mismos miembros a través de parámetros (es decir, usan nombres completamente diferentes) y no hay absolutamente ninguna razón técnica para hacerlo. Por razones técnicas, me refiero a un ejemplo que Kate proporcionó.

Recomendaría dar un paso atrás y, en lugar de centrarse exactamente en la aprobación de los miembros como parámetros, iniciar discusiones con su equipo sobre claridad y legibilidad. Ya sea más formalmente o simplemente en los pasillos, discuta qué hace que algunos segmentos de código sean más fáciles de leer y que otros segmentos de código sean más difíciles. Luego, identifique las medidas de calidad o los atributos del código limpio que como equipo desearía esforzarse. Después de todo, incluso cuando trabajamos en proyectos de campo verde, pasamos más del 90% del tiempo leyendo y tan pronto como se escribe el código (digamos 10-15 minutos después) entra en mantenimiento donde la legibilidad es aún más importante.

Entonces, para su ejemplo específico aquí, el argumento que usaría es que menos código siempre es más fácil de leer que más código. Una función que tiene 3 parámetros es más difícil de procesar para el cerebro que una función que no tiene ninguno o 1 parámetro. Si hay otro nombre de variable, el cerebro tiene que realizar un seguimiento de otra cosa al leer el código. Entonces, para recordar "int m_value" y luego "int localValue" y recordar que uno realmente significa que el otro siempre es más costoso para su cerebro que simplemente trabajar con "m_value".

Para más municiones e ideas, recomendaría recoger una copia del Código Limpio del Tío Bob .

DXM
fuente
Votado abajo después de ver la influencia del libro al que se hace referencia.
Frank Hileman
Aunque una pequeña parte de mí está muy triste porque 5 años después de escribir la respuesta, me quitaste 2 puntos de Internet, hay otra pequeña parte de mí que tiene curiosidad si pudieras proporcionar algunas referencias que serían indicativas de lo (presumiblemente malo) influencia que tuvo el libro al que hice referencia. Eso parecería justo y casi vale la pena esos puntos
DXM
la referencia soy yo mismo, viendo personalmente la influencia en otros desarrolladores. Sin embargo, todas las motivaciones detrás de tales libros son buenas, al especificar que todo el código debe seguir pautas no estándar (es decir, pautas que realmente tienen un propósito), tales libros parecen generar una pérdida de pensamiento crítico, que algunos interpretan como un culto .
Frank Hileman
con respecto a los desafíos del procesamiento del cerebro, considere el concepto de carga cognitiva
jxramos
30

Puedo pensar en una justificación para pasar un campo miembro como un parámetro en un método (privado): hace explícito de qué depende su método.

Como usted dice, todos los campos miembros son parámetros implícitos de su método, porque todo el objeto lo es. Sin embargo, ¿se necesita realmente el objeto completo para calcular el resultado? Si SomeMethodes un método interno del que solo depende _someField, ¿no es más claro hacer explícita esta dependencia? De hecho, hacer explícita esta dependencia también puede indicar que en realidad puede refactorizar este fragmento de código de su clase. (Tenga en cuenta que supongo que no estamos hablando de getters o setters aquí, sino de un código que realmente calcula algo)

No haría el mismo argumento para los métodos públicos, porque la persona que llama no sabe ni le importa qué parte del objeto es relevante para calcular el resultado ...

Andres F.
fuente
2
¿Cuál sería la dependencia implícita restante? Asumí de su ejemplo que _someFieldes el único parámetro necesario para calcular el resultado, y simplemente lo hicimos explícito. (Nota: esto es importante. ¡No agregamos una dependencia, solo la hicimos explícita!)
Andres F.
11
-1 Si no hay dependencias implícitas de los miembros de la instancia, debería ser un miembro estático que tome el valor como parámetro. En este caso, aunque haya encontrado una razón, no creo que sea una justificación válida. Es un mal código.
Steven Evers
2
@SnOrfus En realidad sugerí refactorizar el método completamente fuera de la clase
Andres F.
55
+1. para "... ¿no es más limpio hacer explícita esta dependencia?" Abso volviendo loco. ¿Quién aquí diría "las variables globales son buenas como regla general"? Eso es tan COBOL-68. Escúchame ahora y créeme más tarde. En nuestro código no trivial, a veces, refactorizaré para transmitir explícitamente dónde se usan las variables de clase global. Hemos enloquecido al perro en muchos casos mediante a) el uso arbitrario de un campo privado y su propiedad pública b) ofuscar la transformación de un campo, "ocultando las dependencias". Ahora multiplique esto por una cadena de herencia profunda de 3-5.
radarbob
2
@ Taarion No estoy de acuerdo con el tío Bob en esto. Siempre que sea posible, los métodos deberían ser de función y solo depender de dependencias explícitas. (Al llamar a métodos públicos en OOP, una de estas dependencias es this(o self), pero la llamada en sí la hace explícita obj.method(x)). Otras dependencias implícitas son el estado del objeto; esto generalmente hace que el código sea más difícil de entender. Siempre que sea posible, y dentro de lo razonable, haga que las dependencias sean explícitas y de estilo funcional. En el caso de los métodos privados, si es posible, pase explícitamente todos los parámetros que necesitan. Y sí, ayuda refactorizarlos.
Andres F.
28

Veo una razón importante para pasar las variables miembro como argumentos de función a métodos privados : la pureza de la función. Las variables miembro son efectivamente un estado global desde el punto de vista, además, un estado global mutable si dicho miembro cambia durante la ejecución del método. Al reemplazar las referencias de variables miembro con parámetros de método, podemos hacer que una función sea pura. Las funciones puras no dependen de un estado externo y no tienen efectos secundarios, siempre devuelven los mismos resultados con el mismo conjunto de parámetros de entrada, lo que facilita las pruebas y el mantenimiento futuro.

Claro que no es fácil ni práctico tener todos sus métodos como métodos puros en un lenguaje OOP. Pero creo que usted gana mucho en términos de claridad de código al tener los métodos que manejan la lógica compleja pura y el uso de variables inmutables, mientras mantiene el manejo impuro del estado "global" separado dentro de los métodos dedicados.

Sin embargo, pasar variables miembro a una función pública del mismo objeto cuando se llama a la función externamente constituiría un olor a código importante en mi opinión.

scrwtp
fuente
55
Supurb respuesta. A pesar de que Hwats es ideal, muchas clases en el software de hoy son más grandes y más feas que los programas completos cuando se acuñó la fase "Globals are Evil". Las variables de clase son, a todos los efectos prácticos, globales dentro del alcance de la instancia de clase. Tener grandes cantidades del trabajo realizado en funciones puras hace que el código sea mucho más verificable y robusto.
mattnz
@mattnz, ¿hay alguna forma de proporcionar un enlace a la bibliografía de Hwat o alguno de sus libros sobre programación ideal? He estado buscando en Internet y no puedo encontrar nada sobre él. Google sigue intentando autocorregirlo a "qué".
Buttle Butkus
8

Si la función se llama varias veces, a veces pasando esa variable miembro y otras pasando algo más, entonces está bien. Por ejemplo, no consideraría esto malo en absoluto:

if ( CalculateCharges(newStartDate) > CalculateCharges(m_StartDate) )
{
     //handle increase in charges
}

donde newStartDatees alguna variable local y m_StartDatees una variable miembro.

Sin embargo, si la función solo se llama con la variable miembro que se le pasa, es extraño. Las funciones miembro trabajan en variables miembro todo el tiempo. Pueden estar haciendo esto (dependiendo del idioma en el que esté trabajando) para obtener una copia de la variable miembro; si ese es el caso y no puede verlo, el código podría ser más agradable si explicita todo el proceso.

Kate Gregory
fuente
3
No importa que el método se llame con parámetros distintos de la variable miembro. Lo que importa es que podría llamarse así. No siempre sabes cómo se llamará a un método cuando lo crees.
Caleb
Tal vez debería reemplazar mejor la condición "if" por "needHandleIncreaseChages (newStartDate)" y luego su argumento ya no se mantiene.
Tarion
2

Lo que nadie mencionó es que SomeMethod está protegido virtual. Lo que significa que una clase derivada puede usarla Y volver a implementar su funcionalidad. Una clase derivada no tendría acceso a la variable privada y, por lo tanto, no podría proporcionar una implementación personalizada de SomeMethod que dependa de la variable privada. En lugar de tomar la dependencia de la variable privada, la declaración requiere que el llamante la pase.

Michael Brown
fuente
Lo que falta es que esta variable miembro privado tenga accesos públicos.
tika
¿Entonces está diciendo que el método virtual protegido debería depender del descriptor de acceso público de una variable privada? ¿Y tienes un problema con el código actual?
Michael Brown
Los accesos públicos se crean para ser utilizados. Período.
tika
1

Los marcos de la GUI suelen tener algún tipo de clase 'Ver' que representa elementos dibujados en la pantalla, y esa clase generalmente proporciona un método como invalidateRect(Rect r)marcar parte de su área de dibujo como que necesita ser redibujada. Los clientes pueden llamar a ese método para solicitar una actualización de parte de la vista. Pero una vista también podría llamar a su propio método, como:

invalidateRect(m_frame);

para volver a dibujar toda el área. Por ejemplo, puede hacer esto cuando se agrega por primera vez a la jerarquía de vistas.

No hay nada de malo en hacer esto: el marco de la vista es un rectángulo válido, y la vista en sí misma sabe que quiere volver a dibujarse. La clase View podría proporcionar un método separado que no toma parámetros y utiliza el marco de la vista en su lugar:

invalidateFrame();

Pero, ¿por qué agregar un método especial solo para esto cuando puede usar el más general invalidateRect()? O, si eligió proporcionar invalidateFrame(), lo más probable es que lo implemente en términos de lo más general invalidateRect():

View::invalidateFrame(void)
{
    invalidateRect(m_frame)
}

¿Por qué pasar variable como parámetro de método, si ya tiene acceso a él?

Usted debe pasar variables de instancia como parámetros a su propio método , si el método no funciona específicamente en la variable de instancia. En el ejemplo anterior, el marco de la vista es solo otro rectángulo en lo que respecta al invalidateRect()método.

Caleb
fuente
1

Esto tiene sentido si el método es un método de utilidad. Digamos, por ejemplo, que necesita derivar un nombre corto único de varias cadenas de texto libre.

No querrás codificar una implementación separada para cada cadena, en su lugar, tiene sentido pasar la cadena a un método común.

Sin embargo, si el método siempre funciona en un solo miembro, parece un poco tonto pasarlo como parámetro.

James Anderson
fuente
1

La razón principal para tener una variable miembro en una clase es permitirle establecerla en un lugar y tener su valor disponible para cualquier otro método de la clase. En general, por lo tanto, esperaría que no haya necesidad de pasar la variable miembro a un método de la clase.

Sin embargo, puedo pensar en un par de razones por las que es posible que desee pasar la variable miembro a otro método de la clase. La primera es si necesita garantizar que el valor de la variable miembro debe usarse sin cambios cuando se usa con el método llamado, incluso si ese método necesita cambiar el valor real de la variable miembro en algún momento del proceso. La segunda razón está relacionada con la primera, ya que es posible que desee garantizar la inmutabilidad de un valor dentro del alcance de una cadena de métodos, por ejemplo, al implementar una sintaxis fluida.

Con todo esto en mente, no iría tan lejos como para decir que el código es "malo" como tal si pasa una variable miembro a uno de los métodos de su clase. Sin embargo, sugeriría que generalmente no es lo ideal, ya que podría alentar una gran cantidad de duplicación de código cuando el parámetro se asigna a una variable local, por ejemplo, y los parámetros adicionales agregan "ruido" en el código donde no es necesario. Si eres un fanático del libro Clean Code, sabrás que menciona que debes mantener el número de parámetros del método al mínimo, y solo si no hay otra forma más sensata para que el método acceda al parámetro .

S.Robins
fuente
1

Algunas cosas que se me ocurren al pensar en esto:

  1. En general, las firmas de métodos con menos parámetros son más fáciles de entender; Una razón importante por la que se inventaron los métodos fue para eliminar largas listas de parámetros uniéndolos a los datos en los que operan.
  2. Hacer que la firma del método dependa de la variable miembro hará que sea más difícil cambiar esas variables en el futuro, porque no solo necesita cambiar dentro del método, sino que en todas partes también se llama al método. Y dado que SomeMethod en su ejemplo está protegido, las subclases también deberán cambiar.
  3. Los métodos (públicos o privados) que no dependen de la clase interna no necesitan estar en esa clase. Podrían incluirse en los métodos de utilidad y ser igual de felices. Casi no tienen negocios siendo parte de esa clase. Si ningún otro método depende de esa variable después de mover el (los) método (s), ¡esa variable también debería ir! Lo más probable es que la variable debería estar en su propio objeto con ese método o los métodos que operan para que se convierta en público y esté compuesto por la clase principal.
  4. Pasar datos a varias funciones, como su clase, es un programa de procedimiento con variables globales que simplemente se opone al diseño OO. Esa no es la intención de las variables miembro y las funciones miembro (ver arriba), y parece que su clase no es muy coherente. Creo que es un olor a código que sugiere una mejor manera de agrupar sus datos y métodos.
Colin Hunt
fuente
0

Falta si el parámetro ("argumento") es de referencia o de solo lectura.

class SomeClass
{
    protected SomeType _someField;
    public SomeType SomeField
    {
        get { return _someField; }

        set {
          if (doSomeValidation(value))
          {
            _someField = value;
          }
        }
    }

    protected virtual void ModifyMethod(/*...., */ ref SomeType someVar)
    { 
      // ...
    }    

    protected virtual void ReadMethod(/*...., */ SomeType someVar)
    { 
      // ...
    }

    private void SomeAnotherMethod()
    {
        //.............

        // not recommended, but, may be required in some cases
        ModifyMethod(ref this._someField);

        //.............

        // recommended, but, verbose
        SomeType SomeVar = this.someField;
        ModifyMethod(ref SomeVar);
        this.someField = SomeVar;

        //.............

        ReadMethod(this.someField);
        //.............
    }

};

Muchos desarrolladores, generalmente asignan directamente los campos internos de una variable, en los métodos de construcción. Hay algunas excepciones

Recuerde que los "establecedores" pueden tener métodos adicionales, no solo asignación, y a veces, incluso métodos virtuales, o llamar métodos virtuales.

Nota: sugiero mantener los campos internos de propiedades como "protegidos".

umlcat
fuente