Principio della sola responsabilità - lo sto abusando?


13

Per riferimento - http://it.wikipedia.org/wiki/Single_responsibility_principle

Ho uno scenario di prova in cui in un modulo di applicazione è responsabile della creazione di voci di contabilità generale. Esistono tre attività di base che potrebbero essere svolte:

  • Visualizza le voci di contabilità esistente in formato tabella.
  • Crea una nuova voce di libro mastro usando il pulsante Crea.
  • Fare clic su una voce del libro mastro nella tabella (menzionata nel primo puntatore) e visualizzarne i dettagli nella pagina successiva. È possibile annullare una voce di libro mastro in questa pagina.

(Ci sono altre due operazioni / validazioni in ogni pagina, ma per brevità la limiterò a queste)

Quindi ho deciso di creare tre diverse classi:

  • LedgerLandingPage
  • CreateNewLedgerEntryPage
  • ViewLedgerEntryPage

Queste classi offrono i servizi che potrebbero essere svolti in quelle pagine e i test del selenio usano queste classi per portare l'applicazione in uno stato in cui potrei fare certe affermazioni.

Quando lo stavo rivedendo con il mio collega, lui fu sopraffatto e mi chiese di fare una sola lezione per tutti. Anche se ritengo che il mio design sia molto pulito, sono dubbioso se sto abusando del principio della responsabilità singola


2
IMHO non ha senso cercare di rispondere alla tua domanda in generale, senza sapere due cose: quanto sono grandi queste classi (per numero di metodi e LOC)? E quanto più grandi / complessi dovrebbero crescere nel prossimo futuro?
Péter Török,

2
10 metodi ciascuno è IMHO un'indicazione chiara di non mettere il codice in una classe con 30 metodi.
Doc Brown,

6
Questo è un territorio limite: una classe di 100 LOC e 10 metodi non è troppo piccola e una di 300 LOC non è troppo grande. Tuttavia, 30 metodi in una singola classe suonano troppo per me. Nel complesso, tendo a concordare con @Doc in quanto è meno rischioso avere molte classi minuscole rispetto a una singola classe sovrappeso. Tenendo conto in particolare che le lezioni tendono naturalmente ad aumentare di peso nel tempo ...
Péter Török,

1
@ PéterTörök - Sono d'accordo a meno che il codice non possa essere refactored in una classe che può essere utilizzata in modo intuitivo che riutilizza il codice invece di duplicare funzionalità come mi aspetto che l'OP abbia.
SoylentGray,

1
Forse applicare MVC chiarirebbe tutto: tre viste, un modello (Ledger) e probabilmente tre controller.
Kevin Cline il

Risposte:


19

Citando @YannisRizos qui :

Questo è un approccio istintivo ed è probabilmente corretto, poiché ciò che è più probabile che cambi è la logica aziendale della gestione dei registri. Se ciò accade, sarebbe molto più facile mantenere una classe che tre.

Non sono d'accordo, almeno ora, dopo circa 30 anni di esperienza di programmazione. Classi più piccole di IMHO sono meglio mantenere in quasi tutti i casi a cui riesco a pensare in casi come questo. Più funzioni inserisci in una classe, meno struttura avrai e la qualità del tuo codice diminuisce ogni giorno un po '. Quando ho capito bene Tarun, ognuna di queste 3 operazioni

LedgerLandingPage

CreateNewLedgerEntryPage

ViewLedgerEntryPage

è un caso d'uso, ciascuna di queste classi è costituita da più di una funzione per eseguire l'attività correlata e ognuna può essere sviluppata e testata separatamente. Quindi la creazione di classi per ognuna di esse raggruppa cose che appartengono insieme, rendendo molto più facile identificare la parte del codice da modificare se qualcosa deve cambiare.

Se hai funzionalità comuni tra quelle 3 classi, esse appartengono a una classe base comune, la Ledgerclasse stessa (suppongo che tu ne abbia una, almeno, per le operazioni CRUD) o in una classe helper separata.

Se hai bisogno di più argomenti per creare molte classi più piccole, ti consiglio di dare un'occhiata al libro "Clean Code" di Robert Martin .


questa è l'intera ragione per cui ho ideato tre classi diff invece di spingere tutto in una classe. Dal momento che non esiste alcuna funzionalità comune tra queste, non ho alcuna classe comune. Grazie per il link.
Tarun,

2
"Le classi più piccole sono meglio da mantenere in quasi tutti i casi che mi vengono in mente." Non credo che anche Clean Code andrebbe così lontano. Sosterresti davvero che, in un modello MVC, dovrebbe esserci un controller per pagina (o caso d'uso, come dici tu), per esempio? In Refactoring, Fowler ha due odori di codice insieme: uno che si riferisce al fatto di avere molte ragioni per cambiare una classe (SRP) e uno che si riferisce al dover modificare molte classi per una modifica.
pdr,

@pdr, l'OP afferma che non esiste una funzionalità comune tra queste classi, quindi (per me) è difficile immaginare una situazione in cui è necessario toccarne più di una per apportare una singola modifica.
Péter Török,

@pdr: se hai troppe classi da modificare per una modifica, è per lo più un segno che non hai convertito la funzionalità comune in un posto separato. Ma nell'esempio sopra, ciò porterebbe probabilmente a una quarta classe, e non solo a una.
Doc Brown,

2
@pdr: A proposito, hai effettivamente letto "Clean Code"? Bob Martin si spinge molto oltre nel suddividere il codice in unità più piccole.
Doc Brown,

11

Non esiste un'implementazione definitiva del principio di responsabilità singola o di qualsiasi altro principio. Dovresti decidere in base a ciò che è più probabile che cambi.

Come scrive @Karpie in una risposta precedente :

Hai solo bisogno di una classe e l'unica responsabilità di quella classe è gestire i registri.

Questo è un approccio istintivo ed è probabilmente corretto, poiché ciò che è più probabile che cambi è la logica aziendale della gestione dei registri. Se ciò accade, sarebbe molto più facile mantenere una classe che tre.

Ma questo dovrebbe essere esaminato e deciso per caso. Non dovresti davvero preoccuparti delle preoccupazioni con una minuscola possibilità di cambiamento e concentrarti sull'applicazione del principio su ciò che è più probabile che cambi. Se la logica di gestione dei registri è un processo multistrato e i livelli sono inclini a cambiare in modo indipendente o sono preoccupazioni che dovrebbero essere separate in linea di principio (logica e presentazione), è necessario utilizzare classi separate. È un gioco di probabilità.

Una domanda simile sull'argomento di abusare dei principi di progettazione, che penso che potresti trovare interessante: Modelli di design: quando usare e quando smettere di fare tutto usando i modelli


1
SRP, a mio avviso, non è in realtà un modello di progettazione.
Doc Brown,

@DocBrown Naturalmente non è un modello di progettazione, ma la discussione sulla domanda è estremamente pertinente ... Buona cattura, ho aggiornato la risposta
yannis,

4

Esaminiamo queste classi:

  • LedgerLandingPage: il ruolo di questa classe è mostrare un elenco di libri mastro. Man mano che l'applicazione cresce, probabilmente vorrai aggiungere metodi per ordinare, filtrare, evidenziare e fondere in modo trasparente i dati provenienti da altre origini dati.

  • ViewLedgerEntryPage: questa pagina mostra in dettaglio il libro mastro. Sembra piuttosto semplice. Finché i dati sono diretti. Puoi annullare il libro mastro qui. Potresti anche volerlo correggere. Oppure commentalo o allega un file, una ricevuta o un numero di prenotazione esterno o altro. E una volta consentita la modifica, vuoi sicuramente mostrare una cronologia.

  • CreateLedgerEntryPage: se la ViewLedgerEntryPagefunzionalità di modifica è completa, è possibile adempiere alla responsabilità di questa classe modificando una "voce vuota", che può avere o meno senso.

Questi esempi possono sembrare un po 'inverosimili, ma il punto è che da una UX stiamo parlando di funzionalità qui. Una singola caratteristica è sicuramente una singola, distinta responsabilità. Perché i requisiti di quella funzionalità possono cambiare e i requisiti di due diverse funzionalità possono cambiare in due direzioni opposte, e quindi vorresti essere rimasto bloccato con l'SRP dall'inizio.
Ma al contrario, puoi sempre schiaffeggiare una facciata su tre classi, se avere un'unica interfaccia è più conveniente per qualsiasi motivo ti venga in mente.


Esiste un uso eccessivo di SRP : se hai bisogno di una dozzina di classi per leggere il contenuto di un file, è abbastanza sicuro supporre che lo stai facendo.

Se si guarda all'SRP dall'altra parte, in realtà si dice che una singola responsabilità dovrebbe essere curata da una singola classe. Si noti tuttavia che le responsabilità esistono solo all'interno dei livelli di astrazione. Quindi ha perfettamente senso che una classe di un livello di astrazione superiore assolva la propria responsabilità delegando il lavoro a componenti di livello inferiore, che è meglio ottenere attraverso l' inversione di dipendenza .


Suppongo che non avrei potuto descrivere la responsabilità di queste classi meglio di te.
Tarun

2

Queste classi hanno solo un motivo per cambiare?

Se non lo sapete (ancora), allora potreste essere entrati nella progettazione speculativa / sopra la trappola dell'ingegneria (troppe classi, applicati schemi di progettazione troppo complessi). Non hai soddisfatto YAGNI . Nelle prime fasi del ciclo di vita del software (alcuni) i requisiti potrebbero non essere chiari. Pertanto la direzione del cambiamento non è attualmente visibile per te. Se te ne rendi conto, tienilo in una o in un numero minimo di classi. Tuttavia, ciò comporta una responsabilità: non appena i requisiti e la direzione del cambiamento sono più chiari, è necessario ripensare il progetto attuale ed effettuare un refactoring per soddisfare il motivo del cambiamento.

Se sai già che ci sono tre diversi motivi per cambiare e che si associano a quelle tre classi, allora hai appena applicato la giusta dose di SRP. Ancora una volta ciò comporta una certa responsabilità: se si sbagliano o i requisiti futuri cambiano inaspettatamente, è necessario ripensare il progetto attuale ed effettuare un refactoring per soddisfare il motivo del cambiamento.

L'intero punto è:

  • Tieni d'occhio i driver per il cambiamento.
  • Scegli un design flessibile, che può essere refactored.
  • Preparati al refactoring del codice.

Se hai questa mentalità, modellerai il codice senza molta paura del design speculativo / dell'ingegneria.

Tuttavia c'è sempre il fattore tempo: per molte modifiche non avrai il tempo di cui hai bisogno. Il refactoring costa tempo e fatica. Ho spesso incontrato situazioni in cui sapevo che dovevo cambiare il design, ma a causa di vincoli di tempo ho dovuto rimandarlo. Questo accadrà e non può essere evitato. Ho scoperto che è più semplice eseguire il refactoring in base al codice ingegnerizzato rispetto al codice ingegnerizzato. YAGNI mi offre una buona linea guida: in caso di dubbi, limitare al minimo il numero di classi e l'applicazione dei modelli di progettazione.


1

Esistono tre motivi per invocare SRP come motivo per suddividere una classe:

  • in modo che le future modifiche ai requisiti possano lasciare invariate alcune classi
  • in modo che un bug possa essere isolato più rapidamente
  • per rendere la classe più piccola

Esistono tre motivi per rifiutare di dividere una classe:

  • in modo che le future modifiche ai requisiti non ti consentano di apportare la stessa modifica in più classi
  • in modo che la ricerca di bug non comporti il ​​tracciamento di lunghi percorsi di indiretta
  • resistere alle tecniche di rottura dell'incapsulamento (proprietà, amici, qualunque cosa) che espongono informazioni o metodi a tutti quando sono necessari solo per una classe correlata

Quando guardo il tuo caso e immagino cambiamenti, alcuni di essi causeranno modifiche a più classi (ad esempio, aggiungi un campo al libro mastro, dovrai cambiarli tutti e tre) ma molti non lo faranno, ad esempio aggiungendo l'ordinamento all'elenco dei libri mastro o modificando le regole di convalida quando si aggiunge una nuova voce di contabilità generale. La reazione del tuo collega è probabilmente perché è difficile sapere dove cercare un bug. Se qualcosa sembra non andare nell'elenco dei registri, è perché è stato aggiunto in modo errato o c'è qualcosa che non va nell'elenco? Se c'è una discrepanza tra il sommario e i dettagli, che è sbagliato?

È anche possibile che il tuo collega pensi che i cambiamenti nei requisiti che significano che dovrai cambiare tutte e tre le classi sono molto più probabili di quei cambiamenti in cui ti toglieresti di cambiarne solo una. Potrebbe essere una conversazione davvero utile da avere.


bello vedere una prospettiva diversa
Tarun

0

Penso che se il libro mastro fosse una tabella in db, suggerisco di mettere queste attività thress in una classe (ad esempio DAO). Se il libro mastro ha più logica e non era una tabella in db, suggerisco che dovremmo creare più classe per fare queste attività (può essere di classe due o thress) e fornire una classe di facciata da fare semplicemente per il cliente.


bene, il libro mastro è una funzionalità dell'interfaccia utente e ci sono molte operazioni correlate che potrebbero essere eseguite da pagine diverse.
Tarun,

0

IMHO non dovresti usare affatto le classi. Non ci sono astrazioni di dati qui. I libri mastro sono un tipo di dati concreto. I metodi per manipolarli e visualizzarli sono funzioni e procedure. Ci saranno sicuramente molte funzioni comunemente usate da condividere come la formattazione di una data. C'è poco spazio per i dati da estrarre e nascondere in una classe nelle routine di visualizzazione e selezione.

C'è qualche caso per nascondere lo stato in una classe per la modifica del libro mastro.

Che tu stia abusando o meno dell'SRP, stai abusando di qualcosa di più fondamentale: stai abusando dell'astrazione. È un problema tipico, generato dalla nozione mitica OO è un paradigma di sviluppo sano.

La programmazione è concreta al 90%: l'uso dell'astrazione dei dati dovrebbe essere raro e attentamente progettato. L'astrazione funzionale, d'altra parte, dovrebbe essere più comune, poiché i dettagli del linguaggio e la scelta degli algoritmi devono essere separati dalla semantica dell'operazione.


-1

Hai solo bisogno di una classe e l'unica responsabilità di quella classe è gestire i registri .


2
Per me una lezione di "... manager" di solito mostra che non potresti essere disturbato a scomporlo ulteriormente. Cosa gestisce la classe? La presentazione, la persistenza?
sebastiangeiger,

-1. Mettere 3 o più casi d'uso in 1 classe è esattamente ciò che l'SRP sta cercando di evitare - e per buoni motivi.
Doc Brown,

@sebastiangeiger Quindi cosa stanno facendo le tre classi del PO? Presentazione o persistenza?
sevenseacat,

@Karpie Spero sinceramente che la presentazione. Certo, l'esempio non è proprio buono, ma mi aspetto che "... Page" in un sito Web stia facendo qualcosa di simile a una vista.
sebastiangeiger,

2
In effetti, hai solo bisogno di una classe per l'intera applicazione, con la sola responsabilità di rendere felici gli utenti ;-)
Péter Török
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.