Acting with experience-free wisdom

You can consider wisdom to be pre-made decisions. As opposed to knowledge, wisdom isn’t about facts and techniques; it’s about predicting the future through hindsight. When you’re in new territory with any endeavour, you must work from first principles, figuring things out one step at a time. In this context, wisdom is when you have already seen where these steps lead and decide against taking them.

Wisdom includes insight—the property of extrapolating from a situation to an eventual outcome. Using design patterns to obtain insight is hard without intensely studying the systems emerging from their application. And sadly, due to their solution form, they lose their potential as insight generators.

If most of the GoF book[GoF94] had been presented as techniques for nominalising decisions, then it could have inspired a generation of developers to find new strategies around the core concept. The principle of nominalisation is using a named object rather than procedural code to differentiate behaviour. For example, there’s a typical pattern in languages without lazy garbage collectors to have object lifetimes drive the housekeeping of another resource. In C++, it’s called RAII1. This pattern is helpful. It’s a strongly recurring pattern in C++, but you can also see it used in Visual Basic, D, and now in Rust. It’s the nominalisation of ‘at the end of scope’ or ‘just before returning’. It’s also a great pattern that isn’t called a pattern, because it’s not in the GoF book.

Awareness of limits and refinement of requirements

We can think of wisdom as awareness of any system’s soft and hard limits. The limits can be the theoretical bounds of an algorithm or regular hardware usage constraints. Not being aware of these limits leads to a developer spending time in the abstract design stage of a solution, which is theoretical but not practical. Limits found and imposed early can short-circuit doomed designs, saving much developer time. Some limitation patterns are mathematical, so they can answer questions even with far-future hardware advances. Others are guidelines on what physical or numerical situations can lead you into trouble.

When you discover limits, you can tell whether satisfying your requirements is possible. Limits always exist in things and affect what is achievable, so not knowing them can hinder your decision-making prospects. If you have a known throughput on a channel of 100MB per second, you cannot transfer 1GB of data in under a second. What can you do instead? Once you know your limits and requirements, you can start making decisions. If you know you can cut half the input data without issue and further compress it by 80%, you know what to do. If you can’t, you know you need to look for alternatives. This is where the refinement of requirements aspect of patterns hits hard.

A pattern can provide clues to whether your requirements are too broad. Maybe you can get by with only some of your data up front and stream the rest later. Many content providers see this recurring problem pattern. Online video and audio services usually provide incomplete media. They supply just enough to form a buffer to protect against skipping. They always assume you will consume the content slower than they can deliver it.

The same pattern applies when an application needs to present content triggered by external inputs. The problem appears when the data required to show a complete document is larger than the working memory of the program or machine you are running it on, but the data required to show a specific piece of the document is small enough that you can load a portion thereof, but not everything.

The requirement started as ‘need the data loaded’, but the pattern asked you to reconsider your requirement as ‘need the data you must have for the next N seconds of the runtime’.

Just in time

For example, consider a system where hundreds of possible voice snippets will play in response to user input. The obvious and impossible solution, but one commonly employed in the past, was to load all the samples into memory and play them on demand. This is why so many games could not have lots of dialogue, beyond the cost of storing it all and the cost of recording it. The current wisdom now dictates loading them all into memory is a waste. It takes up too much memory, and only a tiny subset of the loaded data would see use.

The pattern for this problem talks to the other limits you need to consider. Any data that is both on-demand and played back as a time series of samples can be buffered. The remaining audio of any dialogue can be loaded from your media when the initial playback commences. Only a portion must be immediately accessible, waiting to play in response to input. How large that portion needs to be depends on the seek time and bandwidth of the media, plus any latency caused by other systems interacting with the IO driver. Then there’s context. How many of these initial samples must be present? Maybe not that many. Some are more likely to be required than others. That probability will change over time, allowing you to swap out what is buffered as the context changes.

Replace audio samples with customer account transaction records, and we have a similar situation. Holding the previous month’s transactions nearby (on numerous servers) would be better than all or none of them. Holding them all would require expensive servers, but loading records from backing storage when a client connects leads to high latency. It’s a repeating problem with a recurring set of invariants for all good solutions.

This example addressed knowing the hardware’s limits and how to configure your software in response. But it was also about understanding the limits of the context, the soft limits imposed by the requirements. All these pieces of information are easy to find when you know to look for them. That’s the power of wisdom. It is knowing what information to look for, what to filter, and how to interpret it into assumptions you can act on.

When looking for a red sock, you will not find a blue one.

There is humanity in any design. Patterns invite all forces, not just code but also coders and users. Patterns allow us to avoid or recognise biases that lead to inferior designs.

Our perspective is modal. When we know what we are looking for or expecting to see, we will see it, and only it, more often. We see this effect on a larger, longer scale with cognitive bias. We have a subjective worldview we feed with information, and when we process the information, the strength of the bias affects how we interpret all these new events. If we are particularly open to adopting new evidence, we might adjust our subjective model of the world. However, if the change to our worldview is expensive, we are more likely to claim we misunderstood the event, it didn’t happen, or it was contrived against us. What does expensive mean? Expensive ties into why we cannot find a blue sock when looking for a red one.

When not under stress but casually making decisions, the mind is a complex structure of interconnected beliefs that may or may not contradict each other when brought together. We think with only some of our knowledge at a time. When we are in a casual flow of life situation, we do not change our minds. We might pick up new information, but it won’t affect our core beliefs. To avoid changing our core beliefs, we perceive through a set of non-contradictory mental frames. Events or realisations that bring conflicting frames together cause us to grow or learn. However, we can also consciously or unconsciously decide to assume the external event was an error. In part, this is why we say a teacher can teach all they like, but only if the student is willing to learn will there be any learning going on.

This relates to our sock search. We can be in the mental frame of looking for a sock with a particular pattern and expect it to be red. When we see a sock with the correct pattern, but it’s blue, we filter it so that we don’t even see the pattern or the sock. It was the wrong sock, so we skim over it.

The value of ignorance is knowing you don’t know. We often know things that aren’t true, and that causes us a lot of trouble. It’s a shame you can’t teach ignorance.

Patterns can help us remember what we’re looking for by helping us look for a patterned sock, not a red one. When a Strategy pattern asks us to decouple something, it reminds us to separate the decision to act from the decision about which action to take. Data-oriented design reminds us to consider whether an object truly has an identity. When we view our structures through the Interpreter pattern, it asks us to consider whether the information is in the objects or in the way they are linked together.

1

Resource Acquisition Is Initialization. The Wikipedia page calls it an idiom, but by now, you should be able to tell it is a pattern because we can define the context and the forces that it resolves and how it recurs. https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization