Functional Layer Design

Implementing Traceability does impose a few requirements upon the Functional Layer:

  1. All methods must be virtual.
  2. All properties must be virtual.
  3. No (or very few) private methods.
  4. No (or very few) static methods.
  5. 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 values being passed in and as parameters as well as intercepting the returned values. 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 as absolutely 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. Another way to handle it would be to have a specific function that updates the internal value. If this function is made protected virtual, then we can Trace it, and thus know whenever the internal value is updated.

In the above example, the static method was outright removed. The real question should be: what is the static function doing? If it was used as a factory for the instance, then that will be taken care of later in Factories section. If the static function is public, part of class that is instanced, and not being used as a factory, then it most likely should not be part of the class anyway. If the static function is protected or private, and is being used to calculate a value inside of one other function, then it should be changed into protected virtual function so that Traceability may be added to 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 values in the above code are properties that are being traced, the trace output will show all of their, 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. The 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; }

Design note: it is normally considered bad code design to write a single line function that is then called in only one location. Normally I would agree with that sentiment; however, we are moving the code outside of the function for the very specific reason of adding Traceability. From experience, I have found that the time saved being able to analyze data quickly outweighed someone's sense of "code purity".

When creating new instances of a class, a factory pattern needs to be used. Factory patterns will greatly simplify the interaction and maintainability of the two layers. See Factories for more information.

Other than the above caveats, writing the code for the Functional Layer should use the coding standard and design patterns preferred by your design process.

Next: Traceable Layer