Intent Understand how objects in the system collaborate by stepping through examples in a debugger.
How do you discover which objects are instantiated at run-time and how they collaborate?
This problem is difficult because:
- The source code exposes the class hierarchy, not the objects instantiated at run time and how they interact.
- Collaborations are typically spread out through the code. Although it is easy to see which classes and methods are defined in a system, it can be hard to tell by reading the source code alone which sequence of events will lead to an object being created or a method being invoked.
- In the presence of polymorphism, it can be especially difficult to tell which objects are clients of which service providers. Just because an object uses a certain interface that another object provides, does not mean that the former is actually a client of the latter.
- Reading the code will not tell you what concrete scenarios can take place. The actual flow of execution will depend on the internal state of all participating objects and this cannot be inferred directly from the source code.
- The source code will not tell you which objects are long-lived and which are ephemeral (i.e., local to the execution of a single method).
Yet, solving this problem is feasible because:
- You are aware of some typical usage scenarios.
- You can run the code inside a debugger.
- Your attention is focused on part of the system.
Run each of the scenarios and use your debugger to step through the code. Observe which objects collaborate and how they are instantiated. Afterwards, generalize these observations and record your knowledge for future reference, possibly by means of Tie Code and Questions and Record Business Rules as Tests.
It is too time-consuming to step through every single statement of a running system. The assumption here is that you are focused on some specific aspect of the system that is difficult to understand.
- Set breakpoints to interrupt execution when the system enters the code you are interested in.
- Change the internal state of the objects to see how alternative execution paths are triggered.
- Restart a method currently on the execution stack to quickly verify a similar scenario.
- Realistic View. By stepping through the running program, you get a precise picture of how the scenario unfolds. Moreover, you can inspect the internal state of the objects involved, see how new objects are created and observe which objects collaborate under which circumstances.
- Handles complexity. On a small scale it is possible to infer object collaborations from analyzing the source code. Slicing tools for instance may tell you which statements of the source code are affected by a given variable. For large and complex systems however, the number of possibilities and interactions is just too large. Therefore, the only reasonable way to learn how objects collaborate is to study the execution traces.
- Scenario-based. Your must restrict yourself to a limited set of scenarios, hence the observed object-collaborations are necessarily incomplete. Of course you must do your best to choose representative scenarios. Unfortunately, this choice brings you back to square one, because the only way to be sure that you have a representative set of scenarios is to verify whether they cover all possible object-collaborations.
- Restricted Applicability. For systems where time plays a crucial role, stepping through the execution will give you an unrealistic view of the system’s behavior. Worse, for concurrent or distributed systems the mere fact of stepping through concurrent code may perturb the execution of the system itself. As such, you get the same effects as in Heisenberg’s uncertainty experiments, where determining exact positions of quantum particles imply that other attributes about these particles become uncertain.
- Dependency on Tools. You need to have good debugger to Step Through the Execution. Not only must it allow to set and remove breakpoints dynamically, it also should provide the means to examine the state of the objects involved. And to easily verify alternative paths, the debugger should allow you to change the internal state of an object, or even restart a method currently on the execution stack.
You will need concrete scenarios in order to Step Through the Execution (possibly inferred from Interview During Demo). Consider encoding these scenarios as test cases. You can then iteratively Write Tests to Understand as you Step Through the Execution since the insights you gain into the states of collaborating objects can then be formulated as concrete tests.
As you Step Through the Execution, it is a good idea to keep an eye on the way collaborating objects use each other’s interface. Afterwards, you can exploit the knowledge you have gained to Look for the Contracts.