C # pattern per gestire in modo pulito le "funzioni libere", evitando classi statiche di "utilità" in stile Helper

7

Recentemente ho esaminato alcune classi statiche di "utility bag" in stile Helper che fluttuavano intorno a code code C # di grandi dimensioni con cui lavoro, cose che sostanzialmente somigliano al seguente frammento molto condensato:

// Helpers.cs

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

I metodi specifici che ho recensito sono

  • per lo più non correlati tra loro,
  • senza stato esplicito persistente attraverso invocazioni
  • piccolo e
  • ciascuno consumato da una varietà di tipi non correlati.

Modifica: quanto sopra non intende essere un elenco di presunti problemi. È una lista di caratteristiche comuni dei metodi specifici che sto esaminando. È il contesto in cui le risposte aiutano a fornire soluzioni più pertinenti.

Proprio per questa domanda, mi riferirò a questo tipo di metodo come GLUM (metodo generale di utilità leggero). La connotazione negativa di "glum" è in parte intesa. Mi dispiace se mi viene in mente un gioco stupido.

Anche mettendo da parte il mio scetticismo di default su GLUM, non mi piacciono le seguenti cose a riguardo:

  • Una classe statica viene utilizzata esclusivamente come spazio dei nomi.
  • L'identificatore di classe statico non ha praticamente alcun significato.
  • Quando viene aggiunto un nuovo GLUM, o (a) questa classe "bag" viene toccata senza una buona ragione o (b) viene creata una nuova classe "bag" (che di per sé non è di solito un problema; che le nuove classi statiche spesso ripetono semplicemente il problema di non correlazione, ma con meno metodi).
  • La metamorfosi è ineluttabilmente orribile, non standard e solitamente internamente incoerente, sia che si tratti di Helpers , Utilities , o qualsiasi altra cosa.

Che cosa è ragionevolmente buono e amp; schema semplice per refactoring questo, preferibilmente affrontando le preoccupazioni di cui sopra, e preferibilmente con un tocco il più leggero possibile?

Probabilmente dovrei enfatizzare: Tutti i metodi con cui ho a che fare sono a coppie non correlati tra loro. Non sembra esserci un modo ragionevole per scomporli in grani più fini ma ancora metodo-borse di classe statica multi-membro.

    
posta William 08.10.2017 - 06:04
fonte

3 risposte

3

Adotta la convenzione secondo cui le classi statiche sono usate come namespace in situazioni come questa in C #. I namespace ottengono buoni nomi, quindi dovrebbero essere queste classi. La funzione using static di C # 6 rende la vita anche un po 'più facile.

I nomi di classi puzzolenti come Helpers indicano che i metodi dovrebbero essere suddivisi in classi statiche meglio nominate, se possibile, ma uno dei tuoi enfasi è che i metodi con cui hai a che fare sono "a coppie non correlate" , che implica la suddivisione in una nuova classe statica per metodo esistente. Probabilmente va bene, se

  • ci sono buoni nomi per le nuove classi statiche e
  • l'hai giudicato utile per la manutenibilità del tuo progetto.

Come esempio forzato, Helpers.LoadImage potrebbe diventare qualcosa come FileSystemInteractions.LoadImage .

Ancora, potresti finire con i sacchetti di metodo di classe statica. Alcuni modi in cui ciò può accadere:

  • Forse solo alcuni (o nessuno) dei metodi originali hanno un buon nome per le loro nuove classi.
  • Forse le nuove classi si evolveranno in seguito per includere altri metodi pertinenti.
  • Forse il nome originale della classe non era maleodorante e in effetti andava bene.

È importante ricordare che questi sacchetti di metodo statico di classe non sono particolarmente rari nelle codebase in codice C #. Probabilmente non è patologico avere alcuni piccoli .

Se si vede davvero un vantaggio nell'avere ogni metodo di "funzione libera" nel proprio file (che va bene e vale la pena se si giudica onestamente che ciò avvantaggia effettivamente la manutenibilità del progetto), si potrebbe considerare di fare una classe così statica invece una classe parziale statica, impiegando il nome di una classe statica simile a come si farebbe con uno spazio dei nomi e poi a consumarlo tramite using static . Ad esempio:

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() { }
    }
}
    
risposta data 10.10.2017 - 21:35
fonte
17

Penso che tu abbia due problemi strettamente correlati l'uno con l'altro.

  • Le classi statiche contengono funzioni logicamente non correlate
  • I nomi delle classi statiche non forniscono informazioni utili su significato / contesto delle funzioni contenute.

Questi problemi possono essere risolti combinando solo i metodi logicamente correlati in una classe statica e spostando gli altri nella loro classe statica. In base al dominio del problema, tu e il tuo team dovreste decidere quali criteri utilizzare per separare i metodi in classi statiche correlate.

Preoccupazioni e possibili soluzioni

I metodi sono per lo più non correlati tra loro - problema. Combina metodi correlati in una classe statica e sposta metodi non collegati nelle proprie classi statiche.

I metodi sono senza stato esplicito persistente attraverso invocazioni - non un problema. I metodi stateless sono una funzionalità, non un bug. Lo stato statico è difficile da testare comunque e dovrebbe essere utilizzato solo se è necessario mantenere lo stato tra le invocazioni del metodo, ma ci sono modi migliori per farlo, come macchine di stato e la parola chiave yield .

I metodi sono piccoli - non sono un problema. - I metodi dovrebbero essere piccoli.

I metodi sono consumati da una varietà di tipi non correlati - non è un problema. hai creato un metodo generale che può essere riutilizzato in molti posti non correlati.

Una classe statica viene utilizzata esclusivamente come spazio dei nomi - non è un problema. Ecco come vengono utilizzati i metodi statici. Se questo stile di metodi di chiamata ti disturba, prova a utilizzare i metodi di estensione o la dichiarazione using static .

L'identificatore di classe statico è fondamentalmente privo di significato - problema . Non ha senso perché gli sviluppatori stanno introducendo metodi non correlati. Metti metodi non collegati nelle proprie classi statiche e dai loro nomi specifici e comprensibili.

Quando viene aggiunto un nuovo GLUM, o (a) questa classe "borsa" viene toccata (tristezza SRP) - non è un problema se segui l'idea che solo logicamente i metodi correlati dovrebbero essere in una classe statica. SRP non è violato qui, perché il principio dovrebbe essere applicato per le classi o per i metodi. La singola responsabilità delle classi statiche significa contenere diversi metodi per una "idea" generale. Quell'idea può essere una funzionalità o una conversione, o iterazioni (LINQ), o normalizzazione dei dati o convalida ...

o meno frequentemente (b) viene creata una nuova classe "bag" (più borse) - non è un problema. Classi / classi statiche / funzioni - sono le nostre ( sviluppatori), che usiamo per progettare software. La tua squadra ha dei limiti per usare le classi? Quali sono i limiti massimi per "più borse"? La programmazione riguarda tutto il contesto, se per la soluzione nel proprio contesto particolare si ottengono 200 classi statiche che sono nominate in modo comprensibile, collocate logicamente in spazi dei nomi / cartelle con gerarchia logica - non è un problema.

La metamorfosi è ineluttabilmente orribile, non standard, e di solito internamente incoerente, che si tratti di Helpers, Utilità o qualsiasi altro - problema. Fornisci nomi migliori che descrivono comportamento atteso. Mantieni solo i metodi correlati in una classe, che forniscono assistenza per una migliore denominazione.

    
risposta data 08.10.2017 - 07:09
fonte
-5

Generalmente non dovresti avere o richiedere alcun "metodo di utilità generale". Nella tua logica aziendale. Prendi un'altra occhiata e mettile in un posto adatto.

Se stai scrivendo una libreria matematica o qualcosa del genere, ti suggerirei, anche se normalmente li odio, i metodi di estensione.

È possibile utilizzare i metodi di estensione per aggiungere funzioni a tipi di dati standard.

Per esempio diciamo che ho una funzione generale MySort () al posto di Helpers.MySort o aggiungendo un nuovo ListOfMyThings.MySort Posso aggiungerlo a IEnumerable

    
risposta data 08.10.2017 - 09:31
fonte

Leggi altre domande sui tag