Bugia 2: il codice dovrebbe essere progettato attorno a un modello del mondo? [chiuso]


23

Di recente ho letto il post sul blog di Three Big Lies e sto facendo fatica a giustificare la seconda bugia, che è citata qui:

(LIE # 2) IL CODICE DOVREBBE ESSERE PROGETTATO INTORNO A UN MODELLO DEL MONDO

Non c'è valore nel codice come una sorta di modello o mappa di un mondo immaginario. Non so perché questo sia così avvincente per alcuni programmatori, ma è estremamente popolare. Se c'è un razzo nel gioco, sii certo che esiste una classe "Razzo" (Supponendo che il codice sia C ++) che contenga dati per esattamente un razzo e faccia cose rozze. Indipendentemente da ciò che la trasformazione dei dati viene realmente eseguita o dal layout dei dati. O del resto, senza la comprensione di base che dove c'è una cosa, probabilmente ce n'è più di una.

Sebbene ci siano molte penalità in termini di prestazioni per questo tipo di design, il più significativo è che non si ridimensiona. Affatto. Cento razzi costano cento volte tanto quanto un razzo. Ed è estremamente probabile che costi anche di più! Anche per un non programmatore, questo non dovrebbe avere alcun senso. Economia di scala. Se hai più di qualcosa, dovrebbe diventare più economico, non più costoso. E il modo per farlo è progettare correttamente i dati e raggruppare le cose con trasformazioni simili.

Ecco i miei problemi con questa bugia in particolare.

  1. C'è valore nel codice come modello / mappa di un mondo immaginario poiché la modellazione del mondo immaginario aiuta (almeno io, personalmente) a visualizzare e organizzare il codice.

  2. Avere una classe "Rocket" è, per me, una scelta perfettamente valida per una classe. Forse i "Razzi" potrebbero essere suddivisi in tipi di Razzi come AGM-114 Hellfire, ecc. Che conterrebbero forza di carico utile, velocità massima, raggio di sterzata massimo, tipo di bersaglio e così via, ma comunque ogni razzo lanciato dovrebbe avere una posizione e una velocità.

  3. Naturalmente avere 100 missili costa più di 1 missile. Se ci sono 100 missili sullo schermo, ci devono essere 100 diversi calcoli per aggiornare la loro posizione. Il secondo paragrafo sembra sostenere che se ci sono 100 missili, dovrebbe costare meno di 100 calcoli per aggiornare lo stato?

Il mio problema qui è che l'autore presenta un modello di programmazione "imperfetto" ma non presenta un modo per "correggerlo". Forse sto inciampando sull'analogia della classe Rocket, ma mi piacerebbe davvero capire il ragionamento alla base di questa bugia. Qual è l'alternativa?


9
@gnat: questa domanda rientra esattamente nella provincia del design del software, quindi sono propenso a darle un po 'di margine di manovra.
Robert Harvey,

12
Quel post sul blog è scritto in modo piuttosto scadente e non difende e supporta troppo bene le sue affermazioni. Non ci penserei molto.
whatsisname

21
Chiunque abbia scritto quella citazione è un idiota con scarsa comprensione dei concetti di OO o di come questi vengono implementati nel software. Primo, non stiamo mappando su un mondo immaginario, stiamo mappando sul mondo reale. E se hai 100 missili, solo lo stato dei missili aggiuntivi utilizza risorse aggiuntive, non il modello o il comportamento. Sembra avere idee diverse a riguardo e suggerisce di risolvere un problema che non esiste. "Raggruppare cose simili" come un'ottimizzazione a volte può avere senso, ma è totalmente indipendente dall'uso delle classi o meno. Se vuoi imparare, stai alla larga da questo ciarlatano.
Martin Maat,

3
Considerando che l'autore non si è preoccupato di scrivere più di 5 paragrafi in totale spiegando le "3 grandi bugie", probabilmente hai passato più tempo a pensare all'articolo che a lui. Se non si preoccuperà di fare uno sforzo, non dovresti neanche.
Caleb,

9
Penso che ciò a cui sta arrivando è: hai davvero bisogno di 100 "probabilmente allocati dinamicamente anche con metodi virtuali]" oggetti a razzo ", al contrario di un elenco di posizioni, un elenco di velocità, ecc. (Con un elenco di tutte le posizioni e un elenco di velocità significa che potresti essere in grado di utilizzare le istruzioni vettoriali per aggiungere la velocità alla posizione su ciascun aggiornamento tick anziché scrivere un ciclo ingenuo attraverso un elenco di oggetti)
Casuale 832

Risposte:


63

Innanzitutto, diamo un'occhiata al contesto: questo è un game designer che scrive su un blog il cui argomento sta ottenendo l'ultimo calo di prestazioni da una CPU BE Cell. In altre parole: si tratta della programmazione di giochi per console, in particolare della programmazione di giochi per console per PlayStation 3.

Ora, i programmatori di giochi sono un gruppo curioso, i programmatori di giochi per console lo sono ancora di più e Cell BE è una CPU piuttosto strana. (C'è un motivo per cui Sony ha scelto un design più convenzionale per PlayStation 4!)

Quindi, dobbiamo guardare quelle affermazioni in questo contesto.

Ci sono anche alcune semplificazioni in quel post sul blog. In particolare, questa bugia n. 2 è presentata male.

Direi che tutto ciò che si estrae dal mondo reale è un modello in un certo senso. E poiché il software non è reale, ma virtuale, è sempre un'astrazione e quindi sempre un modello. Ma! Un modello non deve avere una mappatura 1: 1 pulita sul mondo reale. Dopotutto, questo è ciò che lo rende un modello.

Quindi, in un certo senso, l'autore ha chiaramente torto: il software è un modello. Periodo. In qualche altro senso, ha ragione: quel modello non deve assolutamente assomigliare al mondo reale.

Darò un esempio che ho già dato in alcune altre risposte nel corso degli anni, il (in) famoso esempio di Introduzione al conto bancario OO 101. Ecco come appare un conto bancario in quasi tutte le classi OO di sempre:

class Account {
  var balance: Decimal
  def transfer(amount: Decimal, target: Account) = {
    balance -= amount
    target.balance += amount
  }
}

Quindi: balancesono i dati ed transferè un'operazione .

Ma! Ecco come appare un conto bancario in quasi tutti i software bancari di sempre:

class TransactionSlip {
  val transfer(amount: Decimal, from: Account, to: Account)
}

class Account {
  def balance = 
    TransactionLog.filter(t => t.to == this).map(_.amount).sum - 
    TransactionLog.filter(t => t.from == this).map(_.amount).sum
}

Quindi, ora transfersono dati ed balanceè un'operazione (una piega a sinistra sul registro delle transazioni). (Noterai anche che TransactionSlipè immutabile, balanceè una funzione pura, TransactionLogpuò essere una struttura dati "quasi" immutabile solo in appendice ... Sono sicuro che molti di voi hanno individuato gli evidenti bug di concorrenza nella prima implementazione, che ora magicamente scompaiono .)

Si noti che entrambi questi sono modelli. Entrambi sono ugualmente validi. Entrambi sono corretti. Entrambi questi modelli la stessa cosa. Eppure, sono esattamente duali l'uno con l'altro: tutto ciò che è dato in un modello è un'operazione nell'altro modello, e tutto ciò che è un'operazione in un modello è dato nell'altro modello.

Quindi, la domanda non è se modellare il "mondo reale" nel codice, ma come modellarlo.

A quanto pare, il secondo modello è in realtà anche il funzionamento del sistema bancario nel mondo reale. Come ho accennato in precedenza, questo secondo modello è per lo più immutabile e puro e immune da bug di concorrenza, il che è in realtà molto importante se si considera che c'è stato un tempo non molto tempo fa, in cui TransactionSliperano veri e propri foglietti di carta che venivano inviati in giro via cavallo e carrozza.

Tuttavia, il fatto che questo secondo modello corrisponda effettivamente sia al modo in cui funziona il mondo reale bancario sia al modo in cui funziona il software del mondo reale, non lo rende automaticamente in qualche modo più "giusto". Perché, in realtà, il primo modello ("sbagliato") si avvicina abbastanza da vicino al modo in cui i clienti bancari vedono la propria banca. Per loro , transferè un'operazione (devono compilare un modulo), ed balanceè un pezzo di dati nella parte inferiore del loro estratto conto.

Quindi, potrebbe benissimo essere vero che nel codice del motore di gioco principale di uno sparatutto PS3 ad alte prestazioni, non ci sarà un Rockettipo, ma comunque, ci sarà qualche modello del mondo in corso, anche se il modello sembra strano a qualcuno che non è un esperto nel dominio della programmazione del motore fisico dei giochi per console.


1
Ciò non significherebbe che il buon codice modella il mondo reale e che in realtà è solo un fraintendimento del mondo reale che causa un modello cattivo e quindi un codice cattivo?
yitzih,

14
Preferirei definirlo "a volte, il mondo reale non è quello che pensi che sia" o "ciò che è" il mondo reale "dipende dal contesto". (Ancora una volta, per il proprietario di un conto bancario, i dati in fondo alla loro dichiarazione sono molto reali, mentre per un impiegato di banca è effimero.) Penso che la dichiarazione nel post del blog sia principalmente causata dall'autore che non capisce che "modellare il mondo reale "non significa" scattare una fotografia e trasformare in classe tutto ciò che vedi lì ".
Jörg W Mittag,

Il front-end per la tua applicazione di banking online probabilmente tratterà balancecome dati e transazioni come più dati e trasferirà come operazioni, perché questo è ciò che l'utente vede, anche se il back-end potrebbe trattarlo in modo diverso.
user253751

@yitzih: ogni modello è un'astrazione e una semplificazione, quindi potresti accusare ogni modello di essere errato, ma non è costruttivo. Ogni modello deve raggiungere uno scopo e deve essere abbastanza buono per quello, non sprecare risorse per cose non necessarie. Per il software di un governo, un essere umano potrebbe essere qualcuno che può partecipare alle elezioni, deve pagare le tasse o può essere sposato con un altro essere umano, per il nostro software CRM, un essere umano è qualcuno che è associato ad ordini e ha un indirizzo di consegna (e nessuno dei due modella come (s) mangia) ...
Holger

2
Se l'essere umano sa qualcosa del settore bancario , troverà il secondo più semplice e, poiché le tecniche bancarie di cui sono a conoscenza sono state inventate per far funzionare il sistema bancario, possono creare software bancario che funziona. Non perché il secondo modello è "più simile al mondo reale", ma perché descrive una banca migliore. Il primo modello potrebbe essere una rappresentazione altrettanto accurata di una banca disfunzionale del mondo reale! Indovina un po ': se vuoi un buon software bancario, i programmatori devono imparare a fare bene il banking, se non altro dai documenti richiesti.
Steve Jessop,

19

Non sono d'accordo con ogni "bugia" che propone.

TL; DR L'autore di questo articolo stava cercando di essere controverso per rendere il loro articolo più interessante, ma le cosiddette "bugie" sono accettate dagli sviluppatori di software per buoni motivi.

Bugia # 1 - Big O è importante ai fini del ridimensionamento. A nessuno importa se una piccola applicazione impiega un tempo più lungo, l'unica cosa che conta le costanti di tempo, a loro importa che quando raddoppiano le dimensioni di input non moltiplica il tempo di esecuzione di un fattore 10.

Bugia # 2 - Modellare i programmi dopo il mondo reale consente a un programmatore di guardare il tuo codice 3 anni dopo per capire facilmente cosa sta facendo. Il codice deve essere gestibile o dovresti passare ore solo cercando di capire cosa sta cercando di fare il programma. Un'altra risposta ha suggerito che puoi avere classi più generiche come LaunchPade MassiveDeviceMover. Queste non sono necessariamente cattive classi da avere, ma avresti comunque bisogno della Rocketclasse. Come si può sapere che cosa MassiveDeviceMoverfa o cosa si muove? Muove montagne, astronavi o pianeti? Ciò significa sostanzialmente che l'aggiunta di classi come MassiveDeviceMoverrende il programma meno efficiente (ma probabilmente molto più leggibile e comprensibile).

Inoltre, il tempo di sviluppo degli sviluppatori ha iniziato a superare il costo dell'hardware molto tempo fa. È un'idea orribile iniziare cercando di progettare con l'ottimizzazione nella parte anteriore dei tuoi pensieri. Lo programmate in modo semplice e comprensibile, quindi modificate il programma dopo aver scoperto quali parti dei programmi impiegano molto tempo per essere eseguite. Non dimenticare: l' 80% dei tempi di esecuzione è utilizzato dal 20% del programma.

Bugia # 3 - Il codice è estremamente importante. Il codice ben scritto (e modulare) consente di riutilizzare (risparmiando innumerevoli ore di lavoro). Inoltre, consente di esaminare e riconoscere i dati errati in modo che possano essere gestiti. I dati sono meravigliosi, ma senza il codice sarebbe impossibile analizzare e ottenere informazioni utili da esso.


5
Penso di essere più solidale con il n. 3. In 30 anni di programmazione, la stragrande maggioranza di bug, problemi di prestazioni e altri problemi che ho visto sono stati risolti risolvendo la rappresentazione dei dati. Se i dati sono corretti, il codice praticamente si scrive da solo.
Lee Daniel Crocker,

6
Il vero problema con il n. 3 è che confronta le mele con le arance, non che il codice sia più importante dei dati o viceversa.
Doc Brown,

3
I dati di input sono a portata di mano, ma il modo in cui rappresentare i dati nel software è interamente al loro interno. Potresti chiamare quella parte di "codifica", ma penso che non lo sia: ad esempio, è spesso indipendente dalla lingua e viene spesso eseguita prima dell'inizio di qualsiasi codifica. Sono d'accordo, tuttavia, che il codice che pulisce i dati di input brutti è spesso una buona cosa; ma non puoi farlo finché non hai una definizione di "pulito".
Lee Daniel Crocker,

3
In realtà, non penso che Lie 3 sia una Lie. Fred Brooks ha già scritto decenni fa: "Mostrami i tuoi diagrammi di flusso e nascondi i tuoi tavoli, e io continuerò ad essere sconcertato. Mostrami i tuoi tavoli e di solito non avrò bisogno dei tuoi diagrammi di flusso; saranno ovvi". (Al giorno d'oggi, probabilmente parleremmo di "algoritmi" e "tipi di dati" o "schemi"). Pertanto, l'importanza dei dati è nota da molto tempo.
Jörg W Mittag,

1
@djechlin Il mio punto non era che i dati non fossero importanti o che il codice fosse più importante. Semplicemente quei dati non sono più importanti del codice. Sono entrambi molto importanti e fanno molto affidamento l'uno sull'altro.
yitzih,

6

In un sistema di e-commerce, non ti occupi di "razzi" a livello di classe, ti occupi di "prodotti". Quindi dipende da cosa stai cercando di realizzare e dal tuo livello di astrazione desiderato.

In un gioco, si potrebbe sostenere che i razzi siano semplicemente uno dei tanti tipi di "oggetti in movimento". La stessa fisica si applica a loro come a tutti gli altri oggetti in movimento. Quindi, almeno, il "razzo" erediterà una classe base più generale di "oggetti in movimento".

In ogni caso, l'autore del brano che hai citato sembra aver esagerato un po 'il suo caso. I razzi possono ancora avere caratteristiche uniche, come "quantità di carburante rimanente" e "spinta", e non hai bisogno di un centinaio di classi per rappresentarlo per un centinaio di razzi, ne hai solo bisogno. La creazione di oggetti ha un costo abbastanza basso nella maggior parte dei linguaggi di programmazione decenti, quindi se devi tenere traccia di cose simili a un razzo, l'idea che non dovresti creare oggetti Rocket perché potrebbe essere troppo costoso non ha molto senso.


5

Il problema con il mondo reale è tutta quella maledetta fisica. Separiamo le cose in oggetti fisici nel mondo reale perché sono più facili da spostare rispetto ai singoli atomi, o una gigantesca scoria fusa di qualcosa che potrebbe potenzialmente essere un razzo.

Allo stesso modo, il mondo reale offre una serie di funzioni utili su cui facciamo affidamento. È davvero facile fare eccezioni Penguin - "tutti gli uccelli volano, tranne ...". Ed è davvero facile etichettare le cose come razzi, voglio dire se chiamo quel pinguino un razzo e lo accendo ... semplicemente non funziona.

Quindi, il modo in cui separiamo le cose nel mondo reale funziona concettualmente sotto tali vincoli. Quando stiamo facendo cose nel codice, dovremmo separare le cose per funzionare bene sotto quei vincoli, che sono decisamente diversi.

Qual è l'alternativa?

Pensa alle reti. Non modelliamo porte, fili e router nel codice. Invece noi astraggiamo la comunicazione di rete in connessioni e protocolli. Lo facciamo perché è un'astrazione utile indipendentemente dall'implementazione nel mondo reale. E pone utili vincoli (ad esempio: non è possibile comunicare fino all'apertura della connessione) che contano solo nel codice .

Quindi sì, a volte modellare il codice dopo che il mondo reale funziona, ma questa è una coincidenza . Quando le persone parlano di OOP, gli oggetti non sono oggetti del mondo reale. Che scuole e tutorial sostengano diversamente è una tragedia lunga decenni.


1
+1: I protocolli sono molto un'astrazione del "mondo reale". Anche nel mondo di oggi, gli agenti del protocollo sono alcune delle persone più importanti del personale per una visita di stato, per esempio. Chi sale per primo sul tappeto rosso alla riunione del G8, Obama o Putin? Si abbracciano o si stringono la mano? Come saluto un arabo contro un indiano? E così via. Abbiamo un sacco di "cose" nel "mondo reale" che non sono "cose" nel "mondo fisico". Modellare il mondo reale non significa modellare il mondo fisico. Anche se non c'è un Rockettipo nel codice di questo ragazzo, sono disposto a scommettere che c'è comunque qualche modello di ...
Jörg W Mittag

... il mondo reale, anche se non corrisponde a qualcosa di "fisico" (nel senso di "toccabile"). Non sarei troppo sorpreso di trovare oggetti "fisici" reali (nel senso di "cose ​​che un fisico potrebbe riconoscere") lì dentro, tuttavia, come quaternioni, tensori, campi, ecc. Che sono, ovviamente, anche " cose del mondo reale "e" modelli del mondo reale ".
Jörg W Mittag,

Alan Kay ha immaginato il Dynabook come un computer che sarebbe stato dato ai bambini alla nascita e che sarebbe diventato un'estensione del loro cervello. Lo scopo del modello MVC sarebbe quindi di far sì che View e Controller colmino il divario tra il cervello e il Modello per supportare la metafora della manipolazione diretta, ovvero l'illusione che il computer sia solo un'estensione del cervello e che si possa manipolare direttamente gli oggetti modello con i propri pensieri. Ed è quello che intendiamo quando diciamo che il modello di dominio modella il "mondo reale". Dovrebbe implementare le astrazioni nel nostro cervello.
Jörg W Mittag,

E quando penso a un motore fisico per videogiochi per console, allora probabilmente non penso ai razzi, e quindi nel mio codice non dovrebbe esserci un modello di razzo. Ma probabilmente sto pensando ad altri "pensieri del mondo reale", e ci dovrebbero essere modelli di quelli nel codice.
Jörg W Mittag,

2

L'alternativa è modellare le cose che interessano ai tuoi programmi. Anche se il tuo programma si occupa di missili, potrebbe non essere necessario avere un'entità chiamata a Rocket. Ad esempio, potresti avere LaunchPadun'entità, LaunchScheduleun'entità e MassiveDeviceMoverun'entità. Il fatto che tutto ciò sia di aiuto nel lancio di missili non significa che tu stia gestendo i missili stessi.


0

Il mio problema qui è che l'autore presenta un modello di programmazione "imperfetto" ma non presenta un modo per "correggerlo". Forse sto inciampando sull'analogia della classe Rocket, ma mi piacerebbe davvero capire il ragionamento alla base di questa bugia. Qual è l'alternativa?

Questo è il vero problema, ma ti darò la mia opinione come sviluppatore, forse ti aiuterà.

In primo luogo, non definirei bugie come idee sbagliate comuni. Chiamarlo bugie è solo hype.

Uno ha ragione, in qualche modo. Non passeremo molto tempo su questo, perché non fa parte della domanda. Ma in sostanza ha ragione. Potrei ribadirlo come "Ciò che funziona in un laboratorio potrebbe non funzionare nella vita reale". Troppe volte gli sviluppatori si attengono a un design che funziona in un "laboratorio" ma fallisce nelle applicazioni del mondo reale.

Tre suona un po 'insaponato per me, ma essenzialmente ha di nuovo ragione. Ma questo potrebbe essere riscritto per "scrivere codice in base alle tue esigenze, non cercare di adattare le esigenze al tuo codice".

Ancora due , qui ha ragione. Ho visto gli sviluppatori passare settimane o più a sviluppare una classe "a razzo" quando una semplice classe "veicolo" avrebbe funzionato, o una classe ancora più semplice, "mobile". Se tutto ciò che il tuo razzo deve fare è spostarti dalla parte sinistra dello schermo a destra e emettere un suono, allora puoi usare la stessa classe che hai fatto per auto, treni, barche e mosche. Il 100 dovrebbe costare meno dell'argomento 1 * 100 sembra essere nel tempo impiegato per lo sviluppo e non tanto nei costi di calcolo. Sebbene attenersi a un minor numero di classi generali che un riutilizzo sia "più economico", molte classi specifiche che non possono essere riutilizzate. Questo potrebbe probabilmente essere riscritto in quanto "le classi generali sono meglio di quelle specifiche,

In sostanza, l'intero articolo potrebbe essere riscritto, con meno parole d'ordine e al massimo sarebbe solo un paragrafo. Detto questo, è un post sul blog focalizzato su una ristretta area di programmazione. Ho fatto un po 'di programmazione integrata, e posso essere d'accordo, con l'idea generale alla base di queste affermazioni, anche se c'è un po' di "lanugine" attorno a loro per renderlo adatto per una presentazione al GDC.

Un'ultima nota, l'articolo è stato scritto nel 2008 (meglio che potessi dire). Le cose cambiano rapidamente. Le affermazioni sono vere oggi, ma i sistemi embedded sono molto più comuni oggi allora allora, e i modelli di sviluppo cambiano. Forse anche in risposta a questo articolo / talk.


-1

Trovo interessante che queste bugie siano incentrate su preoccupazioni accademiche: la piattaforma, l'efficienza dell'uso della memoria e i dati. Ma ignora completamente l'elemento umano.

Il software riguarda le esigenze delle persone. In genere, ciò viene quantificato in termini commerciali: ci sono clienti che desiderano qualcosa e sostenitori che sono disposti a pagare per realizzarlo. Se il software viene scritto in modo da soddisfare le esigenze di entrambi i lati dell'equazione, allora è un buon software, in caso contrario, è un cattivo software.

Se la piattaforma non è importante per il cliente, allora la piattaforma non è importante. Se l'efficienza della memoria non è importante per il cliente, non lo è. Se i dati non sono importanti per il cliente, i dati non sono importanti. Se il codice funziona, ma non può essere letto o mantenuto e il cliente desidera modifiche rapide e affidabili a un prezzo decente, allora un codice scritto male è una cosa negativa. Se il codice funziona, ma non può essere letto o mantenuto, e al cliente non importa o è disposto a pagare costosi refattori, allora un codice scritto male è una buona cosa.

La grande menzogna è che tutto tranne l'elemento umano è importante. Perché i dati sono importanti? Perché c'è qualche cliente o stakeholder che ne ha bisogno. Questa è la "grande verità".


4
Sfortunatamente i clienti vogliono un codice rapido e sporco che sia facile da leggere e mantenere, economico senza sforzi di test e senza bug.
Gonen I,

@ user889742 Ha! Vero. Hai affermato con precisione il problema ingegneristico che gli architetti hanno cercato di risolvere da sempre e che cosa rende l'industria uno spazio così interessante in cui lavorare.
Prezzo Jones,

Ignora l'elemento umano perché è uno sviluppatore di giochi e l'era della manutenzione di un gioco ha una vita relativamente breve, anche se oggi è più lunga di quanto non fosse nel 2008. Le patch del Day 1 sembrano essere la norma nei giochi ormai un giorno. Nel 2008 le patch per i giochi erano ancora relativamente rare.
RubberDuck,

-1

IMHO Se il codice è "progettato attorno a un modello del mondo", è più facile da capire, sia per i progettisti e gli sviluppatori, sia per i manutentori. Ma penso che non sia solo io, e non solo il software. Da Wikipedia: La modellistica scientifica è un'attività scientifica, il cui scopo è quello di rendere più facile da comprendere, definire, quantificare, visualizzare o simulare una particolare parte o caratteristica del mondo facendo riferimento a conoscenze esistenti e generalmente accettate

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.