La registrazione accanto a un'implementazione è una violazione di SRP?


19

Quando penso allo sviluppo di software agile e a tutti i principi (SRP, OCP, ...), mi chiedo come trattare la registrazione.

La registrazione accanto a un'implementazione è una violazione di SRP?

Direi che yesl'implementazione dovrebbe essere in grado di funzionare anche senza registrazione. Quindi, come posso implementare la registrazione in modo migliore? Ho controllato alcuni schemi e sono giunto alla conclusione che il modo migliore per non violare i principi in un modo definito dall'utente, ma per usare qualsiasi modello che è noto per violare un principio è quello di utilizzare un motivo decoratore.

Diciamo che abbiamo un sacco di componenti completamente senza violazione di SRP e quindi vogliamo aggiungere la registrazione.

  • componente A
  • il componente B utilizza A

Vogliamo registrare per A, quindi creiamo un altro componente D decorato con A implementando entrambi un'interfaccia I.

  • interfaccia I.
  • componente L (componente di registrazione del sistema)
  • il componente A implementa I
  • il componente D implementa I, decora / usa A, usa L per la registrazione
  • il componente B utilizza un I

Vantaggi: - Posso usare A senza registrazione - Test A significa che non ho bisogno di alcuna beffa di registrazione - I test sono più semplici

Svantaggio: - più componenti e più test

So che questa sembra essere un'altra domanda di discussione aperta, ma in realtà voglio sapere se qualcuno utilizza strategie di registrazione migliori rispetto a un decoratore o una violazione di SRP. Che dire del logger singleton statico che è NullLogger predefinito e se si desidera la registrazione syslog, si cambia l'oggetto di implementazione in fase di esecuzione?


possibile duplicato di modelli
moscerino

L'ho già letto e la risposta non è soddisfacente, scusa.
Aitch


@MarkRogers grazie per aver condiviso l'articolo interessante. Lo zio Bob dice in "Clean Code", che un bel componente SRP ha a che fare con altri componenti sullo stesso livello di astrazione. Per me questa spiegazione è più facile da capire poiché il contesto può anche essere troppo grande. Ma non posso rispondere alla domanda, perché qual è il contesto o il livello di astrazione di un logger?
Aitch

3
"non è una risposta per me" o "la risposta non è soddisfacente" è un po 'sprezzante. Potresti meditare su ciò che è specificamente insoddisfacente (quale requisito hai che non è stato soddisfatto da quella risposta? Che cosa è specificamente unico per la tua domanda?), Quindi modifica la tua domanda per assicurarti che questo requisito / aspetto unico sia spiegato chiaramente. Lo scopo è di farti modificare la tua domanda per migliorarla per renderla più chiara e più focalizzata, non per chiedere un bollettino che asserisce che la tua domanda è diversa / non dovrebbe essere chiusa senza giustificazione perché. (Puoi anche commentare l'altra risposta.)
DW

Risposte:


-1

Sì, è una violazione di SRP in quanto la registrazione è una preoccupazione trasversale.

Il modo corretto è quello di delegare la registrazione a una classe logger (Intercettazione) il cui unico scopo è quello di effettuare la registrazione - rispettando l'SRP.

Vedi questo link per un buon esempio: https://msdn.microsoft.com/en-us/library/dn178467%28v=pandp.30%29.aspx

Ecco un breve esempio :

public interface ITenantStore
{
    Tenant GetTenant(string tenant);
    void SaveTenant(Tenant tenant);
}

public class TenantStore : ITenantStore
{
    public Tenant GetTenant(string tenant)
    {....}

    public void SaveTenant(Tenant tenant)
    {....}
} 

public class TenantStoreLogger : ITenantStore
{
    private readonly ILogger _logger; //dep inj
    private readonly ITenantStore _tenantStore;

    public TenantStoreLogger(ITenantStore tenantStore)
    {
        _tenantStore = tenantStore;
    }

    public Tenant GetTenant(string tenant)
    {
        _logger.Log("reading tenant " + tenant.id);
        return _tenantStore.GetTenant(tenant);
    }

    public void SaveTenant(Tenant tenant)
    {
        _tenantStore.SaveTenant(tenant);
        _logger.Log("saving tenant " + tenant.id);
    }
}

I vantaggi includono

  • Puoi testarlo senza registrarti: test dell'unità reale
  • puoi facilmente attivare / disattivare la registrazione, anche in fase di esecuzione
  • è possibile sostituire la registrazione con altre forme di registrazione, senza dover modificare il file TenantStore.

Grazie per il bel link. La figura 1 in quella pagina è in realtà quella che definirei la mia soluzione preferita. L'elenco delle preoccupazioni trasversali (registrazione, memorizzazione nella cache, ecc.) E un modello di decorazione sono la soluzione più generica e sono felice di non sbagliarmi completamente con i miei pensieri, anche se la comunità più grande vorrebbe abbandonare quell'astrazione e la registrazione in linea .
Aitch

2
Non ti vedo assegnare la variabile _logger da nessuna parte. Stavi pensando di usare l'iniezione del costruttore e hai dimenticato? In tal caso, probabilmente riceveresti un avviso del compilatore.
user2023861

27
Invece che TenantStore sia DIPed con un Logger generico, che richiede classi N + 1 (quando aggiungi un LandlordStore, un FooStore, un BarStore, ecc.) Hai un TenantStoreLogger che è DIPed con un TenantStore, un FooStoreLogger essendo DIPed con un FooStore , ecc ... che richiedono classi 2N. Per quanto ne so, a beneficio zero. Quando si desidera eseguire test di unità senza registrazione, è necessario riavviare le classi N, anziché semplicemente configurare un NullLogger. IMO, questo è un approccio molto scarso.
user949300

6
In questo modo per ogni singola classe che necessita di registrazione aumenta drasticamente la complessità della base di codice (a meno che così poche classi abbiano la registrazione che non la chiameresti più nemmeno una preoccupazione trasversale). In definitiva, rende il codice meno gestibile semplicemente a causa dell'elevato numero di interfacce da mantenere, il che va contro tutto ciò per cui è stato creato il principio di responsabilità singola.
jpmc26,

9
Downvoted. Hai rimosso il problema relativo alla registrazione dalla classe Tenant, ma ora TenantStoreLoggercambierai ogni volta che TenantStorecambia. Non stai separando le preoccupazioni più che nella soluzione iniziale.
Laurent LA RIZZA,

61

Direi che stai prendendo SRP troppo sul serio. Se il tuo codice è abbastanza ordinato che la registrazione è l'unica "violazione" di SRP, allora stai facendo meglio del 99% di tutti gli altri programmatori e dovresti dare una pacca sulla spalla.

Lo scopo di SRP è di evitare il codice spaghetti orribile in cui il codice che fa cose diverse è tutto confuso. Mischiare la registrazione con il codice funzionale non suona alcun campanello d'allarme per me.


19
@Aitch: Le tue scelte sono di collegare in modo sicuro la registrazione nella tua classe, passare un handle a un logger o non registrare nulla. Se hai intenzione di essere estremamente severo riguardo all'SRP a spese di tutto il resto, ti consiglio di non registrare nulla, mai. Qualunque cosa tu abbia bisogno di sapere su cosa sta facendo il tuo software può essere eliminata con un debugger. La P in SRP sta per "principio", non "legge fisica della natura che non deve mai essere infranta".
Blrfl

3
@Aitch: Dovresti essere in grado di rintracciare la registrazione nella tua classe in base a qualche requisito, altrimenti stai violando YAGNI. Se la registrazione è sul tavolo, si fornisce un handle di logger valido proprio come si farebbe per qualsiasi altra cosa di cui la classe ha bisogno, preferibilmente una di una classe che ha già superato i test. Che si tratti di uno che produce voci di log effettive o le scarica nel bucket bit è la preoccupazione di ciò che ha istanziato l'istanza della tua classe; alla classe stessa non dovrebbe interessare.
Blrfl,

3
@Aitch Per rispondere alla tua domanda sul test unitario:, è PRECISAMENTE Do you mock the logger?quello che fai. Dovresti avere ILoggerun'interfaccia che definisce COSA fa il logger. Il codice in prova viene iniettato con ILoggerquello specificato. Per i test, hai class TestLogger : ILogger. La cosa grandiosa è che TestLoggerpuoi esporre cose come l'ultima stringa o l'errore registrato. I test possono verificare che il codice in prova stia eseguendo correttamente la registrazione. Ad esempio, potrebbe essere un test UserSignInTimeGetsLogged(), in cui il test verifica TestLoggerla registrazione.
CurtisHx,

5
Il 99% sembra un po 'basso. Probabilmente stai meglio del 100% di tutti i programmatori.
Paul Draper,

2
+1 per sanità mentale. Abbiamo bisogno di più di questo tipo di pensiero: meno attenzione a parole e principi astratti e maggiore attenzione ad avere una base di codice mantenibile .
jpmc26,

15

No, non è una violazione di SRP.

I messaggi inviati al registro dovrebbero cambiare per gli stessi motivi del codice circostante.

Ciò che è una violazione di SRP è l'utilizzo di una libreria specifica per accedere direttamente al codice. Se si decide di modificare il modo di registrazione, SRP afferma che non dovrebbe influire sul codice aziendale.

Una sorta di abstract Loggerdovrebbe essere accessibile al codice di implementazione e l'unica cosa che l'implementazione dovrebbe dire è "Invia questo messaggio al registro", senza preoccuparsi del modo in cui è stato fatto. Decidere il modo esatto di registrazione (anche il timestamp) non è responsabilità dell'implementazione.

L'implementazione quindi non dovrebbe inoltre sapere se il logger a cui sta inviando messaggi è a NullLogger.

Detto ciò.

Non spazzerei via la registrazione perché una preoccupazione trasversale è troppo veloce . L'emissione dei registri per tracciare eventi specifici che si verificano nel codice di implementazione appartiene al codice di implementazione.

Ciò che è una preoccupazione trasversale, OTOH, è la traccia dell'esecuzione : la registrazione entra ed esce in ogni metodo. AOP è nella posizione migliore per farlo.


Supponiamo che il messaggio del logger sia "login user xyz", che viene inviato a un logger che antepone un timestamp ecc. Sai cosa significa "login" per l'implementazione? Sta iniziando una sessione con un cookie o altri meccanismi? Penso che ci siano molti modi diversi per implementare un login, quindi cambiare l'implementazione non ha logicamente nulla a che fare con il fatto che un utente accede. Questo è un altro ottimo esempio di decorazione di componenti diversi (diciamo OAuthLogin, SessionLogin, BasicAuthorizationLogin) che fanno la stessa cosa come Logininterfaccia decorata con lo stesso logger.
Aitch

Dipende dal significato del messaggio "login user xyz". Se indica che un accesso ha esito positivo, l'invio del messaggio al registro appartiene al caso d'uso dell'accesso. Il modo specifico di rappresentare le informazioni di accesso come stringa (OAuth, Session, LDAP, NTLM, impronta digitale, ruota del criceto) appartiene alla classe specifica che rappresenta le credenziali o la strategia di accesso. Non è necessario rimuoverlo. Questo caso specifico non è una preoccupazione trasversale. È specifico per il caso d'uso dell'accesso.
Laurent LA RIZZA,

7

Poiché la registrazione è spesso considerata una preoccupazione trasversale, suggerirei di utilizzare AOP per separare la registrazione dall'implementazione.

A seconda della lingua che utilizzeresti per utilizzare un intercettore o un framework AOP (ad es. AspectJ in Java).

La domanda è se questo vale davvero la seccatura. Nota che questa separazione aumenterà la complessità del tuo progetto fornendo al contempo pochissimi benefici.


2
La maggior parte del codice AOP che ho visto riguardava la registrazione di ogni passaggio di entrata e uscita di ogni metodo. Voglio solo registrare alcune parti della logica aziendale. Quindi forse è possibile registrare solo metodi annotati, ma AOP esiste solo nei linguaggi di scripting e negli ambienti di macchine virtuali, giusto? Ad esempio in C ++ è impossibile. Ammetto che non sono molto soddisfatto degli approcci AOP, ma forse non esiste una soluzione più pulita.
Aitch

1
@Aitch. "C ++ è impossibile." : Se vai su Google per "aop c ++" troverai articoli a riguardo. "... il codice AOP che ho visto riguardava la registrazione di ogni passaggio di entrata e uscita di ogni metodo. Voglio solo registrare alcune parti della logica aziendale." Aop consente di definire modelli per trovare i metodi da modificare. cioè tutti i metodi dello spazio dei nomi "my.busininess. *"
k3b

1
La registrazione spesso NON è un problema trasversale, soprattutto quando si desidera che il registro contenga informazioni interessanti, vale a dire più informazioni di quelle contenute in una traccia dello stack delle eccezioni.
Laurent LA RIZZA,

5

Questo suona bene. Stai descrivendo un decoratore di registrazione abbastanza standard. Hai:

componente L (componente di registrazione del sistema)

Ciò ha una responsabilità: la registrazione delle informazioni che gli vengono passate.

il componente A implementa I

Ciò ha una responsabilità: fornire un'implementazione dell'interfaccia I (supponendo che sia correttamente conforme a SRP, cioè).

Questa è la parte cruciale:

il componente D implementa I, decora / usa A, usa L per la registrazione

Se detto in questo modo, sembra complesso, ma guardalo in questo modo: il componente D fa una cosa: riunire A e L.

  • Il componente D non registra; lo delega a L
  • Il componente D non implementa I stesso; lo delega ad A

L' unica responsabilità che ha il componente D è assicurarsi che L sia avvisato quando si usa A. Le implementazioni di A e L sono entrambe altrove. Questo è completamente conforme a SRP, oltre ad essere un chiaro esempio di OCP e un uso piuttosto comune dei decoratori.

Un avvertimento importante: quando D usa il componente di registrazione L, dovrebbe farlo in un modo che ti consenta di cambiare il modo in cui stai registrando. Il modo più semplice per farlo è avere un'interfaccia IL implementata da L. Quindi:

  • Il componente D utilizza un IL per accedere; viene fornita un'istanza di L.
  • Il componente D utilizza un I per fornire funzionalità; viene fornita un'istanza di A.
  • Il componente B utilizza una I; viene fornita un'istanza di D.

In questo modo, nulla dipende direttamente da qualsiasi altra cosa, facilitando la sostituzione. Ciò consente di adattarsi facilmente ai cambiamenti e di prendere in giro parti del sistema in modo da poter eseguire il test dell'unità.


In realtà conosco solo C # che ha il supporto della delega nativa. Ecco perché ho scritto D implements I. La ringrazio per la risposta.
Aitch

1

Ovviamente si tratta di una violazione dell'SRP poiché hai una preoccupazione trasversale. Tuttavia, è possibile creare una classe responsabile della composizione della registrazione con l'esecuzione di qualsiasi azione.

esempio:

class Logger {
   ActuallLogger logger;
   public Action ComposeLog(string msg, Action action) {
      return () => {
          logger.debug(msg);
          action();
      };
   }
}

2
Downvoted. La registrazione è davvero una preoccupazione trasversale. Lo stesso vale per le chiamate ai metodi di sequenziamento nel codice. Non è una ragione sufficiente per rivendicare una violazione di SRP. La registrazione dell'occorrenza di un evento specifico nella propria applicazione NON è un problema trasversale. Il modo in cui questi messaggi vengono portati a qualsiasi utente interessato è effettivamente una preoccupazione separata e descriverlo nel codice di implementazione È una violazione di SRP.
Laurent LA RIZZA,

le "chiamate al metodo di sequenziamento" o la composizione funzionale non sono una preoccupazione trasversale ma piuttosto un dettaglio di implementazione. La responsabilità della funzione che ho creato è comporre un'istruzione di registro, con un'azione. Non ho bisogno di usare la parola "e" per descrivere cosa fa questa funzione.
Paul Nikonowicz,

Non è un dettaglio di implementazione. Ha un profondo effetto sulla forma del tuo codice.
Laurent LA RIZZA,

Penso che sto guardando SRP dal punto di vista di "COSA fa questa funzione", mentre come stai guardando SRP dal punto di vista di "COME funziona questa funzione".
Paul Nikonowicz,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.