Abusons-nous des méthodes statiques?


13

Il y a quelques mois, j'ai commencé à travailler sur un nouveau projet, et lorsque je parcourais le code, il me frappait la quantité de méthodes statiques utilisées. Non seulement les méthodes utilitaires en tant que collectionToCsvString(Collection<E> elements), mais aussi beaucoup de logique métier y sont conservées.

Quand j'ai demandé au gars responsable de la raison derrière cela, il a dit que c'était un moyen d' échapper à la tyrannie de Spring . Cela va quelque chose autour de ce processus de réflexion: pour mettre en œuvre une méthode de création de reçu client, nous pourrions avoir un service

@Service
public class CustomerReceiptCreationService {

    public CustomerReceipt createReceipt(Object... args) {
        CustomerReceipt receipt = new CustomerReceipt();
        // creation logic
        return receipt;
    }
}

Maintenant, le gars a dit qu'il déteste avoir des classes gérées inutilement par Spring, essentiellement parce qu'il impose la restriction que les classes clientes doivent être elles-mêmes des beans Spring. Nous finissons par tout gérer par Spring, ce qui nous oblige à peu près à travailler avec des objets sans état d'une manière procédurale. Plus ou moins ce qui est indiqué ici https://www.javacodegeeks.com/2011/02/domain-driven-design-spring-aspectj.html

Donc, au lieu du code ci-dessus, il a

public class CustomerReceiptCreator {

    public static CustomerReceipt createReceipt(Object... args) {
        CustomerReceipt receipt = new CustomerReceipt();
        // creation logic
        return receipt;
    }
}

Je pourrais argumenter au point d'éviter que Spring gère nos cours lorsque cela est possible, mais ce que je ne vois pas, c'est l'avantage d'avoir tout statique. Ces méthodes statiques sont également sans état, donc pas très OO. Je me sentirais plus à l'aise avec quelque chose

new CustomerReceiptCreator().createReceipt()

Il affirme que les méthodes statiques ont des avantages supplémentaires. À savoir:

  • Plus facile à lire. Importez la méthode statique et nous n'avons qu'à nous soucier de l'action, pas de la classe qui la fait.
  • Est évidemment une méthode sans appels DB, donc bon marché en termes de performances; et c'est une bonne chose de le préciser, de sorte que le client potentiel doive entrer dans le code et vérifier cela.
  • Écriture de tests plus facile.

Mais j'ai juste l'impression qu'il y a quelque chose qui ne va pas complètement dans tout ça, donc j'aimerais entendre les réflexions de développeurs plus expérimentés à ce sujet.

Donc ma question est, quels sont les pièges potentiels de cette façon de programmer?




4
La staticméthode que vous illustrez ci-dessus n'est qu'une méthode d'usine ordinaire. Rendre les méthodes d'usine statiques est la convention généralement acceptée, pour un certain nombre de raisons impérieuses. La question de savoir si la méthode d'usine est appropriée ici est différente.
Robert Harvey

Réponses:


23

Quelle est la différence entre new CustomerReceiptCreator().createReceipt()et CustomerReceiptCreator.createReceipt()? À peu près aucun. La seule différence significative est que le premier cas a une syntaxe considérablement plus délicate. Si vous suivez le premier dans la croyance qu'en évitant d'une manière ou d'une autre les méthodes statiques améliore votre code OO, vous vous trompez gravement. Créer un objet juste pour appeler une seule méthode dessus est une méthode statique par syntaxe obtuse.

Là où les choses deviennent différentes, c'est lorsque vous injectez CustomerReceiptCreatorau lieu de les newingérer. Prenons un exemple:

class OrderProcessor {
    @Inject CustomerReceiptCreator customerReceiptCreator;

    void processOrder(Order order) {
        ...
        CustomerReceipt receipt = customerReceiptCreator.createReceipt(order);
        ...
    }
}

Comparons ceci une version de méthode statique:

void processOrder(Order order) {
    ...
    CustomerReceipt receipt = CustomerReceiptCreator.createReceipt(order);
    ...
}

L'avantage de la version statique est que je peux facilement vous dire comment elle interagit avec le reste du système. Ce n'est pas le cas. Si je n'ai pas utilisé de variables globales, je sais que le reste du système n'a pas été modifié d'une manière ou d'une autre. Je sais qu'aucune autre partie du système ne peut affecter la réception ici. Si j'ai utilisé des objets immuables, je sais que l'ordre n'a pas changé et le createReceipt est une fonction pure. Dans ce cas, je peux librement déplacer / supprimer / etc cet appel sans me soucier des effets aléatoires imprévisibles ailleurs.

Je ne peux pas faire les mêmes garanties si j'ai injecté le CustomerReceiptCreator. Il peut avoir un état interne qui est modifié par l'appel, je peux être affecté par ou changer un autre état. Il peut y avoir des relations imprévisibles entre les instructions dans ma fonction, de sorte que changer l'ordre introduira des bogues surprenants.

D'un autre côté, que se passe-t-il si CustomerReceiptCreatorsoudain a besoin d'une nouvelle dépendance? Disons qu'il doit vérifier un indicateur de fonctionnalité. Si nous injections, nous pourrions faire quelque chose comme:

public class CustomerReceiptCreator {
    @Injected FeatureFlags featureFlags;

    public CustomerReceipt createReceipt(Order order) {
        CustomerReceipt receipt = new CustomerReceipt();
        // creation logic
        if (featureFlags.isFlagSet(Flags::FOOBAR)) {
           ...
        }
        return receipt;
    }
}

Ensuite, nous avons terminé, car le code appelant sera injecté avec un CustomerReceiptCreatorqui sera automatiquement injecté un FeatureFlags.

Et si nous utilisions une méthode statique?

public class CustomerReceiptCreator {
    public static CustomerReceipt createReceipt(Order order, FeatureFlags featureFlags) {
        CustomerReceipt receipt = new CustomerReceipt();
        // creation logic
        if (featureFlags.isFlagSet(Flags::FOOBAR)) {
           ...
        }
        return receipt;
    }
}

Mais attendez! le code appelant doit également être mis à jour:

void processOrder(Order order) {
    ...
    CustomerReceipt receipt = CustomerReceiptCreator.createReceipt(order, featureFlags);
    ...
}

Bien sûr, cela laisse encore la question de savoir d'où processOrdervient son origine FeatureFlags. Si nous sommes chanceux, la piste se termine ici, sinon, la nécessité de passer par FeatureFlags est poussée plus loin dans la pile.

Il y a un compromis ici. Méthodes statiques nécessitant de passer explicitement autour des dépendances, ce qui entraîne plus de travail. La méthode injectée réduit le travail, mais rend les dépendances implicites et donc cachées, rendant le code plus difficile à raisonner.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.