Eine Gruppe von Freunden und ich haben in der letzten Zeit an einem Projekt gearbeitet, und wir wollten eine nette OOP-Methode erfinden, um ein für unser Produkt spezifisches Szenario darzustellen. Grundsätzlich arbeiten wir an einem Bullet-Hell-Spiel im Touhou-Stil , und wir wollten ein System entwickeln, in dem wir jedes mögliche Verhalten von Bullets, das wir uns erträumen können, einfach darstellen können.
Genau das haben wir getan. Wir haben eine sehr elegante Architektur entwickelt, die es uns ermöglicht, das Verhalten eines Aufzählungszeichens in verschiedene Komponenten zu unterteilen, die nach Belieben an Aufzählungszeicheninstanzen angehängt werden können, ähnlich wie das Komponentensystem von Unity . Es funktionierte gut, es war leicht erweiterbar, es war flexibel und deckte alle unsere Basen ab, aber es gab ein kleines Problem.
Unsere Anwendung erfordert auch eine große Menge an prozeduraler Generierung, dh, wir generieren prozedural das Verhalten der Aufzählungszeichen. Warum ist das ein Problem? Nun, unsere OOP-Lösung zur Darstellung des Verhaltens von Kugeln ist zwar elegant, aber ein wenig kompliziert, ohne einen Menschen zu arbeiten. Menschen sind klug genug, um Lösungen für logische und kluge Probleme zu finden. Prozedurale Generierungsalgorithmen sind noch nicht so intelligent, und wir haben es schwierig gefunden, eine KI zu implementieren, die unsere OOP-Architektur in vollem Umfang nutzt. Zugegeben, das ist ein Fehler der Architektur, dass sie nicht in allen Situationen intuitiv ist.
Um dieses Problem zu beheben, haben wir im Grunde alle von verschiedenen Komponenten angebotenen Verhaltensweisen in die Aufzählungsklasse verschoben, sodass alles, was wir uns jemals vorstellen konnten, direkt in jeder Aufzählungsinstanz und nicht in anderen zugeordneten Komponenteninstanzen angeboten wird. Dies macht es ein wenig einfacher, mit unseren Algorithmen zur Prozedurgenerierung zu arbeiten, aber jetzt ist unsere Bullet-Klasse ein riesiges Gott-Objekt . Es ist mit Abstand die größte Klasse im Programm, mit mehr als fünfmal so viel Code wie alles andere. Es ist ein bisschen mühsam, das auch beizubehalten.
Ist es in Ordnung, dass sich eine unserer Klassen in ein Gott-Objekt verwandelt hat, um es einfacher zu machen, mit einem anderen Problem zu arbeiten? Ist es im Allgemeinen in Ordnung, Codegerüche in Ihrem Code zu haben, wenn eine einfachere Lösung für ein anderes Problem zugelassen wird?
quelle
Antworten:
Bei der Erstellung realer Programme gibt es oft einen Kompromiss zwischen Pragmatismus auf der einen Seite und 100% Sauberkeit auf der anderen Seite. Wenn es Ihnen nicht möglich ist, Ihr Produkt rechtzeitig zu versenden, sollten Sie ein wenig Klebeband verwenden , um das verdammte Ding aus der Tür zu holen.
Gesagt, Ihre Beschreibung hört sich anders an - es hört sich so an, als würden Sie nicht ein bisschen Klebeband hinzufügen , sondern Sie würden Ihre gesamte Architektur ruinieren, weil Sie nicht lange genug nach einer besseren Lösung gesucht haben. Anstatt hier bei PSE nach jemandem zu suchen, der Ihnen einen Segen gibt, sollten Sie eine andere Frage stellen, in der Sie einige Details der Probleme beschreiben, die Sie im Detail haben, und prüfen, ob Ihnen jemand eine Idee bietet, die den Gott vermeidet Ansatz.
Vielleicht kann die Aufzählungsklasse so entworfen werden, dass sie einer Reihe anderer Klassen gegenübersteht, sodass die Aufzählungsklasse kleiner wird. Vielleicht kann das Strategiemuster helfen, damit die Kugel unterschiedliche Verhaltensweisen an unterschiedliche Strategieobjekte delegieren kann. Möglicherweise benötigen Sie nur einen Adapter zwischen Ihrer Bullet-Komponente und Ihrem Prozedurgenerator. Aber ehrlich gesagt, ohne mehr über Ihr System zu wissen, kann man nur raten.
quelle
Interessante Frage. Aufgrund meiner bisherigen Erfahrungen bin ich jedoch ein bisschen voreingenommen, was mich dazu auffordert, mit Nein zu antworten.
Kurze Antwort: Wir hören nie auf zu lernen. Wenn Sie so gegen eine Wand stoßen, ist dies eine Chance, Ihre architektonischen / gestalterischen Fähigkeiten zu verbessern, und keine Entschuldigung, um Code-Gerüche hinzuzufügen.
Die längere Version ist, dass mir in meinen Projekten bei der Arbeit häufig ähnliche Fragen gestellt wurden, aber wir haben das Problem zwangsläufig viel ausführlicher diskutiert, als der ursprüngliche Fragesteller es befürwortet hat. Sie möchten Mentalitäten wie die Ursachenanalyse mit einbeziehen.
Ich fand die 5 Gründe besonders hilfreich. Ihre Erklärung hier lautet genau wie die erste, warum:
Was ist es genau, das vereinfacht wird? Und warum ist das so? Gehen Sie weiter so vor, und was normalerweise passiert, ist, dass Sie anfangen, grundlegendere Probleme zu erkennen. Gleichzeitig ermöglichen sie Ihnen aber auch grundlegendere Lösungen.
Lange Rede, kurzer Sinn, nach eingehenderem Nachdenken über das vorliegende Problem und insbesondere seine Ursachen war die ursprüngliche Frage nach meiner Erfahrung für alle Teilnehmer nichtig und äußerst uninteressant. Wenn Sie Zweifel haben, fordern Sie sie heraus, und entweder sind sie weg, oder Sie wissen, was Sie zu tun haben. Wohlgemerkt, es ist meiner Meinung nach ein gutes Zeichen, diese Zweifel an Code / Design zu haben. Andere nennen es ein Bauchgefühl, aber um diese Probleme in Frage zu stellen, müssen Sie sie zuerst erkennen. Herzlichen Glückwunsch! Nun geh das Kaninchenloch runter ...
quelle
Eine solche Götterklasse ist niemals wünschenswert, da dies nicht nur bedeutet, dass Ihre Kugeln jetzt monolithische Objekte sind, sondern dasselbe gilt auch für Ihren Algorithmus zur prozeduralen Generierung.
Der erste Schritt wäre gewesen, zu analysieren, warum genau Ihre KI so große Probleme damit hatte, mit der Komplexität Ihres Musters umzugehen.
Haben Sie zufällig versucht, Ihre KI auch in ein Objekt der Götterklasse zu verwandeln, das sich der Semantik jeder einzelnen möglichen Eigenschaft voll bewusst ist? Wenn Sie das getan haben, ist das der Grund für das Problem.
Die Lösung wäre dann nicht gewesen, alle Strategien in die Bullet-Klasse selbst zu integrieren, sondern die Hinweise für die KI vom KI-Kern auf die Strategieimplementierungen selbst zu verlagern.
Das hätte Ihnen die Flexibilität gegeben, die Sie ursprünglich gewünscht hatten, einschließlich der Option, das System nach Ihren Wünschen um neue Strategien zu erweitern, ohne befürchten zu müssen, dass Sie auf Nebenwirkungen mit altem Verhalten stoßen.
Stattdessen haben Sie jetzt alle Probleme, die mit Gottobjekten verbunden sind: Nicht nur Ihre Gottklasse selbst ist schwer zu verstehen, sondern auch alle anderen Komponenten, die auf eine solche Gottklasse zugreifen. Aufgrund der fehlenden Abstraktion wird Ihre KI zu einem Gräuel von ähnlicher Komplexität, da sie jetzt alle redundanten, individuellen Eigenschaften berücksichtigen muss.
Schon jetzt haben Sie Probleme mit der Wartung. Diese Probleme werden immer schlimmer, besonders wenn Sie die Teammitglieder verlieren, die noch ein kognitives Modell dafür haben, wie diese Klasse funktionieren soll.
Bisher wurde jedes einzelne Projekt, auf das ich gestoßen bin, das solche Gottklassen oder Gottfunktionen verwendete, entweder komplett neu geschrieben oder eingestellt, keine Ausnahmen.
quelle
Jeder schlechte Code seit Anbeginn der Zeit hat eine Geschichte hinter sich, die ihn Schritt für Schritt vernünftig erscheinen lässt. Ihre ist keine Ausnahme. Codierer lernen durch Codierung. Es gibt Aspekte Ihres Problems, die Sie nicht vorausgesehen haben können und die jetzt offensichtlich erscheinen. Sie haben Entscheidungen getroffen, die inkrementell durchaus vernünftig waren, aber Ihre Architektur insgesamt in die falsche Richtung gelenkt haben. Sie müssen diese Entscheidungen identifizieren und erneut prüfen.
Fragen Sie in Ihrem Büro nach, ob Sie Ideen zur Behebung haben. Die meisten Orte, an denen ich gearbeitet habe, haben ungefähr 10-20% der Programmierer ein echtes Talent für solche Dinge und haben nur auf die Chance gewartet. Finde heraus, wer diese Leute sind. Oft sind es Ihre neuen Mitarbeiter, die historisch gesehen nicht in die aktuelle Architektur investiert sind, die Alternativen am einfachsten erkennen. Koppeln Sie sie mit einem Ihrer Veteranen, und Sie werden staunen, was sie zusammen finden.
quelle
In einigen Fällen ist dies definitiv akzeptabel. Es fällt mir jedoch schwer zu glauben, dass es keine gute Lösung gibt, die sowohl die prozedurale Generierung als auch Ihre nette, auf Anhängen und Komponenten basierende Verhaltensarchitektur verwendet. Wenn alle Verhaltensweisen nur in die Bullet-Klasse aufgenommen wurden, gibt es keinen funktionalen Unterschied zwischen dem God-Objekt und der gepflegten Architekturversion. Was hat es Ihren Algorithmen zur Prozedurerzeugung so schwer gemacht, sie zu verwenden?
Ich denke, eine neue Frage (vielleicht hier oder auf gamedev.stackexchange.com?), Wo Sie beschreiben, welche Probleme Sie mit Ihrer Architektur in Kombination mit proc hatten. gen., wäre echt interessant. Lassen Sie uns auch hier wissen, wenn Sie eine neue Frage stellen!
quelle
Als Inspiration möchten Sie vielleicht einen Blick auf die funktionale Programmierung werfen, oder genauer gesagt, auf genau zugeschnittene Typen. Die Idee, dass Dinge nicht funktionieren, ist unmöglich in dem Typensystem darzustellen, das Ihnen zur Verfügung steht. Angenommen, Sie haben eine Waffe mit den Komponenten "Kugeln schießen" und "Kugeln halten". Wenn es sich um zwei separate Komponenten handelt, gibt es Konfigurationen, die keinen Sinn ergeben - Sie können eine Waffe haben, die Kugeln abschießt, aber keine im Lager hat, oder eine Waffe, die Kugeln speichert, aber keine abschießt.
Bei dieser Komplexität denken Sie, "richtig, ein Mensch wird herausfinden, dass dies nutzlos ist, und die unmöglichen Kombinationen vermeiden". Ein besserer Weg könnte sein, es völlig unmöglich zu machen, dies zu tun. Beispielsweise erfordert die Komponente für "Aufzählungszeichen schießen" möglicherweise einen Verweis auf die Komponente "Aufzählungszeichen halten" (oder hält sie einfach als Teil von sich selbst fest, obwohl dies seine eigenen Probleme hat).
Ihre Beispiele mögen zwar viel komplizierter sein, der Schlüssel beschränkt jedoch immer noch die möglichen Kombinationen und fügt Einschränkungen hinzu. In gewisser Weise ist es wahrscheinlich sowieso zu kompliziert, wenn es für die Erstellung von Prozeduren zu kompliziert ist, um es richtig zu machen. Denken Sie darüber nach, wie Sie sich beim Entwerfen der Waffen einschränken - sagen Sie sich, "richtig, diese Waffe hat bereits einen Flammenwerfer, es macht keinen Sinn, einen Granatwerfer hinzuzufügen"? Das können Sie in Ihre Heuristik einbeziehen. Möglicherweise möchten Sie einen Wahrscheinlichkeitsmechanismus verwenden, der etwas komplizierter ist, als nur eine feste Chance zu geben, dass jede Komponente vorhanden ist. Möglicherweise verfügen Sie über Komponenten, die sich gegenseitig ausschließen, oder über Komponenten, die dazu neigen, gut zusammenzuarbeiten.
Und zuletzt überlegen Sie, ob es sich tatsächlich um ein ausreichend großes Problem handelt. Ruiniert es den Spaß am Spiel? Sind die resultierenden Waffen unbrauchbar oder überfordert? Wie viel? Ist einer von 10 Unsinnigen? Spiele wie Borderlands (mit prozedural erzeugten Waffeneffekten) ignorieren oft unsinnige Effektkombinationen. Was bringt es, eine Schrotflinte mit einem 16-fachen Zielfernrohr zu haben? Und doch passiert das in Borderlands sehr oft. Es wird nur zum Lachen gespielt, anstatt als Versagen der Generierungsmechanismen betrachtet zu werden :)
quelle