
I Don't Use Classes - philk10
https://spin.atomicobject.com/2020/03/12/why-i-dont-use-classes/#.Xmom-cfXe3U.hackernews
======
fuzzy2
Hm. Not sold. Sure, you can write terrible code with classes. But that also
works great without classes.

> I’m terrible at making good abstractions with classes.

Then don’t. Or only do it after everything is done and you have explored the
domain. Don’t try to be clever from the start. Unless you have understood
everything, it will backfire.

> But as other classes extend and override those methods to address their
> version of the problem space, they usually end up having some other
> responsibilities in addition to the original task.

Inheritance gone wrong. If a derived type behaves differently, it should not
be a derived type. See Liskov substitution principle. Just because a hash map
is somewhat like a list of key/value pairs doesn’t mean it should derive from
one.

It could also be a problem of missing separation of concerns/responsibilities,
which is entirely unrelated to OOP.

> Secondly, I’ve noticed that classes have a tendency to grow large.

Yes, I did see many big classes. They were always the result of... missing
separation of concerns/responsibilities. The fabled god objects.

The section about state management appears to be very much React-specific,
too. So, not about classes.

~~~
hombre_fatal
> Then don’t [use them, it's that simple!]

The problem with classes in languages that use them as a main abstraction
(Java, Ruby, etc) is that people reach for them for all abstraction and
software design as their Day 1 tool.

All program state and behavior is now immediately fragmented from Day 1 in
arbitrary buckets and my experience some moss quickly grows on them and things
are never changed for the life of the program.

Just seems your post doesn't offer much insight beyond the classic OOP advice
I've heard for years when people realize it's not that great: oh no, you're
doing it wrong, don't do the bad things, just do the good things and ensure
everyone else is doing them too. I don't see how that's actionable nor useful.

Limiting class use as much as possible and using functional module pattern as
TFA does, on the hand hand, is actionable advice for these common issues with
classes.

~~~
cle
I just don't see much of a practical difference between these two approaches.
From a functional perspective, objects are fundamentally a set of namespaced
functions that implicitly take the same state/map/JSobj as an argument bound
to "this". You can pass that state in explicitly and it just becomes what the
author is talking about. And you can have the same state-management nightmares
with that approach, because nothing is fundamentally different. Instead of
obj.method(arg1) you have namespace::function(obj, arg1). I've worked in a
very large frontend codebase using this technique and it's just as much of a
pain as implicitly managing state with objects, if not more of a pain b/c it's
no longer idiomatic.

State management is hard. Changing how you refer to the state isn't going to
make it any easier, just different.

~~~
AnimalMuppet
The difference with this approach is that _any_ function can take obj as an
argument. If it's pass by reference, any function can _mutate_ obj. If obj
winds up in an inconsistent state, any function in the entire codebase could
have done it.

With a class, however, you normally make the data members private; that is,
only the member functions of the class can change them. If the data gets
mangled, you now have a very small set of places to look. That difference
matters when debug time comes around.

~~~
tobr
If you use a functional approach, you’re probably not mutating anything ever.
That usually makes it trivial to find the source of a problem, because you can
quickly isolate it to a specific call stack.

> _only the member functions of the class can change them. If the data gets
> mangled, you now have a very small set of places to look._

But probably anything could have called those mutating methods, so you still
need to consider the entire codebase.

~~~
AnimalMuppet
Anything could have called those mutating methods. But only those mutating
methods could have put the object in an inconsistent state. I therefore have
to fix the method that did that, and then let anything in the entire codebase
freely call those methods.

------
dpcan
Best thing about 20 years of freelancing has been that I have NEVER been asked
about, ridiculed for, or heard complaining about my code. Nobody even sees it,
or cares. It's efficient and responsible code in my opinion, but I don't have
to care about this strange problem where programmers actually have opinions
about how code is put together. I don't know how you guys put up with it, I'd
go nuts if someone told me I had to use more classes, or a certain language,
or tabs over spaces, or whatever.

~~~
SahAssar
I think we put up with it because personal development requires outside
feedback. I couldn't imagine getting better at the pace I am without both
having to justify and explain my decisions to others.

I don't understand your viewpoint at all.

The best part about coding for me is getting a review with a lot of
interesting comments about how it could be done differently. It jogs my brain
down paths I would never have seen by myself.

~~~
kareemm
As a consultant (not the OP) one of the best parts of writing code is having
it solve real business problems (and getting paid well for it). It's a point
of personal pride to write reasonably good code, but the structure doesn't
matter to the person paying the bills - the outcome does.

It's like living in a house: as long as it doesn't fall down and doesn't cost
unreasonable amounts to maintain, I don't really care about how well-formed
the mortar is, or that the drywall is hung perfectly according to company
standards. I just want to live in the house and not worry about it.

~~~
SahAssar
Well, what I'd say to that is:

* Somebody maintains that code. If it is only you you have a bus-factor of one if none of it is actually reviewed. If it is not you you are imposing a single persons idea of what the business problem is and how it should be solved on the maintainer, which they might not agree with

* Personal development depends on realizing when you are wrong or have used the wrong tool. If nobody challenges your decisions you will probably not realize that as often (or at all) and therefore not improve as much.

* Solving business problems means solving problems for a business. Usually businesses evolve over time, so the business problems might change. Who will understand the code in that case? How do you know the code is understandable to other people if nobody else reviewed it?

And as for the house example: before buying that house I would definitely hire
somebody else to check it, and that is legally required in some countries. I
don't trust anyone to rate their own work.

In this case the person who hired your consultancy bought the house, and they
should definitely have an expert that did not build it check it.

\---

Just a FYI: I'm also a consultant, but work on a project where we have
discussions about how to structure the code and do code reviews and so on.

~~~
kareemm
Yes yes, I agree with all that. I much prefer to discuss approach with someone
senior before tackling hairy problems and have others look at my approach
after the fact to improve. But since I often work with clients where I am the
dev team, I rely on my professional network to discuss approach.

Agree that getting a technical review from a third party isn't a bad idea.

FWIW this doesn't change my main point: the business owner cares about
outcomes, and not code structure (a good outcome is dependent on the code
being good enough to maintain e.g. the house not falling down). There's a
simple test for this BTW: write lited, beautiful code that doesn't solve the
business problem you were hired to solve and see how popular you are with your
client.

------
INTPnerd
The main benefit of classes is as a way to define custom types. Otherwise you
are stuck with the types built into the language, which are not designed to
help ensure correct program state, logic, and behavior. When you use classes
in this way, I call it type oriented programming (TOP). Why would I compare
classes to types? Just pass in the initial value at construction time, define
the "operations" on that type by creating methods for your class, and define
which other types those operations work with by setting the types of
parameters those methods work with. Make the class immutable, always returning
a new instance with the new updated value passed into the constructor. Why did
I mention these types helping the program be more correct? This should be used
as the primary form of contracts. But these contracts are very simple to use,
you just specify the relevant type in the parameters and return values, and
you are done defining the contract for any given method. For example, let's
say a method should only work with integers larger than zero, instead of
either accepting an Int or an UnsignedInt, which would allow 0, you could
define your own class called PositiveInt. It would be designed to throw an
exception if you pass anything smaller than 0 into the constructor. Then
instead of writing code inside the method that makes sure the user of the
method is following the contract, you just specify PositiveInt as the
parameter type. If the contract is violated, the exception will be thrown as
early as possible, before the method is even called, helping programmers catch
the original source of the problem. This also makes your code more readable,
because you can see exactly what each method accepts and returns just by
looking at the method signature. When you start thinking this way, you will
notice many core types are missing from the language, that should have been
there from the beginning. Fortunately you now know how to build them yourself.

~~~
chmod775
> The main benefit of classes is as a way to define custom types. Otherwise
> you are stuck with the types built into the language

I'm going to stop you right there, because plenty of languages (especially
functional ones) have ways of declaring custom complex types without using
classes.

~~~
INTPnerd
Which languages would you say are really good for defining your own types?
Would they be a good fit for the example I provided where a function needs to
accept integers larger than zero? Do they also allow you to define your own
operations on those types? That is the part where classes seem to be a good
fit, methods are basically operations supported by a type.

~~~
MaxBarraclough
> Which languages would you say are really good for defining your own types?
> Would they be a good fit for the example I provided where a function needs
> to accept integers larger than zero?

I'm not an Ada expert, but it has excellent support for range-restricted
integer types.[0]

Ada's 'discriminated types' are also fun. They let you create members which
only exist when they're applicable. [1]

> Do they also allow you to define your own operations on those types

Looks like Ada supports operator overloading, yes. [2]

[0]
[https://www.adaic.org/resources/add_content/standards/05rm/h...](https://www.adaic.org/resources/add_content/standards/05rm/html/RM-3-2-2.html)

[1]
[https://www.adaic.org/resources/add_content/standards/05rat/...](https://www.adaic.org/resources/add_content/standards/05rat/html/Rat-3-5.html)

[2]
[https://www.adaic.org/resources/add_content/standards/05aarm...](https://www.adaic.org/resources/add_content/standards/05aarm/html/AA-6-6.html)

~~~
kqr
Ada is also great at separating the various aspects of OOP into separate
language concepts, instead of going "everything is done with classes!!" as
many popular languages do, leading to a lot of confusion in this thread.

------
tomstuart
You’re always going to be on one side of the expression problem [0]: either
your operations live with your datatype (classes), which makes it inconvenient
to add a new operation, or they live somewhere else (“functional modules”),
which makes it inconvenient to add a new case to the datatype. This choice is
probably informed by your expectations of how often you intend to do either of
those things, but there’s going to be inconvenience regardless.

The big benefit of objects, and therefore classes (or something like them), is
polymorphism via dynamic dispatch; it seems a shame to throw the baby out with
the bathwater because you don’t like inheritance or mutable state, both of
which are independent of the choice to use classes.

[0]
[https://en.wikipedia.org/wiki/Expression_problem](https://en.wikipedia.org/wiki/Expression_problem)

~~~
CuriousSkeptic
I’m not sure dynamic dispatch is that much of a killer feature. Take C# it
default to static dispatch of method and you tend to get very far without
explicitly making things virtual.

Event when using interfaces many times it’s seems possible to arrange things
such that parametric polymorphism would be able to use a static type. There
are some boiling proposals for future versions that will help with exactly
this, and indeed some of the latest changes to the languages have included
allowing more static dispatching in polymorphic constructs like disposal and
enumeration.

I’m beginning to suspect that dynamic dispatch could be entirely dropped
without losing to much expressiveness.

Have no experience with Rust but isn’t that kind of the conclusion from that
community?

~~~
uryga
rust has `dyn FooTrait`, which lets you do dynamic diapatch, similarly to
`FooInterface x = foo();` in e.g. java. even Haskell has a way of doing this
via existential types like `(forall a. FooTypeclass a => a)`.

both of these predominantly statically-dispatched langs added ways to do
dynamic dispatch later on, which seems to go against "dynamic dispatch could
be entirely dropped without losing to much expressiveness". i prefer static-
by-default as well, but it looks like it's hard to do without sometimes.

(of course you can always roll your own vtable-ish thing, so technically you
could drop DD from the language without loss of expressiveness, but then you'd
likely get multiple incompatible libraries for that...)

and i'd guess that some sort of dynamic dispatch is unavoidable if you want to
do some kind of plugin architecture

~~~
CuriousSkeptic
Are you sure that Haskell thing is dynamic? I was under the impression that
the type you have there is resolved statically by finding a type class for a

~~~
uryga
existential types allow you to make it dynamic. see
[https://wiki.haskell.org/Existential_type](https://wiki.haskell.org/Existential_type),
there's a section comparing it to OOP-style dynamic dispatch

simple example copied from the linked article:

    
    
      data Obj = forall a. (Show a) => Obj a
    
      xs :: [Obj]
      xs = [Obj 1, Obj "foo", Obj 'c']
    
      doShow :: [Obj] -> String
      doShow [] = ""
      doShow ((Obj x):xs) = show x ++ doShow xs
    

we can have a "heterogenous" list of `Obj`s, like a java
`ArrayList<IPrintable>`. each `Obj` is basically a pointer to the actual value
of some type T and a pointer to T's Show dictionary (vtable). Which is afaik
analogous to how OOP languages do it

------
sreque
Classes are a means to an end: objects. Objects give us the three core
components of OOP:

* encapsulation

* message passing

* late binding

Things like inheritance tend to muddy the waters. If you aren't benefiting
from the above 3 things, then you are probably either using objects wrong or
you are applying them to a problem for which encapsulated, late-bound message
passing isn't a good solution.

As a corollary, state management and OOP are in my opinion orthogonal. You can
write and program with immutable objects just as you can with mutable ones.

I do agree with some other comments that OOP in college isn't taught very
well. Most people leave their first OOP class not really knowing anything
about OOP or why it's useful. My hunch is that OOP is something that should be
taught later in a program, not earlier.

~~~
threatofrain
IMO, when people talk about OOP they aren't talking about bags of static
functions -- that's just a namespace. The interesting objects are those which
hold state.

~~~
sreque
You can have an encapsulated object that is immutable and is not a bag of
static functions anymore than any object is a bag of static functions. Look at
Scala's interfaces for immutable collections.

* they are encapsulated. You don't know how the data is stored internally.

* There is message passing involved (at the JVM level). The methods are virtual and you don't know exactly which method is being called. There can also be multiple implementations using different data structures.

* Late binding is again as defined by the JVM (interface table method lookup)

In contrast, a static function is no different than a global variable. When
you call it, you know _exactly_ (sans JVM linking) which function you are
calling.

------
skocznymroczny
Using classes doesn't necessitate using inheritance and polymorphism. Whenever
I'm programming in a non-OOP language, I still gravitate towards class-like
code. From my perspective, there isn't much different between:

doStuff(&fooStruct, ...) vs. fooClass.doStuff(...)

~~~
djsumdog
The core difference is in testing. With the later, you need to get your class
into the right state to preform the test. You might even need a mock or two.
With the non-OOP approach, you set the entire state of the function and see
the results.

~~~
Double_a_92
> you set the entire state

And how would you easily create that state?

~~~
Ididntdothis
You create a mock :)

------
bfung
One of my favorites:

[https://wiki.c2.com/?ClosuresAndObjectsAreEquivalent](https://wiki.c2.com/?ClosuresAndObjectsAreEquivalent)

The venerable master Qc Na was walking with his student, Anton. Hoping to
prompt the master into a discussion, Anton said "Master, I have heard that
objects are a very good thing - is this true?" Qc Na looked pityingly at his
student and replied, "Foolish pupil - objects are merely a poor man's
closures."

Chastised, Anton took his leave from his master and returned to his cell,
intent on studying closures. He carefully read the entire "Lambda: The
Ultimate..." series of papers and its cousins, and implemented a small Scheme
interpreter with a closure-based object system. He learned much, and looked
forward to informing his master of his progress.

On his next walk with Qc Na, Anton attempted to impress his master by saying
"Master, I have diligently studied the matter, and now understand that objects
are truly a poor man's closures." Qc Na responded by hitting Anton with his
stick, saying "When will you learn? Closures are a poor man's object." At that
moment, Anton became enlightened.

------
nickthemagicman
In school they taught classes and inheritance like it was the biggest thing
since sliced bread.

I feel like they're but one tool, and necessary in limited cases.

For example I recently encountered an API that had to intake 100+ key/value
pairs and manipulate these values, and send them to various places.

I tried to do the mappings and data manipulations and everything in one long
piece of code functionally but it led to some serious spaghetti. When I
separated things out into classes, it was much cleaner code, with a more well
defined domains. It was definitely not perfect but ended up being much
cleaner. This is a simple example but classes can be useful. It's arguable
that the API could have been better written and that's true but sometimes you
have to work with what you're given.

Otherwise I would venture to say most code doesn't require classes.

~~~
LessDmesg
By "I separated things out into classes", do you mean that you used the whole
triad of inheritance + polymorphism + encapsulation?

~~~
simion314
Do you need all 3 of them to have a class? In my experience classes are data+
methods that manipulate that data. Like a Vector class would have a
rotate,normalize etc methods that return a new Vector.

~~~
jerf
That's a question of definition. There are many who will passionately insist
that yes, you absolutely need all of them. I hypothesize but can't prove that
these are people simply regurgitating what they were taught in a classroom and
who have not yet left Java/C++ to see what else is out there and what else can
work. It is perfectly feasible to be educated that way and then spend your
entire career in Java or C++ and come to conceptualize everything else as
"that crazy minority stuff that isn't like Real Programming", and, in their
defense, they are at least correct about the "minority" part. It's easy to
forget if you read HN a lot, but inheritance + polymorphism + encapsulation
remain the _massively_ dominant paradigm, especially if you assume most C# is
still basically written that way despite supporting other paradigms.

Personally I take the loosest possibly view, mostly just "associating data and
methods to operate on that data fairly tightly together, where they all get
passed around as a unit". Some ability to have "x.y()" result in multiple
possible implementations of "y" is necessary, or you just have a weird way of
spelling function calls. That's about all I'll require to call something "OO"
nowadays.

~~~
simion314
I think the number of developers that when starting a project start creating
some inheritance tree is large. Some concepts work fit inheritance very well
like GUI widgets or game entities, you would make a disservice not to tell
students about the concepts and have them re-invent them.

Not sure how other languages do it, but in my daily work the only time I use
inheritance is with an ORM , to get the magic to work you need to extend the
base Model class , add a few lines of configuration code and you are done, for
more advanced uses I think you have to override some methods.

But except the ORM code I don't think I used inheritance in a long time,
though I used interfaces in some other cases.

~~~
meheleventyone
Game entities really don't fit inheritance very well though! Even if using an
OO approach it's far more flexible to compose. Otherwise it suffers from the
classic large inheritance tree issues of lots of state and functionality
leaking up the tree to common base classes. As an example the Unreal Engine
used to have a heinously enormous base class called Actor. Another is that all
the game entities in EVE Online actually bring the AI code with them. You can
make it work but it's in spite of rather than because of inheritance. Anyone
teaching students it's useful is doing them a great disservice.

~~~
simion314
I don't have experience with game engines, but I worked with Qt, WinForms,
Flex4, Swing GUI libraries, and it made sense. All components inherit from a
base widget, This base widget has everything you need, layout, positioning,
mouse and keyboard events, painting. If you want to make a custom button that
say is rotating when you click it you extend the Button and not start from
scratch like you do in html. In html you see many custom components created
from nested DIVs, this custom widgets are missing many basic features like
accessibility and keyboard shortcuts and are broken for edge cases. As an
example PayPal has a money amount text input but the Delete key does not work
on it.

Could there be some insane case where the OOP of the GUIO widget is
problematic, maybe , like when you want your app to have a window in the shape
of a circle ... but I think is fair you get easy to use library for 99% of the
cases and for 1% of the cases you might have to get your hands dirty and go
outside the standard ways and maybe look under the hood.

~~~
meheleventyone
Yeah GUIs seem to end up fitting reasonably well. They tend to be fairly
modelled as specialisations. The problem with games is that it feels like they
should but aren’t.

~~~
simion314
Maybe the issue is that the engine game evolves, new requirements are added
and maybe a developer has a bad day and some ugly code is created.

If we consider a game like Minecraft, I would use something like a base class
for blocks and a different base class for mobs, then if you add a new feature
you put it in base class and all the objects will get it for free. I am not
sure how Minecraft is architected but I loved how all entities including the
player are the same, if you can do something to a player(push, drawn, burn,
catapult, activate circuits , etc) all mobs would work the same.

I think GUIs work well because is a very well known and studied problem where
with game engines you have a generic problem and then each game is bending the
engine to try to use it for different type of games that the engine author did
not consider at the beginning.

My conclusion is that some OOP is a tool you can use, you should not avoid it
because a dude in a blog said so,the same with GOTO - I think I only used GOTO
once , it was more efficient and more readable to use goto there then trying
to workaround using it by creating variables and then adding checks (the
problem was to efficiently break from inside 2 or more nested loops
efficiently)

------
cryptonector
This is like so many "OOP is bad" posts. Yes, OOP is bad.

OOP is bad because inheritance is not as good as interfaces.

OOP is bad because typically it puts state changes inside mutable, shared
objects, which makes concurrency difficult.

Java has interfaces and generics -- use those instead of inheritance.

C++ has templates -- use those instead of inheritance.

Design and implement interfaces that operate on immutable, preferably linear
data types, and concurrency will be easier.

------
smabie
My prefered style is a class that's ideally no more than one level deep. Mix
in some traits if you need them. Also, as such as possible, make every
class/object immutable and the methods side-effect free. I find this leads to
a very natural style in which functions are logically grouped together (also,
helps with intellisense) but never modify any state. If there are
classes/functions that modify state, make it explicit through naming (I append
'_!' for Scala).

Essentially, what I'm describing, is just functional programming where f(a, x)
becomes a.f(x). I find the latter easier to read and lends itself to a fluent,
lightweight style.

In general, I don't really think the discussion of OOP vs FP or whatever is
that meaningful. What is meaningful is mutable vs immutable. Every great
developer I've met has agreed with me that mutation should be avoided as much
as humanly possible: really the only valid reason to introduce mutation in
your program is for performance reasons. And yet, we as an industry still are
by and large writing mutation heavy code for no reason at all. Things have
gotten a lot better in the last 10 years, but we still have a long way to go.

Edit: I'm mostly talking about Scala in this post, btw. I also want to note
that I do think deep class hierarchies are a good solution to some problems.
The two examples that immediately come to mind are UI APIs and collection
libraries. Both these problems are very naturally solved by OO, the essence of
which is the deep (as much as necessary) class hierarchies. Very few problems
I've encountered besides the above actually require these subtyping.

An great alternative approach is row polymorphism, ala OCaml. I've not had any
professional experience with OCaml or row polymorphism, so I'm not sure how
well it actually works in practice.

~~~
whlr
> I find the latter easier to read and lends itself to a fluent, lightweight
> style.

Maybe I'm bikeshedding, but I think this is a nontrivial part of why people
like OOP languages. Being able to read right to left, without nested parens,
makes code comprehension easier.

I think the mathematical notation for function application has held
programming languages back. I realize that there's a huge benefit to
uniformity of syntax across languages, and that syntax is ultimately much less
important than semantics, but I wish we could settle on something better than
f(x). I really appreciate languages that are willing to do something different
(e.g. haskell and lisp).

~~~
smabie
There's the pipe operator in a lot of languages (|>) to try and solve this
problem, but it has always felt clunky to me. Compare:

f(g(h(x)))

x.h.g.f

x |> f |> g |> h

I think most people prefer the second one to all the rest.

~~~
Udik
I think the third example should be:

x |> h |> g |> f

Which is identical to the second, except for a more eloquent choice of the
pipeline symbol?

~~~
whlr
That is the version of the |> operator I'm familiar with from Julia.

------
BiteCode_dev
"As I’ve mentioned above, I like to use modules that expose groups of
functions. These functions accept state and other dependencies. These modules
tend to look like what my colleague Drew describes as the functional module
pattern."

I dislike that.

When I see a class, I expect it exists because there is a central state around
which the methods revolve, and are meant to expose or change it.

When I see a function, I expect it to be side effect free.

Sure you can use any paradigm to make any program, but I use paradigms, just
like I use design patterns, to communicate intent. Solving the problem is half
the battle. In a way, coding style is documentation. I think we should build
boring API, and only use cleverness to find solutions, or make the API easier
to use and more elegant.

I worked once on a code base that broke a lot of implicit expectations. Like,
it could add objects in a registry when you instantiated some classes,
get_stuff could set other stuff, do_stuff would not do stuff but
actually_do_stuff would, etc.

It was a nightmare to work with, because anything could happen anywhere. You
had to look at the code every single time and keep the whole system in your
head, which had 900k lines of highly technical code.

~~~
jvanderbot
You misunderstand, I think. Or maybe I do.

If done properly as described, the functions accept state. This means they
adjust state by adjusting input/output parameters, not by affecting some
internal state. This is basically Dependency Injection extended to state. This
is also how classes are implemented (see python's _self_ parameter to all
class functions).

~~~
BiteCode_dev
Yes, but looking at the function, and banning some project specific naming
convention, there is no way to know its role is to mutate a state.

While in Python, when you see a class, you just know the methods are mostly
about "self".

It helps making sense of the code immensely.

~~~
jvanderbot
Correct, but the effectiveness of the design is separate from its correctness.
Latter of which is what I commented on.

For effectiveness, I think it reduces ancillary class members, and encourages
(necessitates!) dependency injection.

For example, changing "delta t" in a game object update is trivial: update the
one delta-t you have, and pass it into everything that needs it. With classes,
there may be a temptation to have a delta-t member variable, which costs many
updates. Stupid example, but a mistake which is not possible with Dependency
Injection.

But really, proper scoping in typed languages (and naming in dynamic ones) can
allude to input vs output parameters. In C/C++ this is passing by ref vs
value. (In Google C++ style guide, they recommend use of pointers for
"adjusted" or "output" parameters.) In MS C++ from back in the day, I think
they had ways to explicitly specify parameters as input, output, or
input+output. Does someone have an example of this?

------
randomdata
JavaScript, and thus Typescript by extension, lived for _many_ years without
classes. The addition of classes is relatively recent. It stands to reason
that classes are not heavily adopted since since a lot of the foundational
work happened when classes were not even an option.

In contrast, I cannot image being able to get far in Java without using
classes. The concept of using classes is central to that language.

~~~
chrisseaton
> I cannot image being able to get far in Java without using classes

Well you literally can't write a Java program without writing a class.

------
remmargorp64
The #1 complaint I always see with classes is due to inheritance. When classes
inherit from classes that inherit from other classes, you can very easily end
up with a tangled web of methods over-riding and extending other methods, and
run into all sorts of unexpected behaviors and difficulties wrapping your head
around what is going on.

This is why (at least in the Ruby world), it's generally considered best
practice to avoid using inheritance and just use mixins instead (whenever
possible).

Completely avoiding classes because you are using them incorrectly is kind of
like throwing the baby out with the dirty bathwater. Don't throw away your
tools just because you are using them incorrectly!

~~~
crimsonalucard
Inheritance is a debate that already ended long ago with OOP practitioners
vehemently against it.

The argument now is different. There is another flaw with classes and that is
mutation of internal variables with getters and setters. Classes typically
allow you to do this and promote this behavior, structs do not typically allow
you to do this and are not used this way, typically.

It makes it so that a class is more than just a data type, it is a mini
program with it's own internal state and api that you are passing around in
your program. It ups the complexity of your program by 10 fold.

~~~
T-hawk
This is a great post. "A mini program with its own internal state and API" is
a great description of what goes wrong with overdesigned classes.
Encapsulating state into classes originally made sense with early OOP
languages, compared to all of it being global which is even worse, but
nowadays we've got better ways of managing complexity.

Any time something threatens to be a mutable class-level variable, I refactor
that out of the design before proceeding. Most often this refactoring is to
separate the data into a struct-like class and the behavioral methods into a
static class, with what would have been that mutable class-level variable
instead passed in to each behavioral function that needs it.

~~~
meheleventyone
I feel like you missed a bit of the explanation out there. Where does the
struct like class end up being instantiated and how is it linked to the class
that presumably would have previously contained it? Does the struct like class
get put into some kind of data store and the class that needed mutable state
then holds a handle or similar?

------
cryptica
I think the problem is that most developers did not learn OOP correctly. I
noticed a lot of developers these days don't understand basic concepts like
encapsulation and class dependencies.

When I was studying OOP at university, there was an entire course on UML
diagrams and that taught people how to structure OOP software. Good OOP design
tends to produce elegant-looking UML diagrams with relatively few
interconnections between components.

I don't always physically draw UML diagrams, but when I visualize a project in
my head, it looks very much like a UML class diagram. I can't imagine how it's
possible to reason about complex software in any other way TBH.

Sadly, if I had to draw a UML class diagram for most real projects which exist
today, the relationship lines between UML classes would be crossing all over
the place and it would literally look like a tangled mess (definitely more
like a graph than a tree). The people who designed the software had no vision
for it, they just kept adding stuff without ever actually understanding how it
all fits together or thinking about how to minimize complexity and make it
more elegant by minimizing the number of components and the interconnections
between them.

I think that, in a way, functional programming is more forgiving when it comes
to allowing people to keep adding stuff on top without having to visualize the
system as a whole. It doesn't mean that FP adds any net value over OOP. I
would argue that being able to clearly visualize the whole product and the
interaction of data and logic is essential regardless of what paradigm you
follow.

------
peter_d_sherman
Classes are the design-time version of instanciated Objects, which are active
at run-time.

Objects, are like little minature software programs, _inside of a larger
programs_.

Think fractals, that is, smaller things inside of larger ones...

So why have a minature software program inside of a bigger one?

The short answer is _boundaries_.

If the larger program is permitted to modify the internal state of an object,
then _that 's really no different than having one big program where all of the
variables are global_, and any line of code can make any change to any
variable at any time.

Is that OK? For small programs it is, but the larger the program, and the more
programmers that work on it, the more it must be segmented into boundaries,
and these boundaries enforced (aka, encapsulation) in order to prevent bugs.

That, and the ability to divide code into logical groups which can be
individually tested will yield a great deal of clarity and information about a
codebase, when done by a competent programmer or programming team.

At 100,000+ lines of code (and sometimes a lot less), _you will trip over your
own code unless you segregate it into seperately manageable sub-systems with
clearly defined boundaries_. "Divide and conquer" as the old saying goes.

On the other hand, programmers inexperienced with Object-Oriented development
can make code more obscure, unreadable, and harder to work with...

It's a double-edged sword...

Use wisely.

~~~
lalaithion
But Classes and Objects are not the only way to create boundaries, and may not
be the best way.

~~~
peter_d_sherman
Give an example of when they might not be the best way.

~~~
naasking
There are many well known problems with classic OOP:

[http://okmij.org/ftp/Computation/Subtyping/](http://okmij.org/ftp/Computation/Subtyping/)

------
asdfman123
Finally, a man who truly strives to live in a classless society.

~~~
taneq
And as always, first thing a classless society does is to find a way to
emulate classes.

~~~
asdfman123
You win the internet.

------
codr7
Neither do I if I can avoid it, I use type hierarchies and generic methods.

Unfortunately it's not very popular; the only supporting languages I know of
are Common Lisp, Julia & Perl 6.

I've designed and implemented several scripting languages [0] over the years,
but they all have this pattern in common, I've never felt tempted to implement
classes.

[0] [https://github.com/codr7/gfoo](https://github.com/codr7/gfoo)

~~~
jbjohns
Dylan also supports it and it can be done in Python with a library [1]. I
don't know if you'd count it but it's also straight forward in Haskell [2] at
the type class level.

[1]
[https://en.wikipedia.org/wiki/Multiple_dispatch#Python](https://en.wikipedia.org/wiki/Multiple_dispatch#Python)

[2] [https://stackoverflow.com/questions/26303353/can-multiple-
di...](https://stackoverflow.com/questions/26303353/can-multiple-dispatch-be-
achieved-in-haskell-with-pattern-matching-on-type-class)

------
frou_dh
Surely the ultimate example of this phenomenon is OCaml.

It's wearing OOP right on its sleeve in the name of the language, but its
users inevitably say "Oh, we never (or hardly ever) use that part".

~~~
osener
Absolutely. Objects in OCaml is useful for structural typing, but I reach for
classes way less often than I initially thought I would (read: never).

It turns out modules are indeed a better solution for, well, modularization.

OCaml is multi-paradigm in the most broad sense of the word I've experienced,
just with functional and immutable as the default rather than the other way
around. And it has a pretty great OOP implementation too.

Suggested reading for those wondering why the community deemed the O in OCaml
mostly unnecessary and how rest of the language helps achieve the same goals
in a different/better way: [https://discuss.ocaml.org/t/objects-use-cases-in-
ocaml/2282](https://discuss.ocaml.org/t/objects-use-cases-in-ocaml/2282)

------
lmilcin
There are people who think every carpentering problem can be solved with
chisel and the only book they ever red is one that says how to solve every
problem with a chisel.

The response to this is not to throw out all chisels.

The response is to learn to use various tools and once you know how to do
this, how to apply different tools to different types of problems.

There is no shame in overusing a tool for a period of time. The novice
carpenter must be able to play to learn limitations of tools but also to learn
which inventive ways to use tools are actually helpful.

Master carpenter will understand that playtime is necessary step in getting to
mastery and will not call out novices for their overuse of the chisel but
instead will encourage play with the intent to speed up education.

------
ch_123
> Secondly, I’ve noticed that classes have a tendency to grow large. They will
> collect pieces of functionality that need to live in the context of the
> class, usually to access the internal state of that class. This dependency
> on the internal state makes it difficult to break the methods up into
> logical chunks.

Sounds like they need to pay more attention to the Open-Closed principle :)

It's not really clear to me how a random grab-bag of functions is better than
a random grab-bag of methods. If the functions in the too-large module can be
broken up into small units without damaging cohesion, why can't the same be
done for the class with two many methods?

------
bob1029
I've been all over the board on this throughout my career. Currently, we are
looking at a substantial reduction in the number of types in our codebase.
This would imply fewer & larger classes. Which is totally fine, as long as you
know how to manage that complexity.

Our approach for managing this complexity is to simply say that each class in
the domain model aligns to the bounded context of a particular business unit,
process, or activity. So, instead of modeling a bunch of different types
within some namespace, the type itself represents the entire extent of that
business concern, including any business facts as instance properties. Complex
types are modeled as nested classes within each context. Nothing is shared
between contexts except for trivial domain-wide concerns in the outer scope.
This allows for very easy serialization of business state in and out of a
database (among many other things). Consider the power that a method override
might have in this realm if you wanted to construct a hierarchy of such
contexts.

An example of one of these concrete types might be SelfCheckoutContext (if you
were working in a retail business domain). This type could be derived from a
wider CheckoutContext which could be simultaneously shared other concrete
contexts like HumanCheckoutContext.

In our approach, there is also a lot of stuff that exists outside of each
context (Views, Services, Repositories, etc). We basically treat these items
as a platform layer for the contexts to operate on top of.

~~~
CaptArmchair
I hear some echoes of domain driven design / development in your comment. Have
you looked into that?

~~~
bob1029
Yes absolutely. We took what we felt was the bare essential idea of DDD
(bounded contexts) and ran with it. The fewer patterns the better. Allows you
to work with fewer constraints, assuming you are responsible with the added
power.

Bounded contexts are IMO the most powerful abstraction out there for
accurately constructing a very complex business system. They directly get at
the root of the problem, which is aligning the implementation with the same
grain as the business. They also permit complete isolation of various business
units which might have unique perspectives into what would otherwise be shared
(and increasingly-convoluted) models. In our latest implementations, models
are never shared between contexts. There might be a few models defined in the
outer domain scope for things we believe apply universally to the application.
Any cross-context communication is achieved via simple mappers.

------
fetbaffe
Typescript works well without classes because you have great type system, you
can implement an interface on the fly by just passing an object with the
correct properties to fulfill it.

But the class based architecture doesn't necessary have to be as bad as
described in the article.

Inheritance is somewhat of the old school of OOP, modern OOP rarely use
inheritance, it uses composition, just like a functional code base. In my
projects my classes are always marked as final. Can't even remember the last
time I inherited a another class, maybe if I interacted with some legacy code.

It is important to separate your classes, what classes can have state & what
classes can not have state. Classes with state should be small & classes
without can be large, because stateless classes can easily be reused.

Classes have the main advantage that you easily inject dependencies in the
constructor (composition), e.g. a repository depending on the database driver.

You can do that with functions as well, e.g currying, but I find it a bit more
messy if your language is not specialized against that use case & with a class
you "curry" all class methods at once. If you don't inject your depencies in
your functions you will have a much harder time to reuse that function & test
it.

I agree with that class based GUI code rarely works, this is because GUI (if
it is HTML or OpenGL) does not translate well to a class with hierarchies. GUI
code have tendency to more be procedural.

------
mr_tristan
I've noticed that in large Java hierarchies, where I spend a lot of my time,
people often use interfaces where they really should be using modules. Like,
there's this vague concept of a "service" and methods of this service are
bound together via an interface. Which ends up coupling all kinds of weird
dependencies in the class definition.

My guess is that this is a similar case in Typescript that the author is
describing.

So, this approach is, instead of having IFooService with two methods,
IFooService.doOneThing and IFooService.doAnotherThing you just have a package
"fooservice" with functional interfaces DoOneThing and DoAnotherThing. This
seems to still play nice with most of the tooling, and often, keeps the code
simpler and makes it easier to refactor. DoOneThingImpl and DoAnotherThingImpl
can easily share code, but that aspect shouldn't matter at all to the caller.

Another way of thinking about it: don't worry too much about a perfect
interface for layering your business logic initially. Start with just some
functions, then as you spot patterns you may recognize there's a state pattern
that does things better. I don't think this is really specific to Typescript.

------
keymone
Good advice. Don't use classes. At least resist using them as much as
possible.

Consider this simple aspect: one of the key differences between a class method
and a separate funcion is implicit (and sometimes explicit, though that's not
much better) self/this parameter that allows access to all object fields.
Essentially you're opting in to grow the input of every method call by all the
data held by an object. That's a mistake on many levels:

\- it is one of the most important "best practice" advice to only pass the
data that function needs to do the job, with classes you're breaking that
advice immediately

\- ease of access to additional data doesn't leave room to think if it's
actually good idea to do so, factor in laziness and inexperienced contributors
and you get methods that grow in complexity much more than they should

\- testing anything becomes torture, as unlike with simple function class
methods now depend not only on incoming arguments but also on state of the
object itself, i've seen tests with dozens of lines of setup for single line
of test

Prefer simple pure functions, prefer builtin data types, prefer immutability,
only pass arguments that function needs to do the job.

~~~
Udik
> only pass arguments that function needs to do the job.

Why would users of that function bother to know what data it needs to do its
job? It seems you'll end up polluting your whole code with the implementation
details of one of its components. What happens of you want to change those
implementation details?

~~~
keymone
Because the two alternatives we’re talking about here are ‘foo(bar, baz)’
where foo, bar and baz convey meaning and ‘object.doWork()’ which means
nothing and explains nothing.

Yes, it’s a spectrum and there’s a lot of space in between those alternatives,
I simply believe being closer to the first option is better than to the
second.

Pure functions are conductive to transparency, objects, methods and class
hierarchies are conductive to obscurity.

------
asurty
This reminds me of a podcast I listened to recently about creativity. Maybe
its not about the idiomatic question of whether classes are good or bad but
more about the act of placing the limitation that helps drive the masterpiece?

[https://www.npr.org/transcripts/719557642](https://www.npr.org/transcripts/719557642)

~~~
INTPnerd
In Kotlin classes and methods are closed by default, making avoiding
inheritance and polymorphism the default. It also makes you mark each variable
as mutable or immutable, by declaring it with a var or val, making you think
more about (and enforce) state being mutable or immutable, thus avoiding a lot
of state problems. It also has a concept called data classes, which works
really well for most well designed classes. I now consider the case where a
class is not a good candidate for data classes to be a code smell. Most
classes should be a good candidate for data classes or else you are usually
using classes in a way that causes more problems than it solves.

The thing is, it doesn't prevent you from creating classes or methods that are
open for inheritance, nor does it prevent you from allowing mutable state, but
it does use the defaults and the language design to encourage limitations that
are usually good.

Perhaps they could take these concepts a step further by making classes data
classes by default, having a keyword for it to not be a data class.

------
austincheney
I have always associated classes with inheritance. Perhaps there are other
scenarios where classes are unrelated to inheritance. I don't know as I
intentionally avoid classes all together.

I avoid inheritance because inheritance increases complexity. By _complexity_
I simply mean it in terms of _complicate_ , or _to make many_. I am not
speaking to anything related to challenges, easiness, or hardships.
Inheritance is a practice of a concept called polyinstantiation, which allows
easy dynamic replication of an object in memory so that many child or cloned
objects are independently extensible.

* [https://en.wikipedia.org/wiki/Polyinstantiation](https://en.wikipedia.org/wiki/Polyinstantiation)

* [https://en.wikipedia.org/wiki/Complexity](https://en.wikipedia.org/wiki/Complexity)

~~~
westoncb
> I have always associated classes with inheritance.

This is a common pattern I've noticed in discussions about OOP. There is a
tendency to reduce it to "inheritance," which turns the conversation into a
straw man: "here is why inheritance is bad, therefore OOP is bad".

Same goes for identifying classes with inheritance. Their primary feature is
grouping of methods and fields into (potentially) encapsulated units, and
being a mechanism for abstraction: classes are more general than their
instances and serve as a general pattern; upon construction they are
'configured' through parameters to their constructors and particular forms are
arrived at.

------
gumby
I really like programming in C++ and don't write a lot of classes. They are a
useful tool in the toolbox but hardly the only one.

Well, looking again, I do use a lot of structs (or their moral equivalent,
using 'class' to define a struct with some protected state) but don't use
inheritance much.

------
room271
Most useful programs have state. As the program scales you need to break it up
into parts (or 'modules') with clear interfaces. Usually these parts own
state.

The problem with OOP is that it assumes you need _lots_ of these parts.
Whereas really, you might have a few or possibly a handful. Use, class-like
constructs to represent these, and use normal functions within these, with
ideally some kind of polymorphism that also doesn't require classes.

For small programs, don't even bother with this. Just use pure functions for
key logic, and unit test those, and delay testing anything with state for as
long as possible. When you do need to, leverage interfaces and stubs/fakes (
_not_ mocks) to test them.

That's what I do now anyway.

------
keithasaurus
My pet theory about the abundance of classes is that they only gained
popularity because of their inherent complexity. There's no one, authoritative
way to use them, so a bunch of people can went to conferences and wrote papers
about SOLID or encapsulation, or whatever. So people thought there must be
value in them.

It takes a long time for a lot of developers to realize anything you can do
with classes you can do without. And if you structure your code without
classes, it tends to be more refactorable because moving chunks of code in and
out of different contexts is a breeze.

The only things I find potentially useful about classes are:

\- portability (but you can get that with modules, caveat that not every
language does modules well)

\- auto-complete in a noun->verb context

------
Pamar
Question:

The author writes (as an example of his problems relying on classes):

 _Well, some tables when updating need to do more than just “update” a record,
such as insert a log into a side table._

I do not understand exactly if there is a problem here, or anyway I do not
understand what the Functional alternative would be.

Assuming this _is_ a problem, I suppose we have three choices:

a) You have to provide implementation for two distinct methods: \- Update(...)
and LoggedUpdate(...) (the latter calls the former and then logs the result,
most probably).

b) You decide that update() implicitly logs maybe based on a global
configuration or similar mechanism.

c) You provide a boolean flag as parte of the method signature update(log).

What would be the proper Functional approach here?

(Caveat - I have no experience with Functional programming)

------
Xcelerate
Ever since I discovered Julia a few years ago, I’ve found I can’t stand using
classes any more (particularly in Python). Multiple dispatch just feels so
much more elegant than trying to dispatch everything on the type of an
implicit first argument.

------
carapace
My first programming teacher emphasized that this at-the-time-newfangled
"object-oriented" programming was _only_ a way of organizing the code, you
still had the same data and functions.

Use classes, don't use classes. It doesn't matter.

~~~
kragen
I feel like dynamically-dispatched open recursion gives you a level of
flexibility that you can't get in, say, Pascal — you need first-class function
pointers if you don't have virtual functions.

BTW, I finally responded to your comment from the other day; sorry about the
delay!

~~~
carapace
Well, Oberon had language-level OOP-- records with methods --which is what my
teacher was talking about as just a way to arrange the same data and
functions, but the OS also had runtime dynamic message dispatch OOP built on
top of but not intrinsically requiring the language-level OOP. I suspect you
could implement that in Pascal and make all of Hewitt's Actors model with it.

> BTW, I finally responded to your comment from the other day; sorry about the
> delay!

Cheers! I'll go check that out :-)

~~~
kragen
Yeah, the Oberon language added function pointers (I don't remember if those
were in Modula-2), and the Oberon OS used that form of language-level OOP to
implement its runtime dynamic message dispatch OOP. You can't implement that
in Pascal because Pascal doesn't allow you to store function pointers in
records (because they might be nested functions, with all that implies). So in
Pascal the best you can do is write a switch function for each function
pointer type (signature) and use an enumerated type or equivalent to dispatch
calls.

~~~
carapace
> So in Pascal the best you can do is write a switch function for each
> function pointer type (signature) and use an enumerated type or equivalent
> to dispatch calls.

Yeah, you would have to spell it out and you couldn't do compile-time
optimizations, but you could get all the dynamic flexibility you need?

Really, what I was saying above is just that if your problem is easier to
describe as a bunch of interacting "objects" (like a game or a factory sim)
then use OOP, but if it's easier to describe as a set of functions and data
structures then don't use OOP. :-)

~~~
kragen
Well, sort of; I mean clearly Pascal is a Turing-complete programming
language, so it provides you all the flexibility you need. But if you write a
switch function for each function pointer type, it means that local changes in
an OO language, or in C, map to global changes in the Pascal translation of
it, so I'd argue that the OO language, or C, is more _expressive_ in a
fundamentally meaningful way.

(Actually existing implementations of Pascal usually added a function pointer
type of some kind, but I'm talking about the language standard.)

I think the crucial question is whether the objects have different behavior.
If all the objects implement a particular operation in the same way, or in one
of two or three ways, there's no advantage to OOP. If their behavior varies in
an open, expandable way — like, you might have an arbitrarily large number of
monsters whose movement strategy varies arbitrarily — then OOP, or function
pointers, can be helpful. If their behavior needs to vary in orthogonal and
arbitrarily combinable ways, OOP may not be expressive enough, and maybe you
should look at an entity-component-system architecture.

~~~
carapace
That's basically what the Oberon OS messaging system did, it was a little
clunky but it worked. The Gadgets widgets system is still my favorite after
all these years. It was like OLE but _good_.

I agree, if your code doesn't need objects, there's no advantage to OOP. and
ECS is like extra-OOP. :-) (I replied to your reply in the other thread, BTW.)

~~~
kragen
At least the version of the Oberon OS described in the book used the built-in
Oberon support for function pointers for dynamic dispatch; were you looking at
an earlier or a later version than the one in the book?

~~~
carapace
I'm talking about the Viewer message system. There's a "coarse-grained view"
on pg. 46 of
[https://inf.ethz.ch/personal/wirth/ProjectOberon/PO.System.p...](https://inf.ethz.ch/personal/wirth/ProjectOberon/PO.System.pdf)

~~~
kragen
Right, that's what I was talking about too; although the version of the book I
read was the 1992 edition, this part of the system hasn't changed. It works by
using the Oberon language's support for embedding function pointers
("procedure variables") within records, which is precisely what was prohibited
in Pascal. You give different windows ("viewers") different behavior by giving
them different Handle procedures ("WndProcs", you might say, or "overridden
methods") so that, when the system ("controller") sends a message to the
viewer, it can behave in a dynamically determined way.

~~~
carapace
Yes, the Handle procedures are associated with their objects by means of
function pointers (for e.g. C++-style OOP), but also inside themselves they
implement big ol' IF,THEN,ELSE chains to do (Smalltalk/Actor-style OOP)
messaging handling.

~~~
kragen
Oh, I see what you mean — instead of having a function pointer per message
type, say, stored in a vtable, the handlers for each message type are
dispatched by a function, which can do things like delegation (Smalltalk
doesNotUnderstand:). Piumarta's "COLA" approach is a sort of hybrid, where you
dispatch with a function, but the dispatch function returns a function pointer
you can then cache at the call site.

What I was saying about Pascal is that instead you'd have to have a function
that dispatched on the _receiver type_ , not the message type. I thought you
were saying that the Oberon OS was doing that, even though the Oberon language
supported function pointers.

~~~
carapace
Yeah, cheers! ^_^ (It took me _years_ to notice that.)

------
crimsonalucard
I came across a random answer on quora as to why classes are bad (as opposed
to structs). It gets into the very nature of program design and organization.

[https://www.quora.com/Is-senior-full-stack-engineer-Ilya-
Suz...](https://www.quora.com/Is-senior-full-stack-engineer-Ilya-Suzdalnitski-
right-that-theres-no-objective-evidence-that-object-oriented-programming-OOP-
is-better-than-plain-procedural-programming-And-is-OOP-in-fact-a-trillion-
dollar-disaster/answer/Bernard-
Yip-2?__filter__=all&__nsrc__=1&__sncid__=4384812242&__snid3__=7127597147)

Be sure to read the comments, it's pretty interesting.

------
gentleman11
The only classes that really make sense are ones that model something in the
real world to some extent. These classes are incompatible with the single
responsibility principle however and are considered/lead to bad designs with a
lot of coupling. Most of my classes get instantiated very few times as well,
so the blueprint concept is only sometimes applicable. Inheritance, one of the
three pillars, is a trap.

The whole OOP framework is a bit muddled and to make it work you end up in
this web of contradictions. You suspect there is a better way but until you
find it, you use Java or C# because that’s what your company/tools use

~~~
threatofrain
...so when do classes make sense then?

~~~
gentleman11
Polyymorphic implementations are nice - Eg, the State pattern, or using
interfaces to abstract out a server during development. Command pattern.
Things you have a lot of. It’s not the entire app though, just subsets of it

------
lliamander
The biggest upside of programming with objects/classes is dependency
injection.

And no, I don't mean DI frameworks like Spring.

I mean simply the dynamic (i.e. runtime) composition of program behavior,
typically by passing collaborating objects into the constructor. (Functional
programming languages have something similar with higher-order functions.)

Contrary to prior claims, objects aren't great at representing real world
entities: to do that you would also need to add a notion of time and
concurrency to those objects (e.g. actors, processes, services). However, they
are a great way to represent the components and capabilities of such entities.

------
ben509
The fugly state in objects is tricky. One way to look at a class is to treat
it as a kind of coroutine. Consider some pseudocode:

    
    
        class Stats
           sum Num = 0
           sum_squared Num = 0
           count Num = 0
    
           acceptNum(x)
              sum += x
              sum_squared += x ^ 2
              count += 1
    
           mean()
              return sum / count
    
           variance()
              # I think this is right...
              return (sum_squared / count) - (sum / count) ^ 2
    

But that could just as well be:

    
    
        coro Stats
            sum = sum_squared = count = 0
            result = nothing
            loop
                msg = yield result
                switch msg.name
                case acceptNum
                    sum += msg.x
                    ...
                case mean
                    result = sum / count
                ... 
    

We don't do objects like this. We really want a file object, for instance, to
be something like:

    
    
        coro FileReader
            # Not opened yet
            raw_handle = popen(...)
            # Reading
            ...
            if msg.name == read
                yield read(raw_handle)
            ...
            # Data exhausted
            ...
            if msg.name == read
                yield eof
            ...
            # Handle closure
            ...
            if msg.name == read
                throw ReadOnClosedFile
            ...
    

(I think you could handle inheritance with a scheme like this, presumably a
"subclass" would simply yield to the "superclass".)

What's nice about that is the state is explicit in the structure of the
coroutine. Ideally, we'd have some sort of protocol types that reflect that
we're accepting different kinds of messages at different stages in an object
lifecycle. In this example, after a file closes, it's a type error to try and
read from it.

Instead, we generally munge it with properties, depending on lots of checks
instead of structure.

We can solve state through more classes, of course. The Builder pattern
handles a specific case of this quite nicely, you have one mutable construct
that then returns an immutable construct.

But to use that more universally would lead to a profusion of classes.

------
jeffadotio
I like classes. But you must understand your code, even before you write it.
If the syntax that you use (the author is using TS, and JS/TS classes are
syntactic sugar) matches your mental model of the program then I would say
that you are choosing the correct syntax.

If it’s your library and your responsibility then I will try to read it your
way. Comprehensive testing and consistency in style are more important than
any specific syntax or abstraction.

------
winrid
I think I understand what the author means.

When writing Java code I prefer to use public members in my data classes and
only use getters/setters sparingly. Also, I prefer to have the logic in until
classes if I can. I suppose I'm not a huge OOP fan.

Of course I have to follow whatever patterns my teammates follow and hardly
anyone writes Java like that... But my personal projects I think have much
cleaner and easier to understand code with this approach.

------
dana321
Classes are everywhere, but are used in different ways or hidden.

There are types of classes that are hidden and cannot be re-instantiated.

To me, those are modules and the main execution environment.

Whenever i've been caught out when coding its always one or both of these
things, so i always wrap these in a class otherwise when it comes to reusing
something multiple times you're caught short.

So, i always use classes for base functionality.

But i don't throw them around like sugar candy.

------
michelpp
I also tend to avoid writing many classes (in Python). Jack Diederich has a
great video called Stop Writing Classes:

[https://www.youtube.com/watch?v=o9pEzgHorH0](https://www.youtube.com/watch?v=o9pEzgHorH0)

It's about Python, but the observation can apply to several languages. Pithy
quote:

"I hate code and I want as little of it as possible in our product."

------
kalimoxto
(Apologies to the Flaming Lips, and I think the pronunciation shift you have
to do at the end makes it funnier)

I know a girl who

wrote some code

she'll make a linked list

she'll add some nodes

But she don't use classes

and she don't use C

she don't use Haskell

or any of these

she uses assembly

assembly

assembly

------
cmrdporcupine
The first object oriented programming language I learned (LambdaMOO) didn't
have classes. It's entirely conceivable to do nice clean object oriented code
using a system which has only prototypes. In fact it encourages one to think
about the important parts: encapsulation and delegation without becoming
obsessed with classification.

------
kalekold
[http://nomad.uk.net/articles/developers-who-hate-on-oop-
don'...](http://nomad.uk.net/articles/developers-who-hate-on-oop-don't-know-
how-to-use-it.html)

------
jacobn
They’re using TypeScript. Ts classes are horrible: you have to explicitly use
“this.” to access _anything_ in the class - it’s as good as it was possible to
make it with the JS legacy, but it just doesn’t work very well.

Using namespaces with state passed to the relevant function ends up being
basically just as easy, and sometimes easier as you end up using more closures
and can access enclosed variables directly.

Classes in languages designed for OO work a lot better.

------
chadlavi
I don't either. Almost always an arrow function (or just regular old function)
does what I need. Plus, React Hooks!

------
rkagerer
For a small to medium sized codebase, functions and structs are simple, clear
and don't obscure state.

------
beefbroccoli
I use classes a lot in typescript, but not for OOP. Just glorified objects
with a constructor and getters.

------
babypuncher
I once tried to drive a nail with a socket wrench and it went poorly. I have
since cleared my garage of all socket wrenches because they are clearly
useless.

------
TurboHaskal
It's 2020. You're preaching to the choir on this.

I myself use classes just fine. I love CLOS and I'm having a lot of fun with
OOP on Forth as of late.

------
sunstone
Interfaces are good though.

------
beastman82
classes are OOP. FP doesn't need them. The end.

------
gigatexal
A whole article and no code examples. Fail.

~~~
crimsonalucard
Even if he did present code examples I doubt it would change your mind.

I think OOP people should be aware that there is significant backlash against
OOP among smart people who are very familiar with the paradigm and they should
take the time to understand why.

The division is real and given the invention of languages like GO and RUST
that do not have classes I would think really hard about the topic.

~~~
dith3r
GO is simple decomposition of OOP, class was split into structure (with
composition) and interface(with multiple inheritance without implicit
definition what interfaces can can coop with structure). Go only restricted
that you cannot define virtual methods.

~~~
crimsonalucard
Rob Pike designed Go with the philosophy for people not to use structs like
classes. This is intentional and deliberate.

If you are using GO like you use OO, you've missed the entire point.

[https://twitter.com/rob_pike/status/942528032887029760](https://twitter.com/rob_pike/status/942528032887029760)

~~~
dith3r
Please read carefully what I've written. I've never said that struct == class.
Secondly Rop Pike wrote under tweet: `But the more important idea is the
separation of concept: data and behavior are two distinct concepts in Go, not
conflated into a single notion of "class".` which only proves what I presented
in first comment.

~~~
crimsonalucard
Please carefully read what I wrote. I never claimed you said struct == class.

You literally said go removed virtual methods. This is not what pike is
talking about. Pike is talking about putting a function onto your struct. This
is legal in go as functions are first class but rob is saying it should be
done sparingly because data and behavior need to be distinct concepts.

It is not me who failed to parse your writing, but you who failed to
understand pike and my comment.

------
catacombs
> I Don't Use Classes

OK, cool.

------
fdghfg
classes only encapsulate context. nothing else. you don't have to use them if
you don't want to.

------
lincpa
Yes, I never use classes, only data and pipes.

[The Pure Function Pipeline Data Flow v3.0 with Warehouse / Workshop
Model]([https://github.com/linpengcheng/PurefunctionPipelineDataflow](https://github.com/linpengcheng/PurefunctionPipelineDataflow))

1\. Perfectly defeat other messy and complex software engineering
methodologies in a simple and unified way.

2\. Realize the unification of software and hardware on the logical model.

3\. Achieve a leap in software production theory from the era of manual
workshops to the era of standardized production in large industries.

4\. It's the basics and the only way to `Software Design/Develop Automation
(SDA)`, SDA is an innovative and revolutionary approach to develop large-scale
software, just like `Electronic Design Automation (EDA)`, because it
systematically simulates an integrated circuit system.

------
snambi
Sounds like an idiot with opinions.

