Forbidding instantiations outside of a safe zone

Contextual constraints are hard to predict. It is tempting to think that a couple of generic rules will suffice to handle all future programming cases, but in practice they often fall short.

For example, programming languages come with various mechanisms to manage code visibility. Smalltalk makes it easy: state is protected, methods are public. Java takes "the more the better" approach and offers an enhanced set of language constraints: private, public, protected, and package.

Are these enough? Sometimes yes, but most often not. For example, in a Java system we wanted to ensure that a certain part of the model remains immutable to most of the system, but not to a selected set of factory classes. In particular, we needed to ensure that a set of classes, all inheriting from the ImmutableConcept, does not get instantiated outside of those factories.

One possibility would have been to make the constructors have package visibility and place all factories in the same package. This would have solved the immediate technical problem, but it did not match our constraints given that we needed the factories to be packaged in the plugins, not in the model. Thus, we had to let the constructors be public, and we documented that our ImmutableConcept classes should only be instantiated by dedicated factory classes that were annotated with @ImmutableConceptFactory.

The documentation even provided an example of what is allowed. The example looked somewhat like this:

@ImmutableConceptFactory
public class AConceptFactory {
 ...
 ImmutableConcept concept = new ConcreteImmutableConcept();
}

But because we could not sleep at night with a rule that only lives in the documentation, we had to craft a checker for it:

| immutables targetStructures |
immutables := (model allTypes detect: [:each | 'ImmutableConcept' = each name ]) withSubclassHierarchy.
targetVariables := immutables flatCollect: #structuresWithDeclaredType.
(targetVariables flatCollect: #incomingAccesses) select: [:each |
 (each typeScope isAnnotatedWith: 'ImmutableConceptFactory') not ]

Essentially, the checker had to identify all write accesses to variables that were declared of type ImmutableConcept, and ensure that they come from within classes that were annotated explicitly with @ImmutableConceptFactory.

Once the checker crafted, it was integrated in the daily assessment process and we ensured that people do not get astray in the future.

Documentation is great for letting know people why the system looks the way it does. But, documentation alone will not ensure the cohesiveness of the system regardless of how disciplined you want to be.

Posted by Tudor Girba at 18 August 2013, 9:51 pm with tags story, moose, daily, assessment link
|