Quanti schemi di progettazione e livelli di astrazione sono necessari? [chiuso]


29

Come posso dire che il mio software ha troppa astrazione e troppi schemi di progettazione, o viceversa, come faccio a sapere se dovrebbe averne di più?

Gli sviluppatori con cui lavoro programmano in modo diverso riguardo a questi punti.

Alcuni astraggono ogni piccola funzione, usano i modelli di progettazione ove possibile ed evitano la ridondanza ad ogni costo.

Gli altri, incluso me, cercano di essere più pragmatici e scrivono codice che non si adatta perfettamente a ogni modello di progettazione, ma è molto più veloce da capire perché viene applicata meno astrazione.

So che questo è un compromesso. Come posso sapere quando c'è abbastanza astrazione nel progetto e come faccio a sapere che ha bisogno di più?

Esempio, quando un livello di cache generico viene scritto usando Memcache. Abbiamo davvero bisogno Memcache, MemcacheAdapter, MemcacheInterface, AbstractCache, CacheFactory, CacheConnector, ... o è questo più facile da mantenere e ancora buono il codice quando si utilizza solo la metà di quelle classi?

Trovato questo su Twitter:

inserisci qui la descrizione dell'immagine

( https://twitter.com/rawkode/status/875318003306565633 )


58
Se stai trattando i modelli di progettazione come elementi che estrai da un secchio e utilizzi per assemblare i programmi, ne stai usando troppi.
Blrfl,


5
Potresti pensare a modelli di design come i modelli vocali che le persone usano. Perché in un certo senso, modi di dire, metafore, ecc. Sono tutti modelli di design. Se stai usando un linguaggio ogni frase ... probabilmente è troppo spesso. Ma possono aiutare a chiarire i pensieri e aiutare la comprensione di quella che altrimenti sarebbe una lunga parete di prosa. Non c'è davvero un modo giusto di rispondere "ogni quanto devo usare le metafore?", Dipende dal giudizio dell'autore.
Prime il

8
Non è possibile che una singola risposta SE possa coprire adeguatamente questo argomento. Questo richiede letteralmente anni di esperienza e tutoraggio per essere compreso. Questo è chiaramente troppo vasto.
jpmc26,

5
Seguire il principio di progettazione di base nel settore dell'aviazione: "Semplificare e aggiungere più leggerezza". Quando scopri che il modo migliore per correggere un bug è semplicemente quello di eliminare il codice contenente il bug, perché non fa ancora nulla di utile anche se era privo di bug, stai cominciando a ottenere il design giusto!
alephzero,

Risposte:


52

Quanti ingredienti sono necessari per un pasto? Di quante parti hai bisogno per costruire un veicolo?

Sai che hai troppa poca astrazione quando una piccola modifica all'implementazione porta a una cascata di modifiche in tutto il tuo codice. Astrazioni appropriate aiuterebbero a isolare la parte del codice che deve essere modificata.

Sai che hai troppa astrazione quando una piccola modifica dell'interfaccia porta a una cascata di modifiche in tutto il codice, a diversi livelli. Invece di cambiare l'interfaccia tra due classi, ti ritrovi a modificare dozzine di classi e interfacce solo per aggiungere una proprietà o cambiare un tipo di argomento del metodo.

A parte questo, non c'è davvero modo di rispondere alla domanda dando un numero. Il numero di astrazioni non sarà lo stesso da un progetto all'altro, da una lingua all'altra e persino da uno sviluppatore all'altro.


28
Se hai solo due interfacce e centinaia di classi che le implementano, cambiare le interfacce porterebbe a una cascata di cambiamenti, ma ciò non significa che ci sia troppa astrazione, poiché hai solo due interfacce.
Tulains Córdova,

Il numero di astrazioni non sarà nemmeno lo stesso per diverse parti dello stesso progetto!
T. Sar - Ripristina Monica il

Suggerimento: passare da Memcache a un altro meccanismo di memorizzazione nella cache (Redis?) È una modifica dell'implementazione .
Ogre Salmo33

Le tue due regole (linee guida, qualunque cosa tu voglia chiamarle) non funzionano, come dimostrato da Tulains. Sono anche dolorosamente incompleti anche se lo facessero. Il resto del post è una non-risposta, che dice poco più di quanto non possiamo fornire una risposta ragionevolmente mirata. -1
jpmc26,

Direi che nel caso di due interfacce e centinaia di classi che le implementano, è molto probabile che tu abbia teso troppo le tue astrazioni. L'ho visto di sicuro in progetti che riutilizzano un'astrazione molto vaga in molti luoghi ( interface Doer {void prepare(); void doIt();}), e diventa doloroso rifattorizzare quando questa astrazione non si adatta più. La parte fondamentale della risposta è che il test si applica quando un'astrazione deve cambiare - se non lo fa mai, non provoca mai dolore.
James_pic,

24

Il problema con i motivi di design può essere riassunto con il proverbio "quando si tiene in mano un martello, tutto sembra un chiodo". L'atto di applicare un modello di progettazione non migliora in alcun modo il programma. In effetti, direi che stai creando un programma più complicato se aggiungi un modello di progettazione. La domanda rimane se stai facendo buon uso o meno del modello di progettazione, e questo è il nocciolo della domanda "Quando abbiamo troppa astrazione?"

Se stai creando un'interfaccia e una superclasse astratta per una singola implementazione, hai aggiunto due componenti aggiuntivi al tuo progetto che sono superflui e non necessari. Il punto di fornire un'interfaccia è essere in grado di gestirla in modo equo per tutto il programma senza sapere come funziona. Il punto di una superclasse astratta è fornire un comportamento sottostante per le implementazioni. Se si dispone di una sola implementazione, si ottengono tutte le interfacce di complicazione e le classi di abstact e nessuno dei vantaggi.

Allo stesso modo, se stai usando un modello Factory e ti ritrovi a lanciare una classe per utilizzare la funzionalità disponibile solo nella super classe, il modello Factory non aggiunge alcun vantaggio al tuo codice. Hai aggiunto solo una classe aggiuntiva al tuo progetto che avrebbe potuto essere evitato.

TL; DR Il mio punto è che l'obiettivo dell'astrazione non è astratto in sé. Serve a uno scopo molto pratico nel tuo programma e prima di decidere di utilizzare un modello di progettazione o creare un'interfaccia, dovresti chiederti se in tal modo, il programma è più facile da capire nonostante l'ulteriore complessità o il programma è più robusto nonostante la complessità aggiuntiva (preferibilmente entrambi). Se la risposta è no o forse, prenditi un paio di minuti per considerare il motivo per cui volevi farlo e se forse può essere fatto in un modo migliore invece senza necessariamente il requisito di aggiungere astrazione al tuo codice.


L'analogia del martello sarebbe il problema di conoscere solo un modello di progettazione. I modelli di progettazione dovrebbero creare un intero kit di strumenti da cui selezionare e applicare ove appropriato. Non si seleziona un martello per rompere un dado.
Pete Kirkham,

@PeteKirkham Vero, ma anche un'intera gamma di modelli di progettazione a tua disposizione potrebbe non essere adatta a un problema specifico. Se un martello non è più adatto a rompere un dado, e nemmeno un cacciavite, e nemmeno un metro a nastro perché ti manca il martello, ciò non rende un martello la scelta giusta per il lavoro, fa solo è il più appropriato degli strumenti a tua disposizione. Ciò non significa che dovresti usare una mazza per rompere un dado però. Diamine, se siamo sinceri, quello di cui avresti davvero bisogno è uno schiaccianoci, non un martello ..
Neil,

Voglio un esercito di scoiattoli addestrati per il mio crack dado.
icc97,

6

TL: DR;

Non penso che ci sia un numero "necessario" di livelli di astrazioni al di sotto dei quali c'è troppo poco o al di sopra del quale c'è troppo. Come nella progettazione grafica, un buon design OOP dovrebbe essere invisibile e dovrebbe essere dato per scontato. Il cattivo design sporge sempre come un pollice dolente.

Risposta lunga

Molto probabilmente non saprai mai su quanti livelli di astrazioni stai costruendo.

La maggior parte dei livelli di astrazione ci sono invisibili e li diamo per scontati.

Questo ragionamento mi porta a questa conclusione:

Uno degli scopi principali dell'astrazione è salvare al programmatore la necessità di tenere sempre in mente tutto il funzionamento del sistema. Se il design ti costringe a sapere troppo sul sistema per aggiungere qualcosa, probabilmente c'è troppo poca astrazione. Penso che una cattiva astrazione (design scadente, progettazione anemica o ingegneria eccessiva) possa anche costringerti a sapere troppo per aggiungere qualcosa. In un estremo abbiamo un design basato su una classe divina o un gruppo di DTO, nell'altro estremo abbiamo alcune strutture OR / di persistenza che ti fanno saltare attraverso innumerevoli cerchi per raggiungere un mondo ciao. Entrambi i casi ti costringono a sapere troppo.

La cattiva astrazione aderisce a una campana di Gauss nel fatto che una volta passato un punto dolce inizia a mettersi in mezzo. La buona astrazione, d'altra parte, è invisibile e non può essercene troppo perché non ti accorgi che è lì. Pensa a quanti livelli su livelli di API, protocolli di rete, librerie, librerie del sistema operativo, file system, livelli harware, ecc. Su cui l'applicazione è costruita e dà per scontato.

Un altro importante scopo dell'astrazione è la compartimentazione, quindi gli errori non permeano oltre una certa area, non diversamente dal doppio scafo e dai serbatoi separati impediscono ad una nave di allagarsi completamente quando una parte dello scafo ha un buco. Se le modifiche al codice finiscono per creare bug in aree apparentemente non correlate, è probabile che ci sia troppa poca astrazione.


2
"Molto probabilmente non saprai mai su quanti livelli di astrazioni stai costruendo." - In effetti, il punto centrale di un'astrazione è che non sai come viene implementata, IOW non sai (e non puoi ) quanti livelli di astrazioni nasconde.
Jörg W Mittag,

4

I modelli di progettazione sono semplicemente soluzioni comuni ai problemi. È importante conoscere i modelli di progettazione, ma sono solo sintomi di un codice ben progettato (un buon codice può ancora essere nullo della banda di quattro serie di modelli di progettazione), non la causa.

Le astrazioni sono come recinzioni. Aiutano a separare le aree del programma in blocchi verificabili e intercambiabili (requisiti per rendere il codice non fragile non rigido). E proprio come i recinti:

  • Volete astrazioni nei punti di interfaccia naturali per ridurne al minimo le dimensioni.

  • Non vuoi cambiarli.

  • Volete che separino le cose che possono essere indipendenti.

  • Avere uno nel posto sbagliato è peggio che non averlo.

  • Non dovrebbero avere grandi perdite .


4

refactoring

Finora non ho visto la parola "refactoring" menzionata. Quindi, eccoci qui:

Sentiti libero di implementare una nuova funzionalità il più direttamente possibile. Se hai solo una singola, semplice classe, probabilmente non avrai bisogno di un'interfaccia, una superclasse, una fabbrica ecc.

Se e quando noti che espandi la classe in modo che diventi troppo grassa, allora è il momento di dividerla. In quel momento ha molto senso pensare a come dovresti farlo.

I modelli sono uno strumento mentale

I pattern, o più specificamente il libro "Design Patterns" della banda di quattro, sono fantastici, tra le altre ragioni, perché creano un linguaggio su cui gli sviluppatori possono pensare e parlare. È facile dire "osservatore", "fabbrica" ​​o "facciata" e tutti sanno esattamente cosa significa subito.

Quindi la mia opinione sarebbe che ogni sviluppatore dovrebbe avere una conoscenza passante almeno degli schemi nel libro originale, semplicemente per essere in grado di parlare dei concetti di OO senza dover sempre spiegare le basi. Dovresti davvero usare gli schemi ogni volta che appare una possibilità per farlo? Molto probabilmente no.

biblioteche

Le biblioteche sono probabilmente l'area in cui potrebbero trovarsi per errare sul lato di troppe scelte basate su schemi anziché troppo poco. Cambiare qualcosa da una classe "grassa" a qualcosa con più pattern derivato (di solito ciò significa classi più numerose e più piccole) cambierà radicalmente l'interfaccia; e questa è l'unica cosa che di solito non vuoi cambiare in una biblioteca, perché è l'unica cosa di reale interesse per l'utente della tua biblioteca. A loro non importerebbe di meno di come gestisci le tue funzionalità internamente, ma a loro importa molto se devono costantemente cambiare il loro programma quando fai una nuova versione con una nuova API.


2

Il punto dell'astrazione dovrebbe essere innanzitutto il valore che viene portato al consumatore dell'astrazione, cioè il cliente dell'astrazione, gli altri programmatori e spesso te stesso.

Se, come cliente che consuma le astrazioni, trovi che devi mescolare e abbinare molte diverse astrazioni per portare a termine il tuo lavoro di programmazione, allora ci sono potenzialmente troppe astrazioni.

Idealmente, la stratificazione dovrebbe riunire un numero di astrazioni inferiori e sostituirlo con un'astrazione semplice e di livello superiore che i suoi consumatori possono utilizzare senza dover affrontare nessuna di quelle astrazioni sottostanti. Se devono fare i conti con le astrazioni sottostanti, lo strato perde (in quanto incompleto). Se il consumatore ha a che fare con troppe astrazioni diverse, forse manca la stratificazione.

Dopo aver considerato il valore delle astrazioni per i programmatori che consumano, possiamo passare a valutare e considerare l'implementazione, come quella del DRY-ness.

Sì, si tratta di facilitare la manutenzione, ma dovremmo prima considerare la difficile situazione della manutenzione dei nostri clienti, fornendo astrazioni e livelli di qualità, quindi prendere in considerazione la possibilità di facilitare la nostra manutenzione in termini di aspetti di implementazione come evitare la ridondanza.


Esempio, quando un livello di cache generico viene scritto usando Memcache. Abbiamo davvero bisogno di Memcache, MemcacheAdapter, MemcacheInterface, AbstractCache, CacheFactory, CacheConnector, ... o è più facile da mantenere e ancora un buon codice quando si utilizza solo metà di quelle classi?

Dobbiamo guardare al punto di vista del cliente, e se le loro vite sono state semplificate, allora va bene. Se le loro vite sono più complesse, allora è male. Tuttavia, potrebbe esserci uno strato mancante che avvolge queste cose in qualcosa di semplice da usare. Internamente, questi potrebbero benissimo migliorare il mantenimento dell'attuazione. Tuttavia, come sospetti, è anche possibile che sia semplicemente troppo ingegnerizzato.


2

L'astrazione è progettata per facilitare la comprensione del codice. Se uno strato di astrazione renderà le cose più confuse, non farlo.

L'obiettivo è utilizzare il numero corretto di astrazioni e interfacce per:

  • minimizzare i tempi di sviluppo
  • massimizzare la manutenibilità del codice

Estratto solo quando richiesto

  1. Quando scopri che stai scrivendo una super classe
  2. Quando consentirà un riutilizzo significativo del codice
  3. Se l'astrazione renderà il codice diventerà significativamente più chiaro e più facile da leggere

Non astrarre quando

  1. Ciò non avrà alcun vantaggio in termini di riutilizzo o chiarezza del codice
  2. In questo modo il codice sarà significativamente più lungo / più complesso senza alcun vantaggio

Qualche esempio

  • Se hai solo una cache in tutto il tuo programma, non astrarre a meno che non pensi che potresti finire con una superclasse
  • Se si dispone di tre diversi tipi di buffer, utilizzare un'astrazione di interfaccia comune a tutti

2

Penso che questa potrebbe essere una meta-risposta controversa, e sono un po 'in ritardo alla festa, ma penso che sia molto importante menzionarlo qui, perché penso di sapere da dove vieni.

Il problema con il modo in cui vengono utilizzati i modelli di progettazione è che quando vengono insegnati, presentano un caso come questo:

Hai questo scenario specifico. Organizza il tuo codice in questo modo. Ecco un esempio intelligente, ma in qualche modo inventato.

Il problema è che quando inizi a fare vera ingegneria, le cose non sono così semplici. Il modello di progettazione che leggi non si adatta perfettamente al problema che stai cercando di risolvere. Per non parlare del fatto che le biblioteche che stai usando violano totalmente tutto ciò che è indicato nel testo spiegando questi schemi, ognuno nel suo modo speciale. Di conseguenza, il codice che scrivi "sembra sbagliato" e fai domande come questa.

Inoltre, vorrei citare Andrei Alexandrescu, quando si parla di ingegneria del software, che afferma:

L'ingegneria del software, forse più di qualsiasi altra disciplina ingegneristica, mostra una ricca molteplicità: puoi fare la stessa cosa in tanti modi corretti e ci sono infinite sfumature tra giusto e sbagliato.

Forse questo è un po 'esagerato, ma penso che questo spieghi perfettamente un ulteriore motivo per cui potresti sentirti meno sicuro del tuo codice.

È in momenti come questo che la voce profetica di Mike Acton, motore di gioco di Insomniac, mi urla nella testa:

CONOSCI I TUOI DATI

Sta parlando degli input per il tuo programma e degli output desiderati. E poi c'è questa gemma di Fred Brooks del Mythical Man Month:

Mostrami i tuoi diagrammi di flusso e nascondi i tuoi tavoli, e io continuerò a essere sconcertato. Mostrami i tuoi tavoli e di solito non avrò bisogno dei tuoi diagrammi di flusso; saranno ovvi.

Quindi, se fossi in te, ragionerei sul mio problema in base al mio tipico caso di input e se raggiungesse l'output corretto desiderato. E fai domande come questa:

  • I dati di output del mio programma sono corretti?
  • Viene prodotto in modo efficiente / rapido per il mio caso di input più comune?
  • Il mio codice è abbastanza facile da ragionare a livello locale, sia per me che per i miei compagni di squadra? In caso contrario, posso renderlo più semplice?

Quando lo fai, la domanda su "quanti strati di astrazione o modelli di progettazione sono necessari" diventa molto più facile rispondere. Di quanti strati di astrazione hai bisogno? Quante necessarie per raggiungere questi obiettivi, e non di più. "Che dire dei modelli di design? Non ne ho usato nessuno!" Bene, se gli obiettivi di cui sopra sono stati raggiunti senza l'applicazione diretta di un modello, allora va bene. Fallo funzionare e passa al problema successivo. Inizia dai tuoi dati, non dal codice.


2

L'architettura software sta inventando le lingue

Con ogni livello software, crei la lingua in cui tu (o i tuoi colleghi) volete esprimere la loro soluzione di livello successivo (quindi, inserirò alcuni analoghi in linguaggio naturale nel mio post). I tuoi utenti non vogliono passare anni a imparare a leggere o scrivere quella lingua.

Questa visione mi aiuta a decidere in merito a problemi di architettura.

leggibilità

Quel linguaggio dovrebbe essere facilmente comprensibile (rendendo leggibile il codice del livello successivo). Il codice viene letto molto più spesso di quanto scritto.

Un concetto dovrebbe essere espresso con una sola parola: una classe o interfaccia dovrebbe esporre il concetto. (Le lingue slave in genere hanno due parole diverse per un verbo inglese, quindi devi imparare il doppio del vocabolario. Tutte le lingue naturali usano parole singole per più concetti).

I concetti che esponi non dovrebbero contenere sorprese. Si tratta principalmente di convenzioni di denominazione come get- e set-method ecc. E i modelli di progettazione possono aiutare perché forniscono un modello di soluzione standard e il lettore vede "OK, ottengo gli oggetti da una fabbrica" ​​e sa cosa significa. Ma se l'istanza di una classe concreta fa il lavoro, preferirei quello.

usabilità

La lingua dovrebbe essere facile da usare (facilitando la formulazione di "frasi corrette").

Se tutte queste classi / interfacce MemCache diventano visibili al livello successivo, ciò crea una curva di apprendimento ripida per l'utente fino a quando non capisce quando e dove utilizzare quali di queste parole per il singolo concetto di cache.

Esporre solo le classi / i metodi necessari rende più facile per l'utente trovare ciò di cui ha bisogno (vedi la citazione di Antoine de Saint-Exupery di DocBrowns). Esporre un'interfaccia anziché la classe di implementazione può semplificare.

Se si espone una funzionalità in cui è possibile applicare un modello di progettazione consolidato, è meglio seguire quel modello di progettazione piuttosto che inventare qualcosa di diverso. Il tuo utente comprenderà le API seguendo un modello di progettazione più facilmente di alcuni concetti completamente diversi (se conosci l'italiano, lo spagnolo sarà più facile per te del cinese).

Sommario

Introdurre le astrazioni se ciò semplifica l'utilizzo (e vale la pena sovraccarico di mantenere sia l'astrazione che l'implementazione).

Se il tuo codice ha un'attività secondaria (non banale), risolvilo "nel modo previsto", ovvero segui il modello di progettazione appropriato invece di reinventare un diverso tipo di ruota.


1

La cosa importante da considerare è quanto deve sapere il codice di consumo che gestisce effettivamente la logica aziendale su queste classi correlate alla memorizzazione nella cache. Idealmente il tuo codice dovrebbe preoccuparsi solo dell'oggetto cache che vuole creare e forse una fabbrica per creare quell'oggetto se un metodo di costruzione non è sufficiente.

Il numero di modelli utilizzati o il livello di ereditarietà non sono troppo importanti a condizione che ogni livello possa essere giustificato per altri sviluppatori. Ciò crea un limite informale poiché ogni livello aggiuntivo è più difficile da giustificare. La parte più importante è quanti livelli di astrazione sono interessati dalle modifiche ai requisiti funzionali o aziendali. Se è possibile apportare una modifica a un solo livello per un singolo requisito, allora probabilmente non si è troppo astratti o astratti in modo scadente, se si modifica lo stesso livello per più modifiche non correlate, è probabile che si sia sottoposti ad astrazione e sia necessario separare ulteriormente le preoccupazioni.


-1

Innanzitutto, la citazione di Twitter è falsa. I nuovi sviluppatori devono individuare un modello, le astrazioni in genere li aiuterebbero a "ottenere il quadro". A condizione che le astrazioni abbiano un senso ovviamente.

In secondo luogo, il tuo problema non è troppe o troppe poche astrazioni, è che apparentemente nessuno può decidere su queste cose. Nessuno possiede il codice, nessun singolo piano / design / filosofia è implementato, ogni ragazzo successivo può fare qualunque cosa diavolo sembra adatto per quel momento. Qualunque stile tu scelga, dovrebbe essere uno.


2
Asteniamoci dal respingere quelle esperienze come "fasulle". Troppe astrazioni sono un vero problema. Purtroppo le persone aggiungono l'astrazione in anticipo, perché "best practice", piuttosto che risolvere un problema reale. Inoltre, nessuno può decidere "su queste cose" ... le persone lasciano le aziende, le persone si uniscono, nessuno prende la proprietà del loro fango.
Rawkode,
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.