Ho visto il discorso di Stuart Sierra " Pensare nei dati " e ne ho tratto una delle idee come principio progettuale in questo gioco che sto realizzando. La differenza sta lavorando su Clojure e sto lavorando su JavaScript. Vedo alcune importanti differenze tra le nostre lingue in quanto:
- Clojure è una programmazione idiomaticamente funzionale
- La maggior parte dello stato è immutabile
Ho preso l'idea dalla diapositiva "Tutto è una mappa" (da 11 minuti, 6 secondi a> 29 minuti). Alcune cose che dice sono:
- Ogni volta che vedi una funzione che accetta 2-3 argomenti, puoi fare un caso per trasformarla in una mappa e passare semplicemente una mappa. Ci sono molti vantaggi:
- Non devi preoccuparti dell'ordine degli argomenti
- Non devi preoccuparti di ulteriori informazioni. Se ci sono chiavi extra, questa non è davvero la nostra preoccupazione. Scorrono semplicemente, non interferiscono.
- Non è necessario definire uno schema
- Al contrario del passaggio in un oggetto non ci sono dati nascosti. Ma sostiene che nascondere i dati può causare problemi ed è sopravvalutato:
- Prestazione
- Facilità di implementazione
- Non appena comunichi attraverso la rete o attraverso i processi, devi comunque concordare entrambe le parti sulla rappresentazione dei dati. È un lavoro extra che puoi saltare se lavori solo sui dati.
Più pertinente alla mia domanda. Sono trascorsi 29 minuti in "Rendi compostabili le tue funzioni". Ecco l'esempio di codice che usa per spiegare il concetto:
;; Bad (defn complex-process [] (let [a (get-component @global-state) b (subprocess-one a) c (subprocess-two a b) d (subprocess-three a b c)] (reset! global-state d))) ;; Good (defn complex-process [state] (-> state subprocess-one subprocess-two subprocess-three))
Capisco che la maggior parte dei programmatori non ha familiarità con Clojure, quindi lo riscriverò in stile imperativo:
;; Good def complex-process(State state) state = subprocess-one(state) state = subprocess-two(state) state = subprocess-three(state) return state
Ecco i vantaggi:
- Facile da testare
- Facile da vedere quelle funzioni in isolamento
- È facile commentare una riga di questo e vedere qual è il risultato rimuovendo un singolo passaggio
- Ogni sottoprocesso potrebbe aggiungere ulteriori informazioni sullo stato. Se un sottoprocesso è necessario comunicare qualcosa con il sottoprocesso tre, è semplice come aggiungere una chiave / valore.
- Nessun boilerplate per estrarre i dati necessari dallo stato solo per poterli salvare nuovamente. Basta passare in tutto lo stato e lasciare che il sottoprocesso assegni ciò di cui ha bisogno.
Ora, tornando alla mia situazione: ho preso questa lezione e l'ho applicata al mio gioco. Cioè, quasi tutte le mie funzioni di alto livello accettano e restituiscono un gameState
oggetto. Questo oggetto contiene tutti i dati del gioco. Ad esempio: un elenco di badGuys, un elenco di menu, il bottino a terra, ecc. Ecco un esempio della mia funzione di aggiornamento:
update(gameState)
...
gameState = handleUnitCollision(gameState)
...
gameState = handleLoot(gameState)
...
Ciò di cui sono qui per chiedere è: ho creato un abominio che ha pervertito un'idea che è pratica solo in un linguaggio di programmazione funzionale? JavaScript non è idiomaticamente funzionale (anche se può essere scritto in quel modo) ed è davvero difficile scrivere strutture di dati immutabili. Una cosa che mi preoccupa è che presume che ognuno di questi sottoprocessi sia puro. Perché è necessario formulare tale presupposto? È raro che una qualsiasi delle mie funzioni sia pura (con ciò, intendo dire che spesso modificano il gameState
. Non ho altri effetti collaterali complicati oltre a quello). Queste idee cadono in pezzi se non si hanno dati immutabili?
Sono preoccupato che un giorno mi sveglierò e realizzerò che tutto questo design è una finzione e ho davvero implementato l' anti-pattern Big Ball Of Mud .
Onestamente, ho lavorato su questo codice per mesi ed è stato fantastico. Mi sembra di ottenere tutti i vantaggi che ha affermato. Il mio codice è semplicissimo per me ragionare. Ma sono una squadra di uomini, quindi ho la maledizione della conoscenza.
Aggiornare
Ho programmato più di 6 mesi con questo schema. Di solito a questo punto dimentico quello che ho fatto ed è lì che "l'ho scritto in modo pulito?" entra in gioco. Se non lo avessi fatto, avrei davvero delle difficoltà. Finora non sto lottando affatto.
Capisco come sarebbe necessario un altro set di occhi per convalidare la sua manutenibilità. Tutto quello che posso dire è che mi preoccupo innanzitutto della manutenibilità. Sono sempre l'evangelista più rumoroso per il codice pulito, non importa dove lavoro.
Voglio rispondere direttamente a coloro che hanno già una brutta esperienza personale con questo modo di scrivere codice. Allora non lo sapevo, ma penso che stiamo davvero parlando di due modi diversi di scrivere codice. Il modo in cui l'ho fatto sembra essere più strutturato di quello che altri hanno vissuto. Quando qualcuno ha una brutta esperienza personale con "Tutto è una mappa", parlano di quanto sia difficile da mantenere perché:
- Non si conosce mai la struttura della mappa richiesta dalla funzione
- Qualsiasi funzione può mutare l'input in modi che non ti aspetteresti mai. Devi cercare in tutta la base di codice per scoprire come una particolare chiave è entrata nella mappa o perché è scomparsa.
Per quelli con una tale esperienza, forse la base di codice era: "Tutto prende 1 di N tipi di mappe". Il mio è "Tutto richiede 1 di 1 tipo di mappa". Se conosci la struttura di quel tipo 1, conosci la struttura di tutto. Naturalmente, quella struttura di solito cresce nel tempo. Ecco perchè...
C'è un posto dove cercare l'implementazione di riferimento (cioè lo schema). Questa implementazione di riferimento è il codice utilizzato dal gioco in modo da non poter essere aggiornato.
Per quanto riguarda il secondo punto, non aggiungo / rimuovo le chiavi alla mappa al di fuori dell'implementazione di riferimento, muto solo ciò che è già lì. Ho anche una vasta gamma di test automatizzati.
Se questa architettura alla fine crolla sotto il suo stesso peso, aggiungerò un secondo aggiornamento. Altrimenti, supponi che tutto stia andando bene :)