Ich bin in einem verteilten Systemprojekt, das in Java geschrieben wurde, wo wir einige Klassen haben, die sehr komplexen realen Geschäftsobjekten entsprechen. Diese Objekte verfügen über viele Methoden, die Aktionen entsprechen, die der Benutzer (oder ein anderer Agent) auf diese Objekte anwenden kann. Infolgedessen wurden diese Klassen sehr komplex.
Der allgemeine Architekturansatz des Systems hat zu vielen Verhaltensweisen geführt, die sich auf wenige Klassen und viele mögliche Interaktionsszenarien konzentrieren.
Nehmen wir als Beispiel und um die Dinge einfach und klar zu halten, sagen wir, dass Roboter und Auto Klassen in meinem Projekt waren.
In der Roboterklasse hätte ich also viele Methoden im folgenden Muster:
- Schlaf(); isSleepAvaliable ();
- Erwachen(); isAwakeAvaliable ();
- gehen (Richtung); isWalkAvaliable ();
- schießen (Richtung); isShootAvaliable ();
- turnOnAlert (); isTurnOnAlertAvailable ();
- turnOffAlert (); isTurnOffAlertAvailable ();
- aufladen(); isRechargeAvailable ();
- ausschalten(); isPowerOffAvailable ();
- stepInCar (Auto); isStepInCarAvailable ();
- stepOutCar (Auto); isStepOutCarAvailable ();
- Selbstzerstörung(); isSelfDestructAvailable ();
- sterben(); isDieAvailable ();
- ist am Leben(); ist wach(); isAlertOn (); getBatteryLevel (); getCurrentRidingCar (); getAmmo ();
- ...
In der Autoklasse wäre es ähnlich:
- anschalten(); isTurnOnAvaliable ();
- abschalten(); isTurnOffAvaliable ();
- gehen (Richtung); isWalkAvaliable ();
- tanken(); isRefuelAvailable ();
- Selbstzerstörung(); isSelfDestructAvailable ();
- Absturz(); isCrashAvailable ();
- isOperational (); isOn (); getFuelLevel (); getCurrentPassenger ();
- ...
Jedes dieser Elemente (Roboter und Auto) ist als Zustandsmaschine implementiert, wobei einige Aktionen in einigen Zuständen möglich sind und andere nicht. Die Aktionen ändern den Status des Objekts. Die Aktionsmethoden werden ausgelöst, IllegalStateException
wenn sie in einem ungültigen Zustand aufgerufen werden, und die isXXXAvailable()
Methoden geben an, ob die Aktion zu diesem Zeitpunkt möglich ist. Obwohl einige leicht aus dem Zustand abgeleitet werden können (z. B. im Schlafzustand ist Wach verfügbar), sind andere nicht (um zu schießen, muss es wach, lebendig, mit Munition sein und kein Auto fahren).
Darüber hinaus sind auch die Wechselwirkungen zwischen den Objekten komplex. ZB kann das Auto nur einen Roboterpassagier halten. Wenn also ein anderer versucht einzutreten, sollte eine Ausnahme ausgelöst werden. Wenn das Auto abstürzt, sollte der Passagier sterben; Wenn der Roboter in einem Fahrzeug tot ist, kann er nicht aussteigen, auch wenn das Auto selbst in Ordnung ist. Befindet sich der Roboter in einem Auto, kann er vor dem Aussteigen keinen anderen betreten. etc.
Das Ergebnis ist, wie ich bereits sagte, dass diese Klassen wirklich komplex wurden. Um die Sache noch schlimmer zu machen, gibt es Hunderte von möglichen Szenarien, in denen der Roboter und das Auto interagieren. Darüber hinaus muss ein Großteil dieser Logik auf entfernte Daten in anderen Systemen zugreifen. Das Ergebnis ist, dass Unit-Tests sehr schwierig wurden und wir viele Testprobleme haben, von denen eines das andere in einem Teufelskreis verursacht:
- Die Testfall-Setups sind sehr komplex, da sie eine sehr komplexe Welt zum Trainieren erstellen müssen.
- Die Anzahl der Tests ist enorm.
- Die Ausführung der Testsuite dauert einige Stunden.
- Unsere Testabdeckung ist sehr gering.
- Der Testcode wird in der Regel Wochen oder Monate später als der von ihnen getestete Code oder überhaupt nicht geschrieben.
- Viele Tests sind ebenfalls fehlerhaft, hauptsächlich weil sich die Anforderungen des getesteten Codes geändert haben.
- Einige Szenarien sind so komplex, dass sie beim Setup während des Setups fehlschlagen (wir haben in jedem Test ein Timeout konfiguriert, im schlimmsten Fall 2 Minuten lang und selbst dieses Mal haben wir sichergestellt, dass es sich nicht um eine Endlosschleife handelt).
- Fehler treten regelmäßig in der Produktionsumgebung auf.
Dieses Roboter- und Autoszenario ist eine grobe Vereinfachung dessen, was wir in der Realität haben. Diese Situation ist eindeutig nicht beherrschbar. Deshalb bitte ich um Hilfe und Vorschläge, um: 1, die Komplexität der Klassen zu reduzieren; 2. Vereinfachen Sie die Interaktionsszenarien zwischen meinen Objekten. 3. Reduzieren Sie die Testzeit und die Menge des zu testenden Codes.
EDIT:
Ich glaube, ich war nicht klar über die Zustandsmaschinen. Der Roboter selbst ist eine Zustandsmaschine mit den Zuständen "schlafen", "wach", "aufladen", "tot" usw. Das Auto ist eine andere Zustandsmaschine.
EDIT 2: Für den Fall, dass Sie neugierig sind, was mein System tatsächlich ist, sind die Klassen, die interagieren, Dinge wie Server, IPAddress, Disk, Backup, Benutzer, SoftwareLicense usw. Das Szenario Roboter und Auto ist nur ein Fall, den ich gefunden habe das wäre einfach genug, um mein Problem zu erklären.
quelle
Antworten:
Das State- Entwurfsmuster kann hilfreich sein, wenn Sie es nicht bereits verwenden.
Die Kernidee ist , dass Sie eine innere Klasse für jeden einzelnen Staat zu schaffen - so wird Ihre Beispiel fortzusetzen
SleepingRobot
,AwakeRobot
,RechargingRobot
undDeadRobot
alle Klassen sein würde, eine gemeinsame Schnittstelle implementieren.Methoden für die
Robot
Klasse (wiesleep()
undisSleepAvaliable()
) haben einfache Implementierungen, die an die aktuelle innere Klasse delegieren.Zustandsänderungen werden implementiert, indem die aktuelle innere Klasse gegen eine andere ausgetauscht wird.
Der Vorteil dieses Ansatzes besteht darin, dass jede der Zustandsklassen erheblich einfacher ist (da sie nur einen möglichen Zustand darstellt) und unabhängig getestet werden kann. Abhängig von Ihrer Implementierungssprache (nicht angegeben) müssen Sie möglicherweise immer noch alles in derselben Datei haben, oder Sie können Dinge in kleinere Quelldateien aufteilen.
quelle
Ich kenne Ihren Code nicht, aber am Beispiel der "Schlaf" -Methode gehe ich davon aus, dass es sich um etwas handelt, das dem folgenden "simplen" Code ähnelt:
Ich denke, man muss zwischen Integrationstests und Unit-Tests unterscheiden . Es ist sicherlich eine große Aufgabe, einen Test zu schreiben, der Ihren gesamten Maschinenzustand durchläuft. Das Schreiben kleinerer Komponententests, mit denen geprüft wird, ob Ihre Schlafmethode ordnungsgemäß funktioniert, ist einfacher. Zu diesem Zeitpunkt müssen Sie nicht wissen, ob der Maschinenzustand ordnungsgemäß aktualisiert wurde oder ob das "Auto" korrekt auf die Tatsache reagiert hat, dass der Maschinenzustand vom "Roboter" aktualisiert wurde ... usw. Sie erhalten ihn.
Angesichts des obigen Codes würde ich das "machineState" -Objekt verspotten und mein erster Test wäre:
Meine persönliche Meinung ist, dass das Schreiben solcher kleinen Unit-Tests das erste sein sollte, was zu tun ist. Das hast du geschrieben:
Das Ausführen dieser kleinen Tests sollte sehr schnell sein, und Sie sollten im Voraus nichts zu initialisieren haben, wie Ihre "komplexe Welt". Wenn es sich beispielsweise um eine Anwendung handelt, die auf einem IOC-Container basiert (z. B. Spring), sollten Sie den Kontext während Unit-Tests nicht initialisieren müssen.
Nachdem Sie einen guten Prozentsatz Ihres komplexen Codes mit Komponententests abgedeckt haben, können Sie mit dem Erstellen zeitaufwändigerer und komplexerer Integrationstests beginnen.
Schließlich kann dies unabhängig davon erfolgen, ob Ihr Code komplex ist (wie Sie bereits sagten) oder nachdem Sie ihn überarbeitet haben.
quelle
Ich habe den Abschnitt "Ursprung" des Wikipedia-Artikels über das Prinzip der Schnittstellentrennung gelesen und wurde an diese Frage erinnert.
Ich werde den Artikel zitieren. Das Problem: "... eine Hauptberufsklasse ... eine Fettklasse mit einer Vielzahl von Methoden, die für eine Vielzahl unterschiedlicher Kunden spezifisch sind." Die Lösung: "... eine Schicht von Schnittstellen zwischen der Jobklasse und all ihren Clients ..."
Ihr Problem scheint eine Permutation desjenigen zu sein, das Xerox hatte. Anstelle einer Fettklasse haben Sie zwei, und diese beiden Fettklassen sprechen miteinander anstatt mit vielen Kunden.
Können Sie die Methoden nach Interaktionstyp gruppieren und dann für jeden Typ eine Schnittstellenklasse erstellen? Zum Beispiel: RobotPowerInterface-, RobotNavigationInterface-, RobotAlarmInterface-Klassen?
quelle