Esistono sapori di OOP in cui alcuni o tutti i principi SOLID sono antitetici per pulire il codice?


26

Di recente ho avuto una discussione con un mio amico su OOP nello sviluppo di videogiochi.

Stavo spiegando l'architettura di uno dei miei giochi che, con sorpresa del mio amico, conteneva molte piccole classi e diversi livelli di astrazione. Ho sostenuto che questo è stato il risultato del mio concentrarsi sul dare a tutto una singola responsabilità e anche per allentare l'accoppiamento tra i componenti.

La sua preoccupazione era che il gran numero di classi si traducesse in un incubo di manutenzione. La mia opinione era che avrebbe avuto esattamente l'effetto opposto. Abbiamo proceduto a discuterne per quelli che sembravano secoli, alla fine accettando di non essere d'accordo, dicendo che forse c'erano casi in cui i principi SOLID e la corretta OOP non si combinavano davvero bene.

Anche la voce di Wikipedia sui principi SOLID afferma che sono linee guida che aiutano a scrivere codice gestibile e che fanno parte di una strategia globale di programmazione agile e adattiva.

Quindi, la mia domanda è:

Ci sono casi in OOP in cui alcuni o tutti i principi SOLID non si prestano a pulire il codice?

Posso immediatamente immaginare che il principio di sostituzione di Liskov potrebbe essere in conflitto con un altro sapore di eredità sicura. Vale a dire, se qualcuno ha escogitato un altro modello utile implementato tramite l'ereditarietà, è del tutto possibile che l'SPL possa essere in diretto conflitto con esso.

Ce ne sono altri? Forse alcuni tipi di progetti o determinate piattaforme target funzionano meglio con un approccio meno SOLIDO?

Modificare:

Vorrei solo specificare che non sto chiedendo come migliorare il mio codice;) L'unica ragione per cui ho menzionato un progetto in questa domanda è stata quella di dare un piccolo contesto. La mia domanda riguarda OOP e i principi di progettazione in generale .

Se sei curioso del mio progetto, vedi questo .

Modifica 2:

Immaginavo che a questa domanda sarebbe stata data una risposta in 3 modi:

  1. Sì, esistono principi di progettazione OOP che sono parzialmente in conflitto con SOLID
  2. Sì, esistono principi di progettazione OOP che sono completamente in conflitto con SOLID
  3. No, SOLID è le ginocchia dell'ape e OOP sarà sempre migliore con esso. Ma, come con tutto, non è una panacea. Bevi responsabilmente.

Le opzioni 1 e 2 avrebbero probabilmente generato risposte lunghe e interessanti. L'opzione 3, d'altra parte, sarebbe una risposta breve, poco interessante, ma nel complesso rassicurante.

Sembra che stiamo convergendo sull'opzione 3.


Come definisci "codice pulito"?
GrandmasterB

Il codice pulito, nell'ambito di questa domanda, è la struttura del codice in un modo che soddisfa gli obiettivi di OOP e un insieme di principi di progettazione scelti. Quindi, ho familiarità con il codice pulito in termini di obiettivi stabiliti da OOP + SOLID. Mi chiedo se esiste un altro insieme di principi di progettazione che funzionano con OOP ma che sono completamente o parzialmente incompatibili con SOLID.
MetaFight

1
Sono molto più preoccupato per le tue prestazioni di quel tuo gioco che per qualsiasi altra cosa. A meno che non stiamo parlando di giochi basati su testo.
Euforico

1
Questo gioco è essenzialmente un esperimento. Sto provando diversi approcci di sviluppo e sto anche cercando di capire se il codice enterprise-y può funzionare quando si scrive un gioco. Mi aspetto che le prestazioni diventino un problema, ma non lo sono ancora. Se / quando lo fa, la prossima cosa che sperimenterò è come adattare il mio codice al codice performante. Tanto apprendimento!
MetaFight,

1
A rischio di alcuni voti negativi, vorrei tornare alla domanda "perché": OOP nello sviluppo del gioco . A causa della natura dello sviluppo del gioco, OOP "tradizionale" sembra non essere più in voga a favore di vari tipi di sistemi di entità / componenti, una variante interessante qui per esempio. Questo piuttosto mi fa pensare che la domanda non dovrebbe essere "SOLID + OOP" ma piuttosto "OOP + Game Development". Quando guardi un grafico dell'interazione degli oggetti per gli sviluppatori di giochi. vs. dire l'applicazione N-tier, le differenze possono significare che un nuovo paradigma è buono.
J Trana,

Risposte:


20

Ci sono casi in OOP in cui alcuni o tutti i principi SOLID non si prestano a pulire il codice?

In generale, no. La storia ha dimostrato che i principi SOLIDI contribuiscono in gran parte all'aumento del disaccoppiamento, che a sua volta ha dimostrato di aumentare la flessibilità del codice e quindi la tua capacità di adattarsi al cambiamento, nonché di rendere il codice più facile da ragionare, testare, riutilizzare .. in breve, rendi il tuo codice più pulito.

Ora, ci possono essere casi in cui i principi SOLIDI si scontrano con DRY (non ripeterti), KISS (mantienilo stupido) o altri principi di un buon design OO. E, naturalmente, possono scontrarsi con la realtà dei requisiti, i limiti degli umani, i limiti dei nostri linguaggi di programmazione, altri ostacoli.

In breve, i principi SOLID si presteranno sempre al codice pulito, ma in alcuni scenari si presteranno meno di alternative contrastanti. Sono sempre buoni, ma a volte altre cose sono più buone.


14
Mi piace ma a volte altre cose sono più buone . Ha un'atmosfera da "fattoria degli animali". ;)
FrustratedWithFormsDesigner

12
+1 Quando ho appena guardato i principi SOLIDI, vedo 5 "bello da avere". Ma nessuno di questi è DRY, KISS e "chiarisci le tue intenzioni". Usa SOLID, ma mostra cautela e scetticismo.
user949300

Sono d'accordo. Penso che il / i principio / i corretto / i da seguire dipenda chiaramente dalla situazione, ma a parte i severi requisiti di prestazione, che si posizionano sempre al di sopra di ogni altra cosa (questo è importante soprattutto nello sviluppo del gioco), tendo a pensare che DRY e KISS siano di solito entrambi più importanti di SOLID. Naturalmente, più pulito rendi il codice migliore, quindi se puoi seguire tutti i principi senza conflitti, tanto meglio.
Ben Lee,

Che dire della segregazione dell'integrazione rispetto alla progettazione efficiente degli aggregati? Se l'interfaccia "sequenza" di base [es. IEnumerable] include metodi come Counte asImmutable, e proprietà come getAbilities[il cui ritorno indicherebbe se cose come Countsaranno "efficienti"], allora si potrebbe avere un metodo statico che prende sequenze multiple e le aggrega in modo che Mi comporterò come una singola sequenza più lunga. Se tali abilità sono presenti nel tipo di sequenza di base, anche se si
collegano

... e implementarli in modo efficiente se usati con classi base che possono farlo. Se si aggrega un elenco di dieci milioni di articoli che ne conosce il conteggio e un elenco di cinque articoli che può recuperare solo gli articoli in sequenza, una richiesta per la 10.000.000a voce dovrebbe leggere il conteggio dal primo elenco e quindi leggere tre elementi dal secondo . Le interfacce del lavello della cucina possono violare l'ISP, ma potrebbero migliorare notevolmente le prestazioni di alcuni scenari di composizione / aggregazione.
supercat

13

Penso di avere una prospettiva insolita in quanto ho lavorato sia nella finanza che nei giochi.

Molti dei programmatori che ho incontrato nei giochi erano terribili in Ingegneria del Software, ma non avevano bisogno di pratiche come SOLID. Tradizionalmente, mettono il loro gioco in una scatola - poi hanno finito.

In finanza, ho scoperto che gli sviluppatori possono essere davvero sciatti e indisciplinati perché non hanno bisogno delle prestazioni che si fanno nei giochi.

Entrambe le affermazioni precedenti sono ovviamente eccessive generalizzazioni, ma in realtà in entrambi i casi il codice pulito è fondamentale. Per l'ottimizzazione è essenziale un codice chiaro e di facile comprensione. Per motivi di manutenibilità, vuoi la stessa cosa.

Ciò non significa che SOLID non sia privo di critiche. Si chiamano principi e tuttavia dovrebbero essere seguiti come linee guida? Quindi, quando dovrei esattamente seguirli? Quando dovrei infrangere le regole?

Probabilmente potresti guardare due pezzi di codice e dire quale si adatta meglio a SOLID. Ma isolatamente, non esiste un modo oggettivo per trasformare il codice in SOLID. È sicuramente l'interpretazione dello sviluppatore.

Non è un sequitur affermare che "un gran numero di classi può portare a un incubo di manutenzione". Tuttavia, se SRP non viene interpretato correttamente, si potrebbe facilmente finire con questo problema.

Ho visto codice con molti livelli praticamente senza alcuna responsabilità. Le classi che non fanno nulla a parte lo stato di passaggio da una classe all'altra. Il classico problema di troppi strati di indiretta.

SRP se abusato può finire con una mancanza di coesione nel codice. Puoi dirlo quando provi ad aggiungere una funzione. Se devi sempre cambiare più posti contemporaneamente, il tuo codice manca di coesione.

Open-Closed non è privo di critiche. Ad esempio, vedi la recensione di Jon Skeet sul principio Open Closed . Non riprodurrò qui i suoi argomenti.

La tua tesi su LSP sembra piuttosto ipotetica e non capisco davvero cosa intendi con un'altra forma di eredità sicura?

L'ISP sembra in qualche modo duplicare SRP. Sicuramente devono essere interpretati nello stesso modo in cui non si può mai prevedere cosa faranno tutti i client della propria interfaccia. L'interfaccia coerente di oggi è il violatore dell'ISP di domani. L'unica conclusione illogica è di non avere più di un membro per interfaccia.

DIP può portare a codice inutilizzabile. Ho visto classi con un numero enorme di parametri nel nome di DIP. Di solito queste classi avrebbero "rinnovato" le loro parti composite, ma io il nome di capacità di test non dobbiamo mai più usare la nuova parola chiave.

SOLID da solo non è sufficiente per scrivere codice pulito. Ho visto il libro di Robert C. Martin, " Clean Code: A Handbook of Agile Software Craftsrafts ". Il problema più grande è che è facile leggere questo libro e interpretarlo come regole. Se lo fai, ti manca il punto. Contiene un ampio elenco di odori, euristica e principi - molti altri oltre ai cinque di SOLID. Tutti questi "principi" non sono in realtà principi ma linee guida. Lo zio Bob li descrive lui stesso come "buchi nascosti" per i concetti. Sono un atto di bilanciamento; seguire ciecamente le linee guida può causare problemi.


1
Se si dispone di un gran numero di parametri del costruttore, si suggerisce di aver violato l'SRP. Tuttavia, un contenitore DI rimuove comunque il dolore associato a un gran numero di parametri del costruttore, quindi non sono d'accordo con le tue dichiarazioni su DIP.
Stephen,

Tutti questi "principi" non sono in realtà principi ma linee guida. Sono un atto di bilanciamento; come ho detto nel mio post dopo anche SRP, l'estremo stesso può essere problematico.
Dave Hillier,

Buona risposta, +1. Una cosa che vorrei aggiungere: ho letto la recensione di Skeet più un critico della definizione di libri di testo standard dell'OCP, non delle idee fondamentali dietro l'OCP. A mio avviso, l'idea di variazione protetta è ciò che l'OCP avrebbe dovuto essere sin dall'inizio.
Doc Brown,

5

Ecco la mia opinione:

Sebbene i principi SOLID mirino a una base di codice non ridondante e flessibile, questo potrebbe essere un compromesso in termini di leggibilità e manutenzione se ci sono troppe classi e livelli.

Riguarda soprattutto il numero di astrazioni . Un gran numero di classi potrebbe andare bene se si basano su poche astrazioni. Dal momento che non conosciamo le dimensioni e le specifiche del tuo progetto, è difficile da dire, ma è un po 'preoccupante menzionare più livelli oltre a un gran numero di classi.

Immagino che i principi SOLID non siano in disaccordo con OOP, ma è comunque possibile scrivere un codice non pulito rispettandoli.


3
+1 Quasi per definizione, un sistema altamente flessibile deve essere meno gestibile di uno meno flessibile. La flessibilità ha un costo a causa della maggiore complessità che comporta.
Andy,

@Andy non necessariamente. nel mio lavoro attuale, molte delle parti peggiori del codice sono disordinate perché sono semplicemente messe insieme "facendo qualcosa di semplice, basta hackerarlo insieme e farlo funzionare in modo che non diventi così complesso". Di conseguenza, molte parti del sistema sono rigide al 100%. Non è possibile modificarli affatto senza riscriverne grandi parti. È davvero difficile da mantenere. La manutenibilità deriva dall'elevata coesione e dal basso accoppiamento, non dalla flessibilità del sistema.
Sara

3

Nei miei primi giorni di sviluppo, ho iniziato a scrivere un Roguelike in C ++. Mentre volevo applicare la buona metodologia orientata agli oggetti che ho imparato dalla mia educazione, ho immediatamente visto gli oggetti di gioco come oggetti C ++. Potion, Swords, Food, Axe, JavelinsEcc tutti derivati da un nucleo di base Itemdi codice, nome, l'icona, il peso, questo genere di cose la manipolazione.

Quindi, ho codificato la logica della borsa e ho riscontrato un problema. Posso mettere Itemsla borsa, ma poi, se ne scelgo una, come faccio a sapere se è una Potiono una Sword? Ho cercato su Internet come abbattere gli oggetti. Ho hackerato le opzioni del compilatore per abilitare le informazioni di runtime in modo da sapere quali sono i miei Itemveri tipi astratti e ho iniziato a cambiare logica sui tipi per sapere quali operazioni avrei consentito (ad esempio, evitare di bere Swordse mettermi in Potionspiedi)

Fondamentalmente ha trasformato il codice in una grande palla di fango semi-copypasted. Mentre il progetto andava avanti, ho iniziato a capire quale fosse il fallimento del progetto. Quello che è successo è che ho provato a usare le classi per rappresentare i valori. La mia gerarchia di classe non era un'astrazione significativa. Quello che ho dovuto fare è fare Iteml'attuazione di una serie di funzioni, come ad esempio Equip (), Apply ()e Throw (), e rendono ogni comportarsi sulla base di valori. Usare enum per rappresentare gli slot di equipaggiamento. Usare gli enum per rappresentare diversi tipi di armi. Utilizzando più valori e meno classificazione, perché la mia sottoclasse non aveva altro scopo che quello di riempire questi valori finali.

Dal momento che stai affrontando la moltiplicazione degli oggetti sotto uno strato di astrazione, penso che la mia intuizione possa essere preziosa qui. Se sembra che tu abbia troppi tipi di oggetto, è possibile che tu stia confondendo i tipi che dovrebbero essere con quelli che dovrebbero essere valori.


2
Il problema era confondere i tipi con le classi di valori (nota: non sto usando la classe di parole per fare riferimento alla nozione di classe OOP / C ++). Esiste più di un modo per rappresentare un punto in un piano cartesiano (coordinate rettangolari, polari coordinate) ma fanno tutti parte dello stesso tipo. Tuttavia, i linguaggi OOP tradizionali non supportano la classificazione dei valori in modo naturale. La cosa più vicina a questa è l'unione C / C ++, ma è necessario aggiungere un campo in più solo per sapere che diamine hai messo lì.
Doval,

4
La tua esperienza può essere usata come esempio. Non utilizzando SOLID né alcun tipo di metodologia di modellazione, è stato creato un codice non mantenibile e non estensibile.
Euforico

1
@Euforico Ci ho provato molto. Avevo una metodologia orientata agli oggetti. C'è una differenza tra avere una metodologia e implementarla con successo :)
Arthur Havlicek,

2
In che modo questo è un esempio dei principi SOLID in esecuzione in contrasto con il codice pulito in OOP? Sembra più un esempio di un design errato: questo è ortogonale a OOP!
Andres F.

1
La tua classe ITEM di base avrebbe dovuto avere un numero di metodi booleani "canXXXX" tutti in default su FALSE. È quindi possibile impostare "canThrow ()" per restituire true nella classe Javelin e canEat () per restituire true per la classe Postion.
James Anderson,

3

La sua preoccupazione era che il gran numero di classi si traducesse in un incubo di manutenzione. La mia opinione era che avrebbe avuto esattamente l'effetto opposto.

Sono assolutamente dalla parte del tuo amico, ma potrebbe essere una questione dei nostri domini e dei tipi di problemi e progetti che affrontiamo e soprattutto quali tipi di cose potrebbero richiedere cambiamenti in futuro. Problemi diversi, soluzioni diverse. Non credo nel giusto o nel male, solo i programmatori cercano di trovare il modo migliore per risolvere al meglio i loro particolari problemi di progettazione. Lavoro in VFX che non è molto diverso dai motori di gioco.

Ma il problema per me con cui ho lottato in quella che potrebbe almeno essere definita un po 'più di un'architettura conforme a SOLID (era basata su COM), potrebbe essere grossolanamente ridotta a "troppe classi" o "troppe funzioni" come il tuo amico potrebbe descrivere. Direi in particolare "troppe interazioni, troppi posti che potrebbero comportarsi in modo anomalo, troppi posti che potrebbero causare effetti collaterali, troppi posti che potrebbero dover cambiare e troppi posti che potrebbero non fare ciò che pensiamo che facciano ".

Avevamo una manciata di interfacce astratte (e pure) implementate da un carico di sottotipi, in questo modo (fatto questo diagramma nel contesto di parlare dei vantaggi di ECS, ignorando il commento in basso a sinistra):

inserisci qui la descrizione dell'immagine

Laddove un'interfaccia di movimento o un'interfaccia di nodo scena potrebbe essere implementata da centinaia di sottotipi: luci, telecamere, mesh, solutori di fisica, shader, trame, ossa, forme primitive, curve, ecc. Ecc. (E c'erano spesso più tipi di ciascuno ). E l'ultimo problema era proprio che quei progetti non erano così stabili. Avevamo esigenze mutevoli e talvolta le interfacce stesse dovevano cambiare, e quando vuoi cambiare un'interfaccia astratta implementata da 200 sottotipi, questo è un cambiamento estremamente costoso. Abbiamo iniziato a mitigarlo utilizzando classi di base astratte tra le quali ridurre i costi di tali modifiche di progettazione, ma erano comunque costose.

Quindi, in alternativa, ho iniziato a esplorare l'architettura del sistema entità-componente utilizzata piuttosto comunemente nel settore dei giochi. Ciò ha cambiato tutto per essere così:

inserisci qui la descrizione dell'immagine

E wow! Questa era una differenza in termini di manutenibilità. Le dipendenze non fluivano più verso le astrazioni , ma verso i dati (componenti). E almeno nel mio caso, i dati erano molto più stabili e più facili da ottenere in termini di progettazione in anticipo nonostante i requisiti mutevoli (anche se ciò che possiamo fare con gli stessi dati cambia costantemente con i requisiti in evoluzione).

Inoltre, poiché le entità in un ECS utilizzano la composizione anziché l'ereditarietà, in realtà non devono contenere funzionalità. Sono solo il "contenitore di componenti" analogico. Che ha reso così i analogiche 200 sottotipi che hanno implementato un moto un'interfaccia svolta in 200 entità casi (non tipi separati con codice separato) che memorizzano semplicemente un movimento componente (che non è altro che dati associati con il movimento). A PointLightnon è più una classe / sottotipo separato. Non è affatto una lezione. È un'istanza di un'entità che combina solo alcuni componenti (dati) relativi a dove si trova nello spazio (movimento) e alle proprietà specifiche delle luci puntiformi. L'unica funzionalità ad essi associata è all'interno dei sistemi, come ilRenderSystem, che cerca componenti leggeri nella scena per determinare come renderizzare la scena.

Con la modifica dei requisiti nell'ambito dell'approccio ECS, spesso c'era solo la necessità di cambiare uno o due sistemi che operano su quei dati o semplicemente introdurre un nuovo sistema sul lato o introdurre un nuovo componente se fossero necessari nuovi dati.

Quindi, almeno per il mio dominio, e sono quasi certo che non lo sia per tutti, questo ha reso le cose molto più facili perché le dipendenze scorrevano verso la stabilità (cose che non avevano bisogno di cambiare spesso). Questo non era il caso dell'architettura COM quando le dipendenze scorrevano uniformemente verso le astrazioni. Nel mio caso è molto più facile capire quali dati sono necessari per il movimento in anticipo piuttosto che tutte le possibili cose che potresti fare con esso, che spesso cambia un po 'nel corso dei mesi o degli anni quando arrivano nuovi requisiti.

inserisci qui la descrizione dell'immagine

Ci sono casi in OOP in cui alcuni o tutti i principi SOLID non si prestano a pulire il codice?

Bene, codice pulito non posso dire dal momento che alcune persone identificano il codice pulito con SOLID, ma sicuramente ci sono alcuni casi in cui separare i dati dalla funzionalità come fa l'ECS e reindirizzare le dipendenze dalle astrazioni verso i dati può sicuramente rendere le cose molto più facili da cambiare, per ovvi motivi di accoppiamento, se i dati saranno molto più stabili delle astrazioni. Naturalmente le dipendenze dai dati possono rendere difficile il mantenimento degli invarianti, ma ECS tende a mitigarlo al minimo con l'organizzazione di sistema che minimizza il numero di sistemi che accedono a un dato tipo di componente.

Non è necessariamente che le dipendenze dovrebbero fluire verso le astrazioni come suggerirebbe DIP; le dipendenze dovrebbero fluire verso cose che difficilmente avranno bisogno di cambiamenti futuri. Ciò può essere o non essere astrazioni in tutti i casi (certamente non era nel mio).

  1. Sì, esistono principi di progettazione OOP che sono parzialmente in conflitto con SOLID
  2. Sì, esistono principi di progettazione OOP che sono completamente in conflitto con SOLID.

Non sono sicuro che l'ECS sia davvero un sapore di OOP. Alcune persone lo definiscono in questo modo, ma lo vedo molto diverso intrinsecamente dalle caratteristiche di accoppiamento e dalla separazione dei dati (componenti) dalla funzionalità (sistemi) e dalla mancanza di incapsulamento dei dati. Se dovesse essere considerato una forma di OOP, penso che sia molto in conflitto con SOLID (almeno le idee più rigorose di SRP, open / closed, sostituzione di liskov e DIP). Ma spero che questo sia un esempio ragionevole di un caso e dominio in cui gli aspetti più fondamentali di SOLID, almeno come le persone generalmente li interpreterebbero in un contesto OOP più riconoscibile, potrebbero non essere così applicabili.

Classi per adolescenti

Stavo spiegando l'architettura di uno dei miei giochi che, con sorpresa del mio amico, conteneva molte piccole classi e diversi livelli di astrazione. Ho sostenuto che questo è stato il risultato del mio concentrarsi sul dare a tutto una singola responsabilità e anche per allentare l'accoppiamento tra i componenti.

L'ECS ha sfidato e cambiato molto le mie opinioni. Come te, pensavo che l'idea stessa di manutenibilità abbia l'implementazione più semplice per le cose possibili, il che implica molte cose e, inoltre, molte cose interdipendenti (anche se le interdipendenze sono tra le astrazioni). Ha più senso se si esegue lo zoom in avanti su una sola classe o funzione per vedere l'implementazione più semplice e semplice, e se non ne vediamo una, rifattorizzarla e magari anche scomporla ulteriormente. Ma può essere facile perdere ciò che sta succedendo con il mondo esterno di conseguenza, perché ogni volta che dividi qualcosa di relativamente complesso in 2 o più cose, quelle 2 o più cose devono inevitabilmente interagire * (vedi sotto) in alcune modo, o qualcosa al di fuori deve interagire con tutti loro.

In questi giorni trovo che ci sia un atto di equilibrio tra la semplicità di qualcosa e quante cose ci sono e quanta interazione è richiesta. I sistemi in un ECS tendono ad essere piuttosto pesanti con implementazioni non banali per operare sui dati, come PhysicsSystemo RenderSystemo GuiLayoutSystem. Tuttavia, il fatto che un prodotto complesso abbia bisogno di così pochi di essi tende a facilitare il passo indietro e ragionare sul comportamento generale dell'intera base di codice. C'è qualcosa lì che potrebbe suggerire che potrebbe non essere una cattiva idea appoggiarsi al lato di un numero inferiore di classi più voluminose (ancora eseguendo una responsabilità discutibilmente singolare), se ciò significa meno classi da mantenere e ragionare, e meno interazioni durante il sistema.

interazioni

Dico "interazioni" piuttosto che "accoppiamento" (sebbene ridurre le interazioni implica ridurre entrambi), poiché è possibile utilizzare le astrazioni per disaccoppiare due oggetti concreti, ma si parlano comunque. Potrebbero comunque causare effetti collaterali nel processo di questa comunicazione indiretta. E spesso trovo che la mia capacità di ragionare sulla correttezza di un sistema sia correlata a queste "interazioni" più che all '"accoppiamento". Ridurre al minimo le interazioni tende a rendere le cose molto più facili per me ragionare su tutto da una prospettiva a volo d'uccello. Ciò significa che le cose non parlano affatto tra loro, e da quel senso, l'ECS tende anche a minimizzare davvero le "interazioni", e non solo l'accoppiamento, con il minimo dei minimi (almeno non ho '

Detto questo, questo potrebbe essere almeno in parte io e le mie debolezze personali. Ho trovato il più grande impedimento per me nel creare sistemi di dimensioni enormi, e ancora con fiducia ragionare su di essi, navigare attraverso di essi e sentirmi come se potessi apportare qualsiasi potenziale cambiamento desiderato ovunque in modo prevedibile, è la gestione dello stato e delle risorse insieme a effetti collaterali. È il più grande ostacolo che inizia a sorgere mentre vado da decine di migliaia di LOC a centinaia di migliaia di LOC a milioni di LOC, anche per il codice che ho creato completamente da solo. Se qualcosa mi rallenta in una scansione sopra ogni altra cosa, è questo senso che non riesco più a capire cosa sta succedendo in termini di stato dell'applicazione, dati, effetti collaterali. E' Non è il tempo robotico che richiede per fare un cambiamento che mi rallenta tanto quanto l'incapacità di comprendere gli impatti completi del cambiamento se il sistema cresce oltre la capacità della mia mente di ragionarci. E ridurre le interazioni è stato, per me, il modo più efficace per consentire al prodotto di crescere molto più grande con molte più funzionalità senza che io venissi personalmente sopraffatto da queste cose, poiché ridurre le interazioni al minimo riduce anche il numero di posti che possono anche possibilmente cambiare lo stato dell'applicazione e causare effetti collaterali sostanzialmente.

Può trasformare qualcosa del genere (in cui tutto nel diagramma ha funzionalità e ovviamente uno scenario del mondo reale avrebbe molti, molte volte il numero di oggetti, e questo è un diagramma di "interazione", non di accoppiamento, come accoppiamento uno avrebbe astrazioni tra):

inserisci qui la descrizione dell'immagine

... a questo dove solo i sistemi hanno funzionalità (i componenti blu ora sono solo dati, e ora questo è uno schema di accoppiamento):

inserisci qui la descrizione dell'immagine

E ci sono pensieri emergenti su tutto questo e forse un modo per inquadrare alcuni di questi benefici in un contesto OOP più conforme che è più compatibile con SOLID, ma non ho ancora trovato i disegni e le parole, e lo trovo difficile dal momento che la terminologia ero abituato a gettare tutto in relazione direttamente con OOP. Continuo a cercare di capirlo leggendo le risposte delle persone qui e anche facendo del mio meglio per formulare le mie, ma ci sono alcune cose molto interessanti sulla natura di un ECS che non sono riuscito a mettere perfettamente il dito su quello potrebbe essere più ampiamente applicabile anche alle architetture che non lo utilizzano. Spero anche che questa risposta non venga fuori come una promozione ECS! Lo trovo molto interessante dal momento che la progettazione di un ECS ha davvero cambiato drasticamente i miei pensieri,


Mi piace la distinzione tra interazioni e accoppiamento. Sebbene il tuo primo diagramma di interazione sia offuscato. È un grafico planare, quindi non è necessario incrociare le frecce.
D Drmmr,

2

I principi SOLIDI sono buoni, ma KISS e YAGNI hanno la priorità in caso di conflitti. Lo scopo di SOLID è gestire la complessità, ma se l'applicazione del SOLID stesso rende il codice più complesso, ne vanifica lo scopo. Per fare un esempio, se hai un piccolo programma con solo poche classi, l'applicazione di DI, la segregazione delle interfacce ecc. Potrebbe aumentare la complessità complessiva del programma.

Il principio aperto / chiuso è particolarmente controverso. Sostanzialmente dice che dovresti aggiungere funzionalità a una classe creando una sottoclasse o un'implementazione separata della stessa interfaccia, ma lasciando intatta la classe originale. Se si sta mantenendo una libreria o un servizio utilizzato da client non coordinati, questo ha senso, poiché non si desidera interrompere il comportamento su cui può fare affidamento il client in uscita. Tuttavia, se si sta modificando una classe che viene utilizzata solo nella propria applicazione, spesso sarebbe molto più semplice e pulito modificare semplicemente la classe.


La tua discussione su OCP ignora la possibilità di estendere il comportamento di una classe tramite la composizione (ad es. L'utilizzo di un oggetto strategia per consentire la modifica di un algoritmo utilizzato dalla classe), che è generalmente ritenuto un modo migliore di conformarsi al principale rispetto alla sottoclasse .
Jules,

1
Se KISS e YAGNI hanno sempre avuto la priorità, dovresti comunque scrivere codice tra 1 e 0. Gli assemblatori sono solo qualcos'altro che può andare storto. Nessun KISS e YAGNI sottolineano due dei tanti costi di progettazione. Tale costo dovrebbe essere sempre bilanciato rispetto ai vantaggi di qualsiasi lavoro di progettazione. SOLID presenta vantaggi che in alcuni casi compensano i costi. Non esiste un modo semplice per dire quando hai attraversato la linea.
candied_orange

@CandiedOrange: non sono d'accordo sul fatto che il codice macchina sia più semplice del codice in un linguaggio di alto livello. Il codice macchina finisce per contenere molta complessità accidentale a causa della mancanza di astrazione, quindi non è assolutamente KISS decidere di scrivere un'applicazione tipica nel codice macchina.
JacquesB,

@Jules: Sì, la composizione è preferibile, ma richiede comunque di progettare la classe originale in modo tale che la composizione possa essere utilizzata per personalizzarla, quindi è necessario fare ipotesi su quali aspetti devono essere personalizzati in futuro e quali aspetti non sono personalizzabili. In alcuni casi ciò è necessario (ad esempio, si sta scrivendo una libreria per la distribuzione) ma ha un costo, quindi seguire SOLID non è sempre ottimale.
JacquesB,

1
@JacquesB - true. E OCP è essenzialmente al centro di YAGNI; per quanto tu lo raggiunga, è necessario progettare punti di estensione per consentire un successivo miglioramento. Trovare un giusto punto di equilibrio tra i due ideali è ... difficile.
Jules,

1

Nella mia esperienza:-

Le classi più grandi sono esponenzialmente più difficili da mantenere quando diventano più grandi.

Le classi piccole sono più facili da mantenere e la difficoltà di mantenere una raccolta di classi piccole aumenta aritmeticamente al numero di classi.

A volte le grandi classi sono inevitabili, un grosso problema a volte ha bisogno di una grande classe, ma sono molto più difficili da leggere e comprendere. Per classi più piccole ben nominate solo il nome può essere sufficiente per comprendere che non è nemmeno necessario guardare il codice a meno che non ci sia un problema molto specifico con la classe.


0

Trovo sempre difficile tracciare una linea di demarcazione tra la "S" del solido e la necessità (può essere antiquata) di lasciare che un oggetto abbia tutta la conoscenza di se stesso.

15 anni fa ho lavorato con gli sviluppatori Deplhi che avevano il loro santo graal per tenere lezioni che contenessero tutto. Quindi per un mutuo potresti aggiungere assicurazioni e pagamenti, entrate e prestiti e informazioni fiscali e puoi calcolare le tasse da pagare, le tasse da detrarre e potrebbe serializzarsi, persistere su disco ecc. Ecc.

E ora ho lo stesso oggetto suddiviso in moltissime classi più piccole che hanno il vantaggio di poter essere testate in unità molto più facilmente e meglio, ma non offrono una buona panoramica dell'intero modello di business.

E sì, lo so che non vuoi legare i tuoi oggetti al database ma puoi iniettare un repository nella grande classe dei mutui per risolverlo.

Inoltre, non è sempre facile trovare buoni nomi per le classi che fanno solo una piccola cosa.

Quindi non sono sicuro che la "S" sia sempre una buona idea.

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.