OOP best practices that are anti-patterns in (functional) JavaScript

data: 30 sierpnia, 2022
czas czytania: 5 min
autor: Adam Brodziak

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.

Newsletter

Zainteresowały Cię nasze treści?
Sprawdź co jeszcze przygotowaliśmy.

Adres e-mail

Dziękujemy! Na Twój adres e-mail wysłaliśmy prośbę o potwierdzenie zapisu do newslettera.

O nie! Coś poszło nie tak. Nie zapisałeś się.

Gdyby tylko dało się zapisać Twojego maila dwa razy :)

Niepoprawny mail. Spróbuj jeszcze raz.

Cookies

W pracy serwujemy suchar dnia. Tutaj musimy Cię poczęstować ciasteczkami. Dowiedz się więcej.

Administratorem Twoich danych osobowych jest Future Processing S.A. z siedzibą w Gliwicach. Twoje dane będziemy przetwarzać w celu przesyłania cyklicznego newslettera dot. wydarzeń i inicjatyw realizowanych w Future-Processing. więcej informacji znajdziesz w naszej polityce prywatności.