A process and a thing

Christopher Alexander did not consider a building as finished so much as being an attempt, an approximation of an unattainable perfection[TTWoB79]. Every building had an age and a state of repair. The older a building became, the more likely it needed small repairs or an extension to match its new usage. But, for him, even a new but incomplete building was in a state of repair.

Rigid building designs are brittle and not very likely to survive changing times. They don’t allow for adaptation. Most modern buildings are like this. Consider the different physical space and connectivity needs of offices over the last few decades. In contrast, there was a suppleness to all of Christopher Alexander’s designs. He built each building, component, extension, or improvement so they sat comfortably with what was already there and remained conscious of the need to support an unseen future, greater form.

The same value in malleability can be said of code. Old code is only good if it can be repaired when it is no longer fit for purpose. Christopher Alexander wrote[NoO1-01] negatively about buildings with no scope for extension or capacity to adapt. Compositions that can only be put back into their final state were not living but were arrangements brought into existence by a dead process.

… when you build a thing you cannot merely build that thing in isolation, but must also repair the world around it, and within it, so that the larger world at that one place becomes more coherent, and more whole; and the thing which you make takes its place in the web of nature, as you make it.

— Christopher Alexander, A Pattern Language[APL77], p. xiii.

If code cannot be changed, it’s not good code. If you can only make it work by shoring it up so it does not need to change while everything else works around its failings, then it’s still not good code. More to the point, it was never good code.

Patterns are about solving a problem, but they also contain the process of achieving a solution. As with wisdom, we see a reason behind this. Complex things built through traditional methods were always constructed piece-wise. No successful, large, complex thing was ever made in one step. My feelings resonated with the words of John Gall when I read:

A COMPLEX SYSTEM THAT WORKS IS INVARIABLY FOUND TO HAVE EVOLVED FROM A SIMPLE SYSTEM THAT WORKED.

The parallel proposition also appears to be true:

A COMPLEX SYSTEM DESIGNED FROM SCRATCH NEVER WORKS AND CANNOT BE PATCHED UP TO MAKE IT WORK. YOU HAVE TO START OVER, BEGINNING WITH A WORKING SIMPLE SYSTEM.

— John Gall, The Systems Bible[SysBible02], p. 63.

Complex things at scale are only successfully made with traditional methods when built piece by piece. Because steps are required to build well, patterns must also be processes. They are the wise steps to take to achieve a new, better state, with each state being a distinct solution to an expanding problem.

The thing aspect of this has to do with an essential completeness of all patterns. A whole thing in one notable entity means the pattern describes a complete system of elements supporting each other. The system can be tiny, but it has to be complete and self-supporting. As an example, by negation, we can take an element away. The pattern of 105 South facing outdoors from A Pattern Language[APL77] is a good quality pattern of reserving the best light of the sun for the outdoors of a building plot by giving it the south (or north, in the southern hemisphere) of the land. This pattern consists of only the land and the building. By removing the building from the pattern, it has no meaning. By adding neighbouring buildings, it gains additional but worthless complications. The point is to make sitting outside by the focal building a positive experience. Sitting on a plot by a neighbour’s wall is not relevant to the goodness and wholeness of the construction.

Wholeness means a whole thing in one entity and repairing it to a wholesome state. At each step, the entire thing is good—or at least not worse than before. The south-facing wall has a usable seat, for example. And when we see something as incomplete, it’s not healthy. It needs repair. If we plan to add a shape to the building but it would introduce a shadow to the outdoors, we can recognise that and adjust the plan to fix it before it happens, just as tests tell us our proposed change is not good.

Many things will be unstable when we work on a project, but we can decide to work on those tasks that close the most significant gaps. We can look at the needs of the whole project and tackle the highest-impact decisions first. This way, we start with nothing and repair the project into something we are happy with. At each stage, we make it better, more alive, more whole.

Even though design patterns are processes, they regularly explain themselves by describing an outcome. The manipulations we need to apply to get to that outcome from our existing context would be much more helpful documentation. We can look at how a building plot turns into a building over time with design patterns. Residents change. Their needs change as they go through stages of life, both temporary and enduring. The building will no longer be a perfect fit for their changed needs. New patterns will become appropriate as new forces appear, so the building should be repaired into a new state. In software, we see this with changes caused by revelation or modifications required to meet new expectations of users becoming accustomed to features available in other, related software.

At the detail level, we may migrate to using a Strategy where we previously had a direct function call. A recent additional requirement, the need to configure which function we call, would be a new tension needing repair. Perhaps a switch isn’t powerful, extensible, or safe enough for our latest use case. We need a variable to store the action and a way to inspect or serialise it, so a simple functor will not do.

The context for the Strategy example is a behaviour invoked at a certain point in time. The forces are our need to adjust the behaviour at runtime. So, we observe a new variance. In some languages, such as Python, support for this is built in. But when using C++ as an object-oriented language, we need to use something to let us bind to a function at runtime. To do that, we can take the step of nominalisation: converting a verb to a noun. This step is the process part of the process and a thing.

The nominalisation step is the transformation—the change we wish to make to the environment to fix it. It is a process of repair.

Taking the plot of land for a house in the northern hemisphere and splitting it such that the house is at the north end so the south-facing wall can have a semi-private, well-lit open area around it is also a process of repair to the current design. It is the right step to take:

  • for the purpose of the land
  • in the context of benefiting from good light when outside the property but still on the land beside the property
  • to use the land well, to its fullest
  • for a northern hemisphere property

Given context and forces, nominalising a function into an object can be the right step. The Strategy pattern describes the process for when you want to solve, for some constraints, the capacity to adjust which function is called without having to go into the existing code again.

The Strategy pattern is the application of a sequence of steps to get to a solution for a problem in a context. It’s why we can still use it in languages like JavaScript, where you can put a function in a variable or override any class methods whenever you feel like it. All prototype languages can use the Strategy pattern because it’s not just the final object-oriented form but an idea of allowing for dynamic behaviour based on state. The concept was limited to object-oriented programming because of the aims of the book in which we find it. The pattern was defined as a way to store functions in objects but, at the core, it’s just runtime dynamic behaviour.

Because the code used by the authors at the time did not have the sophisticated mechanisms available to modern languages, they did not recognise the pattern for what it was. In effect, the Strategy pattern could be interpreted as Last Minute Decision Making. It allows for a function to respond to an event, but the responding function can be decided upon at runtime. It’s a form of dispatch mechanism, so comes with all the related downsides. The problem with not understanding the problem or process driving the pattern is that when new solutions come along, you don’t replace the old inferior solutions in your pattern definitions.

Some people use Strategy or policy objects in languages where they don’t need to, just because they are now the solution accepted by the culture. They have drifted into being an idiom. If you need to call a different method based on some configuration in Python, you can do this:

class MyClass:
    #...
    def configure(self, new_strategy):
        self.do_it = new_strategy

    def do_thing(self):
        self.do_it(self.context_a, self.context_b)

And if you are using C++11 or beyond, you can do this,

class MyClass {
    public:
        using Func=std::function<void(const TypeA&, const TypeB&)>;

        void configure(Func new_strategy) {
            m_do_it = new_strategy;
        }
        void do_thing() const {
            m_do_it(m_contextA, m_contextB);
        }
    private:
        Func m_do_it;
};
Snippet link: https://godbolt.org/z/rd41o1ssT

And even if you are using a language without these features, there are still workable solutions. You can achieve it in C, which almost proves this was never an object-oriented pattern in the first place.

struct MyStruct {
    void(*do_it)(int, int);
    int m_contextA;
    int m_contextB;
};
void configure(MyStruct *ms, void(*new_strategy)(int, int)) {
    ms->do_it = new_strategy;
}
void do_thing(MyStruct *ms) {
    ms->do_it(ms->m_contextA, ms->m_contextB);
}
Snippet link: https://godbolt.org/z/MfMoxTh5T

So, the essence of the pattern (introduce a family of algorithms that can be selected from at runtime) is maintained in these examples, even though each of these implementations is utterly different from anything in the GoF book.