C # -Muster, um „freie Funktionen“ sauber zu handhaben und statische Klassen im „Utility Bag“ im Helper-Stil zu vermeiden

9

Ich habe kürzlich einige statische "Utility Bag" -Klassen im Helper-Stil überprüft, die in einigen großen C # -Codebasen schweben, mit denen ich arbeite. Grundsätzlich Dinge wie das folgende sehr komprimierte Snippet:

// Helpers.cs

public static class Helpers
{
    public static void DoSomething() {}
    public static void DoSomethingElse() {}
}

Die spezifischen Methoden, die ich überprüft habe, sind

  • meist nicht miteinander verwandt,
  • ohne expliziten Zustand über Aufrufe bestanden,
  • klein und
  • jeweils von einer Vielzahl von nicht verwandten Typen konsumiert.

Bearbeiten: Das oben Gesagte ist nicht als Liste angeblicher Probleme gedacht. Es ist eine Liste allgemeiner Merkmale der spezifischen Methoden, die ich überprüfe. Es ist ein Kontext, der Antworten hilft, relevantere Lösungen bereitzustellen.

Nur für diese Frage werde ich diese Art von Methode als GLUM (General Lightweight Utility Method) bezeichnen. Die negative Konnotation von "bedrückt" ist teilweise beabsichtigt. Es tut mir leid, wenn dies als dummes Wortspiel rüberkommt.

Selbst wenn ich meine Standard-Skepsis gegenüber GLUMs beiseite lasse, mag ich die folgenden Dinge nicht:

  • Eine statische Klasse wird ausschließlich als Namespace verwendet.
  • Die statische Klassenkennung ist grundsätzlich bedeutungslos.
  • Wenn ein neues GLUM hinzugefügt wird, wird entweder (a) diese "Taschen" -Klasse ohne guten Grund berührt oder (b) eine neue "Taschen" -Klasse erstellt (was an sich normalerweise kein Problem darstellt; was schlecht ist, ist, dass die neue statische Klassen wiederholen oft nur das Problem der Nicht-Verwandtschaft, jedoch mit weniger Methoden.
  • Die Meta-Namensgebung ist unentrinnbar schrecklich, Nicht-Standard, und in der Regel intern inkonsistent, wäre es Helpers, Utilitiesoder was auch immer.

Was ist ein einigermaßen gutes und einfaches Muster, um dies umzugestalten, vorzugsweise die oben genannten Bedenken auszuräumen und vorzugsweise mit einer möglichst leichten Berührung?

Ich sollte wahrscheinlich betonen: Alle Methoden, mit denen ich zu tun habe, sind paarweise nicht miteinander verbunden. Es scheint keinen vernünftigen Weg zu geben, sie in feinkörnigere und dennoch mehrköpfige Methodenbeutel für statische Klassen zu zerlegen.

Wilhelm
quelle
1
Über GLUMs ™: Ich denke, die Abkürzung für "Lightweight" könnte entfernt werden, da Methoden leicht sein sollten, indem Best Practices befolgt werden;)
Fabio
@ Fabio Ich stimme zu. Ich habe den Begriff als (wahrscheinlich nicht besonders witzig und möglicherweise verwirrend) Witz aufgenommen, der als Abkürzung für das Schreiben dieser Frage dient. Ich denke, "Leichtgewicht" schien hier angemessen, weil die fraglichen Codebasen langlebig, alt und ein wenig chaotisch sind, dh es gibt zufällig viele andere Methoden (normalerweise nicht diese GUMs ;-), die "Schwergewicht" sind. Wenn ich im Moment mehr (ehem, irgendein) Budget hätte, würde ich definitiv aggressiver vorgehen und letzteres ansprechen.
William

Antworten:

17

Ich denke, Sie haben zwei Probleme, die eng miteinander verbunden sind.

  • Statische Klassen enthalten logisch nicht verwandte Funktionen
  • Namen statischer Klassen liefern keine hilfreichen Informationen über die Bedeutung / den Kontext der enthaltenen Funktionen.

Diese Probleme können behoben werden, indem nur logisch verwandte Methoden in einer statischen Klasse kombiniert und die anderen in ihre eigene statische Klasse verschoben werden. Basierend auf Ihrer Problemdomäne sollten Sie und Ihr Team entscheiden, nach welchen Kriterien Sie Methoden in verwandte statische Klassen unterteilen.

Bedenken und mögliche Lösungen

Methoden sind meist nicht miteinander verbunden - Problem. Kombinieren Sie verwandte Methoden unter einer statischen Klasse und verschieben Sie nicht verwandte Methoden in ihre eigenen statischen Klassen.

Methoden werden ohne expliziten Status über Aufrufe hinweg beibehalten - kein Problem. Zustandslose Methoden sind eine Funktion, kein Fehler. Der statische Status ist ohnehin schwer zu testen und sollte nur verwendet werden, wenn Sie den Status über Methodenaufrufe hinweg beibehalten müssen. Es gibt jedoch bessere Möglichkeiten, dies zu tun, z. B. Statusmaschinen und das yieldSchlüsselwort.

Methoden sind klein - kein Problem. - Methoden sollten klein sein.

Methoden werden jeweils von einer Vielzahl nicht verwandter Typen konsumiert - kein Problem. Sie haben eine allgemeine Methode erstellt, die an vielen nicht verwandten Stellen wiederverwendet werden kann.

Eine statische Klasse wird ausschließlich als Namespace verwendet - kein Problem. So werden statische Methoden verwendet. Wenn Sie diese Art des Aufrufs von Methoden stört, versuchen Sie, Erweiterungsmethoden oder die using staticDeklaration zu verwenden.

Die statische Klassenkennung ist grundsätzlich bedeutungslos - Problem . Es ist bedeutungslos, weil Entwickler nicht verwandte Methoden einfügen. Fügen Sie nicht verwandte Methoden in ihre eigenen statischen Klassen ein und geben Sie ihnen spezifische und verständliche Namen.

Wenn ein neues GLUM hinzugefügt wird, wird entweder (a) diese "Taschen" -Klasse berührt (SRP-Traurigkeit) - kein Problem, wenn Sie der Idee folgen, dass nur logisch verwandte Methoden in einer statischen Klasse sein sollten. SRPwird hier nicht verletzt, da das Prinzip für Klassen oder Methoden angewendet werden sollte. Einzelverantwortung für statische Klassen bedeutet, unterschiedliche Methoden für eine allgemeine "Idee" zu enthalten. Diese Idee kann ein Feature oder eine Konvertierung oder Iterationen (LINQ) oder eine Datennormalisierung oder -validierung sein ...

oder seltener (b) eine neue "Taschen" -Klasse wird erstellt (mehr Taschen) - kein Problem. Klassen / statische Klassen / Funktionen - sind unsere (Entwickler-) Tools, die wir zum Entwerfen von Software verwenden. Hat Ihr Team einige Grenzen für die Verwendung von Klassen? Was sind die Höchstgrenzen für "mehr Taschen"? Bei der Programmierung dreht sich alles um den Kontext. Wenn Sie zur Lösung in Ihrem speziellen Kontext 200 statische Klassen haben, die verständlicherweise benannt und logisch in Namespaces / Ordnern mit logischer Hierarchie abgelegt sind, ist dies kein Problem.

Die Meta-Benennung ist unausweichlich schrecklich, nicht standardisiert und normalerweise intern inkonsistent, egal ob es sich um Helfer, Dienstprogramme oder was auch immer handelt - Problem. Geben Sie bessere Namen an, die das erwartete Verhalten beschreiben. Behalten Sie nur verwandte Methoden in einer Klasse, die Ihnen bei der besseren Benennung helfen.

Fabio
quelle
Es ist keine SRP-Verletzung, aber ganz klar eine Verletzung des offenen / geschlossenen Prinzips. Das heißt, ich habe allgemein festgestellt, dass das Ocp mehr Ärger macht als es wert ist
Paul
2
@ Paul, Open / Close-Prinzip kann nicht für statische Klassen angewendet werden. Das war mein Punkt, dass SRP- oder OCP-Prinzipien nicht für statische Klassen angewendet werden können. Sie können es für statische Methoden anwenden.
Fabio
Okay, hier gab es einige Verwirrung. Die erste Liste der Fragen der Frage ist keine Liste angeblicher Probleme. Es ist eine Liste allgemeiner Merkmale der spezifischen Methoden, die ich überprüfe. Es ist ein Kontext, der den Antworten hilft, anwendbarere Lösungen bereitzustellen. Ich werde bearbeiten, um zu klären.
William
1
In Bezug auf "statische Klasse als Namespace" bedeutet die Tatsache, dass es sich um ein allgemeines Muster handelt, nicht, dass es kein Anti-Muster ist. Die Methode (die im Grunde eine "freie Funktion" ist, um einen Begriff aus C / C ++ auszuleihen) innerhalb dieser speziellen statischen Klasse mit geringer Kohäsion ist in diesem Fall die sinnvolle Entität. In diesem speziellen Kontext scheint es strukturell besser zu sein, einen Sub-Namespace Amit 100 statischen Klassen mit einer Methode (in 100 Dateien) zu haben als eine statische Klasse Amit 100 Methoden in einer einzelnen Datei.
William
1
Ich habe Ihre Antwort ziemlich gründlich aufgeräumt, hauptsächlich kosmetisch. Ich bin mir nicht sicher, worum es in Ihrem letzten Absatz geht. Niemand macht sich Sorgen darüber, wie er Code testet, der die Mathstatische Klasse aufruft .
Robert Harvey
5

Nehmen Sie einfach die Konvention an, dass statische Klassen in solchen Situationen in C # wie Namespaces verwendet werden. Namespaces erhalten gute Namen, sollten diese Klassen auch. Die using staticFunktion von C # 6 erleichtert auch das Leben ein wenig.

Stinkende Klassennamen wie Helperssignalisieren, dass die Methoden nach Möglichkeit in besser benannte statische Klassen aufgeteilt werden sollten. Eine Ihrer hervorgehobenen Annahmen ist jedoch, dass die Methoden, mit denen Sie sich befassen, "paarweise nicht verwandt" sind, was eine Aufteilung auf bedeutet eine neue statische Klasse pro vorhandener Methode. Das ist wahrscheinlich in Ordnung, wenn

  • Es gibt gute Namen für die neuen statischen Klassen und
  • Sie beurteilen dies als Vorteil für die Wartbarkeit Ihres Projekts.

Als erfundenes Beispiel Helpers.LoadImagekönnte so etwas werden FileSystemInteractions.LoadImage.

Trotzdem könnte es zu statischen Klassenmethodenbeuteln kommen. Einige Möglichkeiten, wie dies passieren kann:

  • Vielleicht haben nur einige (oder keine) der ursprünglichen Methoden gute Namen für ihre neuen Klassen.
  • Vielleicht entwickeln sich die neuen Klassen später um andere relevante Methoden.
  • Vielleicht roch der ursprüngliche Klassenname nicht und war eigentlich in Ordnung.

Es ist wichtig, sich daran zu erinnern, dass diese statischen Klassenmethodenbeutel in echten C # -Codebasen keine Seltenheit sind. Es ist wahrscheinlich nicht pathologisch, ein paar kleine zu haben .


Wenn Sie wirklich einen Vorteil darin sehen, dass jede "freie Funktion" -ähnliche Methode in einer eigenen Datei enthalten ist (was in Ordnung und sinnvoll ist, wenn Sie ehrlich beurteilen, dass dies tatsächlich der Wartbarkeit Ihres Projekts zugute kommt), können Sie eine solche statische Klasse anstelle einer statischen Klasse erstellen Teilklasse, wobei der statische Klassenname ähnlich wie bei einem Namespace verwendet und dann über verwendet wird using static. Beispielsweise:

Struktur des Dummy-Projekts unter Verwendung dieses Musters

Program.cs

namespace ConsoleApp1
{
    using static Foo;
    using static Flob;

    class Program
    {
        static void Main(string[] args)
        {
            Bar();
            Baz();
            Wibble();
            Wobble();
        }
    }
}

Foo / Bar.cs

namespace ConsoleApp1
{
    static partial class Foo
    {
        public static void Bar() { }
    }
}

Foo / Baz.cs

namespace ConsoleApp1
{
    static partial class Foo
    {
        public static void Baz() { }
    }
}

Flob / Wibble.cs

namespace ConsoleApp1
{
    static partial class Flob
    {
        public static void Wibble() { }
    }
}

Flob / Wobble.cs

namespace ConsoleApp1
{
    static partial class Flob
    {
        public static void Wobble() { }
    }
}
Wilhelm
quelle
-6

Im Allgemeinen sollten Sie keine "allgemeinen Dienstprogrammmethoden" haben oder benötigen. In Ihrer Geschäftslogik. Schauen Sie noch einmal genau hin und platzieren Sie sie an einem geeigneten Ort.

Wenn Sie eine Mathematikbibliothek oder etwas anderes schreiben, würde ich Erweiterungsmethoden vorschlagen, obwohl ich sie normalerweise hasse.

Sie können Erweiterungsmethoden verwenden, um Funktionen zu Standarddatentypen hinzuzufügen.

Nehmen wir zum Beispiel an, ich habe eine allgemeine Funktion MySort () anstelle von Helpers.MySort oder füge eine neue ListOfMyThings.MySort hinzu. Ich kann sie IEnumerable hinzufügen

Ewan
quelle
Es geht darum, keinen weiteren "harten Blick" zu werfen. Der Punkt ist, ein wiederverwendbares Muster zu haben, das leicht einen "Beutel" von Dienstprogrammen aufräumen kann . Ich weiß, dass Versorgungsunternehmen Gerüche sind. Die Frage besagt dies. Das Ziel ist es sinnvoll , diese unabhängig (aber zu eng co-located) -Domäne Einheiten isoliert jetzt und so einfach wie möglich auf dem Projektbudget gehen.
William
1
Wenn Sie keine "allgemeinen Dienstprogrammmethoden" haben, isolieren Sie wahrscheinlich nicht genug Logikteile vom Rest Ihrer Logik. So weit ich weiß, gibt es in .NET keine universelle URL-Pfadverbindungsfunktion, daher schreibe ich häufig eine. Diese Art von Cruft wäre als Teil der Standardbibliothek besser geeignet, aber jeder Standardbibliothek fehlt etwas . Die Alternative, die Logik direkt in Ihrem anderen Code wiederholen zu lassen, macht sie weitaus weniger lesbar.
jpmc26
1
@Ewan, das ist keine Funktion, es ist eine Allzweck-Delegatensignatur ...
TheCatWhisperer
1
@Ewan Das war der ganze Punkt von TheCatWhisperer: Utility-Klassen sind ein Problem, wenn es darum geht, Methoden in eine Klasse einzufügen. Ich bin froh, dass du damit einverstanden bist.
jpmc26