Wiki
Clone wikiTraceabilityV2 / DesignNotes
#Design Notes
This section covers some of the background reasons covering why I designed the Traceability library the way I did.
##Inheritance vs Decorator Pattern I originally had looked at using the decorator pattern for traceability, however, I quickly ran into a serious issue: it would cause me to lose trace data. The problem was that a decorator only wraps the outer level of a class. For example, we have an interface that implements three methods. MethodA(), MethodB(), and MethodC():
interface IExample
{
void MethodA(...);
int MethodB(...);
string MethodC(...);
}
A decorator would look like this: public class Decorator : IExample { private IExample _example;
public void MethodA(...)
{
// tracing logic
_exmample.MethodA(...)
// tracing logic
}
public void MethodB(...)
{
// tracing logic
_exmample.MethodB(...)
// tracing logic
}
public void MethodC(...)
{
// tracing logic
_exmample.MethodC(...)
// tracing logic
}
}
However, the actual class is implemented such that MethodA() calls MethodB(), which calls MethodC().
public class Example : IExample
{
public void MethodA(...)
{
// ...
MethodB(...)
// ...
}
public void MethodB(...)
{
// ...
MethodC(...)
// ...
}
public void MethodC(...)
{
// ...
}
}
If we call MethodA() through the decorator, we will trace MethodA(), but MethodB() and MethodC() will not be traced. The call stack will look like this: enter Decorator.MethodA enter Example.MethodA enter Example.MethodB enter Example.MethodC exit Example.MethodC exit Example.MethodB exit Example.MethodA exit Decorator.MethodA
This is due to the fact that the underlying class is not aware of the decorator. Nor should it be aware of the decorator. Any decorator is supposed to work without the underlying class knowing it is there.
To solve this issue, we can use inheritance. Now, the example class now has virtual methods:
public class Example // interface not needed
{
public virtual void MethodA(...)
{
// ...
MethodB(...)
// ...
}
public virtual void MethodB(...)
{
// ...
MethodC(...)
// ...
}
public virtual void MethodC(...)
{
// ...
}
}
And the trace class looks like this:
public class TraceExample : Example
{
public override void MethodA(...)
{
// tracing logic
base.MethodA(...)
// tracing logic
}
public override void MethodB(...)
{
// tracing logic
base.MethodB(...)
// tracing logic
}
public override void MethodC(...)
{
// tracing logic
base.MethodC(...)
// tracing logic
}
}
The call stack will now look like this:
enter TraceExample.MethodA
enter Example.MethodA
enter TraceExample.MethodB
enter Example.MethodB
enter TraceExample.MethodC
enter Example.MethodC
exit Example.MethodC
exit TraceExample.MethodC
exit Example.MethodB
exit TraceExample.MethodB
exit Example.MethodA
exit TraceExample.MethodA
Since we used inheritance, we will trace all of the calls.
##Code File Generation One of the important aspects of Traceability is the ability to prove that the trace code is not changing the functionality of the underlying code. After all, if the trace code does change the functionality, that would mean it is no longer extracting what the underlying code was actually doing.
By generating trace code files, we are able to inspect the exact code that will be used. Thus, if necessary we are able to audit that code, and verify that it has not changed any functionality.
Furthermore, we are able to write unit tests in such a way that any unit test ran against a functional class will also be ran against the trace class. Thus, if we have full code coverage when testing the functional class, we can demonstrably show that the trace class has not changed the logic.
Updated