Réflexions et meilleures pratiques sur les classes statiques et les membres [clos]

11

Je suis très curieux de connaître les réflexions et les meilleures pratiques de l'industrie concernant les membres statiques ou des classes statiques entières. Y a-t-il des inconvénients à cela, ou participe-t-il à des anti-patterns?

Je vois ces entités comme des "classes / membres utilitaires", dans le sens où elles ne nécessitent ni n'exigent l'instanciation de la classe pour fournir la fonctionnalité.

Quelles sont les réflexions générales et les meilleures pratiques de l'industrie à ce sujet?

Consultez les exemples de classes et de membres ci-dessous pour illustrer ce à quoi je fais référence.

// 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
    }
}

Merci d'avance!

Thomas Stringer
la source
1
L' article MSDN sur les classes statiques et les méthodes statiques fournit un assez bon traitement.
Robert Harvey
1
@Robert Harvey: Bien que l'article auquel vous avez fait référence soit utile, il ne fournit pas grand-chose en termes de meilleures pratiques et quels sont les pièges lors de l'utilisation de classes statiques.
Bernard

Réponses:

18

En général, évitez la statique - en particulier toute sorte d'état statique.

Pourquoi?

  1. La statique cause des problèmes avec la simultanéité. Puisqu'il n'y a qu'une seule instance, elle est naturellement partagée entre l'exécution simultanée. Ces ressources partagées sont le fléau de la programmation simultanée et n'ont souvent pas besoin d'être partagées.

  2. La statique cause des problèmes lors des tests unitaires. Tout cadre de test unitaire qui en vaut la peine exécute des tests de sel simultanément. Ensuite, vous tombez sur # 1. Pire encore, vous compliquez vos tests avec toutes les choses de configuration / démontage, et le piratage dans lequel vous allez tomber pour essayer de «partager» le code de configuration.

Il y a aussi plein de petites choses. La statique a tendance à être rigide. Vous ne pouvez pas les interfacer, vous ne pouvez pas les remplacer, vous ne pouvez pas contrôler le timing de leur construction, vous ne pouvez pas bien les utiliser dans les génériques. Vous ne pouvez pas vraiment les versionner.

Il y a certainement des utilisations de la statique: les valeurs constantes fonctionnent très bien. Les méthodes pures qui ne rentrent pas dans une seule classe peuvent très bien fonctionner ici.

Mais en général, évitez-les.

Telastyn
la source
Je suis curieux de savoir dans quelle mesure vous ne pouvez pas avoir accès à la concurrence. Pouvez-vous développer? Si vous voulez dire que les membres sont accessibles sans ségrégation, cela est parfaitement logique. Vos cas d'utilisation pour la statique sont ce pour quoi je les utilise généralement: valeurs constantes et méthodes pures / utilitaires. Si c'est le meilleur cas d'utilisation à 99% pour la statique, cela me donne un certain confort. Aussi, +1 pour votre réponse. Excellentes informations.
Thomas Stringer
@ThomasStringer - juste que le plus de points de contact entre les threads / tâches signifie plus d'opportunités pour les problèmes de simultanéité et / ou plus de perte de performances lors de la synchronisation.
Telastyn
Donc, si un thread accède actuellement à un membre statique, les autres threads doivent attendre que le thread propriétaire libère la ressource?
Thomas Stringer
@ThomasStringer - peut-être, peut-être pas. La statique n'est (à peu près, dans la plupart des langues) pas différente de toute autre ressource partagée à cet égard.
Telastyn
@ThomasStringer: Malheureusement, c'est en fait pire que cela, sauf si vous marquez le membre volatile. Vous n'obtenez simplement aucune assurance du modèle de mémoire sans données volatiles, donc changer une variable dans un thread peut ne pas refléter immédiatement ou pas du tout.
Phoshi
17

Si la fonction est "pure", je ne vois aucun problème. Une fonction pure ne fonctionne que dans les paramètres d'entrée et fournit un résultat basé sur cela. Cela ne dépend d'aucun état global ou contexte externe.

Si je regarde votre propre exemple de code:

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

Cette fonction ne prend aucun paramètre. Ainsi, elle n'est probablement pas pure (la seule implémentation pure de cette fonction serait de renvoyer une constante). Je suppose que cet exemple n'est pas représentatif de votre problème réel, je souligne simplement que ce n'est probablement pas une fonction pure.

Prenons un exemple différent:

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

Il n'y a rien de mal à ce que cette fonction soit statique. Nous pourrions même en faire une fonction d'extension, permettant au code client de devenir encore plus lisible. Les fonctions d'extension ne sont fondamentalement qu'un type spécial de fonctions statiques.

Telastyn mentionne correctement la concurrence comme un problème potentiel avec les membres statiques. Cependant, puisque cette fonction n'utilise pas l'état partagé, il n'y a pas de problème de concurrence ici. Un millier de threads peuvent appeler cette fonction simultanément sans aucun problème de concurrence.

Dans le framework .NET, les méthodes d'extension existent depuis un certain temps. LINQ contient de nombreuses fonctions d'extension (par exemple Enumerable.Where () , Enumerable.First () , Enumerable.Single () , etc.). Nous ne les considérons pas comme mauvais, n'est-ce pas?

Les tests unitaires peuvent souvent bénéficier lorsque le code utilise des abstractions remplaçables, ce qui permet au test unitaire de remplacer le code système par un double de test. Les fonctions statiques interdisent cette flexibilité, mais cela est surtout important aux limites de la couche architecturale, où nous voulons remplacer, par exemple, une véritable couche d'accès aux données par une fausse couche d'accès aux données.

Cependant, lors de l'écriture d'un test pour un objet qui se comporte différemment, selon qu'un nombre impair ou pair, nous n'avons pas vraiment besoin de pouvoir remplacer la IsOdd()fonction par une implémentation alternative. De même, je ne vois pas quand nous devons fournir une Enumerable.Where()implémentation différente à des fins de test.

Examinons donc la lisibilité du code client pour cette fonction:

Option a (avec la fonction déclarée comme méthode d'extension):

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

Option b:

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

La fonction statique (extension) rend le premier morceau de code beaucoup plus lisible et la lisibilité est très importante, utilisez donc les fonctions statiques le cas échéant.

Pete
la source