In Defence of Opinionation
Somehow the term “opinionated” has become a criticism. This is a pity because there are significant benefits that we shouldn’t ignore.
One of the projects that has taken up a lot of my time in recent months has been the Crab CLI, a React command line utility to allow code generation and scaffolding for React components, with or without Redux, TypeScript, etc. I’m moderately proud of it.
It’s also a perfect example of the benefits of being opinionated. Not the tool itself — the framework the tool is for. (I’m going to refer to React as a framework for convenience here, as I’m talking largely about React in the context of using it with things like React Router and Redux, so it just makes the most sense to dispense with the library vs framework discussion for now.)
In any case, writing a CLI tool for React generation has been an extremely difficult process, because React is aggressively not opinionated. As a result, you have two choices. You either make a generic solution with no opinions whatsoever that “just works” or you have to start making a few calls on your own.
The former is a lovely idea, but ultimately you reach a point where you simply can’t provide features without being able to rely on a few things. With React you know nothing about the general context. Is this app using Redux? If so, where is it? Where are its actions? Does it use action creators? Are its actions named with an exported constant? How does it combine its reducers?
¯\_(ツ)_/¯
Ember is the opposite of this. It has a rigid, even draconian, set of conventions. For many developers this is seen as a negative, but I disagree. Or rather, even if I did agree I would feel that the outcomes more than make up for any compromises.
In Ember’s case a set of clear conventions for things like folder structure made an application profile that meant all Ember apps look alike. Their structures are identical. They have the same features. They have the same files and standards. There are shared understandings developers can have immediately, assumptions that are correct by definition.
But more than that, because the folder structure is the same and invariant, with some exceptions, Ember’s build pipelines are consistent and exceptionally fast. More particularly, because there is consistency and reliability, Ember’s developer tooling is able to make assumptions that are iron clad. This allows things like code-generation tooling that works reliably and consistently. Because a model will always live in app/models
and my templates will always live in app/templates
there’s no ambiguity.
When you run ember g route about
it will create a route (an Ember-specific MVC abstraction) in app/routes/about.js
and a template in app/templates/about.js
. It doesn’t have to ask where you want it because there’s nowhere else it could go. It will make the assumption that the matched path will be /about
and set up an entry in the router.
Router.map(function() {
this.route('about');
this.route('contact', { path: '/contact-us' });
});
That’s the router for Ember above. Note that the top route benefits from all these opinions. We don’t need to define the path — it’s implied. We don’t need to define the component (or in Ember’s case, route) handling that path because Ember’s conventions already enforce that it will be app/routes/about.js
so there’s no ambiguity. The second route here shows that sometimes these opinions and assumptions aren’t always quite right. The path here actually differs from the default, but a simple “escape hatch” resolves that.
Large frameworks like Ember, Laravel, .Net, or Rails are unashamedly opinionated. They have to be to provide a consistent and cohesive experience.
Deeper to Laravel, you can look at something like the Eloquent ORM, which helps Laravel get access to a database.
Eloquent assumes a number of things about your database structure. It assumes that your Pet
model refers to a table called pets
. It assumes you have an auto-incrementing primary key called id
in there. It assumes that if you have a pet Category model as a one-to-one relationship that will be the category_id
field and correspond to an id
in the categories
table. It assumes that if you have many-to-many relationships they are named by the tables and in alphabetical order, as owners_pets
. It assumes this table has an owner_id
and pet_id
that refer back to an id
column on the owners
and pets
tables, respectively. It assumes all your fields are lowercased, and that you have timestamp tables for date_created
and date_updated
.
These are all opinions. They are opinions that force developers to do things in a specific way, regardless of the developer’s preference. But Eloquent does things right. Although there are opinions in place they can be overruled. You can tell your Pet
model to use a different name.
protected $table = 'FurBabbies';
You can override the primary id name. You can define relationships by explicitly stating the join keys used, and/or the table name. There are escape hatches for these opinions.
But adherence to these opinions and assumptions has an enormous benefit. The amount of code needed drops drastically. The code also follows a more idiomatic, documented works-by-default approach. You can use tools like migrations quickly and easily. Routing will work out of the box and options like implicit bindings can cut down on boilerplate. Working with, rather than against, the opinions of the framework makes for an overall better developer experience. And if that means compromises on some assumptions or expectations, is that not a reasonable price to pay?
Another example is coding standards. When you use a pre-defined standard like PSR-2 for PHP all you are doing is signing up for a bunch of other people’s opinions. And that’s not a criticism.
I hate spaces for indentation. Just by the way. Four space indents is absurd. People literally ask whether they should use spaces for tabs or tabs for tabs, and then ask when they press the tab key whether it should make a tab. Fucking hell, of course it should. What a dumbass question. I’ll get more comments on this point than on the actual article, by the way, and they’ll all say that the IDE can be set up to show x if you use spaces, so you should use spaces. But it does the same if you use a tab… whatever. Ugh.
Anyway, the point here is that I don’t use tabs when I use PHP. I use four spaces. Because my personal preference is way less important than having a shared standard. Because by using an established opinion you can benefit from tooling built into editors. Because by using a third party opinion you can head off the inevitable code-style bickering, and make everyone equally unhappy. Because by using an established standard you can just point new developers to those docs, to downloadable linters or standards, instead of having to go through a standards document in some way.
This is why standards like JSON API exist. Because outsourcing opinions is an incredibly powerful ability. Because bike-shedding standards and specs and folder structure is a very real problem.
As a similar point, Angular made the opinionated call to make TypeScript the idiomatic platform for Angular development. Now, I’m not 100% sold on TypeScript myself. But if I’m doing Angular work, which I do from time to time, I unquestioningly do so in TypeScript. By defining and sticking to an opinion (like it or not) the Angular community ends up with a consistency and coherence that does not exist in more “flexible” platforms like React and especially Vue.
To compare two options other, look at Parcel and Webpack. Parcel is opinionated. It supports a surprisingly broad number of web technologies out of the box. Sass, TypeScript, etc. But not all files are supported. For example, you might use Handlebars templates, or you might use Pug templates. Since March this year, .pug
files will automatically be compiled by Parcel, but .hbs
files won’t be. Opinions. Opinions that are neutral to Handlebars users, but benefit Pug users.
Webpack, though, supports nothing at all by default. Everything has to be explicitly set up with a loader. Handling .vue
files, or .scss
, or compiling TypeScript, transpiling ES6, etc. Every feature is at the same level — approximately zero. For extremely advanced usage there might be features that Webpack provides and Parcel doesn’t. But for simple use, or cases that align with Parcel’s opinions, it is a great deal easier to use it than to make a Webpack config nest.
Once again, let’s talk about what I’m not saying. First of all I’m not saying Ember is better than React. Nor am I saying Broccoli is better than Webpack, Parcel is better than Webpack, or anything like that. I’m not saying Eloquent is good, or making claims about whether you should use an ORM. I’m not even saying that the opinions themselves are always right or the best call.
You can reply with as many comments about performance or patterns as you want, that’s not what I’m talking about. It doesn’t really matter so much to the discussion what the opinions actually are — merely that they’re made for you.
Have a look on communities like Reddit and you’ll routinely see questions about application and folder structure for “unopinionated” tools like React, Vue, Go or Express. These are non-questions for tools like Ember or Laravel. They are the hidden cost of a lack of opinionation. But so, too, is a lack of features and work flows.
Where “opinionated = bad” most commonly seems to come up is in the realm of build tools and similar processes. There seems to be a small but vocal group who feel that if you didn’t wire up every step, or don’t have access to fine-tune or customise every aspect of the pipeline, every technical choice, every path and file type, then it’s completely crap. I consider this akin to NIH Syndrome — the Not Invented Here Syndrome that makes developers refuse to use libraries because they don’t trust them. For me, I only care about the output. I don’t care if my build chain consists of two pigeons and the colour yellow, as long as it’s fast and reliable.
An opinonated tool isn’t necessarily straight jacket or handcuffs. More often it’s sensible defaults and predictable outcomes. A good opinionated tool has escape hatches for necessary opt-outs, but provides an easy path when those opinions and expectations align.
We need to stop talking about “opinionation” as a negative trait itself. What we should be doing instead is talking about and acknowledging the compromises and benefits that come with those opinions, and making informed decisions accordingly.