Può / dovrebbe il Principio di Responsabilità Unica essere applicato al nuovo codice?

20

Il principio è definito come moduli che hanno un motivo per cambiare . La mia domanda è, sicuramente questi motivi per cambiare non sono noti fino a quando il codice inizia effettivamente a cambiare ?? Praticamente ogni pezzo di codice ha numerosi motivi per cui potrebbe probabilmente cambiare, ma sicuramente tentare di anticipare tutti questi e progettare il codice con questo in mente finirebbe con codice molto povero. Non è un'idea migliore iniziare davvero ad applicare SRP solo quando vengono richieste le richieste di modifica del codice? Più specificamente, quando un pezzo di codice è cambiato più di una volta per più di una ragione, dimostrando così che ha più di una ragione per cambiare. Sembra molto anti-Agile cercare di indovinare le ragioni del cambiamento.

Un esempio potrebbe essere un pezzo di codice che stampa un documento. Una richiesta arriva per cambiarla per stamparla in PDF e poi viene fatta una seconda richiesta per cambiarla per applicare una diversa formattazione al documento. A questo punto hai la prova di più di un singolo motivo per cambiare (e violazione di SRP) e dovresti fare il refactoring appropriato.

    
posta SeeNoWeevil 03.05.2013 - 11:11
fonte

10 risposte

27

Naturalmente, il principio YAGNI ti dirà di applicare SRP non prima che tu ne abbia realmente bisogno. Ma la domanda che dovresti porci è: devo applicare prima SRP e solo quando devo effettivamente cambiare il mio codice?

Secondo la mia esperienza, l'applicazione di SRP ti dà un vantaggio molto prima: quando devi trovare dove e come applicare una modifica specifica nel tuo codice. Per questo compito, è necessario leggere e comprendere le funzioni e le classi esistenti. Questo diventa molto più facile quando tutte le tue funzioni e le tue classi hanno una responsabilità specifica. Quindi IMHO dovresti applicare SRP ogni volta che rende il tuo codice più facile da leggere, ogni volta che rende le tue funzioni più piccole e più descrittive. Quindi la risposta è , ha senso applicare SRP anche per il nuovo codice.

Ad esempio, quando il tuo codice di stampa legge un documento, formatta il documento e stampa il risultato su un dispositivo specifico, queste sono 3 chiare responsabilità separabili. Quindi fai almeno 3 funzioni da loro, dai loro nomi corrispondenti. Ad esempio:

 void RunPrintWorkflow()
 {
     var document = ReadDocument();
     var formattedDocument = FormatDocument(document);
     PrintDocumentToScreen(formattedDocument);
 }

Ora, quando ottieni un nuovo requisito per cambiare la formattazione del documento o un altro da stampare in PDF, sai esattamente a quale di queste funzioni o posizioni nel codice devi applicare le modifiche, e anche più importante, dove no.

Quindi, ogni volta che vieni a una funzione che non capisci perché la funzione fa "troppo", e non sei sicuro se e dove applicare una modifica, quindi prendi in considerazione il refactoring funzione in funzioni separate, più piccole. Non aspettare finché non devi cambiare qualcosa. Il codice è più spesso letto di 10 volte rispetto a quello modificato e le funzioni più piccole sono molto più facili da leggere. Secondo la mia esperienza, quando una funzione ha una certa complessità, puoi sempre dividere la funzione in responsabilità diverse, indipendentemente dal sapere quali cambiamenti arriveranno in futuro. In genere, Bob Martin fa un passo avanti, vedi il link che ho fornito nei miei commenti qui sotto.

MODIFICA: al tuo commento: la responsabilità principale della funzione esterna nell'esempio sopra non è quella di stampare su un dispositivo specifico o di formattare il documento: è integrare il flusso di lavoro di stampa . Pertanto, a livello di astrazione della funzione esterna, un nuovo requisito come "i documenti non dovrebbero più essere formattati" o "il documento deve essere inviato anziché stampato" è solo "lo stesso motivo" - vale a dire "il flusso di stampa è cambiato". Se parliamo di cose del genere, è importante attenersi al livello di astrazione giusto .

    
risposta data 03.05.2013 - 11:40
fonte
7

Penso che tu abbia frainteso lo SRP.

L'unico motivo del cambiamento NON riguarda la modifica del codice, ma quello che fa il tuo codice.

    
risposta data 03.05.2013 - 11:59
fonte
3

Penso che la definizione di SRP come "avere una ragione per cambiare" sia fuorviante proprio per questo motivo. Prendilo esattamente al valore nominale: il Principio di Responsabilità Unico afferma che una classe o una funzione dovrebbero avere esattamente una sola responsabilità. Avere solo una ragione per cambiare è un effetto collaterale del solo fare una cosa per cominciare. Non c'è motivo per cui non si possa almeno fare uno sforzo verso una singola responsabilità nel proprio codice senza sapere nulla su come potrebbe cambiare in futuro.

Uno dei migliori indizi per questo genere di cose è quando si scelgono nomi di classi o di funzioni. Se non è immediatamente evidente quale sia il nome della classe, o il nome è particolarmente lungo / complesso, o il nome usa termini generici come "manager" o "utility", probabilmente sta violando SRP. Allo stesso modo, quando si documenta l'API, dovrebbe diventare rapidamente evidente se si sta violando SRP in base a quale funzionalità si sta descrivendo.

Ci sono naturalmente delle sfumature all'SRP che non puoi sapere se non più tardi nel progetto - quella che sembrava una singola responsabilità risultò essere due o tre. Questi sono casi in cui dovrai refactoring per implementare SRP. Ma ciò non significa che SRP debba essere ignorato fino a quando non si ottiene una richiesta di modifica; che sconfigge lo scopo di SRP!

Per parlare direttamente con il tuo esempio, considera la possibilità di documentare il tuo metodo di stampa. Se dovessi dire "questo metodo formatta i dati per la stampa e li invia alla stampante", questo e è ciò che ti dà: non è una singola responsabilità, ci sono due responsabilità: la formattazione e l'invio al stampante. Se riconosci questo e li dividi in due funzioni / classi, poi quando arrivano le richieste di modifica, avresti già una sola ragione per ogni sezione da modificare.

    
risposta data 06.05.2013 - 22:01
fonte
3

An example would be a piece of code which prints a document. A request comes in to change it to print to PDF and then a second request is made to change it to apply some different formatting to the document. At this point you have proof of more than a single reason to change (and violation of SRP) and should make the appropriate refactoring.

Ho così tante volte sparato ai piedi spendendo troppo tempo ad adattare il codice per adattarsi a quei cambiamenti. Invece di stampare semplicemente il dannato PDF.

Refactor per ridurre il codice

Il modello monouso può creare un aumento di codice. Dove i pacchetti sono inquinati da classi specifiche piccole che creano una pila di dati inutili che non ha senso individualmente. Devi aprire dozzine di file sorgente solo per capire come arriva alla parte di stampa. Oltre a ciò ci possono essere centinaia se non migliaia di righe di codice che sono in atto solo per eseguire 10 righe di codice che esegue la stampa effettiva.

Crea un bullseye

Il modello monouso intendeva ridurre il codice sorgente e migliorare il riutilizzo del codice. Doveva creare specializzazione e implementazioni specifiche. Una sorta di bullseye nel codice sorgente per te a go to specific tasks . Quando c'era un problema con la stampa, sapevi esattamente dove andare per risolverlo.

L'uso singolo non significa fratturazione ambigua

Sì, hai un codice che stampa già un documento. Sì, ora devi modificare il codice per stampare anche i PDF. Sì, ora devi modificare la formattazione del documento.

Sei sicuro che usage sia cambiato in modo significativo?

Se il refactoring fa sì che sezioni del codice sorgente diventino eccessivamente generalizzate. Al punto che l'intento originale di printing stuff non è più esplicito, hai creato una frattura ambigua nel codice sorgente.

Will the new guy be able to figure this out quickly?

Conserva sempre il codice sorgente nell'organizzazione più semplice da capire.

Non essere un watchmaker

Troppe volte ho visto gli sviluppatori indossare un oculare e concentrarsi sui piccoli dettagli al punto che nessun altro potrebbe rimettere insieme i pezzi se dovesse cadere a pezzi.

    
risposta data 10.06.2013 - 17:12
fonte
2

Una ragione di cambiamento è, in definitiva, un cambiamento nelle specifiche o informazioni sull'ambiente in cui viene eseguita l'applicazione. Quindi un singolo principio di responsabilità ti dice di scrivere ogni componente (classe, funzione, modulo, servizio ...) in modo che debba considerare il meno possibile le specifiche e l'ambiente di esecuzione.

Dato che conosci le specifiche e l'ambiente quando scrivi il componente, puoi applicare il principio.

Se si considera l'esempio di codice che stampa un documento. Dovresti considerare se puoi definire il modello di layout senza considerare che il documento finirà in PDF. Puoi, quindi SRP ti sta dicendo che dovresti.

Naturalmente YAGNI ti sta dicendo che non dovresti. Devi trovare un equilibrio tra i principi di progettazione.

    
risposta data 03.05.2013 - 13:12
fonte
2

Flup è diretto nella giusta direzione. Il "principio di responsabilità unica" originariamente applicato alle procedure. Ad esempio, Dennis Ritchie direbbe che una funzione dovrebbe fare una cosa e farla bene. Quindi, in C ++, Bjarne Stroustrup direbbe che una classe dovrebbe fare una cosa e farla bene.

Si noti che, tranne che come regole empiriche, queste due formalmente hanno poco o nulla a che fare l'una con l'altra. Si occupano solo di ciò che è conveniente esprimere nel linguaggio di programmazione. Bene, è qualcosa. Ma è una storia abbastanza diversa da quella a cui sta andando il flup.

Le implementazioni moderne (vale a dire, agile e DDD) si concentrano più su ciò che è importante per il business che su ciò che il linguaggio di programmazione può esprimere. La parte sorprendente è che i linguaggi di programmazione non sono ancora stati raggiunti. I vecchi linguaggi FORTRAN catturano le responsabilità che si adattano ai principali modelli concettuali del tempo: i processi applicati a ciascuna carta mentre passava attraverso il lettore di carte o (come in C) l'elaborazione che accompagnava ogni interruzione. Poi vennero i linguaggi ADT, che erano maturati al punto da catturare ciò che le persone DDD avrebbero successivamente reinventato come importanti (sebbene Jim Neighbours ne avesse capito la maggior parte, pubblicato e in uso nel 1968): cosa oggi chiamiamo classi . (Non sono moduli.)

Questo passaggio era meno un'evoluzione che un'oscillazione a pendolo. Quando il pendolo si è spostato sui dati, abbiamo perso la modellazione del caso d'uso inerente a FORTRAN. Va bene quando l'obiettivo principale riguarda i dati o le forme su uno schermo. È un ottimo modello per programmi come PowerPoint, o almeno per le sue semplici operazioni.

Ciò che è perduto è responsabilità del sistema . Non vendiamo gli elementi di DDD. E non facciamo bene i metodi di classe. Vendiamo le responsabilità del sistema. A un certo livello, devi progettare il tuo sistema secondo il principio della responsabilità unica.

Quindi se guardi persone come Rebecca Wirfs-Brock, o me, che usavano parlare dei metodi di classe, ora stiamo parlando in termini di casi d'uso. Questo è ciò che vendiamo. Quelle sono le operazioni di sistema. Un caso d'uso dovrebbe avere un'unica responsabilità. Un caso d'uso è raramente un'unità architettonica. Ma tutti cercavano di fingere che fosse. Per esempio, testimonia le persone SOA.

Ecco perché sono entusiasta dell'architettura DCI di Trygve Reenskaug - che è ciò che è descritto nel libro Lean Architecture sopra. Alla fine dà una certa importanza a quello che era un arbitrario e mistico godimento di "singola responsabilità" - come si trova nella maggior parte delle argomentazioni di cui sopra. Questa statura si riferisce ai modelli mentali umani: prima gli utenti finali E i programmatori in secondo luogo. Si riferisce a problemi di business. E, quasi per caso, incapsula il cambiamento mentre il flup ci sfida.

Il principio della singola responsabilità come lo conosciamo è o un dinosauro rimasto dai suoi giorni di origine o un cavallo da passeggio che usiamo come sostituto per la comprensione. Devi lasciare alcuni di questi cavalli hobby per fare un ottimo software. E questo richiede di pensare fuori dagli schemi. Mantenere le cose semplici e facili da capire funziona solo quando il problema è semplice e facile da capire. Non sono molto interessato a queste soluzioni: non sono tipiche, e non è dove si trova la sfida.

    
risposta data 04.05.2013 - 18:24
fonte
0

Sì, il Principio della singola responsabilità dovrebbe essere applicato al nuovo codice.

Ma! Cos'è una responsabilità?

"Stampa un rapporto è una responsabilità"? La risposta, credo sia "Forse".

Proviamo ad usare la definizione di SRP come "avere un solo motivo per cambiare".

Supponiamo di avere una funzione che stampa i report. Se hai due modifiche:

  1. cambia la funzione perché il tuo rapporto deve avere uno sfondo nero
  2. cambia quella funzione perché devi stampare in pdf

Quindi la prima modifica è "modifica lo stile del report", l'altra è "modifica il formato di output del report" e ora dovresti inserirli in due funzioni diverse perché sono cose diverse.

Ma se il tuo secondo cambiamento sarebbe stato:

2b. cambia quella funzione perché il tuo rapporto ha bisogno di un carattere diverso

Direi che entrambe le modifiche sono "cambia lo stile del report" e possono rimanere in una funzione.

Quindi, dove ci lascia? Come al solito, dovresti cercare di mantenere le cose semplici e facili da capire. Se la modifica del colore di sfondo significa 20 righe di codice e la modifica del font significa 20 righe di codice, renderle nuovamente disponibili. Se è una riga ciascuna, tienila in una.

    
risposta data 03.05.2013 - 15:34
fonte
0

Quando stai progettando un nuovo sistema, è opportuno considerare il tipo di modifiche che potresti dover apportare durante la sua vita e quanto costose saranno fornite all'architettura che stai mettendo a punto. Suddividere il tuo sistema in moduli è una decisione costosa per sbagliare.

Una buona fonte di informazioni è il modello mentale nella testa degli esperti del dominio aziendale. Prendi l'esempio del documento, la formattazione e il pdf. Gli esperti di dominio probabilmente ti diranno che formattano le loro lettere usando i modelli di documento. O su fermo o in Word o altro. Puoi recuperare queste informazioni prima di iniziare la codifica e utilizzarle nel tuo progetto.

Un'ottima lettura di queste cose: Lean Architecture di Coplien

    
risposta data 03.05.2013 - 12:35
fonte
0

"Stampa" è molto simile a "vista" in MVC. Chiunque comprenda le basi degli oggetti lo capirebbe.

È una responsabilità del sistema . È implementato come meccanismo - MVC - che coinvolge una stampante (la vista), la cosa stampata (il modulo) e la richiesta e le opzioni della stampante (dal controller).

Cercare di localizzare questo come una classe o modulo di responsabilità è asinino e riflette il pensiero di 30 anni. Da allora abbiamo imparato molto ed è ampiamente evidenziato nella letteratura e nel codice dei programmatori esperti.

    
risposta data 04.05.2013 - 18:28
fonte
0

Isn't it a better idea to only really start to apply SRP when requests to change the code start coming in?

Idealmente, avrai già una buona idea di quali siano le responsabilità delle varie parti del codice. Dividi le responsabilità in base al tuo primo istinto, forse tenendo conto di ciò che le librerie che vuoi usare (delegare un compito, una responsabilità, ad una libreria di solito è una cosa grandiosa da fare, a condizione che la biblioteca possa effettivamente svolgere il compito ). Quindi, affina la tua comprensione delle responsabilità in base alle mutevoli esigenze. Quanto meglio comprendi il sistema inizialmente, tanto meno hai bisogno di cambiare radicalmente gli incarichi di responsabilità (anche se a volte scopri che una responsabilità è meglio divisa in sub-responsabilità).

Non dovresti passare molto tempo a preoccupartene. Una caratteristica chiave del codice è che può essere modificata in seguito, non è necessario averlo completamente corretto la prima volta. Cerca semplicemente di migliorare nel tempo imparando quali sono le responsabilità della forma in modo da poter fare meno errori in futuro.

An example would be a piece of code which prints a document. A request comes in to change it to print to PDF and then a second request is made to change it to apply some different formatting to the document. At this point you have proof of more than a single reason to change (and violation of SRP) and should make the appropriate refactoring.

Questo è strettamente indicativo del fatto che la responsabilità generale - "stampare" il codice - ha delle responsabilità secondarie e dovrebbe essere suddivisa in parti. Questa non è una violazione di SRP per sé ma piuttosto un'indicazione che il partizionamento (forse in "formattazione" e "rendering" sotto-attività) è probabilmente richiesto. Puoi descrivere chiaramente queste responsabilità in modo che tu possa capire cosa sta succedendo all'interno delle sotto-attività senza guardare alla loro implementazione? Se puoi, è probabile che siano divisioni ragionevoli.

Potrebbe anche essere più chiaro se guardiamo ad un semplice esempio reale. Consideriamo il metodo di utilità sort() in java.util.Arrays . Che cosa fa? Ordina una matrice, e questo è tutto ciò che fa. Non stampa gli elementi, non trova il membro più moralmente adatto, non fischia Dixie . Semplicemente ordina una matrice. Non devi sapere come. L'ordinamento è la sola responsabilità di quel metodo. (In realtà, ci sono molti metodi di ordinamento in Java per ragioni tecniche un po 'brutte da fare con i tipi primitivi, ma non devi prestare attenzione a questo, dato che hanno tutti delle responsabilità equivalenti.)

Rendi i tuoi metodi, le tue classi, i tuoi moduli, facendoli avere un ruolo così chiaramente designato nella vita. Mantiene l'importo che devi capire subito, e questo a sua volta è ciò che ti consente di gestire la progettazione e il mantenimento di un sistema di grandi dimensioni.

    
risposta data 05.05.2013 - 18:48
fonte

Leggi altre domande sui tag