Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

I am in a distributed system project written in java where we have some classes which corresponds to very complex real-world-business objects. These objects have a lot of methods corresponding to actions that the user (or some other agent) may apply to that objects. As a result, these classes became very complex.

The system general architecture approach has lead to a lot of behaviors concentrated on a few classes and a lot of possible interaction scenarios.

As an example and to keep things easy and clear, let's say that Robot and Car were classes in my project.

So, in the Robot class I would have a lot of methods in the following pattern:

  • sleep(); isSleepAvaliable();
  • awake(); isAwakeAvaliable();
  • walk(Direction); isWalkAvaliable();
  • shoot(Direction); isShootAvaliable();
  • turnOnAlert(); isTurnOnAlertAvailable();
  • turnOffAlert(); isTurnOffAlertAvailable();
  • recharge(); isRechargeAvailable();
  • powerOff(); isPowerOffAvailable();
  • stepInCar(Car); isStepInCarAvailable();
  • stepOutCar(Car); isStepOutCarAvailable();
  • selfDestruct(); isSelfDestructAvailable();
  • die(); isDieAvailable();
  • isAlive(); isAwake(); isAlertOn(); getBatteryLevel(); getCurrentRidingCar(); getAmmo();
  • ...

In the Car class, it would be similar:

  • turnOn(); isTurnOnAvaliable();
  • turnOff(); isTurnOffAvaliable();
  • walk(Direction); isWalkAvaliable();
  • refuel(); isRefuelAvailable();
  • selfDestruct(); isSelfDestructAvailable();
  • crash(); isCrashAvailable();
  • isOperational(); isOn(); getFuelLevel(); getCurrentPassenger();
  • ...

Each of these (Robot and Car) is implemented as a state machine, where some actions are possible in some states and some aren't. The actions changes the object's state. The actions methods throws IllegalStateException when called in an invalid state and the isXXXAvailable() methods tell if the action is possible at the time. Although some are easily deducible from the state (e.g, in the sleeping state, awake is available), some aren't (to shoot, it must be awake, alive, having ammo and not riding a car).

Further, the interactions between the objects are complex too. E.g, the Car can only hold one Robot passenger, so if another one try to enter, an exception should be thrown; If the car crashes, the passenger should die; If the robot is dead inside a vehicle, he can't step out, even if the Car itself is ok; If the Robot is inside a Car, he can't enter another one before stepping out; etc.

The result of this, is as I already said, these classes became really complex. To make things worse, there is hundreds possible scenarios when the Robot and the Car interacts. Further, much of that logic does needs to access remote data in other systems. The result is that unit-testing it became very hard and we have a lot of testing problems, one causing the another in a vicious circle:

  • The testcases setups are very complex, because they need to create a significantly complex world to exercize.
  • The number of tests is huge.
  • The test suite takes some hours to run.
  • Our test coverage is very low.
  • The testing code tends to be written weeks or months later than the code that they test, or never at all.
  • A lot of tests are broken too, mainly because the requirements of the tested code changed.
  • Some scenarios are so complex, that they fail on timeout during the setup (we configured a timeout in each test, in the worst cases 2 minutes long and even this time long they timeouts, we ensured that it is not an infinite loop).
  • Bugs regularly slips into production environment.

That Robot and Car scenario is a gross over-simplification of what we have in reality. Clearly, this situation is not manageable. So, I am asking help and suggestions to: 1, Reduce the classes complexity; 2. Simplify the interactions scenarios between my objects; 3. Reduce the test time and the amout of code to be tested.

EDIT:
I think I was not clear about the state machines. the Robot is itself a state machine, with states "sleeping", "awake", "recharging", "dead", etc. The Car is another state machine.

EDIT 2: In the case that you are curious about what my system actually is, the classes that interact are things like Server, IPAddress, Disk, Backup, User, SoftwareLicense, etc. The Robot and Car scenario is just a case that I found that would be simple enough to explain my problem.

share|improve this question
    
did you consider asking at Code Review.SE? Other than that, for design like yours I'd start thinking about Extract Class kind refactoring –  gnat Feb 27 '12 at 7:49
    
I considered Code Review, but that is not the right place. The main problem is not in the code itself, the problem is in the system general architecture approach that leads to a lot of behaviours concentrated on a few classes and a lot of possible interaction scenarios. –  Victor Feb 27 '12 at 7:55
    
@gnat Can you provide an example of how I would implement Extract Class in the given Robot and Car scenario? –  Victor Feb 27 '12 at 8:21
    
I'd extract Car-related stuff from Robot into a separate class. I'd also extract all methods related to sleep+awake into a dedicated class. Other "candidates" that seem to deserve extraction are power+recharge methods, movement related stuff. Etc. Note since this is refactoring, external API for robot should probably stay; at first stage I'd modify only internals. BTDTGTTS –  gnat Feb 27 '12 at 8:27
    
This is not a Code Review question - architecture is off-topic there. –  Michael K Feb 27 '12 at 13:46
show 1 more comment

3 Answers 3

The State design pattern might be of use, if you're not already using it.

The core idea is that you create an inner class for each distinct state - so to continue your example, SleepingRobot, AwakeRobot, RechargingRobot and DeadRobot would all be classes, implementing a common interface.

Methods on the Robot class (like sleep() and isSleepAvaliable()) have simple implementations that delegate to the current inner class.

State changes are implemented by swapping out the current inner class with a different one.

The advantage with this approach is that each of the state classes is dramatically simpler (as it represents just one possible state), and can be independently tested. Depending on your implementation language (not specified), you may still be constrained to having everything in the same file, or you may be able to split things out into smaller source files.

share|improve this answer
    
I am using java. –  Victor Feb 27 '12 at 11:41
    
Good suggestion. This way each implementation has a clear focus which can be tested individually without having a 2.000 line junit class testing all states simultaneously. –  OliverS Feb 27 '12 at 12:47
add comment

I don't know your code, but taking the example of the "sleep" method, I'll assume it is something along the like of the following "simplistic" code:

public void sleep() {
 if(!dead && awake) {
  sleeping = true;
  awake = false;
  this.updateState(SLEEPING);
 }
 throw new IllegalArgumentException("robot is either dead or not awake");
}

I think you have to make a difference between integration tests and unit tests. Writing a test that runs trough your whole machine state is certainly a big task. Writing smaller unit tests that test whether your sleep method works properly is easier. At this point you don't have to know whether the machine state has been properly updated or if the "car" correctly responded to the fact that the machine state has been updated by the "robot"... etc. you get it.

Given the code above, I would mock the "machineState" object and my first test would be:

testSleep_dead() {
 robot.dead = true;
 robot.awake = false;
 robot.setState(AWAKE);
 try {
  robot.sleep();
  fail("should have got an exception");
 } catch(Exception e) {
  assertTrue(e instanceof IllegalArgumentException);
  assertEquals("robot is either dead or not awake", e.getMessage());
 }
}

My personal opinion is that writing such small unit tests should be the first thing to do. You wrote that:

The testcases setups are very complex, because they need to create a significantly complex world to exercize.

Running these small tests should be very fast, and you should not have anything to initialize beforehand like your "complex world". For example, if it's an application based on an IOC container (say, Spring), you should not need to initialize the context during unit tests.

After you have covered a good percentage of your complex code with unit tests, you may start building more time-consuming and more complex integration tests.

Finally, this can be done whether your code is complex (as you said it is now), or after you have refactored.

share|improve this answer
    
I think I was not clear about the state machines. the Robot is itself a state machine, with states "sleeping", "awake", "recharging", "dead", etc. The Car is another state machine. –  Victor Feb 27 '12 at 8:04
    
@Victor OK, feel free to correct my example code if you want. Unless you tell me otherwise, I think my point on unit tests is still valid, I hope so at least. –  Jalayn Feb 27 '12 at 8:07
    
I corrected the example. I don't have priviledge to make it readily visible, so it must be peer-reviewed first. Your comment is helpful. –  Victor Feb 27 '12 at 8:13
    
@Victor I approved your edit. Thanks. –  Jalayn Feb 27 '12 at 8:32
add comment

I was reading the "Origin" section of the Wikipedia article on the Interface Segregation Principle, and I was reminded of this question.

I shall quote the article. The problem: "...one main Job class....a fat class with multitudes of methods specific to a variety of different clients." The solution: "...a layer of interfaces between the Job class and all of its clients..."

Your problem seems a permutation of the one Xerox had. Instead of one fat class you have two, and these two fat classes are talking to each other instead of lots of clients.

Can you group the methods by type of interaction and then create an interface class for each type? For example: RobotPowerInterface, RobotNavigationInterface, RobotAlarmInterface classes?

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.