Unidentifiable

For patterns, there’s another twist to the problem with their findability. It’s not a real word, but I didn’t have to explain it to you for you to know it meant the same as discoverability. Well, that’s one of the problems with software design patterns. Even when you read or hear a pattern name, you cannot be sure you know what it pertains to. How can someone immediately deduce the meaning of even some of the best-named patterns, such as Factory Method or Template Method? These aren’t bad patterns because of their names, but the names are not helping. However, many patterns have even worse names. Few people understand the Interpreter pattern because it shares a name with another concept in computing.

Furthermore, references collide. All three variants of the Interpreter pattern sit under one name. Both interpretations of Adapter (class and object) are hidden under one pattern. Why?

The ambiguity we bring with overlapping terms is a significant problem with patterns, even when you know they exist. When you know a pattern could help you make decisions, it’s almost impossible to know what it might be called without knowing the name. The accepted categories of software patterns don’t improve things. I applaud the authors of Pattern-Oriented Software Architecture[POSA96] on their decision to categorise by what level of abstraction and what class of problem the pattern attempts to solve. The pattern hunter only needs to sift through a few patterns to see if there’s something applicable from those available at the architectural altitude they are designing for. But some offerings merely present a long list of patterns categorised by usage rather than by where and what they solve. These lists are not helpful, but it’s even worse in the case of a catalogue listing them by name or author. It’s as if there are no categories at all. It’s similar to a dictionary sorted by word length first (hmm, quite advantageous if you are playing a word game, I suppose, but not the right form for finding definitions) or by the order first seen in use. Actually, that last one sounds engaging, like a history of language one word at a time, but admittedly, again, not a convenient reference.

So, we end up with an extensive collection of patterns that could solve our problems if only we knew about them before we needed them. We need to know their names and roughly what they help with before we can properly look for them. It’s like we need to be vaccinated against the problems by reading the pattern books before we begin writing any code. I needed to know that route-planning algorithms existed before I could use one in my childhood game project. But there’s another problem when seeking solutions: problem visibility.

When it comes to sorting algorithms (again), you know whether or not you need to sort something. You can quickly tell you have a problem when the order is incorrect but you would prefer it sorted another way. When it comes to the problems design patterns usually help solve, sometimes the problems are invisible when they first appear. The vaccination comparison is even more pertinent in this case. A pattern often solves a problem lacking visibility until very late in development. In this case, we would never seek out the pattern. Avid pattern readers would only stumble upon it by accident, but that would be serendipity, not procedure. And even then, it would only be fully ingested and recalled when its specific situational applicability turns up if it piqued their interest. Consider how often people think to use the Flyweight pattern compared to how often it would be a useful refactoring. You frequently see complicated value objects in domain-driven design, and yet the Flyweight pattern is somehow overlooked as a solution because the problem isn’t made visible by how developers work.

Names inhibit the reach of a good pattern. When we label things, we constrain them by what we can conceive. We assume their title describes all they can solve and limits our imagination. In C++, the Iterator pattern is locked into the naming of the STL. When you get an iterator from a container, it is assumed you will use it to iterate over the container. This is fine if you are using the iterator for iteration, but the name leads many developers to ignore other uses of iterators in the language. In the case of vector, they indicate a piece of memory, so you can use them for much more. They can result from find operations, reinforced by their usage with map. Generally, however, they are overlooked for other purposes because they have this name, and the common attribute is that of iteration. If they were not named thus and instead given unique representations based on what they were, it could open the mind to different opportunities.

An example of the failure of patterns can be seen at the microscopic scale with the nodiscard keyword in C++. The nodiscard keyword provides a mechanism to raise a warning when you ignore a return value. It’s a solution for two different problems.

One is the problem of ambiguity in function names as to whether they are actions or queries. When the keyword was introduced, the standard was updated, and calls to empty on containers gained the tag. It protected users from ambiguity of intent. This is a problem we have with naming things. Should a function called reverse reverse the container in place or return a reversed copy of the container? Without looking at the documentation, it’s hard to know. The nodiscard attribute came to the rescue.

The other main reason for nodiscard was the criticality of responding to the result. Again, they added the attribute to standard library functions returning resources requiring management, such as memory allocations and thread creation. In other situations, discarding is dangerous, such as when you must act on the result. If you request a lock on a resource, such as a mutex, you should act differently depending on the outcome of attempting to take that lock.

There’s nothing wrong with having both variants use nodiscard, but the problem that is solved is different. In one case, you called the wrong function. In the other case, you called the correct function but did not handle it sufficiently. The const keyword nearly fulfils the demands of the first usage (it doesn’t for free-functions, but for methods, it almost works), but few compilers even emit a warning when you fail to use the return value of a const method. Without phenomena, the coder will continue to use the method ineffectively.

class A {
  public:
    int f() const {
        return 5;
    }
};

int main(int argc, char* []) {
    A a;
    a.f(); // this is almost certainly wrong.
    return argc * argc;
}

// Even with -Wall -Wextra -Wpedantic
// No warnings emitted by clang 16 or gcc 13.1

// snippet available at https://godbolt.org/z/TbnbvYPb5

Patterns can have similar overlaps. When they are solution-oriented, they can resolve more than one problem. Named this way, they’re hard to find, but even when you do find them, you may only have one problem variant in mind. So, you disregard them as a solution to your problem, even though they’re compatible.

Let’s return to the Interpreter pattern. Most developers think of it as a way to handle scripting languages. This understanding of the pattern stops developers from thinking about it as a solution to the problem of the interpretation of a structure. The way the GoF wrote about it, it’s hard to extract this buried variant unless you read the last part many times. They understood its power to manipulate structures but buried the lead. So, their examples looked like interpreters. Because the primary interpretation reflects a way to visit a structure to create some kind of result, and the result presented is not a compound in all but one example, we hardly ever recognise it as a pattern of structure updates.

When the Interpreter pattern was analysed, its repeated manifestations deserved more attention and should have been better dissected. Too many different contexts and forces were allowed in. There are multiple patterns here, but the way it was documented weakened it because it covered too much. Just like the Doorway pattern.