Is reflection unhealthy?

Am I the only one who feels a bit dirty after writing code that uses reflection? Most programmers agree that strong typing is a good feature in programming languages, because it allows compilers to catch many kinds of programming errors before you run your program the first time.  But using reflection completely bypasses that security.

Reflection is useful for working around the type system when it gets in the way. For example say you want to write a universal object serializer. It needs to work on classes that haven’t even been defined yet, so there’s no way it can access the instance variables of those classes in a type-safe manner. Reflection provides a way to ask objects what instance variables they contain, the current values of those instance variables, and so on. There are countless applications – I don’t need to list them here. (There’s a good discussion on the Fog Creek discussion board.) But what’s interesting is how reflection subverts strong typing.

First, method invocations and any other operations performed using reflection are not typesafe:

MethodInfo methodInfo = myObject.GetType().GetMethod("MyMethod");
methodInfo.Invoke(myObject, new object[] {});

There is no way at compile time of protecting against a type error in that code sequence. Second, most programmers won’t be able to resist checking that methodInfo is non-null, to protect against a possible null pointer exception:

MethodInfo methodInfo = myObject.GetType().GetMethod("MyMethod");
if (methodInfo != null)
{
  methodInfo.Invoke(myObject, new object[] {});
}

That’s not a bad instinct. After all, who knows what kind of myObject might be passed to this code? But now we’ve subverted the type system twice. It can’t check that we’re calling the method correctly, and it can’t even check that we’re providing the right kind of object. If we provide the wrong kind of object, this code will simply do nothing, probably causing a mysterious, silent failure.

Let’s see, what other kinds of trouble can we get into? Say we refactor the code and change the name MyMethod to something else. A smart IDE can do this automatically for us – but it will miss the reflection-based calls since they are only visible to the IDE as character strings. What will our code example do in this case? It will fail mysteriously and silently again.

Reflection also undermines program correctness by encouraging the creation of implicit contracts. Normally if you want to create a clear contract between two classes, you create an interface. For example, if you have a strange kind of collection class which calls WriteAuditRecord() on each object that is added, you might define the collection to accept only contents of type Auditable, which would be an interface with a WriteAuditRecord() method. Anyone using the collection would get a compile-time type error if they attempted to compile code which inserts objects that do not support that method. That would lead them to read the documentation in the Auditable interface and learn about the contract between the classes. But you could also create a clever solution using reflection to check whether any inserted items support a WriteAuditRecord method, and if so call that method automatically. That would allow programmers to add items to the collection without changing their implementation to implement the Auditable interface. More flexible, right? Except that if a programmer accidentally misspells the method as WriteAudiRecord, it will not get called; another silent error that would normally have been caught by the typechecker. Or even spookier, programmers might not know about this special audit behavior and be surprised when their WriteAuditRecord() method gets called for no apparent reason.

I’d also like to take a poke at another sacred cow: attributes (or annotations if you’re a Java person). My main beef with these is that to use them, you need to use reflection. See above.

I’m not 100% against the use of reflection and annotations. They can be very useful when creating frameworks that need to work on any kind of object (e.g. for serialization and persistence, or for configuration systems like Spring) but my point is they should be used sparingly and you should take a shower afterward.

4 Comments

  1. The dangers of reflection, as with any other powerful tool (such as firearms, knowledge, or hot sauce), are more fully ascribed to its user than to its usage. While reflection can (and occasionally is) misused, it isn’t inherently “unhealthy”.

    Reflection enables (as you point out) many meta-applications not possible with any other mechanism (short of source code analysis or de-compilation). For example, one can programmatically create class diagrams, code complexity metrics, and more via reflection.

    For attributes, in particular, reflection is a logical imperative. Since attributes are simply metadata (that is, at runtime the attributes themselves have no behavior), reflection is the only mechanism by which one can create runtime behavior from attributes. Reflection is the examination of metadata. This isn’t dirty or perverse. It is a decent separation of concerns.

    What is dirty and perverse is your example of bad reflection usage. This is inarguably bad in most cases, hearkening back to IDispatch and late binding behavior in Visual Basic. The Invoke method on MethodInfo is clearly present for completeness, but I’m hard-pressed to imagine a good use for it in most code (as with other impure features of reflection such as SetProperty).

    But, just as F# isn’t a bad functional language because it supports impure function calls, reflection isn’t a bad language feature because it supports impure behavior. On occasion, impure usage of reflection will raise its hackish head, but generally reflection’s pure usages will support elegant possibilities.

    As the saying goes, “Reflection doesn’t subvert strong typing; people do.”

  2. Great post Joe,

    Strictly speaking, I think that anything that makes a type system unsound as a logic “subverts strong typing.” Side-effects, general recursion, and definitely serialization (aka pickling) do that. Whether or not you can reasonably avoid it (especially given an existing project and no mandate for reorganization) is a different question, but I think that if you accept that unsoundness you’ve got to be honest about what you’re giving up. There’s a reason that the PLT world moved on from Scheme.

    You might be interested in the AliceML project’s approach to type-safe pickling (http://www.ps.uni-sb.de/Papers/abstracts/hotPickles2007.html). It got a lot of attention on lambda-the-ultimate a couple of years ago.

    I think that this issue is actually very important. There’s a danger in dismissing the importance of type safety — in fact, just calling it “type safety” I think helps to cloud the issue. Really, it should be called “logical consistency” because (ala Hindley-Milner) that’s *exactly* what it’s about. Improving type systems to support *reasoning* about things like pickling and side-effects goes to make our programs more reliable. You know, when you build a house of cards you can blame the guy who walked by it too quickly for blowing it down, or you can do better to come up with a sound design (a design that can be evaluated before the house is built).

    At the end of all of this, I think that we programmers may need to come to terms with dependently-typed programming languages.

  3. Since you said you wouldn’t list the application, here’s some things that reflection enables that are Good Things:

    Dependency Injection.
    Mocking frameworks.
    And in a certain sense, any kind of “late binding”/plugin application at all. As I understand it, this sort of thing required major hoop-jumping in C/C++, resulting in the unleashing of COM upon the universe. All that is gone with reflection.

  4. I feel a little bit dirty from some of the reflection code I just wrote to implement dependency injection for instantiating an object that has required parameters to it’s constructor. Java does not allow defining a contract for constructors so the contract is implicit.

Leave a Reply

Your email address will not be published. Required fields are marked *