Je suis dans un projet de système distribué écrit en java où nous avons des classes qui correspondent à des objets métier très complexes. Ces objets ont de nombreuses méthodes correspondant aux actions que l'utilisateur (ou un autre agent) peut appliquer à ces objets. En conséquence, ces classes sont devenues très complexes.
L'approche de l'architecture générale du système a conduit à de nombreux comportements concentrés sur quelques classes et à de nombreux scénarios d'interaction possibles.
À titre d'exemple et pour que les choses soient simples et claires, disons que Robot et Car étaient des classes dans mon projet.
Donc, dans la classe Robot, j'aurais beaucoup de méthodes dans le modèle suivant:
- dormir(); isSleepAvaliable ();
- éveillé(); isAwakeAvaliable ();
- marche (Direction); isWalkAvaliable ();
- tirer (Direction); isShootAvaliable ();
- turnOnAlert (); isTurnOnAlertAvailable ();
- turnOffAlert (); isTurnOffAlertAvailable ();
- recharger(); isRechargeAvailable ();
- éteindre(); isPowerOffAvailable ();
- stepInCar (Voiture); isStepInCarAvailable ();
- stepOutCar (Voiture); isStepOutCarAvailable ();
- auto-destruction(); isSelfDestructAvailable ();
- mourir(); isDieAvailable ();
- est vivant(); est réveillé(); isAlertOn (); getBatteryLevel (); getCurrentRidingCar (); getAmmo ();
- ...
Dans la classe Voiture, ce serait similaire:
- allumer(); isTurnOnAvaliable ();
- éteindre(); isTurnOffAvaliable ();
- marche (Direction); isWalkAvaliable ();
- ravitailler(); isRefuelAvailable ();
- auto-destruction(); isSelfDestructAvailable ();
- crash(); isCrashAvailable ();
- isOperational (); est sur(); getFuelLevel (); getCurrentPassenger ();
- ...
Chacun d'entre eux (Robot et voiture) est implémenté comme une machine à états, où certaines actions sont possibles dans certains états et d'autres non. Les actions modifient l'état de l'objet. Les méthodes d'actions sont lancées IllegalStateException
lorsqu'elles sont appelées dans un état non valide et les isXXXAvailable()
méthodes indiquent si l'action est possible à ce moment-là. Bien que certains soient facilement déductibles de l'état (par exemple, dans l'état de sommeil, l'éveil est disponible), certains ne le sont pas (pour tirer, il doit être éveillé, vivant, avoir des munitions et ne pas conduire de voiture).
De plus, les interactions entre les objets sont également complexes. Par exemple, la voiture ne peut contenir qu'un seul passager robot, donc si un autre essaie d'entrer, une exception doit être levée; Si la voiture s'écrase, le passager doit mourir; Si le robot est mort à l'intérieur d'un véhicule, il ne peut pas sortir, même si la voiture elle-même est ok; Si le robot est à l'intérieur d'une voiture, il ne peut pas en entrer une autre avant de sortir; etc.
Le résultat de cela, comme je l'ai déjà dit, ces classes sont devenues vraiment complexes. Pour aggraver les choses, il existe des centaines de scénarios possibles lorsque le robot et la voiture interagissent. En outre, une grande partie de cette logique doit accéder à des données distantes dans d'autres systèmes. Le résultat est que les tests unitaires sont devenus très difficiles et nous avons beaucoup de problèmes de test, l'un causant l'autre dans un cercle vicieux:
- Les configurations de testcases sont très complexes, car elles doivent créer un monde considérablement complexe à exercer.
- Le nombre de tests est énorme.
- La suite de tests prend quelques heures à s'exécuter.
- Notre couverture de test est très faible.
- Le code de test a tendance à être écrit des semaines ou des mois plus tard que le code qu'ils testent, ou jamais du tout.
- De nombreux tests sont également interrompus, principalement parce que les exigences du code testé ont changé.
- Certains scénarios sont si complexes qu'ils échouent lors de la temporisation lors de la configuration (nous avons configuré une temporisation dans chaque test, dans le pire des cas 2 minutes et même cette fois ils dépassent le délai, nous nous sommes assurés qu'il ne s'agit pas d'une boucle infinie).
- Les bugs se glissent régulièrement dans l'environnement de production.
Ce scénario Robot and Car est une simplification grossière de ce que nous avons en réalité. De toute évidence, cette situation n'est pas gérable. Donc, je demande de l'aide et des suggestions pour: 1, réduire la complexité des classes; 2. Simplifier les scénarios d'interactions entre mes objets; 3. Réduisez la durée du test et la quantité de code à tester.
EDIT:
Je pense que je n'étais pas clair sur les machines d'état. le Robot est lui-même une machine d'état, avec des états "endormi", "éveillé", "en recharge", "mort", etc. La voiture est une autre machine d'état.
EDIT 2: Dans le cas où vous êtes curieux de savoir ce qu'est réellement mon système, les classes qui interagissent sont des choses comme Server, IPAddress, Disk, Backup, User, SoftwareLicense, etc. Le scénario Robot and Car est juste un cas que j'ai trouvé ce serait assez simple pour expliquer mon problème.
la source
Réponses:
Le modèle de conception d' état peut être utile si vous ne l'utilisez pas déjà.
L'idée de base est que vous créez une classe interne pour chaque état distinct - à continuer votre exemple,
SleepingRobot
,AwakeRobot
,RechargingRobot
etDeadRobot
seraient tous des cours, la mise en œuvre d' une interface commune.Les méthodes de la
Robot
classe (commesleep()
etisSleepAvaliable()
) ont des implémentations simples qui se délèguent à la classe interne actuelle.Les changements d'état sont implémentés en remplaçant la classe interne actuelle par une autre.
L'avantage de cette approche est que chacune des classes d'état est considérablement plus simple (car elle ne représente qu'un seul état possible) et peut être testée indépendamment. Selon votre langage d'implémentation (non spécifié), vous pouvez toujours être contraint d'avoir tout dans le même fichier, ou vous pouvez être en mesure de diviser les choses en fichiers source plus petits.
la source
Je ne connais pas votre code, mais en prenant l'exemple de la méthode "sleep", je suppose que c'est quelque chose qui ressemble au code "simpliste" suivant:
Je pense qu'il faut faire la différence entre les tests d'intégration et les tests unitaires . L'écriture d'un test qui s'exécute sur l'ensemble de l'état de votre machine est certainement une grosse tâche. Il est plus facile d'écrire des tests unitaires plus petits qui testent si votre méthode de sommeil fonctionne correctement. À ce stade, vous n'avez pas à savoir si l'état de la machine a été correctement mis à jour ou si la "voiture" a correctement répondu au fait que l'état de la machine a été mis à jour par le "robot" ... etc. vous l'obtenez.
Étant donné le code ci-dessus, je me moquerais de l'objet "machineState" et mon premier test serait:
Mon opinion personnelle est que l'écriture de tests unitaires aussi petits devrait être la première chose à faire. Vous avez écrit cela:
L'exécution de ces petits tests devrait être très rapide, et vous ne devriez rien avoir à initialiser au préalable comme votre "monde complexe". Par exemple, s'il s'agit d'une application basée sur un conteneur IOC (disons Spring), vous ne devriez pas avoir besoin d'initialiser le contexte pendant les tests unitaires.
Après avoir couvert un bon pourcentage de votre code complexe avec des tests unitaires, vous pouvez commencer à créer des tests d'intégration plus longs et plus complexes.
Enfin, cela peut être fait que votre code soit complexe (comme vous l'avez dit maintenant), ou après avoir refactorisé.
la source
Je lisais la section "Origine" de l'article de Wikipédia sur le principe de ségrégation d'interface , et cela m'a rappelé cette question.
Je citerai l'article. Le problème: "... une classe d'emplois principale ... une classe grasse avec une multitude de méthodes spécifiques à une variété de clients différents." La solution: "... une couche d'interfaces entre la classe Job et tous ses clients ..."
Votre problème semble être une permutation de celui de Xerox. Au lieu d'une classe de graisse, vous en avez deux, et ces deux classes de graisse se parlent au lieu de beaucoup de clients.
Pouvez-vous regrouper les méthodes par type d'interaction, puis créer une classe d'interface pour chaque type? Par exemple: RobotPowerInterface, RobotNavigationInterface, RobotAlarmInterface classes?
la source