Reflexiones y mejores prácticas sobre clases estáticas y miembros [cerrado]

11

Tengo mucha curiosidad sobre los pensamientos y las mejores prácticas de la industria con respecto a miembros estáticos, o clases estáticas completas. ¿Hay alguna desventaja en esto o participa en algún antipatrón?

Veo estas entidades como "clases / miembros de utilidad", en el sentido de que no requieren ni exigen la creación de instancias de la clase para proporcionar la funcionalidad.

¿Cuáles son los pensamientos generales y las mejores prácticas de la industria sobre esto?

Vea las clases y miembros de ejemplo a continuación para ilustrar a qué me refiero.

// non-static class with static members
//
public class Class1
{
    // ... other non-static members ...

    public static string GetSomeString()
    {
        // do something
    }
}

// static class
//
public static class Class2
{
    // ... other static members ...

    public static string GetSomeString()
    {
        // do something
    }
}

¡Gracias de antemano!

Thomas Stringer
fuente
1
El artículo de MSDN sobre Clases estáticas y métodos estáticos proporciona un tratamiento bastante bueno.
Robert Harvey
1
@Robert Harvey: Aunque el artículo al que hizo referencia es útil, no proporciona mucho en términos de mejores prácticas y cuáles son algunas de las dificultades al usar clases estáticas.
Bernard

Respuestas:

18

En general, evite la estática, especialmente cualquier tipo de estado estático.

¿Por qué?

  1. La estática causa problemas con la concurrencia. Como solo hay una instancia, se comparte naturalmente entre la ejecución concurrente. Esos recursos compartidos son la ruina de la programación concurrente, y a menudo no necesitan ser compartidos.

  2. La estática causa problemas con las pruebas unitarias. Cualquier marco de pruebas unitarias que valga la pena ejecuta pruebas de sal simultáneamente. Entonces te encuentras con el # 1. Peor aún, complica sus pruebas con todas las cosas de configuración / desmontaje, y la piratería informática en la que caerá para tratar de "compartir" el código de configuración.

También hay muchas cosas pequeñas. La estática tiende a ser inflexible. No puede interactuar con ellos, no puede anularlos, no puede controlar el momento de su construcción, no puede usarlos bien en genéricos. Realmente no puedes versionarlos.

Ciertamente hay usos de la estática: los valores constantes funcionan muy bien. Los métodos puros que no caben en una sola clase pueden funcionar muy bien aquí.

Pero en general, evítelos.

Telastyn
fuente
Tengo curiosidad por el grado de incapacidad para la concurrencia que quieres decir. ¿Se puede ampliar al respecto? Si quiere decir que se puede acceder a los miembros sin segregación, eso tiene mucho sentido. Sus casos de uso para estadísticas son para lo que los uso típicamente: valores constantes y métodos puros / de utilidad. Si ese es el mejor y el 99% de casos de uso para estadísticas, entonces eso me da un nivel de comodidad. Además, +1 por tu respuesta. Gran informacion.
Thomas Stringer
@ThomasStringer, solo que la mayor cantidad de puntos de contacto entre subprocesos / tareas significa más oportunidades para problemas de concurrencia y / o más pérdida de rendimiento al realizar la sincronización.
Telastyn
Entonces, si un hilo está accediendo actualmente a un miembro estático, ¿entonces otros hilos deben esperar hasta que el hilo propietario libere el recurso?
Thomas Stringer
@ThomasStringer - tal vez, tal vez no. La estática es (casi, en la mayoría de los idiomas) no es diferente de cualquier otro recurso compartido a este respecto.
Telastyn
@ThomasStringer: Lamentablemente, en realidad es peor que esto, a menos que marques al miembro volatile. Simplemente no obtiene garantías del modelo de memoria sin datos volátiles, por lo que cambiar una variable en un hilo puede no reflejarse de inmediato o en absoluto.
Phoshi
17

Si la función es "pura" no veo problemas. Una función pura solo funciona en los parámetros de entrada y proporciona un resultado basado en eso. No depende de ningún estado global o contexto externo.

Si miro tu propio ejemplo de código:

public class Class1
{
    public static string GetSomeString()
    {
        // do something
    }
}

Esta función no toma ningún parámetro. Por lo tanto, probablemente no sea puro (la única implementación pura de esta función sería devolver una constante). Supongo que este ejemplo no es representativo de su problema real, simplemente estoy señalando que probablemente esta no sea una función pura.

Tomemos un ejemplo diferente:

public static bool IsOdd(int number) { return (number % 2) == 1; }

No hay nada de malo en que esta función sea estática. Incluso podríamos convertir esto en una función de extensión, permitiendo que el código del cliente sea aún más legible. Las funciones de extensión son básicamente un tipo especial de funciones estáticas.

Telastyn menciona correctamente la concurrencia como un problema potencial con los miembros estáticos. Sin embargo, dado que esta función no usa el estado compartido, aquí no hay problemas de concurrencia. Mil hilos pueden llamar a esta función simultáneamente sin ningún problema de concurrencia.

En el marco .NET, los métodos de extensión existen desde hace bastante tiempo. LINQ contiene muchas funciones de extensión (por ejemplo, Enumerable.Where () , Enumerable.First () , Enumerable.Single () , etc.). No los vemos como malos, ¿verdad?

Las pruebas unitarias a menudo pueden beneficiarse cuando el código usa abstracciones reemplazables, lo que permite que la prueba unitaria reemplace el código del sistema con un doble de prueba. Las funciones estáticas prohíben esta flexibilidad, pero esto es principalmente importante en los límites de la capa arquitectónica, donde queremos reemplazar, por ejemplo, una capa de acceso a datos real con una capa de acceso a datos falsa .

Sin embargo, cuando se escribe una prueba para un objeto que se comporta de manera diferente, dependiendo de si algún número es par o impar, realmente no necesitamos poder reemplazar la IsOdd()función con una implementación alternativa. Del mismo modo, no veo cuándo necesitamos proporcionar una Enumerable.Where()implementación diferente con el propósito de probar.

Así que examinemos la legibilidad del código del cliente para esta función:

Opción a (con la función declarada como método de extensión):

public void Execute(int number) {
    if (number.IsOdd())
        // Do something
}

Opción b:

public void Execute(int number) {
    var helper = new NumberHelper();
    if (helper.IsOdd(number))
        // Do something
}

La función estática (extensión) hace que la primera parte del código sea mucho más legible, y la legibilidad es muy importante, así que use funciones estáticas cuando sea apropiado.

Pete
fuente