¿Deberían ser estáticos los métodos que no son "funciones puras" y que interactúan con API o hardware externos?

8

Al leer sobre cuándo hacer que un método sea estático o no, he visto un principio general, tal como se resume en esta publicación , que un método solo debe ser estático si no modifica un estado y su resultado depende solo de los parámetros proporcionados para eso. Sin embargo, la respuesta más votada en esta publicación establece que los métodos estáticos deben usarse siempre que sea posible. Muchas de las respuestas en esta publicación dicen que uno debe hacer lo que sea más lógico.

En mi caso, tengo ~ 15 métodos en una clase de utilidad común porque se usan en varios lugares y no tienen sentido como miembro de ningún modelo o modelo de vista (usando C # MVVM). Existen métodos nulos que interactúan con varios componentes de hardware utilizando sus paquetes (por ejemplo, National Instruments, clientes OPC, etc.). También hay métodos que toman algo de entrada, hacen un GET o PUT a nuestra API y luego devuelven una respuesta: estos métodos usan HttpClient o algo similar. Estos métodos no son simples operadores en la entrada, como Math.Sqrt(), y no cambian de estado.

Entonces, ¿deberían ser estáticos tales métodos (y, en este caso, toda la clase)? Existe el beneficio obvio de tener una clase estática con métodos estáticos: es segura y rápida porque no tiene que crear un objeto. Además, cada uno de estos métodos estáticos utiliza métodos y clases más estáticos para las interacciones API y hardware. El único inconveniente que veo es que tendría que escribir pruebas unitarias con un marco de pago, como TypeMock Isolator. El costo no es un problema si la respuesta es burlarse de ellos usando algo como TypeMock Isolator o algún servicio pago similar, por lo que si ese es el consenso, está bien. Solo quiero tomar una decisión que se adapte bien y deje poca deuda técnica a medida que incorporemos nuevos desarrolladores y que nuestro proyecto crezca.

¡Avíseme si necesito proporcionar más información para aclarar esto!

adjordania
fuente
3
"interactuar con varios componentes de hardware", "También hay métodos que toman algo de entrada, hacen un OBTENER o PONEN a nuestra API, y luego devuelven una respuesta" -> estos son los ejemplos secundarios de interacción con el estado
Caleth
2
Para ampliar el comentario de @ Caleth: Recuerde que "estado" no solo se aplica a los unos y ceros en la memoria. Si llama a una función que mueve el brazo de un robot, ha cambiado de estado, en el mundo real, junto con todas las ramificaciones de hacerlo.
Greg Burghardt
1
"seguro y rápido porque no tienes que crear un objeto". En el caso de la API de red, ciertamente está creando muchos objetos dentro de la llamada . Y, en cualquier caso, el tiempo para crear su objeto es pequeño en comparación con la latencia de la red.
user949300
¿Qué vas a hacer cuando tengas más de un mismo componente de hardware? Copiar / pegar todos los métodos?
user253751

Respuestas:

15

Entonces, ¿deberían ser estáticos tales métodos (y, en este caso, toda la clase)?

No. O al menos, no deberías usarlos directamente como estática.

Si está trabajando con varios componentes de hardware, eres muy muy probable que quieran burlarse de ellos, utiliza los nuevos, y elegir la que uno está utilizando. Todo eso grita por tener una interfaz (u otra abstracción) para que el resto de su código pueda ignorar rápidamente que hay algún componente de hardware. Y la estática en la mayoría de los idiomas juega muy mal con las interfaces (y otros mecanismos de abstracción).

Telastyn
fuente
1
O al menos, es común tener colecciones de métodos estáticos que hacen las llamadas reales que hablan con cosas externas, pero luego construir una clase API con estado encima y exponer esa clase como la forma de hacer las cosas, y es esa clase la que se burla en lugar de las interfaces subyacentes, es decir, el patrón de fachada.
whatsisname
¡Gracias por la respuesta! Este parece ser el consenso, por lo que seguiré con este principio en el futuro.
adjordan
4

Los consejos contradictorios que has leído tienen sentido de alguna manera, pero todos hacen suposiciones (diferentes) o frases ambiguas. Es uno de esos tipos de distinciones que dicen "decir lo mismo con palabras diferentes".

un método solo debe ser estático si no modifica un estado

Tenga en cuenta la ambigüedad de "modificar un estado". El siguiente ejemplo viola esta regla (literalmente), pero conserva el espíritu (figurativo) de la regla:

public static void SetUserNameToBob(User u)
{
    u.Name = "Bob";
}

Esto modifica el estado del Userobjeto, por lo que, de hecho, "modifica un estado".

Sin embargo, este método no se basa en un estado interno particular para decidir su propio flujo lógico (por ejemplo u.Name = currentlySelectedDefaultName(), sería una violación ya que el nombre seleccionado es un estado seleccionado). Y creo que eso es lo que significa: no se modifica ningún estado interno .

[un método solo debe ser estático si] su resultado depende solo de los parámetros que se le proporcionan

Mira el artículo anterior, este dice más o menos lo mismo. Lo que significa es que esto:

public static string currentlySelectedDefaultName; 
public static void SetUserNameToBob(User u)
{
    u.Name = currentlySelectedDefaultName;
}

no debe ser estático, ya que el nombre "actual predeterminado" es un estado y, por lo tanto, no debe ser una variable / método global.

Piense en lo que sucede si se están ejecutando dos subprocesos separados: uno de ellos quiere usar el valor predeterminado "Bob", el otro quiere usar el valor predeterminado "Jim". Terminarán peleando por el valor, lo que puede crear problemas de depuración masiva y comportamiento inesperado.
Sin embargo, si cada hilo tiene su propio DefaultNameSetterobjeto, entonces no pelean por el mismo recurso.

Sin embargo, la respuesta más votada en esta publicación establece que los métodos estáticos deben usarse siempre que sea posible.

Esto es como hacer cumplir una regla al intentar / fallar. ¿ Podemos configurar este método como estático?

  • Si =>bueno! ¡Déjalo de esa forma!
  • No =>Esto prueba que el código depende de un valor no estático en algún lugar y, por lo tanto, no debe hacerse estático.

Para citar indirectamente a Jeff Goldblum en Jurassic Park , no debe argumentar la necesidad de hacer algo simplemente demostrando que se puede hacer.

Nuevamente, el enfoque no es necesariamente (o siempre) incorrecto, pero asume ciegamente que los métodos ya están escritos para ser tan independientes del estado como sea lógicamente posible, lo cual simplemente no es el caso.

Incluso si se suscribe a este enfoque metodológico, solo puede aplicarlo cuando un proyecto ya no está en desarrollo. Si el proyecto aún está en desarrollo, los métodos actuales pueden ser marcadores de posición para la implementación futura. Puede ser posible hacer Foo()estática hoy, pero no mañana, si la lógica dependiente del estado simplemente no se ha implementado todavía.

Muchas de las respuestas en esta publicación dicen que uno debe hacer lo que sea más lógico.

Bueno, no están equivocados; pero, ¿no es solo una pequeña frase de decir "haz lo correcto"? Ese no es un consejo realmente útil, a menos que ya sepa cuándo usar las estadísticas y cuándo evitarlas. Es una trampa 22.


Entonces, ¿cuándo deberías usar estadísticas?

Como habrás notado, no todos están de acuerdo con la regla, o al menos con la forma de formular la regla. Agregaré otro intento aquí, pero tenga en cuenta que esto está creando efectivamente otro estándar :

ingrese la descripción de la imagen aquí

Mantenlo en mente.

La estática son verdades universales.

Ese es el propósito de un espacio de nombres global: cosas que son correctas en toda la capa de aplicación.

Aquí hay un argumento de pendiente resbaladiza. Algunos ejemplos:

var myConfigKey = ConfigurationManager.AppSettings["myConfigKey"];

Este es un ejemplo muy claro. La configuración de la aplicación implica inherentemente que la configuración es global para la aplicación y, por lo tanto, se garantiza un método statis.

bool datesOverlap = DateHelper.HasOverlap(myDateA_Start, myDateA_End, myDateB_Start, myDateB_End);

Este método es universalmente correcto. No le importa qué fechas estás comparando. Al método no le importa el significado de las fechas. Si son fechas de empleo, fechas de contrato, ... no importa el algoritmo del método.

Tenga en cuenta la similitud semántica entre contextual y de estado . Ambos tipos se refieren a un estado "no universal". Esto demuestra que las diferencias contextuales, por lo tanto , dependen del estado y no son adecuadas para hacerse estáticas.

var newPersonObject = Person.Create();

Esto es una verdad universal. El mismo proceso de creación se utiliza en toda la aplicación.

Sin embargo, esta línea puede volverse borrosa:

var newManager = Person.CreateManager();
var newJanitor = Person.CreateJanitor();

Desde una perspectiva técnica, nada ha cambiado. El administrador (y los conserjes) se crean de la misma manera en toda la aplicación. Sin embargo, esto ha creado sutilmente un estado (gerente / conserje), que lenta pero constantemente resta valor a lo universal que es realmente la verdad.

Se puede hacer? Desde una perspectiva técnica; .
¿Debería hacerse? Es una cuestión de si está discutiendo el principio puro o si tiene en cuenta que se deben hacer compromisos para no luchar sin sentido por la perfección lógica. Entonces diría que si no crea un problema mayor que el que el problema intenta resolver .

A medida que se expanden las opciones (gerentes, conserjes, contadores, vendedores, ...), el problema crece. Para un problema suficientemente grande, es deseable un patrón de fábrica .

Si solo tiene dos opciones y no tiene ninguna razón para sospechar que la lista de opciones crecerá, puede argumentar que los métodos de creación estática son suficientes. Algunos podrían estar en desacuerdo, y también veo su punto. Pero tiendo a ser práctico y no demasiado perfeccionista en mi enfoque.

Flater
fuente
3

Sin embargo, la respuesta más votada en esta publicación establece que los métodos estáticos deben usarse siempre que sea posible ...

Los métodos estáticos que afectan el estado al que están directamente acoplados son una muy mala idea. Conducen a un estado global - "acción espeluznante a distancia" - problemas, código de espagueti y similares. Son difíciles de probar, depurar y mantener. Mi lectura de la respuesta más votada no fomenta nada de esto, así que todo está bien.

Entonces, ¿deberían ser estáticos tales métodos (y, en este caso, toda la clase)?

Eso depende. Tomemos un ejemplo de los " métodos nulos que interactúan con varios componentes de hardware utilizando sus paquetes ". Esos métodos pueden ser estáticos, pero solo si están desacoplados de esos paquetes. Una forma de lograrlo es pasar esos paquetes como parámetros. ¿Quieres probar el método sin el hardware? Fácil, pase un paquete ficticio, burlado, que registra acciones, en lugar de acceder al hardware a través del parámetro. Lo mismo se aplica a los métodos HTTP; siempre y cuando los medios reales de acceso a la API se pasen como un parámetro, también.

Pero, ¿realmente está logrando algo de esta manera al crear una instancia e inyectar esos paquetes, clases HTTP, etc. en tiempo de construcción? Probablemente no. Claro, son métodos estáticos, por lo que no necesita una instancia. Pero tiene la complejidad adicional de necesitar exponer todos esos paquetes, etc. a lo largo del código para pasarlos como parámetros. A menudo es mucho más fácil crear una sola instancia y pasarla.

David Arno
fuente
¿Está diciendo que pase una sola instancia de cualquier clase de "UtilityMethods.cs" que haga? Actualmente estoy usando la inyección de dependencia con SimpleIoC, que creo que es la misma idea.
adjordan
2
@adjordan, de hecho. Depende de usted usar un DI puro (pobre) o un marco IoC para ayudar con la inyección. El principio sigue siendo el mismo: es más fácil pasar solo una instancia IUtilityMethodsque pasar varias instancias que deben pasarse a funciones estáticas.
David Arno
1

Lo que recomiendo y lo que estoy usando dentro de mi equipo es usar métodos / clases estáticas si es realmente necesario. No debería ser la primera opción para usar métodos / clases estáticos. Tiene que ser una muy buena razón para eso. Muchos desarrolladores ven que la opción estática es más rápida y, por lo tanto, más barata. Pero eso es quizás al principio. El costo es construir pruebas unitarias más complicadas y carecer de la capacidad de usar contratos (interfaces) con múltiples implementaciones. Para los métodos / clases que interactúan con el hardware. Estos no deben ser estáticos. Primero y para la mayoría, no hay razón. En segundo lugar, es necesario simular estas clases en pruebas unitarias para obtener un código de prueba más simple. Tercero, es posible reemplazar la implementación en el futuro.

Arafat Hegazy
fuente
0

Las funciones estáticas son más desafiantes cuando desea permitir el acceso múltiple en paralelo.

Por ejemplo, es perfectamente razonable, y probablemente una buena idea, permitir que se ejecuten varias llamadas HTTP al mismo tiempo. Con un objeto de instancia, puede mantener fácilmente un estado diferente para esas llamadas: los encabezados, los códigos de resultados, las memorias intermedias, etc.

Con un único método estático, bueno, puede terminar creando alguna instancia interna para mantener ese estado. O sincroniza todo (estoy usando terminología Java, YMMV), que es propenso a errores. O usa una cola. En cualquier caso, sin un juego de pies sofisticado, pierde la capacidad de correr en paralelo. Si una llamada HTTP se atasca debido a una falla de la red, todas las demás tienen que esperar.

Lo mismo para su hardware: en algunos casos, podría tener sentido permitir el acceso en paralelo.

Por estas razones, no estoy de acuerdo con la respuesta de 8 años que citó.

user949300
fuente
44
Una función estática no debe contener estado en absoluto.
Pieter B
1
@Pieter B Por supuesto (aparte de quizás un contador). En el ejemplo HTTP, tendría que crear un objeto MyHTTPHelper por llamada para mantener el estado. En ese momento, es probable que sea más simple hacer que la persona que llama original cree un objeto, no usar un método estático en absoluto.
user949300