15

Tutti,I metodi di estensione nascondono le dipendenze?

Volevo fare qualche riflessione su questo. Ultimamente sto diventando sempre più un sottoscrittore di principi DI/IOC "puristi" durante la progettazione/sviluppo. Parte di questo (una parte importante) consiste nell'assicurarsi che ci sia un piccolo accoppiamento tra le mie classi e che le loro dipendenze siano risolte tramite il costruttore (ci sono sicuramente altri modi per gestirlo, ma si ottiene l'idea).

La mia premessa di base è che i metodi di estensione violano i principi di DI/IOC.

ho creato il seguente metodo di estensione che uso per garantire che le stringhe inserite in tabelle di database vengono troncati alla giusta dimensione:

public static class StringExtensions 
{ 
    public static string TruncateToSize(this string input, int maxLength) 
    { 
     int lengthToUse = maxLength; 
     if (input.Length < maxLength) 
     { 
      lengthToUse = input.Length; 
     } 

     return input.Substring(0, lengthToUse); 
    } 
} 

posso quindi chiamare la mia stringa all'interno di un'altra classe in questo modo:

string myString = "myValue.TruncateThisPartPlease."; 
myString.TruncateToSize(8); 

una fiera traduzione di questo senza usare un metodo di estensione sarebbe:

string myString = "myValue.TruncateThisPartPlease."; 
StaticStringUtil.TruncateToSize(myString, 8); 

Qualsiasi classe che utilizza uno degli esempi precedenti non può essere testata indipendentemente dalla classe che contiene il metodo TruncateToSize (TypeMock a parte). Se non fossi utilizzando un metodo di estensione, e non volevo creare una dipendenza statica, sarebbe più simile:

string myString = "myValue.TruncateThisPartPlease."; 
_stringUtil.TruncateToSize(myString, 8); 

Nell'ultimo esempio, la dipendenza _stringUtil sarebbero risolti tramite il costruttore e la classe potrebbe essere testato senza alcuna dipendenza dalla classe del metodo TruncateToSize effettivo (potrebbe essere facilmente deriso).

Dal mio punto di vista, i primi due esempi si basano su dipendenze statiche (una esplicita, una nascosta), mentre la seconda inverte la dipendenza e fornisce un accoppiamento ridotto e una migliore testabilità.

Così l'uso dei metodi di estensione è in conflitto con i principi DI/IOC? Se sei un abbonato alla metodologia IOC, eviti di usare i metodi di estensione?

+0

Il metodo di estensione non controlla l'input nullo! – canon

+0

@antisanity: AFAIK è logicamente impossibile che il parametro di istanza di un metodo di estensione (* input * in questo caso) sia nullo. –

+3

Sì, Phil ... può essere nullo. – canon

risposta

15

Vedo da dove vieni, tuttavia, se stai cercando di prendere in giro la funzionalità di un metodo di estensione, credo che tu li stia usando in modo errato. I metodi di estensione dovrebbero essere utilizzati per eseguire un'attività che sarebbe semplicemente scomoda sintatticamente senza di essi. Il tuo TruncateToLength è un buon esempio.

Il test di TruncateToLength non implicava il mocking, implicherebbe semplicemente la creazione di alcune stringhe e testava che il metodo restituisse effettivamente il valore corretto.

D'altra parte, se nel proprio livello dati è contenuto del codice contenuto nei metodi di estensione che accede al proprio archivio dati, allora sì, si è verificato un problema e il test sta diventando un problema.

In genere utilizzo solo i metodi di estensione per fornire zucchero sintattico per operazioni piccole e semplici.

18

Penso che vada bene - perché non è come TruncateToSize è un componente realisticamente sostituibile. È un metodo che avrà sempre e solo bisogno di fare una singola cosa.

Non è necessario essere in grado di prendere in giro tutto - solo servizi che interrompono i test delle unità (accesso ai file ecc.) O quelli che si desidera testare in termini di dipendenze reali. Se lo stavi usando per eseguire l'autenticazione o qualcosa del genere, sarebbe una questione molto diversa ... ma eseguendo semplicemente un'operazione di stringa diritta che non ha assolutamente alcuna configurabilità, diverse opzioni di implementazione ecc. - non ha senso vederlo come una dipendenza nel senso normale.

Per dirla in un altro modo: se TruncateToSize fosse un vero membro di String, ci penseresti addirittura di usarlo? Cerchi di deridere anche l'intera aritmetica, introducendo IInt32Adder ecc.? Ovviamente no. Questo è lo stesso, è solo che ti capita di fornire l'implementazione. Unit test the heck out of TruncateToSize e non preoccuparti.

+0

La stessa idea del mio post, concordato – LorenVS

+0

Quindi, non sono d'accordo con la tua risposta: è molto pragmatica. Puoi decisamente prendere IOC (o qualsiasi teoria del design) troppo lontano. Per ragioni: supponendo che tu sia impegnato con IOC, non avresti problemi con il secondo esempio di implementazione (la dipendenza esplicita da una classe statica)? Non ho mai letto un articolo di un sostenitore della IOC che sosteneva che le dipendenze statiche fossero accettabili. –

+0

Cosa accadrebbe se ci fossero due algoritmi per TruncateToSize? Uno che scambia la velocità per la memoria e uno che scambia la memoria per la velocità. Non sarebbe meglio in quel caso se avesse un'interfaccia? E dal momento che non è possibile prevedere la necessità di questi, non dovresti semplicemente renderlo un'interfaccia in primo luogo? –

0

È un dolore perché sono difficili da deridere. Io di solito uso una di queste strategie

  1. Yep, rottami l'estensione sua una valle di lacrime per deridere fuori
  2. usare l'estensione ed appena prova che lo ha fatto la cosa giusta. vale a dire passare i dati nel troncato e controllare che sia troncato
  3. Se non è una cosa banale, e DEVO prenderlo in giro, farò in modo che la mia classe di estensione abbia un setter per il servizio che usa, e lo metta nel test codice.

cioè

static class TruncateExtensions{ 

    public ITruncateService Service {private get;set;} 
    public string TruncateToSize(string s, int size) 
    { 
     return (Service ?? Service = new MyDefaultTranslationServiceImpl()). TruncateToSize(s, size); 
    } 
} 

Questo è un po 'paura, perché qualcuno potrebbe impostare il servizio quando non dovrebbero, ma io sono un po' cavalier a volte, e se era davvero importante, ho potuto fare qualcosa di intelligente con #if TEST flags, o il pattern ServiceLocator per evitare che il setter venga usato in produzione.

0

Metodi di estensione, classi parziali e oggetti dinamici. Mi piacciono molto, comunque devi calpestare attentamente, qui ci sono mostri.

Vorrei dare un'occhiata alle lingue dinamiche e vedere come affrontano questi tipi di problemi giorno per giorno, è davvero illuminante. Soprattutto quando non hanno nulla che impedisca loro di fare cose stupide oltre al buon design e alla disciplina. Tutto è dinamico in fase di esecuzione, l'unica cosa per fermarli è il computer che lancia un grosso errore di runtime. "Duck Typing" è la cosa più pazza che abbia mai visto, un buon codice dipende dal buon design del programma, dal rispetto per gli altri della tua squadra e dalla fiducia che ogni membro, sebbene abbia la capacità di fare alcune cose stravaganti, sceglie di non farlo perché buono il design porta a risultati migliori.

Per quanto riguarda lo scenario di test con oggetti mock/ICO/DI, potresti davvero svolgere un lavoro pesante in un metodo di estensione o semplicemente in alcune semplici cose statiche che funzionano in un modo funzionale? Io tendo ad usarli come faresti in uno stile di programmazione funzionale, l'input entra, i risultati vengono fuori senza magia nel mezzo, solo retta le classi di framework che conosci che i ragazzi della MS hanno progettato e testato: P su cui puoi contare sopra.

Se stai facendo alcune cose di sollevamento pesante usando i metodi di estensione guarderei di nuovo il tuo programma, controlla i tuoi disegni CRC, Modelli di classe, Casi d'uso, DFD, diagrammi di azione o qualsiasi altra cosa ti piaccia usare e capire dove in questo progetto hai pianificato di mettere questa roba in un metodo di estensione invece di una classe appropriata.

Alla fine della giornata, è possibile testare solo il design del sistema e non il codice al di fuori del campo di applicazione.Se intendi utilizzare le classi di estensione, il mio consiglio è di esaminare i modelli di composizione degli oggetti e utilizzare l'ereditarietà solo quando c'è una buona ragione.

Oggetto Composizione vince sempre con me poiché producono codice solido. Puoi collegarli, portarli fuori e fare quello che ti piace con loro. Tenete presente che tutto dipende dall'utilizzo o meno delle interfacce come parte del vostro progetto. Inoltre, se si utilizzano le classi di composizione, l'albero della gerarchia di classi viene convertito in classi discrete e ci sono meno posizioni in cui il metodo di estensione verrà raccolto attraverso le classi ereditate.

Se è necessario utilizzare una classe che agisce su un'altra classe, come nel caso dei metodi di estensione, osservare prima il modello di visitatore e decidere se è un percorso migliore.