Programming Antipattern: The 1 Ply Wrapper

A cartoon illustration of a software developer crying, surrounded by rolls of cheap quality toilet paper

Abstractions come with a hefty cost, yet teams are often willing to incur it at the first sign of a chance to DRY up their code. This comes in all shapes and forms, but in this post, I will focus on an antipattern which I’ve come to call the “1 Ply Wrapper”. The “1 Ply Wrapper” is a shallow and needless abstraction. So thin that in your attempt to clean up you are likely to cause more mess. It often hides away the module you want to be learning and interfacing with directly (as learning and interfacing with it will yield more return than this single abstraction in future projects you contribute to)

That’s enough talk, let’s get into some code examples to illustrate the point. The example will be simple to get the point across, so consider it in a broader context.

We start with the entrypoint of our program. A simple hello world example.

// src/main.ts

import {log} from './log.ts'

function main() {
    log("hello world");
}

Looks reasonable so far, but let’s see what’s inside log.ts

// src/log.ts
function log(message: string) {
    console.log(message)
}

Why wrap a well known function (anyone familiar with Javascript will know of console.log) with your own custom abstraction / interface? This is only creating a shallow and confusing abstraction specialised for your program, with no benefit. The interface is also different to console.log which takes a variable number of arguments (they don’t need to be strings either). Given the author of such code the benefit of the doubt, this may be intentional, but subjectively, it’s also often a case of “we only need a string” for now (with the same logic, why build the abstraction!?).

One may argue, that such abstractions allow the log functions implementation to be replaced in the future. Perhaps we perform some API call instead, or log to a database (whatever example you want to come up with).

I’m proposing, that until that day comes, use console.log directly.

// src/main.ts
function main() {
    console.log("hello world");
}

Search and replace is pretty standard editor / IDE functionality and a refactor to achieve that abstraction in the future is cheap. A lot of bad code starts from good intentions of providing these sorts of abstractions “just in case”, and the reality is that often these scenarios don’t occur, and we overestimate the effort to adapt our code in the cases they do occur. It’s often easier to edit code that is built to purpose vs code that has been abstracted prior to “expected” use cases having emerged.

A more egregious version of this abstraction is when the function signatures are equivalent.

// src/log.ts
// e.g. roughly something like this
// don't come after me if this isn't exactly the same signature as console.log...
// you're smart enough to understand code examples are for illustrative purposes...
function log(...messages: any[]) {
    console.log(message)
}

Why does this suck? Because the interface is the same! Stop it! Use the well known function directly.