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 a un progetto per il passato e volevamo inventare un bel modo OOP di rappresentare uno scenario specifico per il nostro prodotto. Fondamentalmente, stiamo lavorando su un stile Touhou gioco infernale dei proiettili , e volevamo creare un sistema in cui poter rappresentare facilmente qualsiasi possibile comportamento del proiettile che potremmo immaginare.

Quindi questo è esattamente ciò che abbiamo fatto; abbiamo creato un'architettura davvero elegante che ci ha permesso di separare il comportamento di un proiettile in diversi componenti che potevano essere collegati alle istanze dei proiettili a volontà, un po 'come Sistema componente di Unity. Funzionava bene, era facilmente estensibile, flessibile e copriva tutte le nostre basi, ma c'era un piccolo 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, seppur elegante, è un po 'complicata con cui lavorare senza un umano. Gli esseri 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'IA che utilizzi la nostra architettura OOP al massimo potenziale. Indubbiamente, questo è un difetto dell'architettura è che non è intuitivo in tutte le situazioni.

Quindi, per rimediare a questo problema, abbiamo praticamente eliminato tutti i comportamenti offerti dai diversi componenti nella classe bullet, in modo che tutto ciò che potremmo mai immaginare sia offerto direttamente in ogni istanza di bullet rispetto a altre istanze di componenti associate. Questo rende i nostri algoritmi di generazione procedurale un po 'più facili da usare, ma ora la nostra classe bullet è un enorme oggetto dio . È facilmente la più grande classe del programma fino ad ora con più di cinque volte più codice di qualsiasi altra cosa. È anche un po 'difficile da mantenere.

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

    
posta user3002473 13.04.2015 - 07:18
fonte

6 risposte

74

Quando si costruiscono programmi reali, spesso c'è un compromesso tra il rimanere pragmatici da una parte e il rimanere puliti al 100% dall'altra. Se rimanere puliti ti impedisce di spedire il tuo prodotto in tempo, allora stai meglio con un po 'di condotti- nastro per far uscire la cosa d *** d dalla porta.

Detto questo, la tua descrizione sembra diversa - sembra che tu non aggiungerai il un po ' di nastro adesivo, sembra che tu rovini la tua intera architettura perché non hai guardato a lungo e abbastanza difficile per una soluzione migliore. Quindi, invece di cercare qualcuno qui su PSE che ti dia una benedizione su questo, potresti fare una domanda diversa dove descrivi alcuni dettagli dei problemi che hai approfondito, e guarda se qualcuno ti offre un'idea che eviti il dio -approccio di classe.

Forse la classe bullet può essere progettata per essere facciata di un gruppo di altre classi, quindi la classe bullet diventa più piccola. Forse il modello di strategia può aiutare in modo che il proiettile possa delegare comportamenti diversi a diversi oggetti strategici. 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, si può solo indovinare.

    
risposta data 13.04.2015 - 07:50
fonte
25

Domanda interessante. Sono un po 'prevenuto 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 in questo modo, è un'opportunità per migliorare le tue capacità architettoniche / progettuali, non una scusa per aggiungere odori al codice.

La versione più lunga è che mi è stato chiesto molte volte domande simili nei miei progetti al lavoro, ma inevitabilmente, abbiamo finito per discutere il problema in modo molto più approfondito, quindi l'intervistatore originale gli ha dato credito. Vuoi includere mentalità come l'analisi della causa principale con quella.

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

  • "Ora abbiamo questa classe di Dio" Perché?
  • "Perché semplifica l'algoritmo di generazione procedurale"

Che cos'è esattamente che è semplificato? E perché è così? Continuate così e ciò che in genere accade è che si iniziano a riconoscere i problemi più fondamentali, ma allo stesso tempo, consentono anche soluzioni più fondamentali.

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

    
risposta data 13.04.2015 - 07:39
fonte
9

Avere una classe di Dio come questa non è mai desiderabile, poiché non significa solo che i tuoi proiettili sono ora oggetti monolitici, ma lo stesso vale per l'algoritmo di generazione procedurale.

Il primo passo sarebbe stato analizzare, perché esattamente la tua intelligenza artificiale ha avuto così tanti problemi ad affrontare la complessità del tuo pattern?

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

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

Questo ti avrebbe dato la flessibilità che desideravi originariamente, compresa l'opzione di estendere il sistema con nuove strategie come desideri senza la paura di incorrere in effetti collaterali con comportamenti legacy.

Invece ora hai tutti i problemi associati agli oggetti di Dio: non solo la tua classe di Dio è di per sé difficile da capire, difficile da debellare sul monolite, ma lo stesso vale per tutti gli altri componenti che accedono a una tale classe di dio. A causa della mancanza di astrazione, la tua IA ora è destinata a trasformarsi in un abominio di complessità simile, poiché deve essere consapevole di tutte le proprietà ridondanti e individuali ora.

Anche in questo momento, stai già riscontrando problemi con la manutenzione. Questi problemi stanno peggiorando, soprattutto 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 di Dio o funzioni di dio è stato completamente riscritto da zero o cessato, senza eccezioni.

    
risposta data 13.04.2015 - 10:44
fonte
8

Tutto il codice errato fin dall'alba dei tempi ha una storia dietro la sua evoluzione che lo fa sembrare ragionevole passo dopo passo. La tua non fa eccezione. I codificatori imparano codificando. Ci sono aspetti del tuo problema che non avresti potuto prevedere e che ora sembrano ovvi. Ci sono decisioni prese che sono state del tutto ragionevoli in modo incrementale, ma hanno portato la tua architettura nella direzione sbagliata in generale. Devi identificare e riesaminare quelle decisioni.

Chiedi al 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 una vera abilità per quel genere di cose, e ha appena aspettato l'occasione. Scopri chi sono quelle persone. Spesso, sono i tuoi nuovi assunti che non sono storicamente investiti nell'attuale architettura che possono vedere più facilmente le alternative. Accoppialo con uno dei tuoi veterani e potresti rimanere sbalordito da ciò che hanno combinato.

    
risposta data 13.04.2015 - 14:50
fonte
2

In alcuni casi questo è decisamente accettabile. Tuttavia, trovo difficile credere che non ci sia una buona soluzione che utilizzi sia la generazione procedurale sia la tua bella architettura comportamentale collegata / basata sui componenti. Se tutti i comportamenti in cui sono stati inseriti nella classe bullet non esiste alcuna differenza funzionale tra l'oggetto dio e la versione architetturale pulita. Cosa ha reso così difficile l'utilizzo degli algoritmi di generazione procedurale?

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

    
risposta data 13.04.2015 - 09:36
fonte
0

Per trarre ispirazione, potresti voler dare un'occhiata alla programmazione funzionale o, più precisamente, ai tipi strettamente 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 nel suo magazzino, o una pistola che memorizza i proiettili ma non li spara.

A questo livello di complessità, stai pensando "giusto, un umano capirà che questo è inutile ed evita le combinazioni impossibili". Un modo migliore potrebbe essere quello di rendere assolutamente impossibile farlo. Ad esempio, il componente per "sparare proiettili" potrebbe richiedere un riferimento al componente "tenere proiettili" (o tenerlo semplicemente come parte di se stesso, sebbene abbia 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 troppo complicato comunque. 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 di una semplice possibilità di avere ciascun componente - potresti avere componenti che tendono a escludersi a vicenda, o componenti che tendono a funzionare bene insieme. In questo modo è possibile modificare le probabilità di selezione di un nuovo componente in base ai componenti già presenti.

Infine, considera se sia effettivamente un problema abbastanza grande. Sta rovinando il divertimento del gioco? I cannoni risultanti sono inutili o sopraffatti? Quanto? Uno su 10 è privo di senso? Giochi come Borderlands (con effetti di armi generati proceduralmente) spesso ignorano le combinazioni di effetti senza senso: qual è il punto di avere un fucile con un cannocchiale 16x? Eppure ciò accade molto spesso in Borderlands. E 'appena giocato per ridere, piuttosto che essere considerato un fallimento dei meccanismi di generazione:)

    
risposta data 14.04.2015 - 10:41
fonte

Leggi altre domande sui tag