Saturday, October 24, 2009

Encapsulate Collections and Primitives

I recently came across an article entitled Object Calisthenics by Jeff Bay.  The purpose of the exercise is to show the effects of following object-orientation rigorously on a small greenfield project.  After the calisthenics are practiced as rules, they are to be relaxed and used as guidelines in production code.  Two of these exercises are of particular interest.

Rule 3: Wrap all primitives and strings

As referenced above, the goal of rule 3 is not to preclude the use of naked primitives and strings altogether, but to learn when it is appropriate to encapsulate them.  Interestingly, the language of choice affects the overhead of encapsulating primitives and strings.

In Java a wrapped primitive must be an object, thereby adding work for the garbage collector.  Secondly, Java does not allow operator overloading thus adding a lot of ceremony to otherwise simple mathematical operations.  Anyone who has suffered through the use of BigDecimals has experienced this pain. 

In C#, primitives can be wrapped as structs and therefore be allocated on the stack, making GC performance less of a concern.  Also, C# allows for operator overloading of arithmetic operators, eliminating the ugliness of using BigDecimal.  C# also supports implicit casting, making conversion between primitives and a wrapped class succinct, though I have to admit I never use implicit casting because I am always worried it will cast unexpectedly.

Moving past language support, when are the appropriate times to encapsulate strings and primitives?  Type safety in statically typed languages is the first that comes to mind.  Both for mathematical operations and argument passing.  If a method takes two different doubles, it can be refactored so it takes two separate types.  This helps eliminate the mistake of transposing the arguments.  Another advantage of type safety is to restrict mathematical operations to only a subset of types.  F# does a good job of this with its units library.  Below is a trivial example using C#.
// Mechanics.cs
public Mass Calculate(Force f, Acceleration a)
{
return f/a;
}

// MechanicsTests.cs
private readonly Force f = new Force(12);
private readonly Acceleration a = new Acceleration(4);
private readonly Mass m = new Mass(3);

[Test]
public void ForceShouldBeMassTimesAcceleration()
{
    Assert.AreEqual(m, new Mechanics().Calculate(f, a));
}

[Test]
public void WillNotCompileButWouldUsingPrimitives()
{
    Assert.AreEqual(a, new Mechanics().Calculate(f, m));
}

The method Calculate could have a better name, but type safety makes it largely redundant.  Type safety also makes it safer than a more intention revealing method name as the compiler does not enforce the intent of method names.

Another reason to encapsulate a primitive is when the entire range of values for the primitive is not valid for an argument.  Enumerations are a good example of such an encapsulation, as they are really just a subset of integers mapped to meanings.  As an aside, Java allows the addition of methods and thereby a kind of polymorphism to enums making them superior to C# enums.

Strings are always objects.  This has different memory implications for managed vs. unmanaged languages.  Type safety for argument passing and range of values both apply here as well, though obviously not mathematical operations.  A good example is file system paths.  Several characters are not allowed in file system paths, and the object itself should enforce such restrictions.

Rule 8: Use first class collections

Rule 8 should be more than an exercise.  A mutable collection should almost never be passed between classes without being wrapped.  Any operations performed on the collection should be extracted into methods and added to the wrapped collection.  Encapsulating collections adds minimal coding and memory overhead to most languages, and greatly increases readability and encapsulation.

Applying the idea of calisthenics to programming is an interesting idea.  As with physical calisthenics, object calisthenics are helpful to the entire spectrum of developers.

0 comments:

Post a Comment