Sollten Methoden, die keine „reinen Funktionen“ sind und mit externen APIs oder Hardware interagieren, statisch sein?

8

Beim Lesen darüber, wann eine Methode statisch gemacht werden soll oder nicht, habe ich ein allgemeines Prinzip gesehen, wie in diesem Beitrag zusammengefasst , dass eine Methode nur statisch sein sollte, wenn sie einen Zustand nicht ändert und ihr Ergebnis nur von den angegebenen Parametern abhängt es. Die am häufigsten gewählte Antwort in diesem Beitrag besagt jedoch, dass nach Möglichkeit statische Methoden verwendet werden sollten. Viele der Antworten in diesem Beitrag besagen, dass man alles tun sollte, was am logischsten ist.

In meinem Fall habe ich ~ 15 Methoden in einer gemeinsamen Dienstprogrammklasse, da sie an mehreren Stellen verwendet werden und als Mitglied eines Modells oder Ansichtsmodells (unter Verwendung von C # MVVM) keinen Sinn ergeben. Es gibt ungültige Methoden, die mithilfe ihrer Pakete mit verschiedenen Hardwarekomponenten interagieren (z. B. National Instruments, OPC-Clients usw.). Es gibt auch Methoden, die Eingaben vornehmen, ein GET oder PUT für unsere API ausführen und dann eine Antwort zurückgeben. Diese Methoden verwenden HttpClient oder ähnliches. Diese Methoden sind keine einfachen Operatoren für die Eingabe Math.Sqrt()und ändern den Status nicht.

Sollten solche Methoden (und in diesem Fall die gesamte Klasse) statisch sein? Es gibt den offensichtlichen Vorteil einer statischen Klasse mit statischen Methoden: Sie ist sicher und schnell, da Sie kein Objekt erstellen müssen. Außerdem verwendet jede dieser statischen Methoden mehr statische Methoden und Klassen für API- und Hardware-Interaktionen. Der einzige Nachteil, den ich sehe, ist, dass ich Unit-Tests mit einem kostenpflichtigen Framework wie TypeMock Isolator schreiben müsste. Die Kosten sind kein Problem, wenn die Antwort darin besteht, sie mit TypeMock Isolator oder einem ähnlichen kostenpflichtigen Dienst zu verspotten. Wenn dies also der Konsens ist, ist das in Ordnung. Ich möchte nur eine Entscheidung treffen, die sich gut skalieren lässt und wenig technische Schulden hinterlässt, wenn wir neue Entwickler einstellen und unser Projekt wächst.

Lassen Sie mich wissen, wenn ich weitere Informationen benötigen, um dies klarer zu machen!

adjordan
quelle
3
"Interaktion mit verschiedenen Hardwarekomponenten", "Es gibt auch Methoden, die Eingaben
vornehmen
2
So erweitern Sie den Kommentar von @ Caleth: Denken Sie daran, dass "state" nicht nur für die Einsen und Nullen im Speicher gilt. Wenn Sie eine Funktion aufrufen, die den Arm eines Roboters bewegt, haben Sie den Status - in der realen Welt - zusammen mit allen Konsequenzen geändert.
Greg Burghardt
1
"sicher und schnell, weil Sie kein Objekt erstellen müssen". Im Fall der Netzwerk-API erstellen Sie sicherlich viele Objekte innerhalb des Aufrufs . In jedem Fall ist die Zeit zum Erstellen Ihres Objekts im Vergleich zur Netzwerklatenz gering.
user949300
Was werden Sie tun, wenn Sie mehr als eine der gleichen Hardwarekomponenten haben? Alle Methoden kopieren / einfügen?
user253751

Antworten:

15

Sollten solche Methoden (und in diesem Fall die gesamte Klasse) statisch sein?

Nein. Zumindest sollten Sie sie nicht direkt als Statik verwenden.

Wenn Sie mit verschiedenen Hardware - Komponenten arbeiten, sind Sie sehr sehr wahrscheinlich , sie verhöhnen zu wollen, verwenden Sie neue und holen , die man Sie verwenden. Das alles ruft nach einer Schnittstelle (oder einer anderen Abstraktion), damit der Rest Ihres Codes sofort ignorieren kann, dass es überhaupt eine Hardwarekomponente gibt. Und Statik spielt in den meisten Sprachen sehr schlecht mit Schnittstellen (und anderen Abstraktionsmechanismen).

Telastyn
quelle
1
Zumindest ist es üblich, Sammlungen statischer Methoden zu haben, die die tatsächlichen Aufrufe ausführen, die mit externen Inhalten kommunizieren, aber dann eine zustandsbehaftete API-Klasse darüber zu erstellen und diese Klasse als Methode zur Erledigung von Aufgaben verfügbar zu machen Es ist diese Klasse, die eher verspottet wird als die zugrunde liegenden Schnittstellen, dh das Fassadenmuster.
Whatsisname
Danke für die Antwort! Dies scheint der Konsens zu sein, daher werde ich mich in Zukunft an diesen Grundsatz halten.
Adjordan
4

Die widersprüchlichen Ratschläge, die Sie gelesen haben, sind alle in gewisser Weise sinnvoll, aber sie alle machen (unterschiedliche) Annahmen oder formulieren Dinge mehrdeutig. Es ist eine dieser Arten von Unterscheidungen, die "dasselbe mit verschiedenen Worten sagen".

Eine Methode sollte nur statisch sein, wenn sie einen Status nicht ändert

Beachten Sie die Mehrdeutigkeit von "Status ändern". Das folgende Beispiel verstößt (buchstäblich) gegen diese Regel, bewahrt jedoch den (bildlichen) Geist der Regel:

public static void SetUserNameToBob(User u)
{
    u.Name = "Bob";
}

Dadurch wird der Status des UserObjekts geändert, sodass tatsächlich ein Status geändert wird.

Allerdings ist diese Methode nicht verläßt auf einem bestimmten internen Zustand , um seine eigenen logischen Fluss zu entscheiden (zB u.Name = currentlySelectedDefaultName()wäre ein Verstoß als der gewählte Name ein ausgewählter Zustand ist). Und ich denke, das ist gemeint: Kein interner Zustand wird geändert.

[Eine Methode sollte nur statisch sein, wenn] ihr Ergebnis nur von den ihr zur Verfügung gestellten Parametern abhängt

Schauen Sie sich den vorherigen Artikel an, dieser sagt so ziemlich dasselbe. Was es bedeutet ist, dass dies:

public static string currentlySelectedDefaultName; 
public static void SetUserNameToBob(User u)
{
    u.Name = currentlySelectedDefaultName;
}

sollte nicht statisch sein, da der "aktuelle Standard" -Name ein Status ist und daher keine globale Variable / Methode sein sollte.

Überlegen Sie, was passiert, wenn zwei separate Threads ausgeführt werden: Einer möchte standardmäßig "Bob", der andere standardmäßig "Jim". Sie werden am Ende um den Wert streiten, was zu massiven Debugging-Problemen und unerwartetem Verhalten führen kann.
Wenn jedoch jeder Thread ein eigenes DefaultNameSetterObjekt hat, kämpfen sie nicht um dieselbe Ressource.

Die am häufigsten gewählte Antwort in diesem Beitrag besagt jedoch, dass nach Möglichkeit statische Methoden verwendet werden sollten.

Dies ist eine Art Durchsetzung einer Regel durch Versuch / Misserfolg. Können wir diese Methode auf statisch setzen?

  • Ja =>gut! Lass es so wie es ist!
  • Nein =>Dies beweist, dass der Code irgendwo auf einem nicht statischen Wert basiert und daher nicht statisch gemacht werden sollte.

Um Jeff Goldblum im Jurassic Park indirekt zu zitieren , sollten Sie nicht die Notwendigkeit argumentieren , etwas zu tun, indem Sie nur beweisen, dass es möglich ist .

Auch hier ist der Ansatz nicht unbedingt (oder immer) falsch, aber er geht blind davon aus, dass Methoden bereits so zustandsunabhängig wie logisch möglich geschrieben sind, was einfach nicht der Fall ist.

Selbst wenn Sie diesen methodischen Ansatz abonnieren, können Sie ihn nur anwenden, wenn sich ein Projekt nicht mehr in der Entwicklung befindet. Wenn sich das Projekt noch in der Entwicklung befindet, können aktuelle Methoden Platzhalter für die zukünftige Implementierung sein. Es könnte möglich sein, Foo()heute statisch zu machen , aber nicht morgen, wenn die zustandsabhängige Logik einfach noch nicht implementiert wurde.

Viele der Antworten in diesem Beitrag besagen, dass man alles tun sollte, was am logischsten ist.

Nun, sie sind nicht falsch; Aber ist das nicht nur eine kleine Umformulierung von "Mach das Richtige"? Das ist kein wirklich hilfreicher Rat, es sei denn, Sie wissen bereits, wann Sie Statik verwenden und wann Sie sie vermeiden müssen. Es ist ein Haken 22.


Wann sollten Sie Statik verwenden?

Wie Sie bemerkt haben, sind sich nicht alle einig über die Regel oder zumindest darüber, wie die Regel formuliert werden soll. Ich werde hier einen weiteren Versuch hinzufügen, aber sei dir bewusst, dass dies effektiv einen anderen Standard schafft :

Geben Sie hier die Bildbeschreibung ein

Merk dir das.

Statik sind universelle Wahrheiten.

Das ist der Zweck eines globalen Namespace: Dinge, die auf der gesamten Anwendungsschicht korrekt sind.

Hier gibt es ein Argument für einen rutschigen Hang. Einige Beispiele:

var myConfigKey = ConfigurationManager.AppSettings["myConfigKey"];

Dies ist ein sehr klares Beispiel. Die Anwendungskonfiguration impliziert von Natur aus, dass die Konfiguration für die Anwendung global ist, und daher ist eine statis-Methode erforderlich.

bool datesOverlap = DateHelper.HasOverlap(myDateA_Start, myDateA_End, myDateB_Start, myDateB_End);

Diese Methode ist allgemein korrekt. Es ist egal, welche Daten Sie vergleichen. Die Methode kümmert sich nicht um die Bedeutung der Daten. Ob es sich um Beschäftigungsdaten, Vertragsdaten usw. handelt, spielt für den Algorithmus der Methode keine Rolle.

Beachten Sie die semantische Ähnlichkeit zwischen kontextbezogen und zustandsgesteuert . Beide Sortierungen beziehen sich auf einen "nicht universellen" Zustand. Dies beweist, dass Kontextunterschiede daher zustandsabhängig sind und nicht geeignet sind, statisch gemacht zu werden.

var newPersonObject = Person.Create();

Dies ist eine universelle Wahrheit. Der gleiche Erstellungsprozess wird in der gesamten Anwendung verwendet.

Diese Linie kann jedoch unscharf werden:

var newManager = Person.CreateManager();
var newJanitor = Person.CreateJanitor();

Aus technischer Sicht hat sich nichts geändert. Manager (und Hausmeister) werden in der gesamten Anwendung auf dieselbe Weise erstellt. Dies hat jedoch auf subtile Weise einen Staat (Manager / Hausmeister) geschaffen, der langsam, aber stetig davon ablenkt, wie universell die Wahrheit wirklich ist.

Kann es gemacht werden? Aus technischer Sicht; ja .
Sollte es getan werden? Dabei geht es darum, ob Sie das reine Prinzip argumentieren oder berücksichtigen, dass Kompromisse eingegangen werden müssen, um nicht sinnlos nach logischer Perfektion zu streben. Ich würde also sagen, wenn es kein größeres Problem schafft, als das Problem zu lösen beabsichtigt .

Mit der Erweiterung der Optionen (Manager, Hausmeister, Buchhalter, Verkäufer usw.) wächst das Problem. Für ein ausreichend großes Problem ist ein Fabrikmuster wünschenswert.

Wenn Sie nur zwei Optionen haben und keinen Grund zu der Annahme haben, dass die Liste der Optionen erweitert wird, können Sie argumentieren, dass die statischen Erstellungsmethoden ausreichen. Einige mögen anderer Meinung sein, und ich verstehe auch ihren Standpunkt. Aber ich neige dazu, praktisch und nicht übermäßig perfektionistisch zu sein.

Flater
quelle
3

Die am häufigsten gewählte Antwort in diesem Beitrag besagt jedoch, dass statische Methoden verwendet werden sollten, wann immer dies möglich ist ...

Statische Methoden, die den Zustand beeinflussen, an den sie direkt gekoppelt sind, sind eine sehr schlechte Idee. Sie führen zu globalen Zuständen - "gruseligen Aktionen auf Distanz" -, Spaghetti-Code und dergleichen. Sie sind schwer zu testen, zu debuggen und zu warten. Meine Lektüre der am besten bewerteten Antwort ermutigt nichts davon, also ist alles in Ordnung.

Sollten solche Methoden (und in diesem Fall die gesamte Klasse) statisch sein?

Kommt darauf an. Nehmen wir ein Beispiel für die " void-Methoden, die mithilfe ihrer Pakete mit verschiedenen Hardwarekomponenten interagieren ". Diese Methoden können statisch sein, jedoch nur, wenn sie von diesen Paketen entkoppelt sind. Eine Möglichkeit, dies zu erreichen, besteht darin, diese Pakete als Parameter zu übergeben. Möchten Sie die Methode ohne Hardware testen? Übergeben Sie einfach ein verspottetes Dummy-Paket, das Aktionen aufzeichnet, anstatt über den Parameter auf Hardware zuzugreifen. Gleiches gilt für die HTTP-Methoden; solange auch das eigentliche Mittel für den Zugriff auf die API als Parameter übergeben wird.

Aber erreichen Sie auf diese Weise wirklich etwas, indem Sie eine Instanz erstellen und diese Pakete, HTTP-Klassen usw. zur Erstellungszeit einfügen? Wahrscheinlich nicht. Sicher, es handelt sich um statische Methoden, sodass Sie keine Instanz benötigen. Sie haben jedoch die zusätzliche Komplexität, dass Sie alle diese Pakete usw. im gesamten Code verfügbar machen müssen, um sie als Parameter zu übergeben. Es ist oft viel einfacher, eine einzelne Instanz zu erstellen und diese stattdessen weiterzugeben.

David Arno
quelle
Sie sagen, Sie sollen eine einzelne Instanz der von mir erstellten Klasse "UtilityMethods.cs" weitergeben? Ich verwende derzeit die Abhängigkeitsinjektion mit SimpleIoC, was meiner Meinung nach die gleiche Idee ist.
Adjordan
2
@adjordan, in der Tat. Ob Sie reines DI (für arme Männer) oder ein IoC-Framework zur Unterstützung der Injektion verwenden, liegt bei Ihnen. Das Prinzip bleibt dasselbe: Es ist einfacher, nur eine Instanz von IUtilityMethodsweiterzugeben, als mehrere Instanzen weiterzugeben, die an statische Funktionen übergeben werden müssen.
David Arno
1

Was ich empfehle und was ich in meinem Team verwende, ist die Verwendung statischer Methoden / Klassen, wenn dies wirklich erforderlich ist. Es sollte nicht die erste Option sein, statische Methoden / Klassen zu verwenden. Das muss ein sehr guter Grund sein. Viele Entwickler sehen die statische Option schneller und damit billiger. Aber das ist vielleicht am Anfang. Die Kosten bestehen darin, kompliziertere Unit-Tests zu erstellen und keine Verträge (Schnittstellen) mit mehreren Implementierungen verwenden zu können. Für die Methoden / Klassen, die mit Hardware interagieren. Diese sollten nicht statisch sein. Erstens und für die meisten gibt es keinen Grund. Zweitens ist es erforderlich, diese Klassen in Komponententests zu verspotten, um einen einfacheren Testcode zu erhalten. Drittens ist es möglich, die Implementierung in Zukunft zu ersetzen.

Arafat Hegazy
quelle
0

Statische Funktionen sind schwieriger, wenn Sie den Mehrfachzugriff parallel zulassen möchten.

Zum Beispiel ist es durchaus sinnvoll und wahrscheinlich eine gute Idee, mehrere HTTP-Aufrufe gleichzeitig ausführen zu lassen. Mit einem Instanzobjekt können Sie leicht einen anderen Status für diese Aufrufe beibehalten - die Header, Ergebniscodes, Puffer usw.

Mit einer einzigen statischen Methode können Sie möglicherweise eine interne Instanz erstellen, die diesen Status beibehält. Oder Sie synchronisieren alles (ich verwende die Java-Terminologie YMMV), was fehleranfällig ist. Oder verwenden Sie eine Warteschlange. In jedem Fall verlieren Sie ohne ausgefallene Beinarbeit die Fähigkeit, parallel zu laufen. Wenn ein HTTP-Anruf aufgrund eines Netzwerkfehlers ausfällt, müssen alle anderen warten.

Gleiches gilt für Ihre Hardware. In einigen Fällen kann es sinnvoll sein, den parallelen Zugriff zuzulassen.

Aus diesen Gründen bin ich mit der von Ihnen zitierten 8-jährigen Antwort nicht einverstanden.

user949300
quelle
4
Eine statische Funktion sollte überhaupt keinen Status enthalten.
Pieter B
1
@Pieter B Natürlich (außer vielleicht einem Zähler). Im HTTP-Beispiel müsste pro Aufruf ein MyHTTPHelper-Objekt erstellt werden, um den Status zu speichern. An diesem Punkt ist es wahrscheinlich einfacher, den ursprünglichen Aufrufer ein Objekt erstellen zu lassen, als überhaupt eine statische Methode zu verwenden.
user949300