Examples of how anti‑patterns work

Negative examples are much better than positive ones. And because the subject is anti-patterns, I can share some examples that explain how they work. This section can be skipped if you already feel you understand anti-patterns but read on if you wish to garner a deeper understanding of the mechanisms of their survival.

Convenience as a defence mechanism

Sometimes, just being easy and wrong is better than hard and right. These anti-patterns are like sweet food and skipping leg day.

Bringing the problem into the code before analysis

An anti-pattern of increased coupling.

Object-oriented code is a response to the difficulty in creating proper abstractions for the parts of the problems to be solved by software. This is why the problem pieces often end up mirrored in the object design. Unfortunately, when you mirror the problem into the data and code of the solution, you introduce a lot of baggage, expectations, and complications.

When objects in code have a one-to-one mapping with entities in the real world, we have brought too much of the world into the program domain. Objects are more powerful when they represent larger concepts. Because objects are used to represent entities from the real world, with all their meaning still attached, analysis of the state of the world becomes complicated and more expensive.

If your language allows you to bring the problem into the codebase, that implies you haven’t been forced to analyse the problem before writing the solution. Most solutions are complex because they solve an ambiguous or poorly-refined problem. So, any language that allows you to represent a poorly-refined problem will not give you feedback on your next steps. Instead, it will enable you to create an ineffective solution to a problem sooner. Solving problems fast will create a feedback loop that implies this approach is better, as solved problems look like progress. But this is an anti-pattern; we know complexity, hidden or not, is the doom of all large projects.

In Domain Driven Design[DDD04], it’s stated that the domain will be revealed throughout the duration of the development. That means the objects, their names and what they mean are not set in stone at the beginning. It is, in fact, the core activity of an engineer to fully understand a problem and remove ambiguity before settling on a design.

Suppose the domain is to change, and the problem will be better understood over time. In that case, bringing the problem domain as it is understood in the first development phase into the codebase invites change at a fundamental level later on. This is good if you have planned for such a major upheaval, but most languages don’t have good object model migration tools. Refactoring tools are good, but not magic.

Last minute creation, or FetchOrAllocate, GetOrCreate, or FindOrConstruct.

An anti-pattern of repository locking.

We encounter the same issues as with the construction of Singletons. When creating the Singleton on first use, we have to lock. When we create an entity on demand based on a unique identifier, we also have to lock. But there’s a difference inherent in locking against a unique identifier rather than a Singleton. When the location of the entity is not a simple globally allocated slot, but instead some dynamic container, we have to contend with locking that container. Now we can potentially block another entity registering to the same container at the same time.

We see here that the issue with last-minute creation into a repository is that it blocks parallel access. This means you can end up with deadlocks if the entity needs to access different repositories to complete its construction. So, what is the wisdom here? First, the problem with Singletons is that they need to lock their location before they can create the thing to put at the location, so that can cause a deadlock. Second, a last-minute creation adds further complications to that when it’s a uniquely identified object in a repository due to it needing to lock the container of the repository and lock against multiple construction.

We can suggest using containers with concurrent append or insert properties to avoid the issues with deadlocks, but it doesn’t solve circular dependencies between different entities, nor does it really solve multiple construction. Those problems seem like problems of usage, not the pattern itself. Another solution would be to have the constructors of the entities not allowed to call upon other repositories until a later moment. Allocate if necessary, but don’t fully construct until used by that which fetches the entity. In effect, prefer dumb constructors.

Obvious truth as a defence mechanism

Best practices that conflict with other best practices. Things that seem obviously true but in fact may not be. Assuming your problem is unique. Acting on common expectations without evidence.

Physical code layout matches logical

An anti-pattern of unnecessary coupling.

We don’t remember anything in great detail if we have only visited it briefly or been away for a while. We are therefore disadvantaged if we need to keep bouncing between different source files to see how the code works. One class per file is an anti-pattern because we have to jump between files to track the flow of messages between objects.

But if we have to bounce around between files to figure out what is happening, is that the symptom of a different problem? Yes, and it’s because the value in some code is the emergent properties of the interaction between elements. So why is the interaction held across multiple files?

In some languages, this is inevitable. Java demands it as part of the language. But in C++, keeping files and classes mapped one-to-one is a cargo cult anti-pattern. The pattern maintains and reinforces itself through We’ve always done it that way but is often at the root of longer compile times and moves some optimisations from compile to link time. As linking is harder to multi-thread, you push some phases of your compilation into a serial bottleneck.

In data-oriented programming (not data-oriented design, but a different, functional-programming paradigm that espouses the separation of data structures from the transformational logic and eschews mutability in favour of a monadic style for transformation), logic can be grouped by the intent. Utility functions are grouped as simple verbs for a system, and complicated functions are grouped by usage patterns. It follows that a developer can read and easily understand these transformations, as they are all physically located near to where they are used.

The real problem is how related classes are so often kept on different ‘pages’. Moving things away from each other when they are related makes it hard to read the flow of the code. Encapsulation is often used to claim the opposite. Nonetheless, the chance a class is fully understandable by its interface is low, which means you must often inspect the implementation. In debugging and maintenance, this is always the case, even in well-defined APIs; you know something is not doing what it claims to be doing, so now everything is untrustworthy.

The physical layout of the code should match the easiest way to read it. It should follow the process the computer would go through. Whenever the next line the computer runs isn’t the next line in the source code, you’re adding comprehension blockers.

Overgeneralisation

An anti-pattern of removing important connections.

Overgeneralisation is when you lose specificity. It’s when you had information you could have used at one layer of the process, but along the way, due to the generalisation of the API, you could no longer pass it along with the request. This can happen by omission of arguments or context, but it can also happen with type sinks. These are places where type information is lost because a common base type is used to transfer the information to the next participant in the communication.

Strings are potent type sinks as they represent many different types of information. In a 2014 talk, Scott Meyers1 uses examples of filenames, customer names, and regular expressions. The danger of sinks is that they circumvent the type system. You have to remember or name the variables to defend yourself against accidentally using the wrong variable in the wrong function.

All types can be sinks, but some are more likely to sink than others. This principle applies to integers and floats/reals, booleans, and even file handles. A floating point doesn’t have a meaning, but kilograms, cubic centimetres, or Celsius do, and using a bald float ignores that. Files are local, memory, logging, stdout, read, and write. When we let the same variable possibly be any of a set of things at runtime, we can lose vital information that could help with debugging and maintenance or performance.

However, I must repeat: all types can be sinks. Your Kg type might be sinking the maximum payload for a vehicle into a simple mass value, whereas the meaning is payload, not total operating mass. If you use the maximum payload value in the calculation for how many of these you can carry on a ferry, you will be overestimating and the type system will not have saved you.

This is the purpose of Apps Hungarian. In Apps Hungarian, the process for naming required you to prefix with the meaning of the variable, such as Kg_, CC_, TempC_, Px_ for pixels, or Kt_ for kittens. Whatever the team decided was an important distinct type got a prefix. It’s also why we don’t like Systems Hungarian. The prefix there was most usually related to the literal type. A filename was an sz_ type, a zero-terminated string, but so was a customer name. Systems Hungarian was a panacea for the poor IDEs of the time.

Future proofing

An anti-pattern of preparation over valuable action.

When developers suggest building reusable code at the outset, they take a gamble. They bet their code will be sufficiently well documented, discoverable, and isolable to be used as easily as a common runtime library feature. I wouldn’t make that bet. I think it’s better to make the code solve the real problem I have right now. Better still, it’s good to know the odds.

But reusable is good. It’s an obvious truth. So why not aim to make something reusable? What are the chances the code you write today can be reused by some other project in the future? What’s the chance the code you write can be used in multiple places in the same codebase during the project’s lifetime?

The driver for this anti-pattern is compounded by the wonderful feeling we get when our code is reused. We wrote something that was useful more than once. We shipped our contribution in multiple products. We were a force multiplier for the organisation. But we forget all the other times we built reusable software that wasn’t. We remember the good times. We also forget about all the costs of maintaining the reusable but not reused code we wrote all those other times.

To be reusable, code is more brittle and lower performing, both stricter and more generic. Even though it will only be used in one location to start with, it takes on these constraints in the hope of future reuse. We also need to remember that we did not go hunting through the codebase to see if another piece of existing code could solve our problem, so why should we expect anyone to discover ours?

My principle to defend against this anti-pattern is to accept that reuse is a requirement and never intentionally write reusable code—only refactor code into reusable code when the need for reuse finally arrives.

Team growth and company growth

An anti-pattern of overlooking impact.

Teams work best at certain sizes, but successful teams and their leaders can feel like they need more staff to complete even bigger tasks, and their track record proves they are a worthy bet to take by management. So they get the extra staff, which destroys the team’s cohesion, leading to failure. This is an anti-pattern because it’s a recurring pattern of ruin. It would be best if you protected against it. Awareness of the pattern can be enough, but putting physical reminders in place, such as making the spaces for teams only barely large enough to contain an oversized team, can help remind people without reminding them directly.

Wrongly rewarded

Sometimes, the way we reward actions is wrong, leading to the anti-pattern. We create the Cobra Effect, causing problems by not reminding people of our core goals.

Productivity guilt

An anti-pattern of local optimisation.

What often feels the most important is being productive. Developers feel ashamed when not progressing, even if it’s not their fault. Whatever the environment, studio, home, or cafe, every developer aims to produce. Feeling productive is important. We feel more productive when we can go about our tasks as we know best. Strict rules from IT or policies put in place to reduce cost can add friction and shackle developers. The rules interfere with what we value the most.

However, an organisation cannot listen to the desires of the developers at the expense of being on the wrong side of a historic event. Security failures cost lives when they happen to large organisations. They also cost money and time, which goes against the developer’s wish for productivity.

Feeling productive and being productive are not the same thing. A productive developer can be an idle developer. By not doing anything, they are not causing waste and not wasting anyone else’s time. In this way, a developer can help the organisation. Sometimes, the most productive thing to do is to rest, research, and read. Without goals, explicit or otherwise, employees can still find work to do on a project, regardless of how counterproductive their efforts are, because programmers like puzzles. Better developers like making products and avoid creating more work than is necessary.

The difference between a professional developer and a less-than-professional one can often come down to a level of pragmatism regarding what work they don’t do rather than how they manage to do it all. The professional will prefer to do something with directly associated customer value. The less professional developer will choose to do the most interesting work. Interesting does not mean easy. Developers can get caught up trying to solve tough problems that benefit the organisation very little, so they are wasteful but they are not lazy.

Complicated code is cool

An anti-pattern of inattention to global strategy.

There’s a value problem with complicated code. People are proud to have solved a problem in a complex way. They often see little value in going back and simplifying, or when they do, they aren’t provided ample time to rectify the cognitive load. If you go against the grain and try simplifying things, you can be reprimanded for changing things unnecessarily. This is a cultural issue, and it affects all programming in all languages.

This is a problem more prevalent in seniors than in juniors. They feel the code was hard to write, so it should be hard to read or work with. Not all seniors, but many have insecurity issues or impostor syndrome, and that leads to needing to prove themselves—a badge of honour. They wear the badge in the form of a complicated API or being ‘the only one able to debug it’.

The Towers of Hanoi is an example of a system that does not live because it’s always blocking itself. There is, realistically, only one efficient solution to the puzzle. Many puzzles are precisely this: the idea that there is only one way through. There is a breed of programmer that loves this kind of puzzle, and I call them Solvers. Solvers will work through tricky puzzles happily because there’s something rewarding about the challenge of finding a solution or the only solution.

Solvers crave this feedback. Their goal is to solve optimally. With these problems, they can at least be confident when they have found a solution. But even an optimal solution might be optimal in the wrong domain. Think about how you design a housing plot, ensuring you have space for a few cars. You can save space by making a long, narrow drive. But having all your vehicles on a single narrow driveway costs you time when trying to get one car out if it is parked further in. Optimal solutions in space are rarely optimal in the domain of time.

If solving a puzzle or complicated code problem is rewarding, then why would a Solver choose to make programming more straightforward and, therefore, intrinsically less rewarding? There is nothing inherently good about the challenge of making everything fit and work together. Any goodness must come from getting it done. The buzz of a challenge runs counter to that. ‘Solution-finding’ is a craft people can get passionate about, but work is not leisure. So, solving complicated problems is a strong anti-pattern affecting some developers. In fact, some get caught up solving the same problem for years, either over and over or just once to a greater and greater degree, with no end in sight.

1

The part on type sinks is at 56 minutes, near the end of The Most Important Design Guideline: https://www.youtube.com/watch?v=sfLZ7v9gEnc