Deep Stack Engineer

Riffing on the idea of layer gaps, we can surmise that pretty much every layer we ever get to write code for has gaps. If that’s the case, then anticipating layer gaps in our future can lead to different ways to build teams.

A key insight from the previous essay is that when we work with a layer with gaps, we need to understand both this layer and the layer underneath it. For if we ever fall into the gap, we could use that knowledge of the lower layer to orient and continue toward our intended destination.

Which means that when we hire people to work in a certain stack, we are much better off hiring at least one person who has experience with the stack’s lower layer. These are the people who will lead the team out of the layer gaps. To give our full stack engineers the ability to overcome these gaps, we need at least one deep stack engineer.

A simple rule of thumb: for every part of the stack, hire at least one person who has experience working at the layer below. 

For example, if we’re planning to develop our product on top of a Web framework, we must look for someone who deeply understands this framework to join the team. Ideally, this person is a current or former active participant in the framework project.

Approaching this from a slightly different angle and applying the cost of opinion lens, this person will act as the opinion cost estimator for the team. Because they understand the actual intention of the framework, they can help our team minimize the difference of intentions between what we’re engineering in our layer and the intention of the underlying framework. As my good friend Matt wisely said many moons ago, it would help our team “use the platform” rather than waste energy while trying to work around it. Or worse yet, reinvent it.

Note that the experience at the lower layer does not necessarily translate to the experience at the higher layer. I could be a seasoned Web platform engineer, with thousands of lines of rendering engine C++ code under my belt – yet have very little understanding of how Web applications are built.

What we’re looking for in a deep stack engineer is the actual depth: the capacity to span multiple layers, and go up and down these layers with confident ease.

The larger the count of layers they span, the more rare these folks are. It takes a lot of curiosity and experience to get to the level of expert comfort across multiple layers of developer surfaces. Usually, folks tend to nest within one layer and build their careers there. So next time we come across a candidate whose experience spans across two or more, we are apt to pay attention: this might be someone who significantly improves the odds of success in our engineering adventures.

Layer gaps

I’ve been writing a bit more code lately, and so you’ll notice that some of my stories are gravitating that way. Here’s one about layer gaps.

Layer gaps are when a developer surface layer fails to close fully over the layer below. Another way of saying this is “leaky abstraction”, but I’ll use my own term “layer gaps” to define what it entails in a more nuanced way.

To recap what I’ve written previously about layers, every layer tends to offer a bit of its own opinion about how to best bring value to users. When the layers are able to fully express this opinion, we have no layer gaps in this layer. For example, JavaScript is a gapless layer. It’s a language and a firm opinion about a code execution environment. It might not have features that we would like to have in a language. It might have limits within its execution environment. It might even have frustrating bugs that irritate the dickens out of us.

But at no point of using JavaScript we will suddenly go: “whoa, I am no longer in JavaScript. I fell into some weird gap that I wasn’t previously aware of, and now I am experiencing the lower layer on top of which JavaScript was built”.

To better grasp what a layer gap looks like, we don’t have to go far. Let’s look at TypeScript. TypeScript is a really interesting beast: it’s a layer of a type system that is laid over JavaScript. First off, the type system itself is delicious. It reminds me a bit of the C# type system, and I thoroughly enjoyed learning and using both. However, there’s a gap into which I fell into more than once.

Because the type system is compile-time only, the layer disappears at runtime. It simply doesn’t exist anymore once the code is executed by the underlying JavaScript engine. However, compared to other type systems that I am familiar with, I expect at least some runtime type support. 

At least for me, my mental model of a type system includes at least a little bit of ability to reason about types. Like, at least comparing them at runtime. A bit of type reflection might be nice. But because there’s no such thing as TypeScript when the code actually runs, I experience the layer gap.

As developers of layers, we need to remember that if our layer has gaps, our user must not only understand how our layer works, but also how the lower layer works, and have the gaps clearly marked. For if we don’t, we’ll hear frequent screams of anguish as they discover them. A clearly marked gap might look like documentation that helps our developers understand the tradeoffs they are making by using our layer and make the decision to use it on their own terms. It could look like superb tooling that points out the gap as soon as the user gets close to it – and possibly both.

As users of these layers, we need to be ready for every layer to potentially have gaps. We need to invest time upfront to uncover them, and build our practices to fence around the gaps.

I was sharing with my colleagues that using TypeScript is like walking on stilts. I can get really, really good at walking on stilts. I could even learn how to run on stilts and do all kinds of acrobatic tricks while on stills. But I should never forget that I am wearing them. If I do, I may find myself unpleasantly surprised when the ground suddenly hits my face. 

Layer gaps aren’t necessarily a terrible thing. They come with tradeoffs, and sometimes these tradeoffs are worth it. For instance, I embraced TypeScript, because I can delegate some of the mental load of reasoning about the data structures to the TypeScript compiler – and it does a pretty good job of it.

I just need to keep remembering that as I am enjoying the benefits of seeing farther and being taller, I am doing this by wearing the stilts.

Decision-making mindsets

I have this intuition that the process is not the most important ingredient in making better decisions. Instead, the key is the mindset with which we’re entering the decisions-making process. 

The process still plays a valuable part, but it only works when we have the right mix of mindsets. Some mindsets are more effective at decision-making than others.

Here’s a sketch of the different types of mindsets that show up in decision making. These aren’t character labels or personas. People can be in different mindsets depending on the context and circumstances. Mindsets can shift over time, and sometimes in the moment.

Note, that I am cleverly avoiding the actual hard problem: answering the “How do we bring people into these mindsets to make good decisions?”. I am tackling the easy part: identifying the mindsets we need to make better decisions.

💸 Opportunistic

The opportunistic stance is the least helpful of the bunch. In the opportunistic mindset, the process of decision-making is a vehicle for advancing my own agenda. I am not here to make decisions. I am here to use this key moment for my benefit. This stance is why politics gets a bad rap. Am I in this process to move it forward, or am I here to subvert it to serve my needs? This stance can seem beneficial in hostile environments, but at that point, there is no actual decision-making going on. It’s all just political theater.

My friends at FLUX have this amazing lens of “kayfabe”, which applies very well to decision-making processes where everyone is in the opportunistic stance – everyone knows this is not about making the decision, yet preserves the appearance and the form of the process. As soon as we detect this kind of state of affairs, ejecting from this environment as soon as possible might just be the best option available. In other words: run. Otherwise, we’ll likely become the chair in this fake-wrestling match.

🎰Opinionless

The opinionless mindset is not that much better. It usually presents itself as deference to the opinion of others. In this stance, I am a carrier of another’s opinion, rather than proprietor of my own. Typically, the opinionless stance is revealed by turns of phrase like “the studies show” or “the <senior leader> said” or “I heard that” when presenting an opinion. When making decisions, this stance is superfluous. If I find myself in this stance, I am much better off excusing myself from participating in any decision-making. Since I don’t hold this opinion, I can’t articulate the value it contains.

The opinionless stance can lead to weird stalemates when trying to make decisions. If “Alice thinks that we should ship Widgets”, and nobody else can figure out why, yet Alice isn’t here to present the opinion, an easy – and more productive – thing to do is to dismiss the opinion. However, if Alice holds power (be that expertise or rank or any other sort), the opinionless participants beholden to this power will stall and derail the process. In the past, I called the opinionless stance “weak opinions, strongly held”. What’s worse, most of the time, it feels awful to be in that stance, stuck between the rock and the hard place of differing opinions.

It’s not to say that opinions of others don’t matter. They do. However, if I myself am not of this opinion, I improve decisions-making by presenting them as the input for the process, rather than part of it. Let others who actually hold opinions examine this information and incorporate it into their reasoning.

If we look around the room when decisions are being made, and see that most are in the opinionless stance, woe to us. This is not a decision-making meeting, but rather an opinion pachinko machine: it’s hard to know where we will land, but it is clear that the decision will be random and will fail to stick. 

Whenever possible, remove (or self-remove) opinionless folks from the process. At the same time, recognize that often, the opinionless stance is forced: it is a defensive crouch that’s assumed by the folks who feel compelled to hide their actual opinion – usually due to some power dynamic. If that’s the case, opportunistic kayfabe is the next stop in our decision-making adventure, and it might be worth investigating what’s causing the defensive crouch.

🧨 Opinionated

Decision-making gets better when we have an opinionated stance. There’s some experience that we’ve accumulated along the way that gives us enough confidence to claim that we understand what needs to be done. We hold an opinion and it is ours. We can defend it, present evidence that this opinion is correct, and evidence that other opinions aren’t.

With an opinionated mindset in the mix, a decision-making process can get contentious and rather heated. In organizations that overvalue being agreeable, opinionated decision-making may feel like a failure of a process. It may look that way, but it is a definite improvement over the previous two stances. Folks in the opinionated stance often over-identify with their opinions, and understandably infuse emotion into the conversation. When managed poorly, these conversations can get unproductive. However, it is also how we know that we might just make good decisions.

In my experience, I’ve seen so many teams confuse the heat of the opinionated minds grappling around a decision with unproductive decision-making. In the seemingly logical move, people with actual opinions get quietly removed from the conversation, and replaced with folks in an opinionless stance. We are then surprised that our decisions are bland and seemingly random. I would much rather endure cranky engineers fighting over the idea and help them manage their emotions than toss coins into the decision pachinko machine.

Spotting opinionated folks is easy: they usually disrupt a conversation with phrases like “well, that is stupid” or “hey, that’s not right”. This may seem counterintuitive, but these are our markers that we have something valuable: actual opinions. Disagreements are good. First, it means that the environment we’ve created is safe enough to voice these disagreements. And second, it signals that we have a foundation for effective decision-making.

In my experience, when put into the same decision-making situation, folks in opinionated stance and folks in opinionless stance tend to have a strong aversion to each other. From the opinionless stance, the opinionated ones look like rabble rousers and troublemakers, the fire to be put out. From the opinionated stance, an opinionless participant is a dead weight. Their lack of opinion is easily smelled and the bozo bit flipped. When the two are mixed in a meeting, get ready for a mostly dysfunctional gathering, with everyone eventually falling back to the opportunist stance.

🏛️ Principled

An upgrade and likely the zenith of decision-making effectiveness is the principled mindset. When I am in this stance, I understand the problem space enough to see that there are many valid approaches to the problem, and many opinions may lead to a possible solution. I also know that none of these will be perfect.

Instead, I focus on what’s important and let that guide my thinking. Principled stance tends to have a slower start compared to the opinionated stance. In the opinionated stance, I already have my opinion, so I just come out swinging trying to get other opinions out of the way. In the principled stance, I first try to understand the principles: what are the attributes of the solution that are important? What are the desired properties we want the solution to have? I see getting these right as the key part of the process, and folks in other stances may get impatient: what is he doing? Why is he so focused on these silly bullet points?

What I am trying to do is map out the tradeoff space. I anticipate that the ideal solution will not be achievable, so I need to know where I can afford to accumulate decision debt: the downsides of the decision we’re about to make. Because ultimately, all decisions – especially the less-reversible ones – will have unpleasant side effects. Principled stance is about accepting that fact.

Principled decision-making stance typically doesn’t carry the same emotional heat as the opinionated one: there is less identity attachment to the opinion. It feels more deliberate, yet the progress toward the decisions is fairly clear and steady. When most folks in the process are in this stance, decisions are made quickly and they tend to stick.

🌱 Space-holding

There is another mindset that I’ve seen people assume during decision-making: the space-holding stance. When entering a decision-making process while in this mindset, I no longer care about the specifics of the decision. I want to make sure we make good decisions consistently.

This stance may seem similar to the opinionless stance, and because of that, opinionated folks often have an allergic reaction to space-holding folks being present. However, when they get a chance to engage, they quickly unflip the bozo bit, impressed by the fact that space-holding folks deeply understand the problem and can see how a particular opinion fits into the overall problem space.

Primarily, space-holding folks cultivate decision-making space. They help everyone stretch toward the principled mindset. They quickly detect and carefully fence off the opportunists. They give opinionless folks tasks of collecting the data and relieve them of the burden of defending others’ opinions. They disarm pitched battles of opinionated folks with curiosity, separating out the value of their opinions from the opinion-holder’s identity. They help principled folks create and improve upon principles.

I am not that great at holding this stance. When I try to adopt this mindset, I notice that I usually fall into the principled stance, lured by the actual problem that’s being solved. I get too caught up in the what and forget to care about the how. 

In some cases, I crouch into the opinionless stance, where I hold my tongue trying to “let people speak,” becoming a barnacle. Sometimes it’s hard for me to see that “creating space” is not a passive task, but rather a sort of jam session to inspire folks to reveal and present their opinions.

While it would be ideal if everyone made decisions with this mindset, my guess is that the space-holding stance is a combination of skill and probably a unique gift that only a few people possess. 

I’ve had the pleasure of working with a few folks who naturally assume this stance.  It’s a marvel to see them do it. Communities of people sprout around them wherever they go. It’s like they can’t help but create places where people can discuss hard problems and come up with insightful solutions to them. If we are blessed with one in our team, hold onto those people. They are much, much more valuable than they appear.

It is only a while after they leave that we discover that our decision-making grinds to a standstill or becomes political kayfabe. We may not even realize what happened. When did everything suddenly get so political? How did that ornery expert get so downright menacing, to the point where they are shunned by the entire team? Why do our decisions feel so random and never seem to stick?

I hope this little taxonomy helps you get closer to understanding how effective decisions are made. It certainly was clarifying for me.

And oh, look. It’s probably not a surprise, but it’s another transposition of the ADT. The mindsets roughly correspond to the Opportunist, Diplomat, Expert, Achiever, and Redefining-ish stages from Bill Torbert’s developmental stage taxonomy. Hey, if it works, it works.

Shadow Gristle

I’ve been writing a bit of Web components code recently and used Shadow DOM. I am realizing that there’s a fairly useful pattern in incorporating Shadow DOM into Web apps that I will hereby name the “Shadow Gristle”.

First things first. If you don’t like Shadow DOM for one reason or another, this is not an attempt to convince you otherwise. If you have no idea what Shadow DOM is, this will be just a few paragraphs of gobbledygook. Sorry. However, if you do find yourself dabbling with the ye olde Shadow DOM even occasionally, you might find this pattern useful. 

Very simply put, the idea is that we only put the necessary scaffolding code into the Shadow DOM, and leave most of our application code in the light DOM.

When we have the power of Shadow DOM at our fingertips, we have two choices about how we grow the subtree of the DOM elements: one is inside of the shadow tree (in the Shadow DOM), and the other on the outside (in the regular DOM).

So if we want to add another component as a child of our Web component, how do we decide which of the two places it should go into?

My intuition is that placing a child component into a shadow tree is a code smell. It indicates that we  might have lessened our ability to compose elements. There are probably perfectly good reasons to put a component into a shadow tree, but more often than not, it’s probably not the right place.

Child components love light. If they stay in the regular DOM, they remain composable. I can rearrange them or replace them without having to muck with the innards of my component.

Thus, the rule of thumb is: seek to place child components into the regular DOM. Reduce occurrences of them being added to the shadow tree.

So what goes into the Shadow DOM? Mostly gristle. It’s the stuff that connects components together. There may need to be some routing or event handling, and perhaps a few styles to set the foundation. Everything else goes in the regular DOM. For example, I try to avoid styling in the shadow tree. Thanks to the CSS variables, I can use them as pointers and allow the regular DOM tree to supply the specifics.

I hope this little pattern helps you build better Web apps. And yes, the gobbledygook is over now. I promise I’ll write something less obtuse next time. 

The law of tightening aperture

What happens to the teams with wide apertures over time? Does the aperture stay the same? What about the teams with narrow strategy apertures? When I started examining these questions, I recognized a familiar pattern. This pattern seems so pervasive that I will go ahead and boldly proclaim it as a law: the law of tightening aperture. Here it is:

Given a changing environment, the strategy aperture of any organizational unit tends to only tighten over time.

Using super-plain language, the law states that every team’s openness to exploring new opportunities diminishes over time. Every organization that exists today is highly likely to be more strategically flexible than the same organization tomorrow.

💰 The requirement of value

Why does this happen? Strategy aperture appears to be subject to a gravity-like force that can be resisted and sometimes counterbalanced, but never expected to relent: the force of homeostasis. To make it a bit less concrete and easily digestible in the context of this essay, I will rename it to the force of requirement of value.

The requirement of value can be described as a collective expectation that an organization will at some point deliver value. Given how much we put into it, we’d like to receive it back – and get at least a little bit more  (notice how this rhymes with homeostasis). 

There are organizations that don’t have these requirements – but usually, they don’t have strategies, either. In this way, the requirement of value both creates the need for strategy and imposes the law on this strategy. 

The law of tightening aperture applies to any kind of organization, including polities and ecosystems. As long as there’s the requirement of value, the same dynamic will emerge.

The requirement of value doesn’t always come from the need for tangible outcomes (profits, growth, etc.). Team identity can play a big role in the tightening of the aperture. A team that painstakingly discovers who they are and becomes comfortable with that is the team can’t become something altogether different. 

As an illustration, consider Tuckman’s phases of group development model. The “performing” phase is highly sought after and celebrated when reached, yet it is also the one with the narrowest aperture. There is a reason why there’s the “adjourning” or “transforming” stage tacked on at the end: once a team learns how to perform, it must necessarily cease to exist in its current form to do something different. 

Another source of requirement of value might be the collective desire for predictability. This comes up very strongly in the rapidly changing environments. When everything is up in the air, the need for stability can feel existential. This desire may lead to a paradox during the times of change: just when the team needs to keep its aperture wide to better anticipate and see new opportunities, the collective fear of uncertainty will keep tightening the organization’s strategy aperture.

🏛️ Not necessarily a bad thing

Aperture tightening is not always a bad outcome. It might be exactly what we’re looking for. Teams with a narrow aperture are easily pointed at the problems whose shapes are within their aperture. Confidently solving that problem is their unique gift. The key here is to understand and be intentional about the change.

When we seek a tighter aperture, we usually talk about focus. For example, the phrase “more wood behind fewer arrows” heralded such a change for Google back in 2011. Transitioning from a wide aperture to a narrow aperture is often seen as a necessary step to improve a team’s ability to create value efficiently. This is all good. Aperture tightening works out great when the viable niche the organization ended up pointing at is durable in the long term. For instance, it is exceedingly likely that people will want to consume electricity or the Internet for the foreseeable future. No matter how tight it’s aperture, an organization can thrive providing either. 

We just need to remember that – at least according to the law I present in this essay – there will be no easy way to reverse that shift. Becoming more open to contemplating new opportunities is much, much harder once the aperture tightens. If the shape of the industry changes, and the niche shifts, we will suddenly discover that we are unable to adjust, battered by the winds of change and unable to even see them clearly. Like, hey those cassette tapes were cool, but if I built my business on them, I am in for a nasty surprise in the 90s.

⏪ Can the tightening be reversed?

Can the aperture tightening be reversed? Not permanently. As I mentioned earlier, it’s best to view this force as gravity. To keep a soccer ball in the air, I need to exert energy. The default state of the ball is to lay still on the ground. Similarly, the default state of a strategy aperture is to be as narrow as possible. We can resist this force by investing our energy into it, and counteract its effects. But these investments will not have a permanent effect.

Anytime we get an impression that organization has revitalized itself, it is doubtful that this happened to a permanent broadening of the aperture. More likely, a “kick-in-the-pants” event loosened the aperture long enough to spot a new promising similarly-shaped opportunity and allowed re-pointing of the organization toward it. Or perhaps we’re actually observing a wholly different organization, similar in name only – an outcome of (likely painful) transformation. 

As organization’s leaders, when we decide where we want to invest our time, it might be useful to look at where we want to be in relation to our current strategy aperture. Relative to the aperture we have, do we want it tightened or broadened?

If we need a tighter aperture, we must be very careful about embarking on our team-focusing venture: is this a long-term viable niche? If yes, let’s plow ahead. If it is likely to shift (or is shifting already), we are making ourselves more vulnerable to disruption.

If we determine that we need to broaden our strategy aperture, it might be that the entirety of our job will be resisting the law of tightening aperture. This is why leading teams and organizations may feel so challenging and downright futile: we keep trying to walk against gravity. It’s a task that is feasible in the short-term, but not in the long term. Eventually, that soccer ball has to come down.

The gardener

I often use the analogies of gardens and gardening in my writing. It’s only fitting that I talk about gardeners. This was inspired by something that my friend Alex Komoroske said once – though now he tells me that he can’t remember saying that. Oh well. 

When we talk about gardening, we picture a flourishing environment, where everything is neat and lovely and blossoming in the serene calm. We tend to imagine gardens the opposite of the hustle and bustle, dog-eat-dog kinds of environments.

However, to become these environments, gardens need gardeners. The key property of a garden is not the fertile soil, or a picturesque location, or even the choice of seedlings. The key property of a garden is the gardener — someone who is able and willing to exercise significant power to ensure that the garden is protected from the rest of the environment.

Gardeners skillfully wield all kinds of violent, cutting instruments to ensure that the garden is growing well. They get on their knees and ruthlessly pull out anything that is not supposed to be there. They engage in prolonged battles with pests, who keep finding new and clever ways to get into the garden. Gardeners fight for the garden.

The reason why gardens exist is because they have gardeners: individuals who are willing to put their sweat and tears into them.

When the gardener leaves, the garden dies. It doesn’t die quickly. For a little while, it may even look like the garden is going to be fine. Like all the work that the gardener has put into it has finally paid off and the garden can live on its own. But that is not to be. Eventually, the rabbits dig out the roots. The mites take over the leaves. And the garden withers. Over time, the surrounding environment swallows it, making it part of itself.

Sometimes, a garden gets lucky and gets another gardener. But the new gardeners see the inefficiencies of how the flowerbeds were drawn, and how the soil is too heavy on clay. How the water supply could be moved to a more central location. They have different ideas about the kinds of plants they want and where. With the same gusto as the previous gardener, they mold the garden to their liking. Gardens are shaped like their gardeners, and if they aren’t, they will be.

Here’s to gardeners. Those who are willing to expend their effort and their capital on building a patch of something that’s perhaps quirkier and weirder, but undeniably more intentional than the rest of the environment. I salute you.

Strategy aperture gifts and curses

Now that I’ve sketched out the concept of strategy aperture, let’s play with it. Let’s imagine two teams: one with a wide strategy aperture and the other with narrow. What are the gifts and curses of these teams?

A team with a wide strategy aperture will have the gift of sensing: it will be able to discern a massive variety of opportunities. The flexibility of the wide aperture gives it the capacity to try everything. Dabble in this, taste test that. Write a quick prototype here, throw together a demo there.

One thing that this team won’t be able to do is doggedly pursue one particular opportunity. When sensing is a gift, commitment is a curse. Teams with a wide strategy aperture usually stink at delivering on the opportunities. They are idea factories. Their curse is that someone else usually takes these ideas to market. PARC, Bell Labs and many other venerable institutions of technology innovation are all subject to that curse.

Let’s look at the other team. Its narrow aperture gives the team the gift of focus. This team knows how to take a vague idea and make it real. As long as this opportunity is within its capabilities, the team will find a way. Unlike the first team, this one won’t get distracted by a new shiny and accidentally forget about what matters. Like a tractor or any other power tool, if we point this team to a problem, we know that they will give it their all.

The curse of this team is that it’s pretty much blind to other opportunities. Once the target is locked, there might as well be no other opportunities – everything is poured into the one that’s chosen. As a member of engineering teams, I’ve seen this pattern repeat quite often. It’s like watching a train wreck in slow motion. When new disconfirming evidence emerges, the narrow-aperture team just keeps on chugging. Even when everyone knows the effort is going to fail, nobody dares to mess with the gears. People just keep shrugging and saying: “Yep, this one’s going to end poorly.”

This vignette gives us a nice distinction to build upon. If we look around various teams in our organization, can we spot the ones with the narrow aperture? Can we point at the ones with the wide aperture? Knowing their gifts and curses, can we predict what will happen next with the project they are working on?

Strategy Aperture

The concept of embodied strategy continues to captivate me. Recently, I found a more resonant way to talk about the narrowness and breadth of the cone of embodied strategy: the strategy aperture.

As I explored earlier,  organizations tend to have a certain gait, a way of doing and thinking that they develop over time. No matter how much we try to convince them otherwise, they will always veer toward that certain way – hence the term “embodied” strategy, used in contrast to “stated” strategy.

The cone of the embodied strategy is a degree to which the organization is subject to its embodied strategy. Put very simply, the cone of the embodied strategy indicates how much flexibility we have in pursuing various future opportunities. Narrow cones indicate very little flexibility – we are set in our ways, and that’s the way we are. Broad cones indicate a lot of flexibility – the world is our oyster.

Borrowing the term from physics, we can use the word “aperture” to indicate the breadth/narrowness of the cone of embodied strategy. 

More narrow aperture means that as an organization, we are well-suited for producing only a certain class of ideas. Think of a team that’s laser-focused on a particular problem or highly specialized to build a certain kind of product. 

Broader apertures allow organizations to be nimble, more mercurial, able to anticipate and act on a wide variety of opportunities. Some – most – of these will not pan out, and some will hit the motherlode.

Neither broad or narrow strategy aperture is bad or good in itself – however, it must be matched to the environment.

Narrow strategy aperture is very useful in more elephant-like environments, where it’s all about optimizing the fit into the well-known, stable niche. The more narrow our aperture, the more elephant-like environments will be attractive to us.

The flexibility the broader aperture brings is very effective in dandelion’s environments: brand new spaces, where constraints aren’t clearly defined. The broader the aperture, the more comfortable the team is in a dandelion environment.

How do we find out strategy aperture for our organization? My intuition is that we need to look at how this organization is constrained. Put differently, what are the limits that confine the cone of  its embodied strategy?

Applying a framing from the problem understanding framework, we can look for three  limits: time, capacity, and attachment. Note: these limits are nearly always tangled with each other in  mutually reinforcing ways.

The time limit is the easiest to spot and is the most intuitive. Does everything within our organization seem to happen slower than on the outside? The more emphatically we confirm this statement, the more likely our aperture is on the narrower side. Conversely, does it feel like our team moves faster than everyone can blink? Then we probably have a broader strategy aperture.

The limit of capacity is also fairly straightforward. What we don’t know creates a negative space  where we can’t create new ideas. Skills, expertise, and the breadth of experience are key factors in pushing the organization’s capacity limits outward. How specialized are we as a team? Deeper specialization narrows the aperture – and so do team cultures that produce echo chambers and groupthink. 

The third limit is attachment. Words like “risk”, “downside”, and “uncertainty” typically come up to describe this limit’s contributing factors. For example, the more customers we’re serving with our products, the more risk we will be taking to pursue new opportunities – this narrows our embodied strategy aperture. The more existential the idea of change feels within our organization, the less broad our aperture is.

By studying these limits, we should get a pretty good sense of our team’s strategy aperture. Now comes the key question: does it match our environment? If the answer is a confident yes, then we are set to accomplish amazing things. And more than likely, we’re not even asking ourselves these questions, busy doing those things. However, if trying to answer this question sows doubt in our minds, we might be in a mismatched environment: our embodied strategy prevents us from being effective to achieve what we’re aiming for.

The Wallpapering Principle

This principle builds on the layering principle, and deals with a common decision point that most software developers reach many times in the course of their work.

The situation that leads to this point unfolds something like this. There is some code at the lower layer that isn’t giving us the results we need for implementing the functionality of our layer. There’s some wart that was left there by developers of that layer, and we have to do something to minimize the exposure of our customers to this wart.

What do we do? The most intuitive action to take here is wallpapering: adding some code at our layer to reduce the gnarliness of the wart. This happens so commonly and so pervasively that many writers of code don’t even recognize they are doing it. Web development has a proud tradition of wallpapering. There are entire communities of libraries (jQuery, React, etc.) that invested a ton of time into wallpapering over the warts of the Web platforms. 

Especially when we are not thinking in terms of layering, we might just presume that we are simply writing good code. However, what is really happening here is a shift in layering responsibility – or perhaps a “layer entanglement” is a more catchy term. The code we are writing to fix the wart is out of place in our layer: it actually needs to live at the lower layer. And that means that by wallpapering, we are most definitely violating our layering principle. The code we write might be astoundingly good, but it’s kind of jammed sideways between the two layers.

As a result, the wallpapering code tends to be a drag on both layers. The layer below, now constrained by the specific way in which the wallpapering code consumes it, is grumpy about the loss of agency in addressing the original wart. By wrapping itself over the wart, our code now amber-ified it, preserving it forever.

At our layer, the code is an albatross. I already pointed at the CSS Selector-parsing code in jQuery as one example. Because it belongs to a lower, more general and more slowly moving layer, every wallpapering code saps efficiency of the team that needs to maintain it.

Perhaps most importantly, the wallpapering code has the capacity to misinform the layers above of the nature of the machinery below. If the opinion of the wallpapering code deviates strongly from the lower layer’s intention, the consumers at higher layers will form inaccurate mental models of how the lower layer works. And that is where the compounding costs really get us in the long term. The story that my friend Alex Russell has been telling about the state of modern web performance is a dramatic and tragic example of that.

All in all, we are best to avoid wallpapering at all cost. However, this is easier said than done. Most of the time, our bedrock layers (the lower layers we’re building on top of) are imperfect. They will have warts. And so here we are at the primary tension that the wallpapering principle helps us resolve: the tension between the intention to avoid wallpapering and the need to deliver reasonable products to our customers.

To resolve this tension, we must first acknowledge that both of these forces have merit, and in extreme, both result in unhappy outcomes. To navigate the tension, we must lean toward minimizing wallpapering, while seeking to reduce the cost of opinion of our wallpapers when we must employ them.

The key technique here is polyfilling (and its close cousin, prollyfilling):  when we choose to  wallpaper, do it as closely to the spirit of the lower layer as possible. For example, if our cloud API is occasionally emitting spurious characters, we might be better off filing the “please trim those characters” bug against this API, and then trimming these characters as closely as possible to the code that receives them from the network. Then, when the bug is fixed, we just remove the trimming code. 

A good polyfill is like a temporary tenant in an otherwise crowded family home: ready to move out as soon as the conditions permit. Wallpapering is usually a bad idea. But if we feel we must wallpaper, think of the code we’re about to write as a polyfill –  code that really wants to live at the lower layer, but can’t yet.

The Layering Principle

Preference toward layering is probably one of the more fundamental principles of software development. It pops up pretty quickly as soon as we start writing code. I wrote about layering extensively in the past, but basically, when we start connecting bits of code together, a tension arises between the bits. Some need to change faster and some need to stay put. This tension quickly forces our code to be arranged in layers, whether we want it or not. I sometimes joke that layering is either something we chose to do or something that happens to our code anyway.

Thinking of layering ahead of time is costly and usually involves discipline that is not always possible, especially when timelines are tight or the shape of the software we’re writing is not yet known. Often, our initial layering designs are wrong, and a whole different layering eventually emerges. These surprises might not be pleasant, but they are to be expected. Layers accrete. We are just here to garden them into the shape that’s most suitable for our needs.

Thus, as we engage in software development, we have to contend with two conflicting forces: one of expedience and convenience that beckons us away from layering, and one of intentionality that pulls us toward it. To resolve the conflict between these forces, here’s the layering principle: lean toward intentional layering, but give the layers room to develop.

A good rule of thumb here is to define layers early on as loosely as possible, and watch for where the layer boundaries are potentially crossed. When this crossing seems to happen, take the opportunity to clarify the layering. Watch for new layers to emerge and don’t add them without a clear need. 

Here’s a concrete example of loose layer definition. Suppose we’re building a client library for a cloud service. We might define three layers, listed here in reverse order (from bottom to top):

  1. Raw REST.  At the bottom, there’s the raw REST-ful API that is literally HTTP calls to the cloud service. This is the bedrock for us – we consume it, but don’t build ourselves. Don’t forget to have a bedrock layer. There’s always something that we build on.
  2. Core. In the middle, there’s the idiomatic layer that translates raw calls into constructs that are common for the target environment of the library. For example, if our target environment is Node, we might have something that uses http module or a new-fangled fetch to make the REST calls and return JSON.
  3. Features. Things that make the cloud service easier to use go in the top layer. This is where we can add fun syntactic sugar that lets us write the code in three lines instead of twenty, or address a particular use case in a particularly elegant way.

This might seem counterintuitive, but start writing code without explicitly putting these layers in place. Don’t force them. Think of the process as growing a seedling. Just keep giving them a glance as more code is added. Does this particular function seem like it could be in the Core layer? How would it group with others like it? Especially at the very early stages, think of layers as aspirational, and feel free to adjust the aspiration. Be patient: they will start showing up and becoming real.

Once the layers start coming together, it helps to develop a layering hygiene: imagine that a developer chooses to engage with a layer directly, instead using the full stack. If they are making raw REST calls, are they missing anything? Can they still get the same results? If they decide to write their own specialization layer, are they missing any of the core functionality?

Finally, as we develop features, watch for what’s happening to the code. Is there a new clump of code that seems to be forming? Maybe there’s a layer of specialization that starts emerging, or perhaps the core layer is splitting into idiomatic service calls and scaling/configuration layers?

The trick to the layering principle is in recognizing that there’s no simple answer: layering is a bit of a paradox that requires flexible thinking and continuous keen observation, rather than precise solutions.