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?
Respuestas:
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 .
fuente
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
SomeMethod
es 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 ...
fuente
_someField
es 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!)this
(oself
), pero la llamada en sí la hace explícitaobj.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.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.
fuente
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:
donde
newStartDate
es alguna variable local ym_StartDate
es 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.
fuente
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.
fuente
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: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:
Pero, ¿por qué agregar un método especial solo para esto cuando puede usar el más general
invalidateRect()
? O, si eligió proporcionarinvalidateFrame()
, lo más probable es que lo implemente en términos de lo más generalinvalidateRect()
: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.fuente
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.
fuente
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 .
fuente
Algunas cosas que se me ocurren al pensar en esto:
fuente
Falta si el parámetro ("argumento") es de referencia o de solo lectura.
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".
fuente