A reflection on code reflection: where it helps, and where it hinders.

Today at work I broke some of my team project’s unit tests from a seemingly harmless code change (C#). I simply changed a protected member into an auto-property. Unfortunately the code change was bundled with other changes, for which any other innocent coder would have thought be the changes to blame. But it was the small, innocent (almost code-cosmetic), change that was carried out using a click of the button with Resharper. The test blew up because there was an important piece of code (somewhere) that used reflection to search the class for non-public instance (visible to the class type) member variables and collate members that inherited a specific interface. What is this!? Some silent protocol?? How fragile!

Image

Reflection is a powerful tool that has blessed us with many awesome easy to use API’s. But clearly, it is not suitable to solve all problems. So when should we use reflection? And when should we avoid it? Here are a few common pros and cons that tend to crop up around the topic of reflection (this is not by all means a comprehensive list!):

Good reflection 🙂

  • Dependency injection frameworks.
    Reflection has given us killer IoC tools like Windsor and Unity to solve our dependency problems.
    Clearly refection is a key enabler in the technology, as dependencies and instaintiation is all achievable via binary metadata analysis.
  • Plugin frameworks.
    Plugins frameworks commonly use reflection to dynamically load 3rd party plugins, which it could not do so easily without dynamically loading the additional libraries via reflection.

Bad reflection 😦

  • Refactoring tools and code analysis tools are incompatible.
    The opening example of this post shows that refactoring tools cannot cover what reflection can do: it can make your code brittle. It’s much better to be explicit with your code design; avoid establishing implicit protocols in your code base which your reflection code requires in order to work correctly. Note that static code analysis tools such as refactoring tools, or features like discovering method usage (e.g. with the reshaper tool) are rendered useless with code that uses reflection. This is a dangerous place to be.
  • Adds a super-generic layer of indirection.
    Indirection is a double edged sword: it can improve the design (and yield a number of benefits), but with the cost of adding complexity. The problem with reflection is that it adds a higher degree of indirection than non-reflective code, because it hides static detail such as class names, method names, and property names.  So heavy use of reflection makes the program near impossible to run through static code walk throughs. It also can be very difficult to debug. 
  • Run-time errors instead of compile-time errors.
    This argument can be used for all sorts of mechanisms (such as dynamic type-checking features), but it is a good point to make.  If you have the option of a design that doesn’t require reflection, at least you have a chance your compiler will complain if code changes have broken something. A design using reflection is subject to runtime errors, which in the worst case may not be detected until a release cycle (or in production!).
  • Invocation via reflection is much slower.
    Generally the performance hit from reflection is neglectable, but in sections of code where reflection is used heavily performance will degrade. Performance is much slower in reflection because during invocation the binaries metadata must be inspected at runtime (rather than being precompiled at compile time).

Conclusion

Avoid reflection.

If you think you need to solve a problem by reflection, rework the design (don’t be lazy!). Also don’t use reflection to get at protected data (e.g. non-public members), violating standard language conventions will get you into all sorts of trouble further down the line. Only use reflection where it is absolutely the only way to meet your needs. An acceptable place to use reflection is where there would be no way – at least without enforcing a difficult/cumbersome protocol to adhere to – to implement a solution. So be wary of the drawbacks of reflection before you get crazy with it, and always strive for a solid design!

Advertisements

Call-by-reference for primitives in Java

Java is call-by-value for primitives and offers no referencing / dereferencing operators. To emulate call by reference on primitive types you must use mutable classes that encapsulate them… a simple design pattern that does the job:


public class Mutable {
  private static Mutable instance = new Mutable();
  private Mutable() {}

  public class Integer {
    public Integer(int val) {
      this.value = val;
    }
    public int value;
  }

  public class Long {
    public Long(long val) {
      this.value = val;
    }
    public long value;
  }

  public static Integer createMutableInteger(int val) {
    return instance.new Integer(val);
  }

  public static Long createMutableLong(long val) {
    return instance.new Long(val);
  }
}

Simple, and doesn’t busy up your project will a class-per mutable primitive… as they are all packaged as inner classes :). Also the mutable design pattern can avoid the need to have to create tuple classes in order to retreive multiple data for a single call.

To use:

public int foo(String str, Mutable.Integer out1, Mutable.Integer out2) {
  ...
  out1.value = result1;
  out1.value = result2;
  ...
  return result3;
}

public void bar() {

  Mutable.Integer res1;
  Mutable.Integer res2;</code>

  int res3 = foo("test", res1, res2);</code>

  ...
}

KERN_DEBUG message level is not for debugging

LINUX KERNEL HACKERS / MODDERS BEWARE

If you are totally lost on why you aren’t getting debug messsages when you expect them in linux kernel space … or if you are relying on them for state information … DONT USE KERN_DEBUG. Grrr I have been slaving all day trying all these little test programs/modules for a project I’m working on. I finally clicked that the /var/log/* files are buffered to quite a lot of messages (well at least the /var/log/debug file). I think its mainly if you are printing multiple messages of the same content.