
5 Reasons I Stayed Away from JavaScript - eliottenos
https://javascript.works-hub.com/blog/5-Reasons-I-Stayed-Away-from-JavaScript?utm_source=Hacker%20News&utm_medium=Content&utm_campaign=Eliott
======
davidjnelson
Doing logic in a constructor is trouble in any language. Also, typescript and
to a lesser extent flow are even nicer than vanilla es6.

~~~
aisofteng
>Doing logic in a constructor is trouble in any language.

This remark is often made, as is the similar comment that throwing exceptions
from a constructor is an antipattern. However, it's often said without saying
why it should be the case, and I get the feeling that sometimes it is parroted
without understanding. (OP: this is a general comment that is not directed at
you). For that reason, it took me a while to figure out the motivation for the
idea through experience (read: making mistakes and realizing them in
retrospect).

So, why is it bad to have logic in a constructor, or have a constructor that
can throw an exception?

There is nothing, necessarily, inherently wrong with doing so, at the level of
the specific object; the concern is at a higher level. The idea is, rather,
that doing so is an indicator of more widespread design problems in the
overall application. Let's look at an example.

Suppose that I have an object that is supposed to serve as a container for
data that is retrieved from a database - a User object, perhaps, which is
supposed to hold, say, a user's username, GitHub profile link, and other such
things. Suppose that I define my User class so that its constructor queries my
database for the its data and throws a SQLException if the query fails.

A fundamental principle of good software design is that of decoupling.
Decoupling essentially refers to the idea says that a component of a system
should not know more than it needs to know in order fulfill its role, nor
interact with more components of the system than it absolutely needs to.

If our object is supposed to hold data that is needed by other parts of the
application, it should not matter to it where the data comes from or how it
gets there; that is, if I have a User class that can throw a SQLException
while being constructed, then the User class simply knows too much - a
database connection error has nothing to do with a User, and so if User's
constructor does database retrieval in its constructor, then it hasn't been
decoupled from the data layer of the application.

To illustrate why this principle exists, suppose we have to switch our users
data store from Postgres to Mongo; the User class should not have to be
modified. Having User do query logic or be able to throw a SQLException in its
constructor means that it hasn't been decoupled from the data layer. This is
why people say, broadly, that doing logic or throwing exceptions from
constructors is "bad" \- it's not bad in and of itself, but it is bad in the
sense that it indicates that the overall application does not have good
decoupling, which can be a long term problem; furthermore, the severity of the
potential problem increases probably exponentially with the overall size of
the application. Imagine having dozens of data object that each do database
queries in their constructor - switching databases means you'll have to modify
every single one of these classes!

Just as important is commentary on how to avoid getting into this situation.
Let's use the same example - how do we decouple our User class, which does a
database query in its constructor, from our data layer?

There are numerous ways, but the general idea is to define a component of the
application whose job is to connect to the database and read data out, along
with a separate component of the layer that contains abstractions like User
which are constructed from this retrieved data. Concretely, in our User
example, we might first define something like a UserLocator class which takes
in a database connection and that has a method which takes a User ID and
returns some corresponding data from the database, and then a User class whose
constructor takes that data and pulls out what it needs. Later, were we to
switch underlying databases, we would only change UserLocator to query a
different database but have it still return the same data format that our new,
decoupled User class' constructor expects. This way, in the long term, our
application is easier to adapt to changes because we have good decoupling.
Applying this principle (and others) across an entire codebase can end up
being the difference between spending 90% of your time maintaining legacy code
versis spending 10% of your time updating existing code to meet new
requirements.

Hopefully someone finds this useful. I wish I had learned principles like
these earlier in my career; this sort of thing is definitely not taught in
computer science programs (nor, arguably, should it be - but many people get
hired out of such programs into places where knowledge of design principles is
very important).

