OOP best practices that are anti-patterns in (functional) JavaScript
- Processes, standards and quality
- Technologies
- Others
What is considered a good practice in Object-Oriented Programming (OOP) and Design Patterns, but is considered an anti-pattern in JavaScript (the functional programming (FP) flavor of JS at least)?
Here are the main points:
- The good: factory pattern
- The bad: constructor
- The ugly:
class Foo extends Bar
For classic OOP languages (Java, C#, PHP) things look pretty much the same. C# is probably better of, but I have very little experience with it, so I will be using Java as a reference point. The (anti)patterns are the same in all languages, though.
Disclaimer: The above does not mean that Object-Oriented Programming or languages that follow OOP paradigms and use classes are bad. It’s more that JavaScript uses a different paradigm and provides greater expressiveness, so it’s good to make use of it.
The good: factory pattern
In fact, you have 2 factory patterns in OOP:
Both make sense, as they are solving the problem with a particular language. The big one here is: you want to code the creation of objects, without knowning their class
yet. Look at the provided code examples in Java, how complicated it is and how much boilerplate is necessary.
In JavaScript none of those limitations exist: there are no object classes (more on class
keyword later), no need to check interfaces and such. Any function can return an object, so you can do:
const createUser = ({ name }) => ({
name,
setName(name) {
this.name = name;
return this;
}
});
const joe = createUser({ name: 'Joe Doe' });
The code above is a factory function that simply returns an object. No classes, static methods, polymorphism are necessary. Have a read on factory functions with ES2015 syntax to learn how to handle default parameters, type inference and others.
In other words: the wide use of factory patterns in OOP languages make sense, but in JavaScript it is just a function returning object literal. Too simple to be called a design pattern 😉
The bad: constructor
So JavaScript has got a constructor function that should be invoked using new
operator:
function User(name) {
this.name = name;
}
const joe = new User('Joe Doe');
console.log(joe instanceof User);
Just like Java, well almost. If you forget to use new
the constructor will be called a regular function, in such case this
will be bound to the global object (i.e. window
in the browser). Vars like names are added to the global scope and the whole construction falls apart (pun intended). It is a major gotcha and cause of many bugs.
You can explore more traits of constructor functions, but I agree with Kyle Simpson there are better solutions than constructor, prototypes hackery and classes, like OLOO pattern. If you really need objects with methods, of course 😉
Long story short: avoid using new
keyword and constructors. As a consequence instanceof
might not be necessary as well. Use more powerful factory functions described above. Just as Douglas Crockford said:
If a feature is sometimes dangerous, and there is a better option, then always use the better option. ~ Douglas Crockford
The use of the new
operator in OOP languages is discouraged too, to avoid coupling with a specific class and its implementation. Factory patterns are preferred instead, which enable Dependency Injection, but it’s a story for another post.
The ugly: class Foo extends Bar
ES2015 introduced class
and extends
keywords to JavaScript. No, it does not mean JavaScript has got classes – it’s still using the prototype chain. Basically it’s a syntax sugar over constructor pattern with some magic done behind the curtains.
In fact, the worst offender here is the extends
keyword, because it encourages extending classes for code sharing. That is the wrong motivation. The extends
should be used to express “is a” relationship in design, but such a case rarely happens in reality. This is the highest form of coupling in OOP.
Extensive use of extends
leads to deeply nested class inheritance hierarchies. It does not seem bad at first, but believe me, I’ve worked in a system with such hierarchy 7 levels deep and we constantly keep fixing code in this tree. Fix on one level caused regressions few levels upper or lower, leading to the downward spiral. That is simply the Seventh Circle of Hell kind of issue.
Other often used name for this anti-pattern is “The Gorilla / Banana problem” coined by Joe Armstrong:
The problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle.
On the other hand class
can be useful. For a long time using classes was the recommendation for creating React components. Notice that you always had extends React.Component
– there was no deep inheritance hierarchy in the code, right? Right?! I don’t think class keyword in JS is broken, but it comes with its own problems, just like the new
keyword does. As an analogy suggests – better to avoid them both.
Objects and classes are not the software
OOP aficionados often tend to focus on classes, objects, UML, diagrams — all of that is great, but not the core. What is the most important thing is: what the software does. With that in mind objects without methods are pretty useless, even though Java (used to) force you to creating classes to hold your functions — an awful idea.
For your next JavaScript feature or project start to think about how you can solve the problem the simplest way. Try to avoid all the OOP ceremony and focus on function(s) of the software.
The article was originally published here.