Wiki

Clone wiki

TraceabilityV2 / Integration / TracingCode

Home

Project Integration

#Tracing Code

##Preparing a class for Tracing Adding Traceability to a project is not as simple as adding a reference to the appropriate dll. Classes that are going t be traced need to meet the following requirements:

  • All methods must be virtual.
  • All properties must be virtual.
  • No (or very few) private methods.
  • No (or very few) static methods.
  • Factories must be used to generate any new instances of a class.

We need the methods to be virtual the simple reason of allowing the Traceable class to be able to override them, and thus intercept the calls and extracting the trace data. Likewise, properties need to be virtual as well.

Any internal values, or methods that are private or static cannot be overridden; thus we need to avoid private or static members as absolutely much as possible.

Here is an example of class that has several elements that may not be made Traceable:

namespace FunctionalLayer.Objects
{
    public class NonTraceableExample
    {
        private int _internalValue;
        public string TypicalProperty { get; set; }
        public int CalculateValue()
        { ... }
        protected int ProtectedCall()
        { ... }
        private int PrivateCall()
        { ... }
        public static Type StaticCall(...)
        { ... }
    }
}

We are able to make the class ready to be traced by some rather straightforward modifications:

namespace FunctionalLayer.Objects
{
    public class TraceableReadyExample
    {
        private int _internalValue; // still not traceable
        public virtual string TypicalProperty { get; set; }
        public virtual int CalculateValue()
        { ... }
        protected virtual int ProtectedCall()
        { ... }
        protected virtual int PrivateCall() // made protected
        { ... }
    }
}

Note that the internal value is still not traceable, and that the static method was removed.

We cannot make the internal value Traceable for the simple fact that it is not a property. One way to handle it would be to make it a protected virtual property. Then we could trace its value anytime it is used.

In the above example, the static method was outright removed. The real question should be: what is the static function doing? Do we really need it to be part of the class? Most likely, a little bit of refactoring can be used to remove it.

Another concern to keep in mind is temporary values inside of a function call. For example: public virtual double GetSmallestValue(double scale) { double a = _m1 * _x1 + _b1; double b = _m2 * _x2 + _b2; double c = _m3 * _x3 + _b3; return scale * Math.Min(Math.Min(a, b), c); // intermediate values of a. b. and c will not be traced }

If all of the fields in the above code are properties that are being traced, the trace output will show all of their values used, but it will not show the final result of each line. During several of my investigations, this was the specific type of information I needed. One way fix to this issue is to move each calculation to a separate protected virtual function. Those functions may then be traced, and we get all the information.

public virtual double GetSmallestValue(double scale)
{
    double a = Calculation1();
    double b = Calculation2();
    double c = Calculation3();
    return scale * Math.Min(Math.Min(a, b), c);
}

protected virtual double Calculation1()
{
    return _m1 * _x1 + _b1;
}

protected virtual double Calculation2()
{
    return _m2 * _x2 + _b2;
}

protected virtual double Calculation3()
{
    return _m3 * _x3 + _b3;
}

The necessity of this information should be the primary driver to deciding how the code is written.

##Trace Class Trace classes may be written manually, or automatically generated. While the preferred method is generated, it is still a good idea to know how to do it manually just in case the generated code has issues with an unforeseen edge case.

Each trace class should meet these requirements: * There should be a one to one correspondence between a class in the Functional Layer and the Traceable Layer. * The traceable class must derive from the functional class it is mimicking. * The Traceable Layer must never have business logic. * Factories must be used to generate any new instances of a class. * The Traceable Layer should be kept in a separate library from the Functional Layer.

The one-to-one ratio is fairly straightforward: if we want to track what is going inside of a class in the Functional Layer, then we want to make a corresponding class in the Traceable Layer. What is less straightforward is that any class in the Traceable Layer must inherit from the corresponding Functional Layer. This requirement ensures that the Traceable class is able to take the place of the Functional class with no issues. The idea behind this is that then a Traceable class may be introduced at any point, and there is no fear its presence will will cause any disruption in the service.

Making sure that no new logic is introduced in the Traceable Layer is of the utmost importance. The purpose of the Traceable Layer is to give us visibility into what is happening inside of the Functional Layer. If we add new logic in the Traceable Layer, we no longer have an accurate view.

By using factories we are able to greatly reduce the complexity of maintaining the Traceable Layer. See Factories for more details.

Finally, by keeping the code of the Traceable Layer in a separate library, we are better able to ensure that the Traceable Layer is not accidentally used in a performance critical environment. Simply put, if the Traceable library is not present on the machine, we know it is not being used.

###Constructors The interface of a Traceable Layer class will only differ from the interface of the corresponding Functional Layer class in one way: all of the constructors in the Traceable class will take an additional parameter: the Tracer instance. For example:

namespace FunctionalLayer.Objects
{
    public class Example
    {
        public Example()
        { ... }

        Public Example(DataStream dataStream)
        { ... }
    }
}

namespace TraceableLayer.Objects
{
    public class Example : FunctionalLayer.Objects.Example
    {
        public Example(ITracer tracer)
        {
            _tracer = tracer;
        }

        Public Example(ITracer tracer, DataStream dataStream)
            : base(dataStream)
        {
            _tracer = tracer;
        }

        private ITracer _tracer;
    }
}

This single difference is why we will use Factories to create everything. The factories will allow us to to switch between using Functional classes and Traceable classes transparently since their object construction calls will appear the same.

Note: I have found it better to place the ITracer parameter at the front of the parameter list for any constructor. This reinforces the fact that the Trace class's purpose is to generate trace data. It also protects any default values that parameters may be using. The second fact is less important, since we should be using factories to generate any instance, but it is there just in case.

###Tracing a Method, Simple Tracing a method with no parameters and no return value is very straightforward: the wrapping trace method simply calls _tracer.FunctionStart(), then the base method, and finally _tracer.FunctionEnd().

Functional: class FunctionalClass { ... public virtual void Method() { ... } }

Traceable: class TraceableClass : FunctionalClass { ... public override void Method() { _tracer.FunctionStart("FunctionalClass.Method"); base.Method(); _tracer.FunctionEnd(); } ... private readonly Itracer _tracer; }

###Tracing a Method, with return

Functional: class FunctionalClass { ... public virtual int Method() { ... } }

Traceable: class TraceableClass : FunctionalClass { ... public override int Method() { _tracer.FunctionStart("FunctionalClass.Method"); var result = base.Method(); _tracer.FunctionEnd(result); } ... private readonly Itracer _tracer; }

###Tracing a Method, with parameters

Functional: class FunctionalClass { ... public virtual void Method(int param1, string param2) { ... } }

Traceable: class TraceableClass : FunctionalClass { ... public override void Method(int param1, string param2) { _tracer.FunctionStart("FunctionalClass.Method"); _tracer.Parameter("param1", param1); _tracer.Parameter("param2", param2); base.Method(); _tracer.FunctionEnd(); } ... private readonly Itracer _tracer; }

###Tracing a Method, with ref or out parameters

Functional: class FunctionalClass { ... public virtual void Method(ref int param1, out int param2) { ... } }

Traceable: class TraceableClass : FunctionalClass { ... public override void Method(ref int refParam, out int outParam) { _tracer.FunctionStart("FunctionalClass.Method"); _tracer.Parameter("refParam", refParam); base.Method(); _tracer.Parameter("refParam", refParam); _tracer.Parameter("outParam", outParam); _tracer.FunctionEnd(); } ... private readonly Itracer _tracer; }

###Tracing a Property

Functional: class FunctionalClass { ... public virtual int Property { get; set;} }

Traceable: class TraceableClass : FunctionalClass { ... public override int Property { get { return _tracer.GetProperty(""FunctionalClass.Property"", base.Property); } set { base.Property = _tracer.SetProperty(""FunctionalClass.Property"", value); } } ... private readonly Itracer _tracer; }

Next: Factories

Updated