Missbrauchen wir statische Methoden?

12

Vor ein paar Monaten habe ich angefangen, in einem neuen Projekt zu arbeiten, und als ich den Code durchgesehen habe, habe ich die Menge der verwendeten statischen Methoden gemerkt. In ihnen sind nicht nur Utility-Methoden collectionToCsvString(Collection<E> elements), sondern auch viele Geschäftslogiken enthalten.

Als ich den Verantwortlichen nach den Hintergründen fragte, sagte er, es sei ein Ausweg aus der Tyrannei des Frühlings . Um diesen Denkprozess dreht sich etwas: Um eine Methode zur Erstellung von Kundenbelegen zu implementieren, könnten wir einen Service haben

@Service
public class CustomerReceiptCreationService {

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

Nun, der Typ sagte, dass er es nicht mag, Klassen unnötigerweise von Spring verwalten zu lassen, da dies die Einschränkung auferlegt, dass Client-Klassen selbst Spring Beans sein müssen. Am Ende wird alles von Spring verwaltet, was uns ziemlich zwingt, prozedural mit staatenlosen Objekten zu arbeiten. Mehr oder weniger, was hier angegeben ist https://www.javacodegeeks.com/2011/02/domain-driven-design-spring-aspectj.html

Anstelle des obigen Codes hat er also

public class CustomerReceiptCreator {

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

Ich könnte bis zu dem Punkt argumentieren, dass Spring es nach Möglichkeit vermeiden sollte, unsere Klassen zu managen, aber ich sehe nicht den Vorteil, dass alles statisch ist. Diese statischen Methoden sind auch zustandslos, also auch nicht sehr OO. Ich würde mich mit sowas wohler fühlen als

new CustomerReceiptCreator().createReceipt()

Er behauptet, dass statische Methoden einige zusätzliche Vorteile haben. Nämlich:

  • Einfacher zu lesen. Importieren Sie die statische Methode und wir müssen uns nur um die Aktion kümmern, nicht um welche Klasse.
  • Ist natürlich eine Methode frei von DB-Aufrufen, also leistungsmäßig günstig; und es ist eine gute Sache, es klar zu machen, so dass der potenzielle Client in den Code gehen und dies überprüfen muss.
  • Einfacheres Schreiben von Tests.

Ich habe jedoch das Gefühl, dass etwas nicht ganz in Ordnung ist, daher würde ich gerne einige erfahrene Entwickler dazu hören.

Meine Frage ist also, was sind die potenziellen Tücken dieser Art der Programmierung?

user3748908
quelle
4
Die oben dargestellte staticMethode ist nur eine normale Factory-Methode. Die Statisierung von Factory-Methoden ist aus einer Reihe von zwingenden Gründen die allgemein anerkannte Konvention. Ob die Fabrikmethode hier angemessen ist, ist eine andere Sache.
Robert Harvey

Antworten:

22

Was ist der Unterschied zwischen new CustomerReceiptCreator().createReceipt()und CustomerReceiptCreator.createReceipt()? So ziemlich keine. Der einzige wesentliche Unterschied besteht darin, dass der erste Fall eine erheblich umständlichere Syntax aufweist. Wenn Sie der ersten Überzeugung folgen, dass das Vermeiden statischer Methoden Ihren Code besser macht, irren Sie sich ernsthaft. Das Erstellen eines Objekts zum Aufrufen einer einzelnen Methode ist eine statische Methode mit stumpfer Syntax.

Wo die Dinge anders werden, ist, wenn Sie injizieren, CustomerReceiptCreatoranstatt newes zu tun . Betrachten wir ein Beispiel:

class OrderProcessor {
    @Inject CustomerReceiptCreator customerReceiptCreator;

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

Vergleichen wir dies mit einer statischen Methodenversion:

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

Der Vorteil der statischen Version ist, dass ich Ihnen leicht sagen kann, wie sie mit dem Rest des Systems interagiert. Das tut es nicht. Wenn ich keine globalen Variablen verwendet habe, weiß ich, dass der Rest des Systems nicht irgendwie geändert wurde. Ich weiß, dass hier kein anderer Teil des Systems den Beleg beeinflusst. Wenn ich unveränderliche Objekte verwendet habe, weiß ich, dass sich die Reihenfolge nicht geändert hat und dass createReceipt eine reine Funktion ist. In diesem Fall kann ich diesen Aufruf frei verschieben / entfernen / usw., ohne mir Sorgen über zufällige unvorhersehbare Effekte an anderer Stelle zu machen.

Ich kann nicht die gleichen Garantien geben, wenn ich das gespritzt habe CustomerReceiptCreator. Es hat möglicherweise einen internen Status, der durch den Aufruf geändert wird, ich bin möglicherweise von einem anderen Status betroffen oder ändere ihn. Es kann unvorhersehbare Beziehungen zwischen Anweisungen in meiner Funktion geben, sodass das Ändern der Reihenfolge zu überraschenden Fehlern führt.

Was passiert dagegen, wenn CustomerReceiptCreatorplötzlich eine neue Abhängigkeit benötigt wird? Angenommen, es muss ein Feature-Flag überprüft werden. Wenn wir spritzen, könnten wir etwas machen wie:

public class CustomerReceiptCreator {
    @Injected FeatureFlags featureFlags;

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

Dann sind wir fertig, denn dem aufrufenden Code wird ein injiziert, CustomerReceiptCreatorder automatisch ein injiziert wird FeatureFlags.

Was wäre, wenn wir eine statische Methode verwenden würden?

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

Aber warte! Der aufrufende Code muss ebenfalls aktualisiert werden:

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

Natürlich bleibt die Frage offen, woher sie processOrderkommt FeatureFlags. Wenn wir Glück haben, endet der Trail hier. Wenn nicht, wird die Notwendigkeit, FeatureFlags zu passieren, weiter nach oben geschoben.

Hier gibt es einen Kompromiss. Statische Methoden, bei denen die Abhängigkeiten explizit weitergegeben werden müssen, was zu mehr Arbeit führt. Eingespritzte Methoden reduzieren die Arbeit, machen jedoch die Abhängigkeiten implizit und damit verborgen, was das Ermitteln von Code erschwert.

Winston Ewert
quelle