Books on a sunlit sofa

Anti-Patterns and Cow Paths

Sean Bennett

It's a tale as old as software engineering itself: a few blocks of problematic code in a common repository catch the eye of a particularly perceptive engineer; the engineer identifies a broader pattern and socializes the concern with their stakeholders; an initiative is undertaken to address the recognized anti-patterns and guard against their reintroduction; yet, a year down the line, these previously-identified anti-patterns are more prevalent in the codebase than ever. What happened?

This scenario highlights a perilously common pitfall. Refactoring to eliminate instances of problematic code and even applying mechanisms to prevent re-introduction are valuable and necessary steps, but are ultimately doomed to failure if maintainers fail to take a step back and ask the most important question: how are our brilliant developers so frequently and independently arriving at the same bad practice?

The answer is obvious, yet easily missed: the stakeholders have failed to pursue the chain of causation and have mistaken a mere symptom for the whole of the problem.

Say your codebase has a foundational dependency, Foo, which exposes an API to your developers. If just one developer abuses it to do something they shouldn't, that's probably a them problem. But, if dozens of developers consistently adopt a pattern you flag as problematic, it's time to ask if there is a fundamental problem in Foo itself.

For a concrete example, let's look at React, a popular JavaScript library for building frontend interfaces for the web. In February 2019, the introduction of functional components and "hooks" presented a drastic change to the previously class-based public-facing API. While some misgivings were voiced, community reception was broadly positive and the new approach become near universal.

As adoption proceeded, however, it quickly became evident that developers frequently fell into the same recurring pitfalls with several of the hook APIs. The React team tackled the symptoms. The anti-patterns were quickly socialized and clearly documented. Linting rules were introduced to attempt to guard against these where possible. But, five years on, it's become increasingly clear that the misuse is merely symptomatic of a fundamentally flawed design which fails to account for how consumers naturally interact with it.

This brings us to the cow path analogy. If a rancher has planned out a route for their herd which they believe to be ideal, only to find the herd consistently charts a seemingly more dangerous path, the rancher had better ask "Why?" before expending excessive effort to coax or coerce the cows into compliance. While the answer may truly come down to the cows being stubborn and stupid, it may also be that the supposed ideal route has pitfalls obvious to the heard which the rancher fails to identify or acknowledge the severity with which the cows perceive it.

Applying maximum coercion to truly force the cows onto the rancher's preferred path may end disastrously for all involved. At best, the cows will be frustrated and stressed. At worst, the cattle rile up the den of rattlesnakes they know to be present, but which has been unnoticed or ignored by the rancher. Taking the time to look into and resolve a root cause of the behavior is better for everyone.

We might even say that the propensity to focus on the symptom in lieu of pursuing a root cause is itself an anti-pattern, but one rooted in maladaptations of the human psyche—our code is flawed. Lacking the means to fundamentally rewrite how the human mind has evolved to assess problems, we're left with no other option than to treat the symptoms: we socialize the importance of pursuing the cause of a problem and implement mechanisms to better guarantee such is accounted for. By doing so, we improve the odds of identifying and properly addressing root causes in those domains we can control.

To close out, it's important to note that none of the above should be taken to discourage raising awareness of a problem prior to its being root caused. Diving into the chain of causation can be resource intensive and nowhere near as straightforward as identifying the symptom itself, so taking the first step of socializing the surface-level problem and its severity should be done early to start a discussion, begin gathering data points, and mitigate any immediate harm.

If you see something, say something!

Back to Blogs