Principio della singola responsabilità - lo sto esagerando?

13

Per riferimento - link

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

  • Visualizza le voci della contabilità in formato tabella.
  • Crea una nuova voce mastro utilizzando il pulsante crea.
  • Fare clic su una voce di contabilità generale nella tabella (menzionata nel primo puntatore) e visualizzarne i dettagli nella pagina successiva. In questa pagina puoi annullare la voce di un libro mastro.

(Ci sono un paio di operazioni / convalide in ogni pagina ma per brevità lo limiterò a queste)

Quindi ho deciso di creare tre classi diverse -

  • LedgerLandingPage
  • CreateNewLedgerEntryPage
  • ViewLedgerEntryPage

Queste classi offrono i servizi che potrebbero essere eseguiti in quelle pagine ei test di selenio usano queste classi per portare l'applicazione in uno stato in cui potrei fare certe asserzioni.

Quando lo stavo rivedendo con il mio collega, lui era sopraffatto e mi ha chiesto di fare una sola lezione per tutti. Anche se ritengo che il mio progetto sia molto pulito, dubito che mi stia abusando del principio di responsabilità singola

    
posta Tarun 30.11.2011 - 06:57
fonte

8 risposte

19

Citare @YannisRizos qui :

That's an instinctual approach and it's probably correct, as what's more probable to change is the business logic of managing ledgers. If that happens, it'd be a lot easier to maintain one class than three.

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

LedgerLandingPage

CreateNewLedgerEntryPage

ViewLedgerEntryPage

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

Se hai funzionalità comuni tra queste 3 classi, esse appartengono a una classe base comune, la classe Ledger stessa (presumo tu abbia almeno una, 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 a il libro di Robert Martin "Codice pulito ".

    
risposta data 30.11.2011 - 08:24
fonte
11

Non esiste un'attuazione definitiva del principio di responsabilità unica o di qualsiasi altro principio. Dovresti decidere in base a cosa è più probabile cambiare.

Come @Karpie scrive in una risposta precedente :

You only need one class, and the single responsibility of that class is to manage ledgers.

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

Ma dovrebbe essere esaminato e deciso per ogni caso. Non dovresti davvero preoccuparti delle preoccupazioni con una minuscola possibilità di cambiamento e concentrarti nell'applicare il principio su ciò che è più probabile che cambi. Se la logica di gestione dei registri è un processo multistrato e gli strati sono soggetti a modifiche indipendenti 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 sul tema dell'uso eccessivo dei principi di progettazione, che penso tu possa trovare interessante: Modelli di design: quando usare e quando smettere di fare tutto usando i pattern

    
risposta data 30.11.2011 - 07:45
fonte
4

Esaminiamo queste classi:

  • LedgerLandingPage: il ruolo di questa classe è mostrare un elenco di libri mastri. Man mano che l'applicazione cresce, probabilmente vorrai aggiungere metodi per l'ordinamento, il filtraggio, l'highliting e la fusione trasparente in dati provenienti da altre fonti di dati.

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

  • CreateLedgerEntryPage: se ViewLedgerEntryPage ha capacità di modifica completa, la responsabilità di questa classe può essere soddisfatta modificando una "voce vuota", che può avere o meno senso.

Questi esempi possono sembrare un po 'inverosimili, ma il punto è che da un UX stiamo parlando di caratteristiche qui. Una singola caratteristica è sicuramente una singola e distinta responsabilità. Poiché i requisiti di tale funzionalità possono cambiare e i requisiti di due diverse funzionalità possono cambiare in due direzioni opposte, e quindi si desidera che si sia bloccato con l'SRP dall'inizio.
Al contrario, puoi sempre dare uno schiaffo a una facciata su tre classi, se avere una singola interfaccia è più conveniente per qualsiasi motivo tu possa pensare.

Esiste un uso eccessivo di SRP : se hai bisogno di una dozzina di classi per leggere il contenuto di un file, è abbastanza sicuro presumere che tu stia facendo proprio questo.

Se osservi l'SRP dall'altra parte, in realtà dice che una singola responsabilità dovrebbe essere gestita da una singola classe. Si noti tuttavia che le responsabilità esistono solo all'interno dei livelli di astrazione. Quindi è perfettamente logico che una classe di un livello di astrazione più elevato esegua la propria responsabilità delegando il lavoro al componente di livello inferiore, che è meglio ottenere attraverso inversione di dipendenza .

    
risposta data 30.11.2011 - 21:34
fonte
2

Queste classi hanno solo una ragione per cambiare?

Se non lo conosci (ancora), allora potresti essere entrato nella trappola del design speculativo / dell'ingegneria (troppe classi, modelli di progettazione troppo complessi applicati). Non hai soddisfatto YAGNI . Nelle prime fasi del ciclo di vita del software (alcuni) i requisiti potrebbero non essere chiari. Quindi la direzione del cambiamento non è al momento visibile per te. Se te ne rendi conto, tienilo in una o una quantità minima di classi. Tuttavia ciò comporta una responsabilità: non appena i requisiti e la direzione del cambiamento sono più chiari, è necessario riconsiderare il progetto corrente e apportare un refactoring per soddisfare il motivo del cambiamento.

Se già sai che ci sono tre diverse ragioni per il cambiamento e che si associano a queste tre classi, hai appena applicato la giusta dose di SRP. Anche in questo caso si ha una certa responsabilità: se si sbaglia o se i requisiti futuri cambiano in modo imprevisto, è necessario ripensare il progetto corrente e apportare 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 codice di refactoring

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

Tuttavia c'è sempre il fattore tempo: per molte modifiche non avrai il tempo che ti serve. Il refactoring costa tempo e fatica. Ho incontrato spesso situazioni in cui sapevo che dovevo cambiare il design, ma a causa dei limiti di tempo ho dovuto rimandarlo. Questo accadrà e non può essere evitato. Ho scoperto che è più facile refactoring sotto codice ingegnerizzato rispetto al codice over engineered. YAGNI mi dà una buona linea guida: in caso di dubbio, tieni la quantità di classi e l'applicazione dei modelli di progettazione a minimo.

    
risposta data 01.12.2011 - 10:35
fonte
1

Ci sono tre motivi per invocare SRP come motivo per dividere una classe:

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

Ci sono tre motivi per rifiutarsi di dividere una classe:

  • in modo che le future modifiche ai requisiti non ti inducano a eseguire la stessa modifica in più classi
  • in modo che l'individuazione dei bug non implichi la traccia di lunghi percorsi di riferimento indiretto
  • per resistere alle tecniche di incapsulamento (proprietà, amici, qualsiasi cosa) che espongono informazioni o metodi a tutti quando sono necessari solo da una classe correlata

Quando guardo il tuo caso e immagino cambiamenti, alcuni di essi causeranno cambiamenti a più classi (ad esempio aggiungerai un campo al libro mastro, dovrai modificarli tutti e tre) ma molti non lo faranno - ad esempio aggiungendo l'ordinamento al elenco di libri mastri o modifica delle 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 appare errato nell'elenco dei libri mastri, è perché è stato aggiunto in modo errato o c'è qualcosa di sbagliato nella lista? Se c'è una discrepanza tra il sommario e i dettagli, che è sbagliato?

È anche possibile che il tuo collega ritenga che le modifiche ai requisiti che comportano la necessità di cambiare tutte e tre le classi siano molto più probabili di quelle in cui cambieresti cambiando solo una. Potrebbe essere una conversazione davvero utile.

    
risposta data 12.12.2011 - 19:07
fonte
0

Penso che se ledger fosse una tabella in db, suggerisco di mettere questi task di sinistri in una classe (es. DAO). Se il libro mastro ha più logica e non era una tabella in db, suggerisco di creare più classe per fai queste attività (può essere di due o tre classi) e fornisci una lezione di facciata da fare semplicemente per il cliente.

    
risposta data 01.12.2011 - 06:15
fonte
0

IMHO non dovresti usare affatto le classi. Non ci sono astrazioni di dati qui. I libri mastri sono un tipo di dati concreti. 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 sottrarre e nascondere in una classe nelle routine di visualizzazione e selezione.

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

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

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.

    
risposta data 12.12.2011 - 15:21
fonte
-1

Hai solo bisogno di una classe, e la sola responsabilità di quella classe è di gestire i libri mastri .

    
risposta data 30.11.2011 - 07:36
fonte

Leggi altre domande sui tag