L'utilisation de beaucoup de méthodes statiques est-elle une mauvaise chose?

97

J'ai tendance à déclarer comme statiques toutes les méthodes d'une classe lorsque cette classe n'a pas besoin de garder une trace des états internes. Par exemple, si j'ai besoin de transformer A en B et que je ne compte pas sur un état interne C qui peut varier, je crée une transformation statique. S'il y a un état interne C que je veux pouvoir ajuster, j'ajoute un constructeur pour définir C et n'utilise pas de transformation statique.

J'ai lu diverses recommandations (y compris sur StackOverflow) de NE PAS abuser des méthodes statiques, mais je ne comprends toujours pas ce qui ne va pas avec la règle empirique ci-dessus.

Est-ce une approche raisonnable ou non?

Lolo
la source

Réponses:

153

Il existe deux types de méthodes statiques courantes:

  • Une méthode statique "sûre" donnera toujours la même sortie pour les mêmes entrées. Il ne modifie aucun global et n'appelle aucune méthode statique «non sûre» de n'importe quelle classe. Essentiellement, vous utilisez un type limité de programmation fonctionnelle - n'ayez pas peur de celles-ci, elles vont bien.
  • Une méthode statique "unsafe" mute l'état global, ou les proxies vers un objet global, ou un autre comportement non testable. Ce sont des retours en arrière à la programmation procédurale et devraient être refactorisés si possible.

Il existe quelques utilisations courantes de la statique «non sûre» - par exemple, dans le modèle Singleton - mais sachez que malgré les jolis noms que vous les appelez, vous ne faites que muter des variables globales. Réfléchissez bien avant d'utiliser des statiques dangereuses.

John Millikin
la source
C'était exactement le problème que je devais résoudre - l'utilisation, ou plutôt la mauvaise utilisation, des objets Singleton.
dépassé le
Merci pour cette excellente réponse. Ma question est la suivante: si les singletons sont transmis en tant que paramètres aux méthodes statiques, cela rend-il la méthode statique dangereuse?
Tony D
1
Les termes «fonction pure» et «fonction impure» sont des noms donnés dans la programmation fonctionnelle à ce que vous appelez la statique «sûre» et «non sûre».
Omnimike du
19

Un objet sans état interne est une chose suspecte.

Normalement, les objets encapsulent l'état et le comportement. Un objet qui n'encapsule que le comportement est étrange. Parfois, c'est un exemple de poids léger ou poids mouche .

D'autres fois, c'est une conception procédurale réalisée dans un langage objet.

S.Lott
la source
6
J'entends ce que vous dites, mais comment quelque chose comme un objet Math peut-il encapsuler autre chose qu'un comportement?
JonoW
10
Il a juste dit suspect, pas mal, et il a absolument raison.
Bill K
2
@JonoW: Math est un cas très particulier où il existe de nombreuses fonctions sans état. Bien sûr, si vous faites de la programmation fonctionnelle en Java, vous auriez de nombreuses fonctions sans état.
S.Lott
14

Ce n'est vraiment qu'une suite à la grande réponse de John Millikin.


Bien qu'il puisse être sûr de rendre statiques des méthodes sans état (qui sont à peu près des fonctions), cela peut parfois conduire à un couplage difficile à modifier. Considérez que vous avez une méthode statique en tant que telle:

public class StaticClassVersionOne {
    public static void doSomeFunkyThing(int arg);
}

Ce que vous appelez:

StaticClassVersionOne.doSomeFunkyThing(42);

Ce qui est bien beau, et très pratique, jusqu'à ce que vous rencontriez un cas où vous deviez modifier le comportement de la méthode statique, et que vous soyez étroitement lié StaticClassVersionOne. Vous pourriez peut-être modifier le code et ce serait bien, mais s'il y avait d'autres appelants dépendants de l'ancien comportement, ils devront être pris en compte dans le corps de la méthode. Dans certains cas, ce corps de méthode peut devenir assez laid ou impossible à maintenir s'il essaie d'équilibrer tous ces comportements. Si vous divisez les méthodes, vous devrez peut-être modifier le code à plusieurs endroits pour en tenir compte, ou faire des appels à de nouvelles classes.

Mais considérez si vous avez créé une interface pour fournir la méthode, et l'avez donnée aux appelants, maintenant que le comportement doit changer, une nouvelle classe peut être créée pour implémenter l'interface, qui est plus propre, plus facile à tester et plus maintenable, et cela est plutôt donné aux appelants. Dans ce scénario, les classes appelantes n'ont pas besoin d'être modifiées ou même recompilées, et les modifications sont localisées.

Ce n'est peut-être pas une situation probable ou non, mais je pense que cela vaut la peine d'être considéré.

Grundlefleck
la source
5
Je soutiens que ce n'est pas seulement un scénario probable, cela fait de la statique un dernier recours. La statique fait aussi du TDD un cauchemar. Partout où vous utilisez la statique, vous ne pouvez pas faire de maquette, vous devez savoir ce que sont l'entrée et la sortie pour tester une classe sans rapport. Maintenant, si vous modifiez le comportement de la statique, vos tests sur les classes non liées qui utilisent cette statique sont interrompues. En outre, cela devient une dépendance cachée que vous ne pouvez pas transmettre au constructeur pour informer les développeurs d'une dépendance potentiellement importante.
DanCaveman
6

L'autre option consiste à les ajouter en tant que méthodes non statiques sur l'objet d'origine:

c'est-à-dire en changeant:

public class BarUtil {
    public static Foo transform(Bar toFoo) { ... }
}

dans

public class Bar {
    ...
    public Foo transform() { ...}
}

Cependant, dans de nombreuses situations, ce n'est pas possible (par exemple, la génération de code de classe ordinaire à partir de XSD / WSDL / etc), ou cela rendra la classe très longue, et les méthodes de transformation peuvent souvent être une vraie douleur pour les objets complexes et vous les voulez juste dans leur propre classe distincte. Alors oui, j'ai des méthodes statiques dans les classes utilitaires.

JeeBee
la source
5

Les classes statiques conviennent tant qu'elles sont utilisées aux bons endroits.

À savoir: les méthodes qui sont des méthodes «feuille» (elles ne modifient pas l'état, elles transforment simplement l'entrée en quelque sorte). De bons exemples de ceci sont des choses comme Path.Combine. Ces sortes de choses sont utiles et créent une syntaxe terser.

Les problèmes que j'ai avec la statique sont nombreux:

Premièrement, si vous avez des classes statiques, les dépendances sont masquées. Considérer ce qui suit:

public static class ResourceLoader
{
    public static void Init(string _rootPath) { ... etc. }
    public static void GetResource(string _resourceName)  { ... etc. }
    public static void Quit() { ... etc. }
}

public static class TextureManager
{
    private static Dictionary<string, Texture> m_textures;

    public static Init(IEnumerable<GraphicsFormat> _formats) 
    {
        m_textures = new Dictionary<string, Texture>();

        foreach(var graphicsFormat in _formats)
        {
              // do something to create loading classes for all 
              // supported formats or some other contrived example!
        }
    }

    public static Texture GetTexture(string _path) 
    {
        if(m_textures.ContainsKey(_path))
            return m_textures[_path];

        // How do we know that ResourceLoader is valid at this point?
        var texture = ResourceLoader.LoadResource(_path);
        m_textures.Add(_path, texture);
        return texture; 
    }

    public static Quit() { ... cleanup code }       
}

En regardant TextureManager, vous ne pouvez pas dire quelles étapes d'initialisation doivent être effectuées en regardant un constructeur. Vous devez fouiller dans la classe pour trouver ses dépendances et initialiser les choses dans le bon ordre. Dans ce cas, il faut que ResourceLoader soit initialisé avant de s'exécuter. Maintenant, augmentez ce cauchemar de dépendance et vous pouvez probablement deviner ce qui va se passer. Imaginez essayer de maintenir du code là où il n'y a pas d'ordre explicite d'initialisation. Comparez cela avec l'injection de dépendances avec des instances - dans ce cas, le code ne sera même pas compilé si les dépendances ne sont pas remplies!

De plus, si vous utilisez des statiques qui modifient l'état, c'est comme un château de cartes. Vous ne savez jamais qui a accès à quoi, et le design a tendance à ressembler à un monstre spaghetti.

Enfin, et tout aussi important, l'utilisation de la statique lie un programme à une implémentation spécifique. Le code statique est l'antithèse de la conception pour la testabilité. Tester un code criblé de statique est un cauchemar. Un appel statique ne peut jamais être échangé contre un double de test (sauf si vous utilisez des frameworks de test spécialement conçus pour simuler des types statiques), donc un système statique fait de tout ce qui l'utilise un test d'intégration instantané.

En bref, la statique convient à certaines choses et pour les petits outils ou le code jetable, je ne découragerais pas leur utilisation. Cependant, au-delà de cela, ils sont un cauchemar sanglant pour la maintenabilité, la bonne conception et la facilité des tests.

Voici un bon article sur les problèmes: http://gamearchitect.net/2008/09/13/an-anatomy-of-despair-managers-and-contexts/

Mark Simpson
la source
4

La raison pour laquelle vous êtes averti des méthodes statiques est que leur utilisation renonce à l'un des avantages des objets. Les objets sont destinés à l'encapsulation des données. Cela évite les effets secondaires inattendus, ce qui évite les bogues. Les méthodes statiques n'ont pas de données encapsulées * et ne bénéficient donc pas de cet avantage.

Cela dit, si vous n'utilisez pas de données internes, elles sont faciles à utiliser et légèrement plus rapides à exécuter. Assurez-vous cependant de ne pas toucher aux données globales qu'ils contiennent.

  • Certains langages ont également des variables au niveau de la classe qui permettraient d'encapsuler des données et des méthodes statiques.
Steve Rowe
la source
4

Cela semble être une approche raisonnable. La raison pour laquelle vous ne voulez pas utiliser trop de classes / méthodes statiques est que vous finissez par vous éloigner de la programmation orientée objet et plus dans le domaine de la programmation structurée.

Dans votre cas où vous transformez simplement A en B, dites que tout ce que nous faisons est de transformer le texte à partir de

"hello" =>(transform)=> "<b>Hello!</b>"

Une méthode statique aurait alors du sens.

Cependant, si vous invoquez fréquemment ces méthodes statiques sur un objet et qu'il a tendance à être unique pour de nombreux appels (par exemple, la façon dont vous l'utilisez dépend de l'entrée), ou si cela fait partie du comportement inhérent de l'objet, cela soyez sage de l'intégrer à l'objet et d'en maintenir un état. Une façon de le faire serait de l'implémenter en tant qu'interface.

class Interface{
    method toHtml(){
        return transformed string (e.g. "<b>Hello!</b>")
    }

    method toConsole(){
        return transformed string (e.g. "printf Hello!")
    }
}


class Object implements Interface {
    mystring = "hello"

    //the implementations of the interface would yield the necessary 
    //functionality, and it is reusable across the board since it 
    //is an interface so... you can make it specific to the object

   method toHtml()
   method toConsole()
}

Edit: Un bon exemple d'une grande utilisation des méthodes statiques sont les méthodes d'assistance html dans Asp.Net MVC ou Ruby. Ils créent des éléments html qui ne sont pas liés au comportement d'un objet, et sont donc statiques.

Edit 2: Changement de la programmation fonctionnelle en programmation structurée (pour une raison quelconque, je suis devenu confus), des accessoires à Torsten pour le souligner.

MunkiPhD
la source
2
Je ne pense pas que l'utilisation de méthodes statiques soit qualifiée de programmation fonctionnelle, donc je suppose que vous entendez une programmation structurée.
Torsten Marek
3

J'ai récemment refactoré une application pour supprimer / modifier certaines classes initialement implémentées en tant que classes statiques. Au fil du temps, ces classes ont tellement acquis et les gens ont continué à marquer les nouvelles fonctions comme statiques, car il n'y avait jamais d'instance flottant.

Donc, ma réponse est que les classes statiques ne sont pas intrinsèquement mauvaises, mais il pourrait être plus facile de commencer à créer des instances maintenant, puis de devoir refactoriser plus tard.

dépassé
la source
3

Je considérerais cela comme une odeur de design. Si vous utilisez principalement des méthodes statiques, vous n'avez probablement pas une très bonne conception OO. Ce n'est pas forcément mauvais, mais comme pour toutes les odeurs, cela me ferait arrêter et réévaluer. Cela laisse entendre que vous pourriez être en mesure de faire une meilleure conception OO, ou que vous devriez peut-être aller dans l'autre direction et éviter complètement OO pour ce problème.

patros
la source
2

J'avais l'habitude d'aller et venir entre une classe avec un tas de méthodes statiques et un singleton. Les deux résolvent le problème, mais le singleton peut être beaucoup plus facilement remplacé par plus d'un. (Les programmeurs semblent toujours si certains qu'il n'y aura qu'une seule chose et je me suis assez trompé pour abandonner complètement les méthodes statiques sauf dans certains cas très limités).

Quoi qu'il en soit, le singleton vous donne la possibilité de passer plus tard quelque chose dans l'usine pour obtenir une instance différente et cela change le comportement de tout votre programme sans refactorisation. Changer une classe globale de méthodes statiques en quelque chose avec des données de "sauvegarde" différentes ou un comportement légèrement différent (classe enfant) est une douleur majeure.

Et les méthodes statiques n'ont aucun avantage similaire.

Alors oui, ils sont mauvais.

Bill K
la source
1

Tant qu'aucun état interne n'entre en jeu, c'est bien. Notez que les méthodes statiques sont généralement censées être thread-safe, donc si vous utilisez des structures de données auxiliaires, utilisez-les de manière thread-safe.

Lucero
la source
1

Si vous savez que vous n'aurez jamais besoin d'utiliser l'état interne de C, c'est très bien. Si cela devait changer à l'avenir, cependant, vous devrez rendre la méthode non statique. S'il n'est pas statique pour commencer, vous pouvez simplement ignorer l'état interne si vous n'en avez pas besoin.

Adam Crume
la source
1

Si c'est une méthode utilitaire, c'est bien de la rendre statique. Guava et Apache Commons reposent sur ce principe.

Mon avis à ce sujet est purement pragmatique. S'il s'agit du code de votre application, les méthodes statiques ne sont généralement pas la meilleure chose à avoir. Les méthodes statiques ont de sérieuses limitations des tests unitaires - elles ne peuvent pas être facilement simulées: vous ne pouvez pas injecter une fonctionnalité statique simulée dans un autre test. Vous ne pouvez généralement pas non plus injecter de fonctionnalités dans une méthode statique.

Donc, dans ma logique d'application, j'ai généralement de petits appels de méthode statiques de type utilitaire. C'est à dire

static cutNotNull(String s, int length){
  return s == null ? null : s.substring(0, length);
}

l'un des avantages est que je ne teste pas de telles méthodes :-)

Andrey Chaschev
la source
1

Eh bien, il n'y a pas de solution miracle bien sûr. Les classes statiques conviennent aux petits utilitaires / assistants. Mais utiliser des méthodes statiques pour la programmation en logique métier est certainement un mal. Considérez le code suivant

   public class BusinessService
   {

        public Guid CreateItem(Item newItem, Guid userID, Guid ownerID)
        {
            var newItemId = itemsRepository.Create(createItem, userID, ownerID);
            **var searchItem = ItemsProcessor.SplitItem(newItem);**
            searchRepository.Add(searchItem);
            return newItemId;
        }
    }

Vous voyez un appel de méthode statique à ItemsProcessor.SplitItem(newItem);ça sent la cause

  • Vous n'avez pas de dépendance explicite déclarée et si vous ne creusez pas dans le code, vous risquez de négliger le couplage entre votre classe et le conteneur de méthode statique
  • Vous ne pouvez pas tester en l' BusinessServiceisolant ItemsProcessor(la plupart des outils de test ne se moquent pas des classes statiques) et cela rend les tests unitaires impossibles. Pas de tests unitaires == faible qualité
Konstantin Chernov
la source
0

Les méthodes statiques sont généralement un mauvais choix, même pour le code sans état. Au lieu de cela, créez une classe singleton avec ces méthodes qui est instanciée une fois et injectée dans les classes souhaitant utiliser les méthodes. Ces classes sont plus faciles à simuler et à tester. Ils sont beaucoup plus orientés objet. Vous pouvez les envelopper avec un proxy si nécessaire. Les statiques rendent OO beaucoup plus difficile et je ne vois aucune raison de les utiliser dans presque tous les cas. Pas à 100% mais presque tous.

John
la source