Ist es in Ordnung, Code-Gerüche zu haben, wenn eine einfachere Lösung für ein anderes Problem zugelassen wird? [geschlossen]

31

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?

user3002473
quelle
1
Nun ... das ist schwer zu beantworten. Meiner Meinung nach NEIN. Vor allem, wenn Sie mit anderen Menschen zusammenarbeiten. Wenn Sie alleine arbeiten, können Sie tun, was Sie wollen. Aber im Allgemeinen kommt es darauf an, mit wem Sie arbeiten.
Sarkastische Kartoffel
13
"Wenn es dumm ist und es funktioniert, ist es nicht dumm." Abgesehen davon müssen Sie die Überlegung anstellen, dass Sie selbst dann, wenn es genau so funktioniert, wie Sie es möchten, die zukünftige Codierung für diese Gottklasse aufgrund Ihrer Entscheidung, das Problem zu beheben, nahezu unmöglich machen.
Flater
8
Es ist besser zu riechen und zu wissen, dass Sie stinken, als nur andere Leute zu riechen, die es bemerken. :)
Reactgular
1
Klingt so, als ob Sie eine Adapterklasse benötigen, die eine Nicht-OO-Schnittstelle für Ihren prozeduralen Code bereitstellt, damit Sie den Rest Ihres Codes sauber halten können.
Nikie
1
Es hört sich so an, als hätten Sie ein wunderbares System aufgebaut und beschlossen, alles in die Luft zu jagen, jetzt funktioniert es bei einem Feature nicht mehr wie erwartet. Wollen Sie das wirklich? Sei stolz auf deine Arbeit!
Mast

Antworten:

74

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.

Doc Brown
quelle
6
Um diese Antwort zu bekräftigen, wollte ich etwas mit denselben zwei Empfehlungen schreiben. In Bezug auf den letzten Absatz scheint das, was OP beschreibt, ein Code-Geruch zu sein, der anzeigt, dass eine andere Indirektionsebene erforderlich ist. Ob dies eine bessere Fabrikklasse, Fassaden oder sogar eine vollwertige DSL ist, für die die KI generieren kann, bleibt dem OP überlassen.
Daniel B
2
Gute Vorschläge zu Facade / Strategy, um den Code in kleinere Teile aufzuteilen. Ohne weitere Einzelheiten zu kennen, ist es jedoch schwierig zu wissen, was zu empfehlen ist.
user949300
25

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:

  • "Wir haben jetzt diese Gottklasse" Warum?
  • "Weil es den prozeduralen Generierungsalgorithmus vereinfacht"

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 ...

Frank
quelle
9

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.

Ext3h
quelle
Eine Sache, die in Diskussionen darüber, wie groß oder komplex Klassen sein sollten, häufig fehlt, ist die Unterscheidung zwischen "wie viel Code, der eine Referenz vom Typ X enthält, damit umgehen kann" und "viel Logik, die in der Klassendatei enthalten sein sollte" X ". Wenn ein Client wahrscheinlich Sammlungen von Objekten hat, die eine Vielzahl von Funktionen unterstützen (z. B. "fnorble"), und häufig alle Mitglieder einer Sammlung auffordern möchte, z. B. "fnorble, wenn möglich, sonst nichts tun", dann eine Schnittstelle Die Kombination vieler solcher Methoden ist möglicherweise weitaus hilfreicher als der Versuch, die Schnittstelle zu teilen.
Supercat
Bei einer solchen Schnittstelle ist es möglich, dass Code Objekte mit einer beliebigen Kombination von Fähigkeiten einkapselt, ohne dass unterschiedliche Kombinationen separat behandelt werden müssen. Die Trennung der Schnittstellen würde es erforderlich machen, viele verschiedene Proxy-Klassen zu schreiben, da Proxys für einen Typ, der Methode X unterstützt, nicht zum Umbrechen von Objekten verwendet werden können, die nicht X können, und Proxys, die Objekte umbrechen können, die nicht X können. Die Methode X kann nicht verfügbar gemacht werden, auch wenn sie ein Objekt umschließt, das dies unterstützen könnte.
Supercat
8

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.

Karl Bielefeldt
quelle
2

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!

Roy T.
quelle
3
Können Sie ein Beispiel für eine Situation geben, in der eine Gottklasse sowohl akzeptabel als auch wünschenswert wäre, während diese Klasse nicht nur eine automatisierte Zusammenstellung einzelner Quellen ist?
Ext3h
Mit akzeptabel bezog ich mich auf "Ist es in Ordnung, Codegerüche zu haben, wenn es eine einfachere Lösung für ein anderes Problem zulässt?" Gottklassen sind im Allgemeinen durch Verwendung von Komposition leicht lösbar. Aber ja, es gibt bestimmte Code-Gerüche, die es definitiv nicht wert sind, immer zu 100% beseitigt zu werden. Am Ende ist etwas Pragmatismus erforderlich, um die Arbeit zu erledigen :). (Dies bedeutet nicht, dass Sie aus einer Laune heraus Best Practices herausgeben sollten. Ich glaube, dass die meisten Softwarehäuser besser daran sind, Best Practices strenger zu befolgen).
Roy T.
0

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 :)

Luaan
quelle