DEV Community

Rense Bakker
Rense Bakker

Posted on

Why it is bad to be opinionated

As we all know, everybody always has an opinion, but that is not what this article is about. I want to talk about opinionatedness in relation to software engineering!

If you're a developer for a while, I'm sure you've heard someone (or yourself) dismiss an external library or framework, because it is "too opinionated", but what does it actually mean? And why is it so important for an external library to not be opinionated?

Defining Opinionatedness

Opinionatedness is when something makes a choice for you, outside of the scope of that thing. Internet Explorer is a good example here. If you are a Windows user, up till a few years ago, you were forced to have Internet Explorer installed. I hope we can all agree at this point, that the choice for a web browser should by outside of the scope of an operating system (I'm looking at you MacOS/Safari).

The Downsides of Opinionatedness

Let's look at a simple example:

function processResponse(res: { body: Record<string, unknown> }){
  return JSON.stringify(res.body)
}
Enter fullscreen mode Exit fullscreen mode

At first glance there's nothing wrong with this function, but suppose that in the future, JSON.stringify turns out to be a very slow way to serialize your object? Or other serializers offer additional features that you want? You now have to change the JSON.stringify call inside the function. In this example that is not a big problem, but you can imagine in bigger applications, where the usage of a particular solution may be spread across several different files and functions, it can become quite problematic to make such changes.

Preventing Opinionatedness

Looking back at the previous example of the processResponse function, we can make it unopinionated and future proof by making a few simple changes:

// Allow for passing in a custom serializer
function processResponse(res: { body: Record<string, unknown> }, options?: { serializer?: (Record<string, unknown>) => string }){
  const { serializer = JSON.stringify } = options || {}
  return serializer(res.body)
}
Enter fullscreen mode Exit fullscreen mode

Now, I'm not suggesting that every function you write, should include an options object to customize every aspect (dependency injection). This is merely one example of how you can make your code less opinionated and more future proof, by making it open for extension (remember the open/closed principle?).

Another good way to make your code less opinionated and more future proof, is by using the middleware pattern, like ExpressJS does for example:

const app = express()
app.use((req, res, next) => {
  // Any request/response handling middleware goes here
  // Even ones that havent been invented yet
  console.log('Time:', Date.now())
  next()
})
Enter fullscreen mode Exit fullscreen mode

Possible Advantages of Opinionatedness

Sometimes the choice for an opinionated technology may have actual benefits, aslong as you are aware of the risks. A good example at the moment is NextJS. This is a React framework, with batteries included. It makes some choices for you, that may or may not be out of scope. For example, it has a very particular way of doing routing. However, because of the way they do routing, it is very easy to implement things that are generally very hard, like Server Side Rendering (SSR), Static Site Generation (SSG) and Incremental Static Regeneration (ISR). So, When something has very clear benefits, it may be worth it, even though it might limit you in the future.

So, I hope the next time you have an opinion, you will remember this article and take some time to think about the long term implications of your choices! The developers who come after you, will appreciate the effort. 😉

Some additional tips:

  • Allow configuration and customization
  • Use dependency injection
  • Document limitations and trade-offs of solutions
  • Prioritize simplicity and modularity
  • Support extensions and plugins

Top comments (5)

Collapse
 
freddyhm profile image
Freddy Hidalgo-Monchez • Edited

What I enjoy the most is when frameworks or platforms give me a "paved road" approach, meaning there's a default, opinionated yet simple-to-use way of performing a common action (say logging). But then I can also easily swap that part with my own custom function later down the road. I feel a good example of this is .NET Core which allows lots of customization while providing good default options.

Collapse
 
strokirk profile image
Dan Strokirk

All my experience has taught me to use dependency injection as little as possible to solve your problem, and wait until you have a clear need to expand the API. This is not to say that all composition, configuration and customization are bad, but it can really hurt the code readability and maintainability when done carelessly.

Collapse
 
brense profile image
Rense Bakker

Well yes I would not recommend to do anything carelessly! 😁 And you are right, you should not spend too much time building for future features that might never be requested. When you write code that is going to be used by other developers though, like when you're the author of a library or framework or a set of utils that's only used internally, it might be a good idea to keep extensibility in mind. (If you want to save yourself a lot of work in the future)

Collapse
 
strokirk profile image
Dan Strokirk

For sure - in library code it's much more common to have a good need for some sort of dependency injection. Especially since library code by necessity also needs to be a bit more rigid and change less than application code.

Whenever I do apply DI solutions, I like your approach in the article - keep good defaults! The defaults both serve as a simplified interface for the user, and as documentation for whoever has to read and debug the code in the future.

Collapse
 
jmfayard profile image
Jean-Michel (jmfayard.dev) • Edited

I hope we can all agree at this point, that the choice for a web browser should by outside of the scope of an operating system (I'm looking at you MacOS/Safari).

Side note: that's even a legal requirement imposed by the European Union.
In general, all the antitrust legislation already exists, because we used to uderstand thanks to the robber barons that monopolies are bad.
Somehow we forgot the lesson in the Reagan area
Fast forward today and the tech robber barons can have their monopolies.
For now.
Because the legislation is still here