Va bene avere odori di codice se ammette una soluzione più semplice per un altro problema? [chiuso]


31

Un gruppo di amici e io abbiamo lavorato su un progetto per un po 'di tempo, e volevamo inventare un bel modo OOP di rappresentare uno scenario specifico per il nostro prodotto. Fondamentalmente, stiamo lavorando a un gioco infernale in stile Touhou e volevamo creare un sistema in cui potessimo rappresentare facilmente qualsiasi possibile comportamento di proiettile che potessimo immaginare.

Quindi è esattamente quello che abbiamo fatto; abbiamo realizzato un'architettura davvero elegante che ci ha permesso di suddividere il comportamento di un proiettile in diversi componenti che potevano essere collegati a istanze di proiettile a piacimento, un po 'come il sistema di componenti di Unity . Funzionava bene, era facilmente estensibile, flessibile e copriva tutte le nostre basi, ma c'era un leggero problema.

La nostra applicazione comporta anche una grande quantità di generazione procedurale, ovvero generiamo proceduralmente i comportamenti dei proiettili. Perché questo è un problema? Bene, la nostra soluzione OOP per rappresentare il comportamento dei proiettili, sebbene elegante, è un po 'complicata con cui lavorare senza un essere umano. Gli umani sono abbastanza intelligenti da pensare a soluzioni a problemi che sono sia logici che intelligenti. Gli algoritmi di generazione procedurale non sono ancora così intelligenti e abbiamo trovato difficile implementare un'intelligenza artificiale che utilizza la nostra architettura OOP al massimo delle sue potenzialità. Certo, questo è un difetto dell'architettura è che non è intuitivo in tutte le situazioni.

Quindi, per ovviare a questo problema, fondamentalmente abbiamo inserito tutti i comportamenti offerti da diversi componenti nella classe bullet, in modo che tutto ciò che potremmo mai immaginare sia offerto direttamente in ogni istanza di bullet anziché in altre istanze di componente associate. Questo rende i nostri algoritmi di generazione procedurale un po 'più facili da lavorare, ma ora la nostra classe di proiettili è un enorme oggetto divino . È facilmente la classe più grande del programma finora con più di cinque volte più codice di qualsiasi altra cosa. È anche un po 'doloroso da mantenere.

Va bene che una delle nostre classi si è trasformata in un oggetto divino, solo per facilitare il lavoro con un altro problema? In generale, va bene avere odori di codice nel tuo codice se ammette una soluzione più semplice a un problema diverso?


1
Beh ... questo è un po 'difficile rispondere. Secondo me NO. Soprattutto se lavori insieme ad altre persone. Se lavori da solo puoi fare quello che vuoi. Ma in generale dipende da chi lavori.
Sarcastic Potato

13
"Se è stupido, e funziona; non è stupido." Detto questo, devi considerare che anche se funziona esattamente come lo desideri, indipendentemente dal fatto che non stai rendendo la codifica futura su quella classe divina quasi impossibile a causa di come hai deciso di risolverlo.
Flater,

8
È meglio annusare e sapere che puzzi, poi annusare solo gli altri lo notano. :)
Reactgular,

1
Sembra che tu abbia bisogno di una classe adattatore che fornisca un'interfaccia non OO al tuo codice procedurale, in modo da poter mantenere pulito il resto del tuo codice (er).
Nikie,

1
Sembra che tu abbia costruito un sistema meraviglioso e abbia deciso di far saltare tutto all'inferno ora non funziona come previsto su una caratteristica. È davvero quello che vuoi? Sii orgoglioso del tuo lavoro!
Albero

Risposte:


74

Quando si costruiscono programmi nel mondo reale, c'è spesso un compromesso tra il rimanere pragmatici da un lato e il 100% di pulizia dall'altro. Se rimanere puliti ti proibisce di spedire il tuo prodotto in tempo, allora stai meglio con un po 'di nastro adesivo per far uscire la fottuta cosa dalla porta.

Detto questo, la tua descrizione sembra diversa: sembra che non stai per aggiungere un po 'di nastro isolante, sembra che stai per rovinare tutta la tua architettura perché non sei sembrato abbastanza lungo e duro per una soluzione migliore. Quindi, invece di cercare qualcuno qui su PSE che ti dia una benedizione su questo, potresti fare una domanda diversa in cui descrivi alcuni dettagli dei problemi che hai in profondità e vedere se qualcuno ti offre un'idea che evita il dio approccio di classe.

Forse la classe proiettile può essere progettata per essere facciata a un gruppo di altre classi, quindi la classe proiettile diventa più piccola. Forse il modello di strategia può aiutare in modo che il proiettile possa delegare comportamenti diversi a diversi oggetti di strategia. Forse hai bisogno solo di un adattatore tra il tuo componente bullet e il tuo generatore procedurale. Ma onestamente, senza conoscere più dettagli del tuo sistema, puoi solo indovinare.


6
Solo per riaffermare questa risposta, stavo per scrivere qualcosa con le stesse due raccomandazioni. Per quanto riguarda l'ultimo paragrafo, ciò che OP descrive sembra essere un odore di codice in sé che indica che è necessario un altro livello di riferimento indiretto. Sia che si tratti di una classe di fabbrica migliore, di facciate o persino di una DSL in piena regola per la quale l'IA può generare viene lasciato all'OP.
Daniel B,

2
Buoni suggerimenti sulla facciata / strategia per suddividere il codice in blocchi più piccoli. Sebbene senza conoscere ulteriori dettagli, è difficile sapere cosa suggerire.
user949300,

25

Domanda interessante. Sono un po 'di parte anche se a causa delle mie precedenti esperienze, che mi spinge a rispondere con No.

Risposta breve: non smettiamo mai di imparare. Quando colpisci un muro del genere, è un'opportunità per migliorare le tue capacità di architettura / design, non una scusa per aggiungere odori di codice.

La versione più lunga è che mi sono state poste molte volte domande simili nei miei progetti al lavoro, ma inevitabilmente, abbiamo finito per discutere la questione in modo molto più approfondito, quindi l'interrogatore originale gli ha dato credito. Vuoi includere mentalità come l'analisi delle cause alla radice.

Ho trovato i 5 Whys particolarmente utili. La tua spiegazione qui si legge proprio come il primo perché:

  • "Abbiamo questa classe divina ora" Perché?
  • "Perché semplifica l'algoritmo di generazione procedurale"

Cos'è esattamente ciò che è semplificato? E perché è così? Continua su questa linea e ciò che accade in genere è che inizi a riconoscere molti problemi fondamentali, ma allo stesso tempo ti consentono anche soluzioni più fondamentali.

Per farla breve, dopo aver riflettuto più profondamente sul problema in questione e in particolare sulle sue cause, nella mia esperienza la domanda originale è risultata essere nulla e assolutamente non interessante per tutti i partecipanti. Se hai dei dubbi, sfidali e o se ne andranno, o saprai cosa devi fare. Intendiamoci, è un buon segno secondo me avere questi dubbi su codice / design. Altri lo definiscono una sensazione viscerale, ma per sfidare questi problemi, devi prima riconoscerli. Da quando hai fatto quel primo passo, congratulazioni! Ora scendi nella tana del coniglio ...


9

Avere una divinità come questa non è mai desiderabile, poiché non significa solo che i tuoi proiettili sono ora oggetti monolitici, ma lo stesso vale anche per il tuo algoritmo di generazione procedurale.

Il primo passo sarebbe stato analizzare, perché esattamente la tua IA ha avuto così tanti problemi a gestire la complessità del tuo modello?

Per caso, hai provato a trasformare anche la tua IA in un oggetto di classe divina, pienamente consapevole della semantica di ogni singola proprietà possibile? Se lo hai fatto, è qui che ha avuto origine il problema.

La soluzione non sarebbe stata quindi quella di integrare tutte le strategie nella classe bullet stessa, ma scaricare invece il suggerimento per l'IA dal core AI alle implementazioni della strategia stesse.

Ciò ti avrebbe dato la flessibilità che originariamente desideravi, inclusa l'opzione di estendere il sistema con nuove strategie come desideri senza la paura di imbatterti in effetti collaterali con comportamenti ereditati.

Invece ora hai tutti i problemi associati agli oggetti divini: non solo la tua stessa classe divina è un monolito difficile da capire, difficile da eseguire il debug, ma lo stesso vale anche per tutti gli altri componenti che accedono a tale classe divina. A causa della mancanza di astrazione, la tua IA è ora destinata a trasformarsi in un abominio di complessità simile in quanto deve essere consapevole di tutte le proprietà individuali ridondanti.

Anche in questo momento, stai già riscontrando problemi con la manutenzione. Questi problemi peggiorano, specialmente non appena perdi i membri del team che hanno ancora un modello cognitivo di come dovrebbe funzionare quella classe.

Finora ogni singolo progetto che ho incontrato che utilizzava tali classi o funzioni di dio era completamente riscritto da zero o cessato, senza eccezioni.


Una cosa che spesso manca nelle discussioni su quanto dovrebbero essere grandi o complesse le classi è una distinzione tra "quanto dovrebbe essere il codice che contiene un riferimento di tipo X" contro "quanto molta logica dovrebbe essere nel file di classe per X". Se un client avrebbe probabilmente raccolte di oggetti che supportano una varietà di capacità (ad esempio "fnorble"), e spesso si ritroverà a voler chiedere a tutti i membri di una raccolta di es. "Fnorble se possibile, altrimenti non fare nulla", quindi avere un'interfaccia che combina molti di questi metodi può essere molto più utile che cercare di dividere l'interfaccia.
supercat

Data tale interfaccia, è possibile avere codice incapsulare oggetti con qualsiasi combinazione di abilità, senza dover gestire diverse combinazioni separatamente. La segregazione delle interfacce renderebbe necessario scrivere molte diverse classi proxy, poiché i proxy per un tipo che supporta il metodo X non possono essere utilizzati per avvolgere oggetti che non possono X e proxy che possono avvolgere oggetti che non possono X essere in grado di esporre il metodo X anche se stava avvolgendo un oggetto in grado di supportarlo.
supercat

8

Tutti i codici errati dall'alba dei tempi hanno una storia dietro la sua evoluzione che lo fa sembrare ragionevole passo dopo passo. La tua non fa eccezione. I programmatori imparano codificando. Ci sono aspetti del tuo problema che non avresti potuto prevedere che sembrano ovvi ora. Ci sono decisioni che sono state del tutto ragionevolmente incrementali, ma che hanno portato la tua architettura nella direzione sbagliata in generale. È necessario identificare e riesaminare tali decisioni.

Chiedi in giro per il tuo ufficio se le persone hanno qualche idea su come risolverlo. La maggior parte dei posti in cui ho lavorato, circa il 10-20% dei programmatori ha un vero talento per quel genere di cose e sta aspettando l'occasione. Scopri chi sono quelle persone. Spesso sono i tuoi nuovi assunti che non sono storicamente investiti nell'architettura attuale che possono vedere più facilmente le alternative. Abbinalo a uno dei tuoi veterani e potresti essere sorpreso di quello che escono insieme.


2

In alcuni casi questo è decisamente accettabile. Tuttavia, trovo difficile credere che non ci sia una buona soluzione usando sia la generazione procedurale sia la tua bella architettura comportamentale basata su componenti / componenti. Se tutti i comportamenti sono appena entrati nella classe dei proiettili, non esiste alcuna differenza funzionale tra l'oggetto god e la versione ben progettata. Cosa ha reso così difficile l'utilizzo dei tuoi algoritmi di generazione procedurale?

Penso a una nuova domanda (forse qui, o su gamedev.stackexchange.com?), In cui descrivi quali problemi hai avuto con la tua architettura in combinazione con proc. gen., sarebbe davvero interessante. Facci sapere qui se anche tu fai una nuova domanda!


3
Puoi fornire qualche esempio per una situazione in cui avere una classe divina sarebbe sia accettabile che desiderabile mentre questa classe non è solo una raccolta automatica di singole fonti?
Ext3h

Con accettabile mi riferivo a "Va bene avere odori di codice se ammette una soluzione più semplice per un altro problema?" Le classi di Dio sono generalmente facilmente risolvibili usando la composizione. Ma sì, ci sono alcuni odori di codice che sicuramente non valgono sempre al 100%. Alla fine è necessario un po 'di pragmatismo per portare a termine il lavoro :). (Questo non significa che penso che dovresti buttare le migliori pratiche per capriccio. Credo che la maggior parte delle case di software sarebbe meglio seguire le migliori pratiche più rigorosamente).
Roy T.

0

Per ispirazione, potresti voler dare un'occhiata alla programmazione funzionale, o più precisamente, tipi su misura. L'idea è che le cose non funzionano sono impossibili da rappresentare nel sistema di tipi che hai a disposizione. Ad esempio, supponiamo di avere una pistola con i componenti "sparare proiettili" e "tenere proiettili". Se sono due componenti separati, ci sono configurazioni che non hanno senso: puoi avere una pistola che spara proiettili ma non ne ha nella sua conservazione, o una pistola che immagazzina proiettili ma non li spara.

A questo livello di complessità, stai pensando "giusto, un essere umano capirà che è inutile ed evita le combinazioni impossibili". Un modo migliore potrebbe essere quello di rendere assolutamente impossibile farlo. Ad esempio, potrebbe essere necessario il componente per "sparare proiettili" un riferimento al componente "trattenere proiettili" (o semplicemente tenerlo come parte di se stesso, anche se ha i suoi problemi).

Mentre i tuoi esempi potrebbero essere molto più complicati, la chiave sta ancora limitando le possibili combinazioni, aggiungendo vincoli. In un certo senso, se è troppo complicato per la generazione procedurale, probabilmente è comunque troppo complicato. Pensa a tutti i modi in cui ti stai costringendo a progettare le armi: ti dici "giusto, questa pistola ha già un lanciafiamme, non ha senso aggiungere un lanciagranate"? È qualcosa che puoi incorporare nella tua euristica. Potresti voler usare un meccanismo di probabilità che è un po 'più complicato del semplice dare una possibilità fissa di avere ciascun componente: potresti avere componenti che tendono ad escludersi a vicenda o componenti che tendono a lavorare bene insieme.

E infine, considera se in realtà è un problema abbastanza grande. Sta rovinando il divertimento del gioco? Il risultato sono pistole inutili o sopraffatte? Quanto? Uno su 10 è privo di senso? Giochi come Borderlands (con effetti di arma generati proceduralmente) spesso ignorano le combinazioni di effetti senza senso: qual è il punto di avere un fucile con portata 16x? Eppure ciò accade molto spesso in Borderlands. È solo suonato per le risate, piuttosto che essere considerato un fallimento dei meccanismi di generazione :)

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.