
When I use classes, when I don’t, what I do instead and why - galfarragem
https://blog.kentcdodds.com/classes-complexity-and-functional-programming-a8dd86903747
======
nulagrithom
Understanding 'this' in JavaScript is simply table stakes at this point. You
and anyone else who touches your JS code should have a decent grasp on 'this'.
Citing "complexity of this" while using React is just absurd in my opinion...

 _You Don 't Know JS: this & Object Prototypes_ cleared it up nicely for
me.[0] Love the series in general too.

Also -- and I'm not sure how it's typical done when taking a functional
approach -- where do these functions "live"? What if we want to use a Person
in another module/component/whatever? Do we basically just have a collection
of "static" functions on a helper class that we're going to be using? I'm not
sure that's a real gain. Would rather see the functions available on the
Person itself (though maybe that's just my OOP ways talking).

And then there's the "pure function" part. Without explicit documentation and
no other knowledge of this individual's functional predilections, I would be
surprised when the setName function actually creates an entirely new Person
and then sets it name.

I don't know. I really do enjoy functional approaches, but I don't think
avoiding 'this' is a good reason, and I feel like the example given isn't the
best scenario to illustrate.

[0] [https://github.com/getify/You-Dont-Know-
JS/tree/master/this%...](https://github.com/getify/You-Dont-Know-
JS/tree/master/this%20%26%20object%20prototypes)

~~~
jtchang
The more I learn JS the more I am worried that I am not using 'this' right.

I swap back and forth between javascript/python and with JS it always feels
like I am one step away from disaster if I accidentally forget to bind this.

~~~
gcoda
I feel you, but sometimes, I am using

    
    
        var that = this
    

just to be sure

------
acdha
It seems odd to read something written in 2017 about the confusion from using
`this` which only mentions arrow functions briefly in passing. A fairly high
percentage of discussion on that issue could be replaced with “use an arrow
function, it works the way you expect”, and browser support should be
basically the same as ES6 classes – doesn't work in IE11 but multiple tools
are available for backwards compatibility.

~~~
supergreg
Do arrow functions get added to the prototype or the instance of the object?
Can they be created directly in the class body or have to be added inside the
class constructor?

~~~
martin-adams
My understanding is that arrow functions aren’t used as part of a class
definition, but for things like anonymous functions in callbacks, preserving
the ‘this’ context when something else is invoking it.

E.g. el.onClick((e) => {this.doSomething();});

In that case something else will invoke the onClick callback but we want the
‘this’ context to the same as when we set up the onClick.

~~~
acdha
That's why I mentioned it since it seems to be something like 90% of the bugs
I've seen related to this. It's not a coincidence that this is the example
used in the article.

------
azangru
The title would benefit from the word javascript :-)

~~~
thinkxl
I don't know why, but just by reading the title my mind implied JavaScript,
and I noticed it doesn't have that word only after reading your comment.

Weird.

~~~
stochastic_monk
I thought of C++, but I have never written any JavaScript in my life. It's
worth considering that even in C++, there are times when inventing a class to
simply perform a function is excessive.

------
seasonalgrit
If you're going to go with a functional approach (which he introduces in the
section "How to avoid 'this'"), instead of creating a brand-new function for
each particular piece of data (e.g., setName, setGreeting) -- which will
result in a ton of extra code -- just use a simple, generic _assoc_ function.

"Inventing a class with its own interface to hold a piece of information is
like inventing a new language to write every short story." \-- Rich Hickey

~~~
deckar01
I actually have to maintain a js codebase that uses a generic “addProperty”
function at its core to generate getters and setters. It is terrible to debug.
Properly encapsulated class methods are much easier to read and debug.

This problem has been solved for a long time. Some data doesn’t need a class,
but when it does, don’t roll your own.

------
wellpast
This post is painfully confusing the important concepts of complexity &
"complecting"/coupling.

Javascript's _this_ semantics may be confusing and hard to reason about but
that is a different charge than the complecting/coupling sort of complexity
which is the _much, much_ bigger problem in the world of building software
systems.

The much bigger problem with a Person class is not to do with this or that
programming language vagaries vis a vis the _this_ keyword. The bigger problem
exists in all programming languages: ADTs like Person classes introduce
unnecessary coupling of information -- and this is what really matters: this
is what hurts productivity.

It sucks that I have to learn the finicky vagaries of _this_ in Javascript,
but once I do, I'm fairly good going forward. However, no amount of learning
on my side is going to help me when I need to add new pieces of information to
some people data -- your ADT has decided for and _requires_ me to jam my
knowledge and facts into an unnecessary rigid categorization in order to
evolve your ADT-ridden data processing system.

This post's refactoring of class hasn't changed that game for me. You've just
moved _this_ to each function's first parameter and called it "person" \-- so
while you've saved me momentarily from having to learn about _this_
semantics[1], you haven't gotten rid of the truly offensive bit: the whole
notion of the "person" classification itself and the rigid, upfront,
unnecessary, and likely-to-be-wrong-in-the-future assumption that only its
members can be given these things called names.

[1] Which I should have learnt anyway being a Javascript programmer.

~~~
s4vi0r
What do you mean by ADTs introduce unnecessary coupling of information? ADTs
allow you to clarify and safely handle otherwise vague data. Maybe I'm
misunderstanding, but your comment reads to me like you're in support of
stringly typed programming.

~~~
wellpast
> ADTs allow you to clarify and safely handle otherwise vague data.

This is false. You can program with clear, strong, safe semantics without
needing to taxonomize/classify your business domain/entities with ADTs.

In most business applications, ADTs hurt you: they are used to pre-classify
the world in ways (unnecessary ways) that very frequently collide with future
system evolution & future use-cases.

~~~
always_good
That doesn't seem to have anything to do with ADTs but rather all code in
general.

I'm not sure how "// request.user will be undefined or { id, uname }" is any
more future durable or omniscient than `Request.User : Guest | LoggedIn User`.

The latter just encodes the possible states directly and explicitly.

Your line of reasoning is like saying that Mongo lets you adapt to future
requirements better than Postgres because you don't have a schema.

~~~
wellpast
> The latter just encodes the possible states directly and explicitly.

The very concept of "possible states" is the problem. By deciding that your
domain entity has possible valid states _in all situations_ (which is more or
less what an ADT is saying), therein is the coupling, the pain, the
productivity impediment. Not only do you not _need_ to say that for all
moments and places in my program a person must have a { id, name, uname, age,
etc, etc }, it _hurts_ you to assert that because the future is always much
more creative than you and will soon put demands that exceed the assertion.

> Your line of reasoning is like saying that Mongo lets you adapt to future
> requirements better than Postgres because you don't have a schema.

Yes, that's precisely correct. As anyone who has had to collaborate across
multiple features and/or teams can attest. In a NoSQL like system, I can bring
in a new fact/information without flinching. In Postgres, I have to negotiate
with the schema system and _all_ of the consumers of the data, which obviously
isn't necessary b/c NoSQL. (What Postgres may buy you is some transactional
semantics and other relational db goodies etc but it is certainly not buying
you adaptability/agility over the NoSQL/NoSchema store.)

~~~
s4vi0r
Can you clarify your argument/provide a possible solution?

Your line of thinking seems kind of absurd to me - "Hey, we can't possibly
predict our data model requirements in the future, and explicitly encoding
them into a schema/ADT/etc hinders flexibility (and somehow productivity?)"
doesn't really hold up to scrutiny. Flexibility is great, but if anything the
lesson mongo taught us over the past few years is that stability and safety
are more important, and will cause you less of a headache in the long term. I
feel like the same applies to ADTs - of course its more flexible to just have
a free floating `name: String` or whatever, but then you also get to have the
fun of validating your data all day as opposed to letting your compiler do the
dirty work for you.

~~~
wellpast
> Flexibility is great, but if anything the lesson mongo taught us over the
> past few years is that stability and safety are more important, and will
> cause you less of a headache in the long term.

It sounds like you don't really need clarification, that you've learned the
same lessons I have, but that you're just prioritizing differently and
explicitly choosing "stability and safety" _over_ flexibility
(adaptability/agility).

I go the other way. The biggest software costs in my experience are wanting to
pivot on this or that business idea/requirement but being severely hindered[1]
due to some unnecessary data taxonomization decisions that are so woven into
the code (via ADTs/entity types) _presumably_ for "safety" or "stability".

By locking things down with ADTs and type taxonomies you most certainly get
stability, I won't argue with that. But that stability by definition is
rigidity. I _want_ things to be dynamic. I _want_ to be able to readily change
my code assets because in most business operations the future demands that.

Do you get "safety" with this stability as well? Probably, perhaps - but this
would warrant a whole conversation about what is safety.

In a dynamic/agile business environment, I personally will give up a little
bit of safety for agility and dynamism. Because having an easily-fixable bug
show up in production is far less costly to me than being paralyzed by an
increasingly rigid/"stable" system that is hard to modify for future purposes.
And even if I'm truly worried about bug volume, I can decouple that concern
with the code agility concern. That is, I don't have to weave my bug
mitigation concern into my codebase via hard type taxonomies. I can use other
measures like code reviews, QA & testing, etc. and turn the knob up or down on
these as a I see fit.

The systems that weave all these concerns together in my experience _always_
become harder and harder to bring new capability into. This is and should be
no surprise, though. The world does not fit neatly into static, hard-line
taxonomies. Our OCD minds would love it if it they did and people really try
to make it fit into that (hence all the ADT hoopla we see everywhere) -- but
in my experience the world _always_ finds creative ways to frustrate our
taxonomies...and systems that predicate themselves on a taxonomy _always_ get
frustrated by that.

[1] Severely hindered means that something that _should_ take very little time
with very little impact on your app suite and very little coordination across
the codebase(s) and team(s) instead requires all of these things.

------
dboreham
This unfortunately is one of those cases where the fact that someone has taken
some time to write a nice looking article about a subject does not signal that
they have something interesting and valuable to say.

------
jryan49
Too bad avoiding this just looks like a spaghetti mess with no cohesion. If
you're going to be using a language, you might as well learn how it works
instead of avoiding it with strange hacks. I'm also confused how the 'pure'
version is less complicated than the class version. I see it the opposite?

------
zamalek
A few years back I wrote an JS+async to JS compiler. I made a very real
attempt to forget everything I knew about statically typed languages.

I organically found a very compositional form. The parser created a tree
containing objects such as, for an if statement, { if: { ... }, accept: () =>
... }. The next stage would decompose that into more compositions, maybe by
adding a block and condition property. While it probably had garbage
performance, the power of dynamic languages finally clicked.

I used to be an aspiring game developer; writing a component entity system in
C# (my weapon of choice) required absurd amounts of carefully crafted code.
Statically-typed languages have a ton of front-loaded overhead if you need
something outside of their formula.

You can approach problems any way that you please in dynamic languages (such
as the functional approach in the article) and, while they still _really_
aren't for me, I completely understand why people use them.

~~~
weberc2
It's not clear to me what you mean by the power of dynamic languages. I'm
inclined to think your perspective on static languages is limited to C# or
some other OOP static languages. In particular, your example doesn't seem very
novel for a statically typed language. Heck, I just built a reasonably elegant
parser framework in Go and it doesn't even have generics. This makes me wonder
if you're not attributing your OOP frustrations to static typing.

~~~
zamalek
I really did mean to say OOP - I have been attempting a real project in Go and
I'm smitten.

Still, even Go has a prescribed way of approaching problems; as unusually
broad as it is for a statically typed language. As a very relevant example,
there is far more syntactical overhead in Go if you wish to fake FP (e.g.
`func Foo(a A) func(b) func(c) func(d) e` to allow currying); where-as in
JavaScript you simply bind or apply.

I've yet to see an article gushing over how natural functional is in Go (which
is fine, Go isn't designed for that). This must be the 20th article I've seen
on HN about functional in JavaScript.

~~~
weberc2
Yeah, I agree with your assessments of Go. I didn't mean for it to sound like
Go is a good example of expressive static typing--quite the opposite: I was
able to build something elegant with Go _in spite_ of its limited, static type
system.

------
baron816
If anyone is interest, you can write stateful React components without 'this'
or 'class.' I explain how here:
[https://medium.com/@baronmaximilianwilleford/react-
without-t...](https://medium.com/@baronmaximilianwilleford/react-without-
this-39a76b8f2160)

I've been doing it for a while and haven't had any problems and have really
enjoyed doing it.

------
pshc
I use classes to bind a data structure to a group of functions. That's mostly
it.

Inheritance, overrides, super calls? The less I use those, the less I miss
them.

~~~
dboreham
This is in fact what classes are.

------
dlbucci
What bugs me about the class syntax more than anything is that it encourages
prototype classes, which in my humble opinion, are just worse than the
Crockford module pattern. C Modules solve this/binding issues, and give you
privacy. Maybe they use more memory (in theory), but I never thought that was
a great argument unless you actually measure usage and it's noticeably worse.
You're basically creating those functions anyway when you call bind, so I
wouldn't be surprised if there's not a big effect for most applications.

------
sshine
TL;DR: Read JavaScript: The Good Parts.

~~~
Veen
That book was written in 2008. JavaScript classes were introduced with
ECMASCRIPT 2015.

~~~
thinkxl
JavaScript classes are syntactic sugar, the class type doesn't exist in
ES2015; everything still works with prototypical inheritance.

So, I'd say that this book is still relevant Today. If you don't understand
how inheritance work behind the scenes, you don't understand JavaScript.

------
ryanmarsh
Code should be written first and foremost for humans to understand, and
incidentally for compilers to understand.
[https://mitpress.mit.edu/sicp/front/node3.html](https://mitpress.mit.edu/sicp/front/node3.html)

If we can all agree about that then we have to ask ourselves if encapsulating
data and behavior at the granularity of classes makes the most sense for human
comprehension. In naive OO examples (Animal -> Cat) it does. In practice
however, it's debatable (at best).

I highly recommend this video by Brian Will who does a much better job
explaining this problem than I could.
[https://www.youtube.com/watch?v=QM1iUe6IofM](https://www.youtube.com/watch?v=QM1iUe6IofM)

Douglas Crockford also does a good job of explaining some of the trouble with
classes.
[https://www.youtube.com/watch?v=bo36MrBfTk4#t=28m50s](https://www.youtube.com/watch?v=bo36MrBfTk4#t=28m50s)

~~~
Veedrac
> In naive OO examples (Animal -> Cat) it does.

An inheritance hierarchy consists of three things: a taxonomy, behavioural
inheritance and structural inheritance. The first is only useful on occasion,
and the last two don't even apply to Animal → Cat; "animal" is not a
behavioural constraint, and a cat certainly doesn't have a platonic ideal of
an animal hidden somewhere inside its gut.

------
ES2048
" Usage of class is not bad. It definitely has its place. If you have some
really “hot” code that’s a bottleneck for your application, then using class
can really speed things up. But 99% of the time, that’s not the case. And I
don’t see how classes and the added complexity of this is worth it for most
cases (let’s not even get started with prototypal inheritance). I have yet to
have a situation where I needed classes for performance. So I only use them
for React components because that’s what you have to do if you need to use
state/lifecycle methods (but maybe not in the future). "

Why would classes have any inherent performance benefits (and over what) ?

~~~
rhinoceraptor
Since classes are just sugar over prototypes, their performance is just about
the same for instantiation. I assume the article is comparing them to
functions that return an object literal with methods.

Here is a simple example of equivalent code in a class, function with a
closure, and a prototype:

    
    
      class FooClass {
        constructor (bar) { this.bar = bar; }
        getBar () { return this.bar; }
        setBar (bar) { this.bar = bar; }
      }
    
      function FooClosure (bar) {
        var _bar = bar;
        return {
          getBar () { return _bar; },
          setBar (bar) { _bar = bar; }
        };
      }
    
      function FooPrototype (bar) { this.bar = bar; }
      FooPrototype.prototype.getBar = function () { return this.bar; };
      FooPrototype.prototype.setBar = function (bar) { this.bar = bar; };
    

When instantiating a lot of objects, the prototype and class are practically
the same, but the function closure pattern is a lot slower. On my machine,
instantiating one hundred million of the class or prototype each took about
500ms, but the closure function took about 3s.

------
southpolesteve
I also find `this` is terrible for linting. Most tools won't be able to figure
you what you attached to `this` at runtime. It's a grab bag object ripe for
bugs.

------
cdevs
What about the cognitive load on someone reading person1 person2 person3. Now
I have to realize person 3 in your code is person2 and person2 was person1,
person1 and person2 are now dangling and will never be touched again
hopefully. I love the idea of pure functions but I have always thought there
was a missing piece in functional programming.

~~~
Anderkent
No sane language makes you write person1 person2 person3. Immutability doesn't
mean you can't reuse names; only that the reassignment is in lexical scope,
and doesn't impact any other references to what you saw as person1.

~~~
cdevs
Rust calls it shadowing and throws this example: fn main() { let x = 5; let x
= x + 1; let x = x * 2; println!("The value of x is: {}", x); }

Sure this is a syntax example and you shouldn't be creating Immutable variable
x and then adding +1 to it for no reason. If you did have a reason you should
add it to the variable name so it's more likely developers say let xPlusOne =
x + 1; Or a kitchen example: Let cream = getRefridgeratorItem(cream); Let
whippedCream = blend(cream);

So you reply no sane language makes you do this but why didn't the author show
that? His goal was to make things more readable but lets be honest functional
programmers would judge you all day for making a promise and then copying over
that promise with something else. You may do it at work but the purist trolls
hate reading it.

------
connorelsea
A lot of the typical use cases for classes in a language like Java are
replaced by the user's choice of module format in JavaScript (ES6, commonjs,
etc.). Though, I don't think "this" was that hard to learn (See: YDKJS) and
classes/prototypes definitely are an important tool in the JavaScript
toolbelt.

------
adityapurwa
I don't see why would I want to assign my bound class methods to external
variable, and if you do that, you are responsible to bind the method to an
instance. The reason it is a class method is because it operates on that
class, taking it out and using it externally is against the purpose of it's
existence.

------
Waterluvian
I avoid classes as a default behaviour, using them when they're clearly a good
fit. The ease of testability that functions give me is worth its weight in
gold.

I love how much I can do with JavaScript without classes. Same with Python!
You'd be surprised how often you don't need them.

------
supergreg
The case would be solved if they added some syntax to bind a function to the
class. Something like

    
    
        class Foo {
          bind function bar() {
            console.log(this);
          }
        }
    

So behind the scene it would do `this.bar = this.bar.bind(this);`

~~~
spraak
That'd be awesome. Submit a proposal!
[https://github.com/tc39/proposals](https://github.com/tc39/proposals)

~~~
supergreg
Looked around and there's a proposal for an easier way to bind `this` and some
discussion about extending that to class methods:
[https://github.com/tc39/proposal-bind-
operator/issues/39](https://github.com/tc39/proposal-bind-operator/issues/39)

Using arrow functions at the class level would also solve this without adding
new syntax. Edit, this is a thing already:
[https://babeljs.io/docs/plugins/transform-class-
properties/](https://babeljs.io/docs/plugins/transform-class-properties/)

------
spion
`this` is hard to learn.

No it isn't. Its a function argument that is implicitly passed "left of the
dot". Whatever object that function was attached to when you call it? Thats
the object thats gonna become the `this` argument.

Normal arguments, you pass them like so: `someFunction(normalArgument1,
normalArgument2)`

The argument `this` you pass it like so:
`thisArgument.someFunction(normalArgument1, normalArgument2)`

From this it immediately becomes clear why you can't pass that function away
by itself. When whoever got it calls it, its going to have nothing "left of
the dot" to call it with.

Done, `this` explained.

~~~
munificent
> No it isn't. Its a function argument that is implicitly passed "left of the
> dot".

That is true in JavaScript, but not in other languages that also have "this"
and closures. So the confusion comes from two issues: 1. JavaScript is
inconsistent with other languages, and 2. It's not at all clear that
JavaScript's semantics are actually _good_.

In other languages where "this" is not loosely bound, there is next-to-no
confusion around them and no problems, so the evidence is there that the
semantics JavaScript chose aren't user-friendly.

The _reason_ JS has those semantics is because it (at the time) had no way to
distinguish between a method declaration (where this should be dynamically
bound) and a function declaration (where this should be lexically bound).
Other languages with class syntax don't have that problem.

JavaScript does have class syntax _now_ , but changing the semantics of how
this works would have been very difficult this many years later.

~~~
spion
All languages have different semantics. Otherwise they would've been one
language.

Python has an explicit self that is unbound. Other languages have it implicit
and bound... Etc

~~~
twic
What do you mean by "unbound"? Some Python:

    
    
        >>> class Foo:
        >>> 	def __init__(self, x): self.x = x
        ...
        ... 	def bar(self): print('x =', self.x)
        ...
        ...
        >>> f = Foo(23)
        >>> b = f.bar
        >>> b()
        x = 23
    

Note that when you extract the method from the object as a callable, it hangs
onto the right self. Same deal as Java:

    
    
        class Foo {
          private int x;
          Foo(int x) { this.x = x; }
          void bar() { System.out.println("x = " + x); }
        }
    
        public static void main(String[] args) {
          Foo f = new Foo(23);
          Runnable b = f::bar;
          b.run(); // prints x = 23
        }

~~~
spion
I meant "bound", sorry.

Funny that you mention "::" in Java. ES7/ES8 was going to have a bind operator
"::" that extracts a bound method with a slightly different syntax. i.e.
`::f.bar`

------
graycat
I don't have a sushi knife because I don't need one because I don't eat sushi.

I was in a project in rule based programming, but I left that project and
f'got about such programming because I don't have any rules I want to program.

I was in a high end project for object-oriented programming and another for
object-oriented data definitions (OSI/ISO CMIP/S) but f'got about those
because I don't have any objects to program or use for data.

In my current project, I have defined a few classes, but it's not really
object oriented programming because I do next to nothing with methods, never
use inheritance, and usually allocate only one instance of each class.

So I use classes as really just versions of structures as in C and, much
better than C, as in PL/I where the structures are terrific and in execution
time much more efficient than objects. For fast program to program
communications over TCP/IP sockets, I do like class instance de/serialization.

Basically, net, to me, object oriented programming is a solution for a niche
problem I don't have, have never had, and likely never will have.

Also the OP illustrates more of why I don't pay attention to object-oriented
programming: The syntax of that example is tricky gibberish I want nothing to
do with.

Object oriented programming seems to have helped a lot with Microsoft's .NET.
Okay, I use .NET. But I'm not writing .NET classes!

Sorry guys, I don't eat sushi and in my code don't see any real need for
_object-oriented_ programming.

More generally, I've been programming for a long time, and it all looks much
the same to me: (A) Define places for the data need to store, (B) manipulate
the data with assignment statements, if-then-else, do-while, call-return, and
occasionally allocate storage(I let Microsoft's garbage collection handle
freeing the storage), and (C) use some .NET classes.

I could use a sushi knife if I ate sushi, but I don't. I could use all that
stuff about objects if I had objects in my programming, but mostly I don't.

To borrow from the old movie _The Treasure of the Sierra Madre,_ "Objects?
What objects? We don't need no stinkin' objects!"

------
samfisher83
Why Can't JavaScript just have c++/java/c# type classes? At least it makes it
easy to identify what you class variables are and what your class functions
are.

~~~
saas_co_de
Yeah I was just thinking, why can't javascript be easy and simple like C++?

